Repository: SonarSource/sonar-csharp Branch: master Commit: cf16e98336f4 Files: 6476 Total size: 21.9 MB Directory structure: gitextract_opbg6mf9/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── GitHub.shproj │ ├── ISSUE_TEMPLATE/ │ │ ├── 1-FalsePositive.yml │ │ ├── 2-FalseNegative.yml │ │ ├── 3-AD0001.yml │ │ ├── 4-NewRule.yml │ │ └── config.yml │ └── workflows/ │ ├── LabelIssue.yml │ └── SlackNotification.yml ├── .gitignore ├── .globalconfig ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── SECURITY.md ├── analyzers/ │ ├── .runsettings │ ├── CI.NuGet.Config │ ├── CodeAnalysis.targets │ ├── Common.targets │ ├── README.md │ ├── SonarAnalyzer.sln │ ├── Version.targets │ ├── packaging/ │ │ ├── Licenses/ │ │ │ └── THIRD_PARTY_LICENSES/ │ │ │ ├── Google.Protobuf-LICENSE.txt │ │ │ ├── Roslyn-LICENSE.txt │ │ │ └── StyleCop.Analyzers-LICENSE.txt │ │ ├── SonarAnalyzer.CFG.nuspec │ │ ├── SonarAnalyzer.CSharp.Styling.nuspec │ │ ├── SonarAnalyzer.CSharp.nuspec │ │ ├── SonarAnalyzer.VisualBasic.nuspec │ │ ├── tools-cs/ │ │ │ ├── install.ps1 │ │ │ └── uninstall.ps1 │ │ └── tools-vbnet/ │ │ ├── install.ps1 │ │ └── uninstall.ps1 │ ├── rspec/ │ │ ├── cs/ │ │ │ ├── S100.html │ │ │ ├── S100.json │ │ │ ├── S1006.html │ │ │ ├── S1006.json │ │ │ ├── S101.html │ │ │ ├── S101.json │ │ │ ├── S103.html │ │ │ ├── S103.json │ │ │ ├── S104.html │ │ │ ├── S104.json │ │ │ ├── S1048.html │ │ │ ├── S1048.json │ │ │ ├── S105.html │ │ │ ├── S105.json │ │ │ ├── S106.html │ │ │ ├── S106.json │ │ │ ├── S1066.html │ │ │ ├── S1066.json │ │ │ ├── S1067.html │ │ │ ├── S1067.json │ │ │ ├── S107.html │ │ │ ├── S107.json │ │ │ ├── S1075.html │ │ │ ├── S1075.json │ │ │ ├── S108.html │ │ │ ├── S108.json │ │ │ ├── S109.html │ │ │ ├── S109.json │ │ │ ├── S110.html │ │ │ ├── S110.json │ │ │ ├── S1104.html │ │ │ ├── S1104.json │ │ │ ├── S1109.html │ │ │ ├── S1109.json │ │ │ ├── S1110.html │ │ │ ├── S1110.json │ │ │ ├── S1116.html │ │ │ ├── S1116.json │ │ │ ├── S1117.html │ │ │ ├── S1117.json │ │ │ ├── S1118.html │ │ │ ├── S1118.json │ │ │ ├── S112.html │ │ │ ├── S112.json │ │ │ ├── S1121.html │ │ │ ├── S1121.json │ │ │ ├── S1123.html │ │ │ ├── S1123.json │ │ │ ├── S1125.html │ │ │ ├── S1125.json │ │ │ ├── S1128.html │ │ │ ├── S1128.json │ │ │ ├── S113.html │ │ │ ├── S113.json │ │ │ ├── S1133.html │ │ │ ├── S1133.json │ │ │ ├── S1134.html │ │ │ ├── S1134.json │ │ │ ├── S1135.html │ │ │ ├── S1135.json │ │ │ ├── S1144.html │ │ │ ├── S1144.json │ │ │ ├── S1147.html │ │ │ ├── S1147.json │ │ │ ├── S1151.html │ │ │ ├── S1151.json │ │ │ ├── S1155.html │ │ │ ├── S1155.json │ │ │ ├── S1163.html │ │ │ ├── S1163.json │ │ │ ├── S1168.html │ │ │ ├── S1168.json │ │ │ ├── S1172.html │ │ │ ├── S1172.json │ │ │ ├── S1185.html │ │ │ ├── S1185.json │ │ │ ├── S1186.html │ │ │ ├── S1186.json │ │ │ ├── S1192.html │ │ │ ├── S1192.json │ │ │ ├── S1199.html │ │ │ ├── S1199.json │ │ │ ├── S1200.html │ │ │ ├── S1200.json │ │ │ ├── S1206.html │ │ │ ├── S1206.json │ │ │ ├── S121.html │ │ │ ├── S121.json │ │ │ ├── S1210.html │ │ │ ├── S1210.json │ │ │ ├── S1215.html │ │ │ ├── S1215.json │ │ │ ├── S122.html │ │ │ ├── S122.json │ │ │ ├── S1226.html │ │ │ ├── S1226.json │ │ │ ├── S1227.html │ │ │ ├── S1227.json │ │ │ ├── S1244.html │ │ │ ├── S1244.json │ │ │ ├── S125.html │ │ │ ├── S125.json │ │ │ ├── S126.html │ │ │ ├── S126.json │ │ │ ├── S1264.html │ │ │ ├── S1264.json │ │ │ ├── S127.html │ │ │ ├── S127.json │ │ │ ├── S1301.html │ │ │ ├── S1301.json │ │ │ ├── S1309.html │ │ │ ├── S1309.json │ │ │ ├── S131.html │ │ │ ├── S131.json │ │ │ ├── S1312.html │ │ │ ├── S1312.json │ │ │ ├── S1313.html │ │ │ ├── S1313.json │ │ │ ├── S134.html │ │ │ ├── S134.json │ │ │ ├── S138.html │ │ │ ├── S138.json │ │ │ ├── S1449.html │ │ │ ├── S1449.json │ │ │ ├── S1450.html │ │ │ ├── S1450.json │ │ │ ├── S1451.html │ │ │ ├── S1451.json │ │ │ ├── S1479.html │ │ │ ├── S1479.json │ │ │ ├── S1481.html │ │ │ ├── S1481.json │ │ │ ├── S1541.html │ │ │ ├── S1541.json │ │ │ ├── S1607.html │ │ │ ├── S1607.json │ │ │ ├── S1643.html │ │ │ ├── S1643.json │ │ │ ├── S1656.html │ │ │ ├── S1656.json │ │ │ ├── S1659.html │ │ │ ├── S1659.json │ │ │ ├── S1694.html │ │ │ ├── S1694.json │ │ │ ├── S1696.html │ │ │ ├── S1696.json │ │ │ ├── S1698.html │ │ │ ├── S1698.json │ │ │ ├── S1699.html │ │ │ ├── S1699.json │ │ │ ├── S1751.html │ │ │ ├── S1751.json │ │ │ ├── S1764.html │ │ │ ├── S1764.json │ │ │ ├── S1821.html │ │ │ ├── S1821.json │ │ │ ├── S1848.html │ │ │ ├── S1848.json │ │ │ ├── S1854.html │ │ │ ├── S1854.json │ │ │ ├── S1858.html │ │ │ ├── S1858.json │ │ │ ├── S1862.html │ │ │ ├── S1862.json │ │ │ ├── S1871.html │ │ │ ├── S1871.json │ │ │ ├── S1905.html │ │ │ ├── S1905.json │ │ │ ├── S1939.html │ │ │ ├── S1939.json │ │ │ ├── S1940.html │ │ │ ├── S1940.json │ │ │ ├── S1944.html │ │ │ ├── S1944.json │ │ │ ├── S1994.html │ │ │ ├── S1994.json │ │ │ ├── S2053.html │ │ │ ├── S2053.json │ │ │ ├── S2068.html │ │ │ ├── S2068.json │ │ │ ├── S2077.html │ │ │ ├── S2077.json │ │ │ ├── S2092.html │ │ │ ├── S2092.json │ │ │ ├── S2094.html │ │ │ ├── S2094.json │ │ │ ├── S2114.html │ │ │ ├── S2114.json │ │ │ ├── S2115.html │ │ │ ├── S2115.json │ │ │ ├── S2123.html │ │ │ ├── S2123.json │ │ │ ├── S2139.html │ │ │ ├── S2139.json │ │ │ ├── S2148.html │ │ │ ├── S2148.json │ │ │ ├── S2156.html │ │ │ ├── S2156.json │ │ │ ├── S2166.html │ │ │ ├── S2166.json │ │ │ ├── S2178.html │ │ │ ├── S2178.json │ │ │ ├── S2183.html │ │ │ ├── S2183.json │ │ │ ├── S2184.html │ │ │ ├── S2184.json │ │ │ ├── S2187.html │ │ │ ├── S2187.json │ │ │ ├── S2190.html │ │ │ ├── S2190.json │ │ │ ├── S2197.html │ │ │ ├── S2197.json │ │ │ ├── S2198.html │ │ │ ├── S2198.json │ │ │ ├── S2201.html │ │ │ ├── S2201.json │ │ │ ├── S2219.html │ │ │ ├── S2219.json │ │ │ ├── S2221.html │ │ │ ├── S2221.json │ │ │ ├── S2222.html │ │ │ ├── S2222.json │ │ │ ├── S2223.html │ │ │ ├── S2223.json │ │ │ ├── S2225.html │ │ │ ├── S2225.json │ │ │ ├── S2234.html │ │ │ ├── S2234.json │ │ │ ├── S2245.html │ │ │ ├── S2245.json │ │ │ ├── S2251.html │ │ │ ├── S2251.json │ │ │ ├── S2252.html │ │ │ ├── S2252.json │ │ │ ├── S2257.html │ │ │ ├── S2257.json │ │ │ ├── S2259.html │ │ │ ├── S2259.json │ │ │ ├── S2275.html │ │ │ ├── S2275.json │ │ │ ├── S2290.html │ │ │ ├── S2290.json │ │ │ ├── S2291.html │ │ │ ├── S2291.json │ │ │ ├── S2292.html │ │ │ ├── S2292.json │ │ │ ├── S2302.html │ │ │ ├── S2302.json │ │ │ ├── S2306.html │ │ │ ├── S2306.json │ │ │ ├── S2325.html │ │ │ ├── S2325.json │ │ │ ├── S2326.html │ │ │ ├── S2326.json │ │ │ ├── S2327.html │ │ │ ├── S2327.json │ │ │ ├── S2328.html │ │ │ ├── S2328.json │ │ │ ├── S2330.html │ │ │ ├── S2330.json │ │ │ ├── S2333.html │ │ │ ├── S2333.json │ │ │ ├── S2339.html │ │ │ ├── S2339.json │ │ │ ├── S2342.html │ │ │ ├── S2342.json │ │ │ ├── S2344.html │ │ │ ├── S2344.json │ │ │ ├── S2345.html │ │ │ ├── S2345.json │ │ │ ├── S2346.html │ │ │ ├── S2346.json │ │ │ ├── S2357.html │ │ │ ├── S2357.json │ │ │ ├── S2360.html │ │ │ ├── S2360.json │ │ │ ├── S2365.html │ │ │ ├── S2365.json │ │ │ ├── S2368.html │ │ │ ├── S2368.json │ │ │ ├── S2372.html │ │ │ ├── S2372.json │ │ │ ├── S2376.html │ │ │ ├── S2376.json │ │ │ ├── S2386.html │ │ │ ├── S2386.json │ │ │ ├── S2387.html │ │ │ ├── S2387.json │ │ │ ├── S2436.html │ │ │ ├── S2436.json │ │ │ ├── S2437.html │ │ │ ├── S2437.json │ │ │ ├── S2445.html │ │ │ ├── S2445.json │ │ │ ├── S2479.html │ │ │ ├── S2479.json │ │ │ ├── S2486.html │ │ │ ├── S2486.json │ │ │ ├── S2551.html │ │ │ ├── S2551.json │ │ │ ├── S2583.html │ │ │ ├── S2583.json │ │ │ ├── S2589.html │ │ │ ├── S2589.json │ │ │ ├── S2612.html │ │ │ ├── S2612.json │ │ │ ├── S2629.html │ │ │ ├── S2629.json │ │ │ ├── S2674.html │ │ │ ├── S2674.json │ │ │ ├── S2681.html │ │ │ ├── S2681.json │ │ │ ├── S2688.html │ │ │ ├── S2688.json │ │ │ ├── S2692.html │ │ │ ├── S2692.json │ │ │ ├── S2696.html │ │ │ ├── S2696.json │ │ │ ├── S2699.html │ │ │ ├── S2699.json │ │ │ ├── S2701.html │ │ │ ├── S2701.json │ │ │ ├── S2737.html │ │ │ ├── S2737.json │ │ │ ├── S2743.html │ │ │ ├── S2743.json │ │ │ ├── S2755.html │ │ │ ├── S2755.json │ │ │ ├── S2757.html │ │ │ ├── S2757.json │ │ │ ├── S2760.html │ │ │ ├── S2760.json │ │ │ ├── S2761.html │ │ │ ├── S2761.json │ │ │ ├── S2857.html │ │ │ ├── S2857.json │ │ │ ├── S2925.html │ │ │ ├── S2925.json │ │ │ ├── S2930.html │ │ │ ├── S2930.json │ │ │ ├── S2931.html │ │ │ ├── S2931.json │ │ │ ├── S2933.html │ │ │ ├── S2933.json │ │ │ ├── S2934.html │ │ │ ├── S2934.json │ │ │ ├── S2952.html │ │ │ ├── S2952.json │ │ │ ├── S2953.html │ │ │ ├── S2953.json │ │ │ ├── S2955.html │ │ │ ├── S2955.json │ │ │ ├── S2970.html │ │ │ ├── S2970.json │ │ │ ├── S2971.html │ │ │ ├── S2971.json │ │ │ ├── S2995.html │ │ │ ├── S2995.json │ │ │ ├── S2996.html │ │ │ ├── S2996.json │ │ │ ├── S2997.html │ │ │ ├── S2997.json │ │ │ ├── S3005.html │ │ │ ├── S3005.json │ │ │ ├── S3010.html │ │ │ ├── S3010.json │ │ │ ├── S3011.html │ │ │ ├── S3011.json │ │ │ ├── S3052.html │ │ │ ├── S3052.json │ │ │ ├── S3059.html │ │ │ ├── S3059.json │ │ │ ├── S3060.html │ │ │ ├── S3060.json │ │ │ ├── S3063.html │ │ │ ├── S3063.json │ │ │ ├── S3168.html │ │ │ ├── S3168.json │ │ │ ├── S3169.html │ │ │ ├── S3169.json │ │ │ ├── S3172.html │ │ │ ├── S3172.json │ │ │ ├── S3215.html │ │ │ ├── S3215.json │ │ │ ├── S3216.html │ │ │ ├── S3216.json │ │ │ ├── S3217.html │ │ │ ├── S3217.json │ │ │ ├── S3218.html │ │ │ ├── S3218.json │ │ │ ├── S3220.html │ │ │ ├── S3220.json │ │ │ ├── S3234.html │ │ │ ├── S3234.json │ │ │ ├── S3235.html │ │ │ ├── S3235.json │ │ │ ├── S3236.html │ │ │ ├── S3236.json │ │ │ ├── S3237.html │ │ │ ├── S3237.json │ │ │ ├── S3240.html │ │ │ ├── S3240.json │ │ │ ├── S3241.html │ │ │ ├── S3241.json │ │ │ ├── S3242.html │ │ │ ├── S3242.json │ │ │ ├── S3244.html │ │ │ ├── S3244.json │ │ │ ├── S3246.html │ │ │ ├── S3246.json │ │ │ ├── S3247.html │ │ │ ├── S3247.json │ │ │ ├── S3249.html │ │ │ ├── S3249.json │ │ │ ├── S3251.html │ │ │ ├── S3251.json │ │ │ ├── S3253.html │ │ │ ├── S3253.json │ │ │ ├── S3254.html │ │ │ ├── S3254.json │ │ │ ├── S3256.html │ │ │ ├── S3256.json │ │ │ ├── S3257.html │ │ │ ├── S3257.json │ │ │ ├── S3260.html │ │ │ ├── S3260.json │ │ │ ├── S3261.html │ │ │ ├── S3261.json │ │ │ ├── S3262.html │ │ │ ├── S3262.json │ │ │ ├── S3263.html │ │ │ ├── S3263.json │ │ │ ├── S3264.html │ │ │ ├── S3264.json │ │ │ ├── S3265.html │ │ │ ├── S3265.json │ │ │ ├── S3267.html │ │ │ ├── S3267.json │ │ │ ├── S3329.html │ │ │ ├── S3329.json │ │ │ ├── S3330.html │ │ │ ├── S3330.json │ │ │ ├── S3343.html │ │ │ ├── S3343.json │ │ │ ├── S3346.html │ │ │ ├── S3346.json │ │ │ ├── S3353.html │ │ │ ├── S3353.json │ │ │ ├── S3358.html │ │ │ ├── S3358.json │ │ │ ├── S3363.html │ │ │ ├── S3363.json │ │ │ ├── S3366.html │ │ │ ├── S3366.json │ │ │ ├── S3376.html │ │ │ ├── S3376.json │ │ │ ├── S3397.html │ │ │ ├── S3397.json │ │ │ ├── S3398.html │ │ │ ├── S3398.json │ │ │ ├── S3400.html │ │ │ ├── S3400.json │ │ │ ├── S3415.html │ │ │ ├── S3415.json │ │ │ ├── S3416.html │ │ │ ├── S3416.json │ │ │ ├── S3427.html │ │ │ ├── S3427.json │ │ │ ├── S3431.html │ │ │ ├── S3431.json │ │ │ ├── S3433.html │ │ │ ├── S3433.json │ │ │ ├── S3440.html │ │ │ ├── S3440.json │ │ │ ├── S3441.html │ │ │ ├── S3441.json │ │ │ ├── S3442.html │ │ │ ├── S3442.json │ │ │ ├── S3443.html │ │ │ ├── S3443.json │ │ │ ├── S3444.html │ │ │ ├── S3444.json │ │ │ ├── S3445.html │ │ │ ├── S3445.json │ │ │ ├── S3447.html │ │ │ ├── S3447.json │ │ │ ├── S3449.html │ │ │ ├── S3449.json │ │ │ ├── S3450.html │ │ │ ├── S3450.json │ │ │ ├── S3451.html │ │ │ ├── S3451.json │ │ │ ├── S3453.html │ │ │ ├── S3453.json │ │ │ ├── S3456.html │ │ │ ├── S3456.json │ │ │ ├── S3457.html │ │ │ ├── S3457.json │ │ │ ├── S3458.html │ │ │ ├── S3458.json │ │ │ ├── S3459.html │ │ │ ├── S3459.json │ │ │ ├── S3464.html │ │ │ ├── S3464.json │ │ │ ├── S3466.html │ │ │ ├── S3466.json │ │ │ ├── S3532.html │ │ │ ├── S3532.json │ │ │ ├── S3597.html │ │ │ ├── S3597.json │ │ │ ├── S3598.html │ │ │ ├── S3598.json │ │ │ ├── S3600.html │ │ │ ├── S3600.json │ │ │ ├── S3603.html │ │ │ ├── S3603.json │ │ │ ├── S3604.html │ │ │ ├── S3604.json │ │ │ ├── S3610.html │ │ │ ├── S3610.json │ │ │ ├── S3626.html │ │ │ ├── S3626.json │ │ │ ├── S3655.html │ │ │ ├── S3655.json │ │ │ ├── S3717.html │ │ │ ├── S3717.json │ │ │ ├── S3776.html │ │ │ ├── S3776.json │ │ │ ├── S3869.html │ │ │ ├── S3869.json │ │ │ ├── S3871.html │ │ │ ├── S3871.json │ │ │ ├── S3872.html │ │ │ ├── S3872.json │ │ │ ├── S3874.html │ │ │ ├── S3874.json │ │ │ ├── S3875.html │ │ │ ├── S3875.json │ │ │ ├── S3876.html │ │ │ ├── S3876.json │ │ │ ├── S3877.html │ │ │ ├── S3877.json │ │ │ ├── S3878.html │ │ │ ├── S3878.json │ │ │ ├── S3880.html │ │ │ ├── S3880.json │ │ │ ├── S3881.html │ │ │ ├── S3881.json │ │ │ ├── S3884.html │ │ │ ├── S3884.json │ │ │ ├── S3885.html │ │ │ ├── S3885.json │ │ │ ├── S3887.html │ │ │ ├── S3887.json │ │ │ ├── S3889.html │ │ │ ├── S3889.json │ │ │ ├── S3897.html │ │ │ ├── S3897.json │ │ │ ├── S3898.html │ │ │ ├── S3898.json │ │ │ ├── S3900.html │ │ │ ├── S3900.json │ │ │ ├── S3902.html │ │ │ ├── S3902.json │ │ │ ├── S3903.html │ │ │ ├── S3903.json │ │ │ ├── S3904.html │ │ │ ├── S3904.json │ │ │ ├── S3906.html │ │ │ ├── S3906.json │ │ │ ├── S3908.html │ │ │ ├── S3908.json │ │ │ ├── S3909.html │ │ │ ├── S3909.json │ │ │ ├── S3923.html │ │ │ ├── S3923.json │ │ │ ├── S3925.html │ │ │ ├── S3925.json │ │ │ ├── S3926.html │ │ │ ├── S3926.json │ │ │ ├── S3927.html │ │ │ ├── S3927.json │ │ │ ├── S3928.html │ │ │ ├── S3928.json │ │ │ ├── S3937.html │ │ │ ├── S3937.json │ │ │ ├── S3949.html │ │ │ ├── S3949.json │ │ │ ├── S3956.html │ │ │ ├── S3956.json │ │ │ ├── S3962.html │ │ │ ├── S3962.json │ │ │ ├── S3963.html │ │ │ ├── S3963.json │ │ │ ├── S3966.html │ │ │ ├── S3966.json │ │ │ ├── S3967.html │ │ │ ├── S3967.json │ │ │ ├── S3971.html │ │ │ ├── S3971.json │ │ │ ├── S3972.html │ │ │ ├── S3972.json │ │ │ ├── S3973.html │ │ │ ├── S3973.json │ │ │ ├── S3981.html │ │ │ ├── S3981.json │ │ │ ├── S3984.html │ │ │ ├── S3984.json │ │ │ ├── S3990.html │ │ │ ├── S3990.json │ │ │ ├── S3992.html │ │ │ ├── S3992.json │ │ │ ├── S3993.html │ │ │ ├── S3993.json │ │ │ ├── S3994.html │ │ │ ├── S3994.json │ │ │ ├── S3995.html │ │ │ ├── S3995.json │ │ │ ├── S3996.html │ │ │ ├── S3996.json │ │ │ ├── S3997.html │ │ │ ├── S3997.json │ │ │ ├── S3998.html │ │ │ ├── S3998.json │ │ │ ├── S4000.html │ │ │ ├── S4000.json │ │ │ ├── S4002.html │ │ │ ├── S4002.json │ │ │ ├── S4004.html │ │ │ ├── S4004.json │ │ │ ├── S4005.html │ │ │ ├── S4005.json │ │ │ ├── S4015.html │ │ │ ├── S4015.json │ │ │ ├── S4016.html │ │ │ ├── S4016.json │ │ │ ├── S4017.html │ │ │ ├── S4017.json │ │ │ ├── S4018.html │ │ │ ├── S4018.json │ │ │ ├── S4019.html │ │ │ ├── S4019.json │ │ │ ├── S4022.html │ │ │ ├── S4022.json │ │ │ ├── S4023.html │ │ │ ├── S4023.json │ │ │ ├── S4025.html │ │ │ ├── S4025.json │ │ │ ├── S4026.html │ │ │ ├── S4026.json │ │ │ ├── S4027.html │ │ │ ├── S4027.json │ │ │ ├── S4035.html │ │ │ ├── S4035.json │ │ │ ├── S4036.html │ │ │ ├── S4036.json │ │ │ ├── S4039.html │ │ │ ├── S4039.json │ │ │ ├── S4040.html │ │ │ ├── S4040.json │ │ │ ├── S4041.html │ │ │ ├── S4041.json │ │ │ ├── S4047.html │ │ │ ├── S4047.json │ │ │ ├── S4049.html │ │ │ ├── S4049.json │ │ │ ├── S4050.html │ │ │ ├── S4050.json │ │ │ ├── S4052.html │ │ │ ├── S4052.json │ │ │ ├── S4055.html │ │ │ ├── S4055.json │ │ │ ├── S4056.html │ │ │ ├── S4056.json │ │ │ ├── S4057.html │ │ │ ├── S4057.json │ │ │ ├── S4058.html │ │ │ ├── S4058.json │ │ │ ├── S4059.html │ │ │ ├── S4059.json │ │ │ ├── S4060.html │ │ │ ├── S4060.json │ │ │ ├── S4061.html │ │ │ ├── S4061.json │ │ │ ├── S4069.html │ │ │ ├── S4069.json │ │ │ ├── S4070.html │ │ │ ├── S4070.json │ │ │ ├── S4136.html │ │ │ ├── S4136.json │ │ │ ├── S4143.html │ │ │ ├── S4143.json │ │ │ ├── S4144.html │ │ │ ├── S4144.json │ │ │ ├── S4158.html │ │ │ ├── S4158.json │ │ │ ├── S4159.html │ │ │ ├── S4159.json │ │ │ ├── S4200.html │ │ │ ├── S4200.json │ │ │ ├── S4201.html │ │ │ ├── S4201.json │ │ │ ├── S4210.html │ │ │ ├── S4210.json │ │ │ ├── S4211.html │ │ │ ├── S4211.json │ │ │ ├── S4212.html │ │ │ ├── S4212.json │ │ │ ├── S4214.html │ │ │ ├── S4214.json │ │ │ ├── S4220.html │ │ │ ├── S4220.json │ │ │ ├── S4225.html │ │ │ ├── S4225.json │ │ │ ├── S4226.html │ │ │ ├── S4226.json │ │ │ ├── S4260.html │ │ │ ├── S4260.json │ │ │ ├── S4261.html │ │ │ ├── S4261.json │ │ │ ├── S4275.html │ │ │ ├── S4275.json │ │ │ ├── S4277.html │ │ │ ├── S4277.json │ │ │ ├── S4347.html │ │ │ ├── S4347.json │ │ │ ├── S4423.html │ │ │ ├── S4423.json │ │ │ ├── S4426.html │ │ │ ├── S4426.json │ │ │ ├── S4428.html │ │ │ ├── S4428.json │ │ │ ├── S4433.html │ │ │ ├── S4433.json │ │ │ ├── S4456.html │ │ │ ├── S4456.json │ │ │ ├── S4457.html │ │ │ ├── S4457.json │ │ │ ├── S4462.html │ │ │ ├── S4462.json │ │ │ ├── S4487.html │ │ │ ├── S4487.json │ │ │ ├── S4502.html │ │ │ ├── S4502.json │ │ │ ├── S4507.html │ │ │ ├── S4507.json │ │ │ ├── S4524.html │ │ │ ├── S4524.json │ │ │ ├── S4545.html │ │ │ ├── S4545.json │ │ │ ├── S4581.html │ │ │ ├── S4581.json │ │ │ ├── S4583.html │ │ │ ├── S4583.json │ │ │ ├── S4586.html │ │ │ ├── S4586.json │ │ │ ├── S4635.html │ │ │ ├── S4635.json │ │ │ ├── S4663.html │ │ │ ├── S4663.json │ │ │ ├── S4790.html │ │ │ ├── S4790.json │ │ │ ├── S4792.html │ │ │ ├── S4792.json │ │ │ ├── S4830.html │ │ │ ├── S4830.json │ │ │ ├── S5034.html │ │ │ ├── S5034.json │ │ │ ├── S5042.html │ │ │ ├── S5042.json │ │ │ ├── S5122.html │ │ │ ├── S5122.json │ │ │ ├── S5332.html │ │ │ ├── S5332.json │ │ │ ├── S5344.html │ │ │ ├── S5344.json │ │ │ ├── S5443.html │ │ │ ├── S5443.json │ │ │ ├── S5445.html │ │ │ ├── S5445.json │ │ │ ├── S5542.html │ │ │ ├── S5542.json │ │ │ ├── S5547.html │ │ │ ├── S5547.json │ │ │ ├── S5659.html │ │ │ ├── S5659.json │ │ │ ├── S5693.html │ │ │ ├── S5693.json │ │ │ ├── S5753.html │ │ │ ├── S5753.json │ │ │ ├── S5766.html │ │ │ ├── S5766.json │ │ │ ├── S5773.html │ │ │ ├── S5773.json │ │ │ ├── S5856.html │ │ │ ├── S5856.json │ │ │ ├── S6354.html │ │ │ ├── S6354.json │ │ │ ├── S6377.html │ │ │ ├── S6377.json │ │ │ ├── S6418.html │ │ │ ├── S6418.json │ │ │ ├── S6419.html │ │ │ ├── S6419.json │ │ │ ├── S6420.html │ │ │ ├── S6420.json │ │ │ ├── S6421.html │ │ │ ├── S6421.json │ │ │ ├── S6422.html │ │ │ ├── S6422.json │ │ │ ├── S6423.html │ │ │ ├── S6423.json │ │ │ ├── S6424.html │ │ │ ├── S6424.json │ │ │ ├── S6444.html │ │ │ ├── S6444.json │ │ │ ├── S6507.html │ │ │ ├── S6507.json │ │ │ ├── S6513.html │ │ │ ├── S6513.json │ │ │ ├── S6561.html │ │ │ ├── S6561.json │ │ │ ├── S6562.html │ │ │ ├── S6562.json │ │ │ ├── S6563.html │ │ │ ├── S6563.json │ │ │ ├── S6566.html │ │ │ ├── S6566.json │ │ │ ├── S6575.html │ │ │ ├── S6575.json │ │ │ ├── S6580.html │ │ │ ├── S6580.json │ │ │ ├── S6585.html │ │ │ ├── S6585.json │ │ │ ├── S6588.html │ │ │ ├── S6588.json │ │ │ ├── S6602.html │ │ │ ├── S6602.json │ │ │ ├── S6603.html │ │ │ ├── S6603.json │ │ │ ├── S6605.html │ │ │ ├── S6605.json │ │ │ ├── S6607.html │ │ │ ├── S6607.json │ │ │ ├── S6608.html │ │ │ ├── S6608.json │ │ │ ├── S6609.html │ │ │ ├── S6609.json │ │ │ ├── S6610.html │ │ │ ├── S6610.json │ │ │ ├── S6612.html │ │ │ ├── S6612.json │ │ │ ├── S6613.html │ │ │ ├── S6613.json │ │ │ ├── S6617.html │ │ │ ├── S6617.json │ │ │ ├── S6618.html │ │ │ ├── S6618.json │ │ │ ├── S6640.html │ │ │ ├── S6640.json │ │ │ ├── S6664.html │ │ │ ├── S6664.json │ │ │ ├── S6667.html │ │ │ ├── S6667.json │ │ │ ├── S6668.html │ │ │ ├── S6668.json │ │ │ ├── S6669.html │ │ │ ├── S6669.json │ │ │ ├── S6670.html │ │ │ ├── S6670.json │ │ │ ├── S6672.html │ │ │ ├── S6672.json │ │ │ ├── S6673.html │ │ │ ├── S6673.json │ │ │ ├── S6674.html │ │ │ ├── S6674.json │ │ │ ├── S6675.html │ │ │ ├── S6675.json │ │ │ ├── S6677.html │ │ │ ├── S6677.json │ │ │ ├── S6678.html │ │ │ ├── S6678.json │ │ │ ├── S6781.html │ │ │ ├── S6781.json │ │ │ ├── S6797.html │ │ │ ├── S6797.json │ │ │ ├── S6798.html │ │ │ ├── S6798.json │ │ │ ├── S6800.html │ │ │ ├── S6800.json │ │ │ ├── S6802.html │ │ │ ├── S6802.json │ │ │ ├── S6803.html │ │ │ ├── S6803.json │ │ │ ├── S6930.html │ │ │ ├── S6930.json │ │ │ ├── S6931.html │ │ │ ├── S6931.json │ │ │ ├── S6932.html │ │ │ ├── S6932.json │ │ │ ├── S6934.html │ │ │ ├── S6934.json │ │ │ ├── S6960.html │ │ │ ├── S6960.json │ │ │ ├── S6961.html │ │ │ ├── S6961.json │ │ │ ├── S6962.html │ │ │ ├── S6962.json │ │ │ ├── S6964.html │ │ │ ├── S6964.json │ │ │ ├── S6965.html │ │ │ ├── S6965.json │ │ │ ├── S6966.html │ │ │ ├── S6966.json │ │ │ ├── S6967.html │ │ │ ├── S6967.json │ │ │ ├── S6968.html │ │ │ ├── S6968.json │ │ │ ├── S7039.html │ │ │ ├── S7039.json │ │ │ ├── S7130.html │ │ │ ├── S7130.json │ │ │ ├── S7131.html │ │ │ ├── S7131.json │ │ │ ├── S7133.html │ │ │ ├── S7133.json │ │ │ ├── S818.html │ │ │ ├── S818.json │ │ │ ├── S8367.html │ │ │ ├── S8367.json │ │ │ ├── S8368.html │ │ │ ├── S8368.json │ │ │ ├── S8380.html │ │ │ ├── S8380.json │ │ │ ├── S8381.html │ │ │ ├── S8381.json │ │ │ ├── S881.html │ │ │ ├── S881.json │ │ │ ├── S907.html │ │ │ ├── S907.json │ │ │ ├── S927.html │ │ │ ├── S927.json │ │ │ └── Sonar_way_profile.json │ │ └── vbnet/ │ │ ├── S101.html │ │ ├── S101.json │ │ ├── S103.html │ │ ├── S103.json │ │ ├── S104.html │ │ ├── S104.json │ │ ├── S1048.html │ │ ├── S1048.json │ │ ├── S105.html │ │ ├── S105.json │ │ ├── S1066.html │ │ ├── S1066.json │ │ ├── S1067.html │ │ ├── S1067.json │ │ ├── S107.html │ │ ├── S107.json │ │ ├── S1075.html │ │ ├── S1075.json │ │ ├── S108.html │ │ ├── S108.json │ │ ├── S1110.html │ │ ├── S1110.json │ │ ├── S112.html │ │ ├── S112.json │ │ ├── S1123.html │ │ ├── S1123.json │ │ ├── S1125.html │ │ ├── S1125.json │ │ ├── S1133.html │ │ ├── S1133.json │ │ ├── S1134.html │ │ ├── S1134.json │ │ ├── S1135.html │ │ ├── S1135.json │ │ ├── S114.html │ │ ├── S114.json │ │ ├── S1147.html │ │ ├── S1147.json │ │ ├── S1151.html │ │ ├── S1151.json │ │ ├── S1155.html │ │ ├── S1155.json │ │ ├── S1163.html │ │ ├── S1163.json │ │ ├── S117.html │ │ ├── S117.json │ │ ├── S1172.html │ │ ├── S1172.json │ │ ├── S1186.html │ │ ├── S1186.json │ │ ├── S119.html │ │ ├── S119.json │ │ ├── S1192.html │ │ ├── S1192.json │ │ ├── S1197.html │ │ ├── S1197.json │ │ ├── S122.html │ │ ├── S122.json │ │ ├── S1226.html │ │ ├── S1226.json │ │ ├── S126.html │ │ ├── S126.json │ │ ├── S1301.html │ │ ├── S1301.json │ │ ├── S131.html │ │ ├── S131.json │ │ ├── S1313.html │ │ ├── S1313.json │ │ ├── S134.html │ │ ├── S134.json │ │ ├── S138.html │ │ ├── S138.json │ │ ├── S139.html │ │ ├── S139.json │ │ ├── S1451.html │ │ ├── S1451.json │ │ ├── S1479.html │ │ ├── S1479.json │ │ ├── S1481.html │ │ ├── S1481.json │ │ ├── S1541.html │ │ ├── S1541.json │ │ ├── S1542.html │ │ ├── S1542.json │ │ ├── S1643.html │ │ ├── S1643.json │ │ ├── S1645.html │ │ ├── S1645.json │ │ ├── S1654.html │ │ ├── S1654.json │ │ ├── S1656.html │ │ ├── S1656.json │ │ ├── S1659.html │ │ ├── S1659.json │ │ ├── S1751.html │ │ ├── S1751.json │ │ ├── S1764.html │ │ ├── S1764.json │ │ ├── S1821.html │ │ ├── S1821.json │ │ ├── S1862.html │ │ ├── S1862.json │ │ ├── S1871.html │ │ ├── S1871.json │ │ ├── S1940.html │ │ ├── S1940.json │ │ ├── S1944.html │ │ ├── S1944.json │ │ ├── S2053.html │ │ ├── S2053.json │ │ ├── S2068.html │ │ ├── S2068.json │ │ ├── S2077.html │ │ ├── S2077.json │ │ ├── S2094.html │ │ ├── S2094.json │ │ ├── S2166.html │ │ ├── S2166.json │ │ ├── S2178.html │ │ ├── S2178.json │ │ ├── S2222.html │ │ ├── S2222.json │ │ ├── S2225.html │ │ ├── S2225.json │ │ ├── S2234.html │ │ ├── S2234.json │ │ ├── S2257.html │ │ ├── S2257.json │ │ ├── S2259.html │ │ ├── S2259.json │ │ ├── S2302.html │ │ ├── S2302.json │ │ ├── S2304.html │ │ ├── S2304.json │ │ ├── S2339.html │ │ ├── S2339.json │ │ ├── S2340.html │ │ ├── S2340.json │ │ ├── S2342.html │ │ ├── S2342.json │ │ ├── S2343.html │ │ ├── S2343.json │ │ ├── S2344.html │ │ ├── S2344.json │ │ ├── S2345.html │ │ ├── S2345.json │ │ ├── S2346.html │ │ ├── S2346.json │ │ ├── S2347.html │ │ ├── S2347.json │ │ ├── S2348.html │ │ ├── S2348.json │ │ ├── S2349.html │ │ ├── S2349.json │ │ ├── S2352.html │ │ ├── S2352.json │ │ ├── S2354.html │ │ ├── S2354.json │ │ ├── S2355.html │ │ ├── S2355.json │ │ ├── S2357.html │ │ ├── S2357.json │ │ ├── S2358.html │ │ ├── S2358.json │ │ ├── S2359.html │ │ ├── S2359.json │ │ ├── S2360.html │ │ ├── S2360.json │ │ ├── S2362.html │ │ ├── S2362.json │ │ ├── S2363.html │ │ ├── S2363.json │ │ ├── S2364.html │ │ ├── S2364.json │ │ ├── S2365.html │ │ ├── S2365.json │ │ ├── S2366.html │ │ ├── S2366.json │ │ ├── S2367.html │ │ ├── S2367.json │ │ ├── S2368.html │ │ ├── S2368.json │ │ ├── S2369.html │ │ ├── S2369.json │ │ ├── S2370.html │ │ ├── S2370.json │ │ ├── S2372.html │ │ ├── S2372.json │ │ ├── S2373.html │ │ ├── S2373.json │ │ ├── S2374.html │ │ ├── S2374.json │ │ ├── S2375.html │ │ ├── S2375.json │ │ ├── S2376.html │ │ ├── S2376.json │ │ ├── S2387.html │ │ ├── S2387.json │ │ ├── S2429.html │ │ ├── S2429.json │ │ ├── S2437.html │ │ ├── S2437.json │ │ ├── S2551.html │ │ ├── S2551.json │ │ ├── S2583.html │ │ ├── S2583.json │ │ ├── S2589.html │ │ ├── S2589.json │ │ ├── S2612.html │ │ ├── S2612.json │ │ ├── S2692.html │ │ ├── S2692.json │ │ ├── S2737.html │ │ ├── S2737.json │ │ ├── S2757.html │ │ ├── S2757.json │ │ ├── S2761.html │ │ ├── S2761.json │ │ ├── S2925.html │ │ ├── S2925.json │ │ ├── S2951.html │ │ ├── S2951.json │ │ ├── S3011.html │ │ ├── S3011.json │ │ ├── S3063.html │ │ ├── S3063.json │ │ ├── S3329.html │ │ ├── S3329.json │ │ ├── S3358.html │ │ ├── S3358.json │ │ ├── S3363.html │ │ ├── S3363.json │ │ ├── S3385.html │ │ ├── S3385.json │ │ ├── S3431.html │ │ ├── S3431.json │ │ ├── S3449.html │ │ ├── S3449.json │ │ ├── S3453.html │ │ ├── S3453.json │ │ ├── S3464.html │ │ ├── S3464.json │ │ ├── S3466.html │ │ ├── S3466.json │ │ ├── S3598.html │ │ ├── S3598.json │ │ ├── S3603.html │ │ ├── S3603.json │ │ ├── S3655.html │ │ ├── S3655.json │ │ ├── S3776.html │ │ ├── S3776.json │ │ ├── S3860.html │ │ ├── S3860.json │ │ ├── S3866.html │ │ ├── S3866.json │ │ ├── S3869.html │ │ ├── S3869.json │ │ ├── S3871.html │ │ ├── S3871.json │ │ ├── S3878.html │ │ ├── S3878.json │ │ ├── S3884.html │ │ ├── S3884.json │ │ ├── S3889.html │ │ ├── S3889.json │ │ ├── S3898.html │ │ ├── S3898.json │ │ ├── S3900.html │ │ ├── S3900.json │ │ ├── S3903.html │ │ ├── S3903.json │ │ ├── S3904.html │ │ ├── S3904.json │ │ ├── S3923.html │ │ ├── S3923.json │ │ ├── S3926.html │ │ ├── S3926.json │ │ ├── S3927.html │ │ ├── S3927.json │ │ ├── S3949.html │ │ ├── S3949.json │ │ ├── S3966.html │ │ ├── S3966.json │ │ ├── S3981.html │ │ ├── S3981.json │ │ ├── S3990.html │ │ ├── S3990.json │ │ ├── S3992.html │ │ ├── S3992.json │ │ ├── S3998.html │ │ ├── S3998.json │ │ ├── S4025.html │ │ ├── S4025.json │ │ ├── S4036.html │ │ ├── S4036.json │ │ ├── S4060.html │ │ ├── S4060.json │ │ ├── S4136.html │ │ ├── S4136.json │ │ ├── S4143.html │ │ ├── S4143.json │ │ ├── S4144.html │ │ ├── S4144.json │ │ ├── S4158.html │ │ ├── S4158.json │ │ ├── S4159.html │ │ ├── S4159.json │ │ ├── S4201.html │ │ ├── S4201.json │ │ ├── S4210.html │ │ ├── S4210.json │ │ ├── S4225.html │ │ ├── S4225.json │ │ ├── S4260.html │ │ ├── S4260.json │ │ ├── S4275.html │ │ ├── S4275.json │ │ ├── S4277.html │ │ ├── S4277.json │ │ ├── S4423.html │ │ ├── S4423.json │ │ ├── S4428.html │ │ ├── S4428.json │ │ ├── S4507.html │ │ ├── S4507.json │ │ ├── S4545.html │ │ ├── S4545.json │ │ ├── S4581.html │ │ ├── S4581.json │ │ ├── S4583.html │ │ ├── S4583.json │ │ ├── S4586.html │ │ ├── S4586.json │ │ ├── S4663.html │ │ ├── S4663.json │ │ ├── S4790.html │ │ ├── S4790.json │ │ ├── S4792.html │ │ ├── S4792.json │ │ ├── S4830.html │ │ ├── S4830.json │ │ ├── S5042.html │ │ ├── S5042.json │ │ ├── S5443.html │ │ ├── S5443.json │ │ ├── S5445.html │ │ ├── S5445.json │ │ ├── S5542.html │ │ ├── S5542.json │ │ ├── S5547.html │ │ ├── S5547.json │ │ ├── S5659.html │ │ ├── S5659.json │ │ ├── S5693.html │ │ ├── S5693.json │ │ ├── S5753.html │ │ ├── S5753.json │ │ ├── S5773.html │ │ ├── S5773.json │ │ ├── S5856.html │ │ ├── S5856.json │ │ ├── S5944.html │ │ ├── S5944.json │ │ ├── S6145.html │ │ ├── S6145.json │ │ ├── S6146.html │ │ ├── S6146.json │ │ ├── S6354.html │ │ ├── S6354.json │ │ ├── S6418.html │ │ ├── S6418.json │ │ ├── S6444.html │ │ ├── S6444.json │ │ ├── S6513.html │ │ ├── S6513.json │ │ ├── S6561.html │ │ ├── S6561.json │ │ ├── S6562.html │ │ ├── S6562.json │ │ ├── S6563.html │ │ ├── S6563.json │ │ ├── S6566.html │ │ ├── S6566.json │ │ ├── S6575.html │ │ ├── S6575.json │ │ ├── S6580.html │ │ ├── S6580.json │ │ ├── S6585.html │ │ ├── S6585.json │ │ ├── S6588.html │ │ ├── S6588.json │ │ ├── S6602.html │ │ ├── S6602.json │ │ ├── S6603.html │ │ ├── S6603.json │ │ ├── S6605.html │ │ ├── S6605.json │ │ ├── S6607.html │ │ ├── S6607.json │ │ ├── S6608.html │ │ ├── S6608.json │ │ ├── S6609.html │ │ ├── S6609.json │ │ ├── S6610.html │ │ ├── S6610.json │ │ ├── S6612.html │ │ ├── S6612.json │ │ ├── S6613.html │ │ ├── S6613.json │ │ ├── S6617.html │ │ ├── S6617.json │ │ ├── S6930.html │ │ ├── S6930.json │ │ ├── S6931.html │ │ ├── S6931.json │ │ ├── S7130.html │ │ ├── S7130.json │ │ ├── S7131.html │ │ ├── S7131.json │ │ ├── S7133.html │ │ ├── S7133.json │ │ ├── S907.html │ │ ├── S907.json │ │ ├── S927.html │ │ ├── S927.json │ │ └── Sonar_way_profile.json │ ├── src/ │ │ ├── AssemblyInfo.Shared.cs │ │ ├── Directory.Build.targets │ │ ├── RuleCatalog.targets │ │ ├── RuleDescriptorGenerator/ │ │ │ ├── Descriptors/ │ │ │ │ ├── Rule.cs │ │ │ │ └── RuleParameter.cs │ │ │ ├── Program.cs │ │ │ ├── RuleDescriptorGenerator.csproj │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.CFG/ │ │ │ ├── CfgSerializer/ │ │ │ │ ├── CfgSerializer.RoslynCfgWalker.cs │ │ │ │ ├── CfgSerializer.RoslynLvaWalker.cs │ │ │ │ ├── CfgSerializer.SonarCfgWalker.cs │ │ │ │ ├── CfgSerializer.cs │ │ │ │ └── DotWriter.cs │ │ │ ├── Common/ │ │ │ │ ├── RoslynVersion.cs │ │ │ │ └── UniqueQueue.cs │ │ │ ├── Extensions/ │ │ │ │ ├── BasicBlockExtensions.cs │ │ │ │ ├── ControlFlowGraphExtensions.cs │ │ │ │ ├── ControlFlowRegionExtensions.cs │ │ │ │ ├── DictionaryExtensions.cs │ │ │ │ ├── ExpressionSyntaxExtensions.cs │ │ │ │ ├── IEnumerableExtensions.cs │ │ │ │ ├── IOperationExtensions.cs │ │ │ │ ├── IsPatternExpressionSyntaxWrapperExtensions.cs │ │ │ │ ├── PatternSyntaxWrapperExtensions.cs │ │ │ │ ├── PropertyInfoExtensions.cs │ │ │ │ ├── SemanticModelExtensions.cs │ │ │ │ ├── StringExtensions.cs │ │ │ │ ├── SyntaxNodeExtensions.cs │ │ │ │ └── UnaryPatternSyntaxWrapperExtensions.cs │ │ │ ├── LiveVariableAnalysis/ │ │ │ │ ├── LiveVariableAnalysisBase.cs │ │ │ │ └── RoslynLiveVariableAnalysis.cs │ │ │ ├── Operations/ │ │ │ │ └── Utilities/ │ │ │ │ ├── OperationExecutionOrder.cs │ │ │ │ └── OperationFinder.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── Roslyn/ │ │ │ │ ├── BasicBlock.cs │ │ │ │ ├── CfgAllPathValidator.cs │ │ │ │ ├── ControlFlowBranch.cs │ │ │ │ ├── ControlFlowGraph.cs │ │ │ │ ├── ControlFlowGraphCache.cs │ │ │ │ ├── ControlFlowRegion.cs │ │ │ │ └── TypeLoader.cs │ │ │ ├── Sonar/ │ │ │ │ ├── AbstractControlFlowGraphBuilder.cs │ │ │ │ ├── BlockIdProvider.cs │ │ │ │ ├── Blocks/ │ │ │ │ │ ├── BinaryBranchBlock.cs │ │ │ │ │ ├── BinaryBranchingSimpleBlock.cs │ │ │ │ │ ├── Block.cs │ │ │ │ │ ├── BranchBlock.cs │ │ │ │ │ ├── ExitBlock.cs │ │ │ │ │ ├── ForInitializerBlock.cs │ │ │ │ │ ├── ForeachCollectionProducerBlock.cs │ │ │ │ │ ├── JumpBlock.cs │ │ │ │ │ ├── LockBlock.cs │ │ │ │ │ ├── SimpleBlock.cs │ │ │ │ │ ├── TemporaryBlock.cs │ │ │ │ │ └── UsingEndBlock.cs │ │ │ │ ├── CSharpControlFlowGraph.cs │ │ │ │ ├── CSharpControlFlowGraphBuilder.cs │ │ │ │ ├── CfgAllPathValidator.cs │ │ │ │ └── IControlFlowGraph.cs │ │ │ ├── SonarAnalyzer.CFG.csproj │ │ │ ├── Syntax/ │ │ │ │ └── Utilities/ │ │ │ │ └── SyntaxClassifierBase.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.CSharp/ │ │ │ ├── Metrics/ │ │ │ │ ├── CSharpCognitiveComplexityMetric.cs │ │ │ │ ├── CSharpCyclomaticComplexityMetric.cs │ │ │ │ ├── CSharpExecutableLinesMetric.cs │ │ │ │ └── CSharpMetrics.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── Rules/ │ │ │ │ ├── AbstractClassToInterface.cs │ │ │ │ ├── AbstractTypesShouldNotHaveConstructors.cs │ │ │ │ ├── AllBranchesShouldNotHaveSameImplementation.cs │ │ │ │ ├── AlwaysSetDateTimeKind.cs │ │ │ │ ├── AnonymousDelegateEventUnsubscribe.cs │ │ │ │ ├── ArgumentSpecifiedForCallerInfoParameter.cs │ │ │ │ ├── ArrayCovariance.cs │ │ │ │ ├── ArrayPassedAsParams.cs │ │ │ │ ├── AspNet/ │ │ │ │ │ ├── AnnotateApiActionsWithHttpVerb.cs │ │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromController.cs │ │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.cs │ │ │ │ │ ├── AvoidUnderPosting.cs │ │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.cs │ │ │ │ │ ├── CallModelStateIsValid.cs │ │ │ │ │ ├── ControllersHaveMixedResponsibilities.cs │ │ │ │ │ ├── ControllersReuseClient.cs │ │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.cs │ │ │ │ │ ├── SpecifyRouteAttribute.cs │ │ │ │ │ └── UseAspNetModelBinding.cs │ │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.cs │ │ │ │ ├── AssertionsShouldBeComplete.cs │ │ │ │ ├── AssignmentInsideSubExpression.cs │ │ │ │ ├── AsyncAwaitIdentifier.cs │ │ │ │ ├── AsyncVoidMethod.cs │ │ │ │ ├── AvoidDateTimeNowForBenchmarking.cs │ │ │ │ ├── AvoidExcessiveClassCoupling.cs │ │ │ │ ├── AvoidExcessiveInheritance.cs │ │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.cs │ │ │ │ ├── AvoidUnsealedAttributes.cs │ │ │ │ ├── BeginInvokePairedWithEndInvoke.cs │ │ │ │ ├── BinaryOperationWithIdenticalExpressions.cs │ │ │ │ ├── BlazorQueryParameterRoutableComponent.cs │ │ │ │ ├── BooleanCheckInverted.cs │ │ │ │ ├── BooleanCheckInvertedCodeFix.cs │ │ │ │ ├── BooleanLiteralUnnecessary.cs │ │ │ │ ├── BooleanLiteralUnnecessaryCodeFix.cs │ │ │ │ ├── BreakOutsideSwitch.cs │ │ │ │ ├── BypassingAccessibility.cs │ │ │ │ ├── CallToAsyncMethodShouldNotBeBlocking.cs │ │ │ │ ├── CallerInformationParametersShouldBeLast.cs │ │ │ │ ├── CastConcreteTypeToInterface.cs │ │ │ │ ├── CastShouldNotBeDuplicated.cs │ │ │ │ ├── CatchEmpty.cs │ │ │ │ ├── CatchRethrow.cs │ │ │ │ ├── CatchRethrowCodeFix.cs │ │ │ │ ├── CertificateValidationCheck.cs │ │ │ │ ├── CheckArgumentException.cs │ │ │ │ ├── CheckFileLicense.cs │ │ │ │ ├── CheckFileLicenseCodeFix.cs │ │ │ │ ├── ClassAndMethodName.cs │ │ │ │ ├── ClassNamedException.cs │ │ │ │ ├── ClassNotInstantiatable.cs │ │ │ │ ├── ClassShouldNotBeEmpty.cs │ │ │ │ ├── ClassWithEqualityShouldImplementIEquatable.cs │ │ │ │ ├── ClassWithOnlyStaticMember.cs │ │ │ │ ├── CloudNative/ │ │ │ │ │ ├── AzureFunctionsCatchExceptions.cs │ │ │ │ │ ├── AzureFunctionsLogFailures.cs │ │ │ │ │ ├── AzureFunctionsReuseClients.cs │ │ │ │ │ ├── AzureFunctionsStateless.cs │ │ │ │ │ └── DurableEntityInterfaceRestrictions.cs │ │ │ │ ├── CognitiveComplexity.cs │ │ │ │ ├── CollectionEmptinessChecking.cs │ │ │ │ ├── CollectionEmptinessCheckingCodeFix.cs │ │ │ │ ├── CollectionPropertiesShouldBeReadOnly.cs │ │ │ │ ├── CollectionQuerySimplification.cs │ │ │ │ ├── CollectionsShouldImplementGenericInterface.cs │ │ │ │ ├── CommentKeyword.cs │ │ │ │ ├── CommentedOutCode.cs │ │ │ │ ├── CommentedOutCodeCodeFix.cs │ │ │ │ ├── CommentsShouldNotBeEmpty.cs │ │ │ │ ├── ComparableInterfaceImplementation.cs │ │ │ │ ├── CompareNaN.cs │ │ │ │ ├── ConditionalSimplification.cs │ │ │ │ ├── ConditionalSimplificationCodeFix.cs │ │ │ │ ├── ConditionalStructureSameCondition.cs │ │ │ │ ├── ConditionalStructureSameImplementation.cs │ │ │ │ ├── ConditionalsShouldStartOnNewLine.cs │ │ │ │ ├── ConditionalsWithSameCondition.cs │ │ │ │ ├── ConstructorArgumentValueShouldExist.cs │ │ │ │ ├── ConstructorOverridableCall.cs │ │ │ │ ├── ConsumeValueTaskCorrectly.cs │ │ │ │ ├── ControlCharacterInString.cs │ │ │ │ ├── CryptographicKeyShouldNotBeTooShort.cs │ │ │ │ ├── DangerousGetHandleShouldNotBeCalled.cs │ │ │ │ ├── DatabasePasswordsShouldBeSecure.cs │ │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.cs │ │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.cs │ │ │ │ ├── DeadStores.RoslynCfg.cs │ │ │ │ ├── DeadStores.SonarCfg.cs │ │ │ │ ├── DeadStores.cs │ │ │ │ ├── DebugAssertHasNoSideEffects.cs │ │ │ │ ├── DebuggerDisplayUsesExistingMembers.cs │ │ │ │ ├── DeclareEventHandlersCorrectly.cs │ │ │ │ ├── DeclareTypesInNamespaces.cs │ │ │ │ ├── DefaultSectionShouldBeFirstOrLast.cs │ │ │ │ ├── DelegateSubtraction.cs │ │ │ │ ├── DisposableMemberInNonDisposableClass.cs │ │ │ │ ├── DisposableNotDisposed.cs │ │ │ │ ├── DisposableReturnedFromUsing.cs │ │ │ │ ├── DisposableTypesNeedFinalizers.cs │ │ │ │ ├── DisposeFromDispose.cs │ │ │ │ ├── DisposeNotImplementingDispose.cs │ │ │ │ ├── DoNotCallAssemblyGetExecutingAssemblyMethod.cs │ │ │ │ ├── DoNotCallAssemblyLoadInvalidMethods.cs │ │ │ │ ├── DoNotCallExitMethods.cs │ │ │ │ ├── DoNotCallGCCollectMethod.cs │ │ │ │ ├── DoNotCallGCSuppressFinalize.cs │ │ │ │ ├── DoNotCallMethodsCsharpBase.cs │ │ │ │ ├── DoNotCatchNullReferenceException.cs │ │ │ │ ├── DoNotCatchSystemException.cs │ │ │ │ ├── DoNotCheckZeroSizeCollection.cs │ │ │ │ ├── DoNotCopyArraysInProperties.cs │ │ │ │ ├── DoNotDecreaseMemberVisibility.cs │ │ │ │ ├── DoNotExposeListT.cs │ │ │ │ ├── DoNotHardcodeCredentials.cs │ │ │ │ ├── DoNotHardcodeSecrets.cs │ │ │ │ ├── DoNotHideBaseClassMethods.cs │ │ │ │ ├── DoNotInstantiateSharedClasses.cs │ │ │ │ ├── DoNotLockOnSharedResource.cs │ │ │ │ ├── DoNotLockWeakIdentityObjects.cs │ │ │ │ ├── DoNotMarkEnumsWithFlags.cs │ │ │ │ ├── DoNotNestTernaryOperators.cs │ │ │ │ ├── DoNotNestTypesInArguments.cs │ │ │ │ ├── DoNotOverloadOperatorEqual.cs │ │ │ │ ├── DoNotOverwriteCollectionElements.cs │ │ │ │ ├── DoNotShiftByZeroOrIntSize.cs │ │ │ │ ├── DoNotTestThisWithIsOperator.cs │ │ │ │ ├── DoNotThrowFromDestructors.cs │ │ │ │ ├── DoNotUseCollectionInItsOwnMethodCalls.cs │ │ │ │ ├── DoNotUseDateTimeNow.cs │ │ │ │ ├── DoNotUseLiteralBoolInAssertions.cs │ │ │ │ ├── DoNotUseOutRefParameters.cs │ │ │ │ ├── DoNotWriteToStandardOutput.cs │ │ │ │ ├── DontMixIncrementOrDecrementWithOtherOperators.cs │ │ │ │ ├── DontUseTraceSwitchLevels.cs │ │ │ │ ├── DontUseTraceWrite.cs │ │ │ │ ├── EmptyMethod.cs │ │ │ │ ├── EmptyMethodCodeFix.cs │ │ │ │ ├── EmptyNamespace.cs │ │ │ │ ├── EmptyNamespaceCodeFix.cs │ │ │ │ ├── EmptyNestedBlock.cs │ │ │ │ ├── EmptyStatement.cs │ │ │ │ ├── EmptyStatementCodeFix.cs │ │ │ │ ├── EncryptionAlgorithmsShouldBeSecure.cs │ │ │ │ ├── EnumNameHasEnumSuffix.cs │ │ │ │ ├── EnumNameShouldFollowRegex.cs │ │ │ │ ├── EnumStorageNeedsToBeInt32.cs │ │ │ │ ├── EnumerableSumInUnchecked.cs │ │ │ │ ├── EnumsShouldNotBeNamedReserved.cs │ │ │ │ ├── EqualityOnFloatingPoint.cs │ │ │ │ ├── EqualityOnModulus.cs │ │ │ │ ├── EquatableClassShouldBeSealed.cs │ │ │ │ ├── EscapeLambdaParameterTypeNamedScoped.cs │ │ │ │ ├── EventHandlerDelegateShouldHaveProperArguments.cs │ │ │ │ ├── ExceptionRethrow.cs │ │ │ │ ├── ExceptionRethrowCodeFix.cs │ │ │ │ ├── ExceptionShouldNotBeThrownFromUnexpectedMethods.cs │ │ │ │ ├── ExceptionsNeedStandardConstructors.cs │ │ │ │ ├── ExceptionsShouldBeLogged.cs │ │ │ │ ├── ExceptionsShouldBeLoggedOrThrown.cs │ │ │ │ ├── ExceptionsShouldBePublic.cs │ │ │ │ ├── ExceptionsShouldBeUsed.cs │ │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.cs │ │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.cs │ │ │ │ ├── ExpressionComplexity.cs │ │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespace.cs │ │ │ │ ├── ExtensionMethodShouldNotExtendObject.cs │ │ │ │ ├── FieldShadowsParentField.cs │ │ │ │ ├── FieldShouldBeReadonly.cs │ │ │ │ ├── FieldShouldBeReadonlyCodeFix.cs │ │ │ │ ├── FieldShouldNotBePublic.cs │ │ │ │ ├── FieldsShouldBeEncapsulatedInProperties.cs │ │ │ │ ├── FileLines.cs │ │ │ │ ├── FileShouldEndWithEmptyNewLine.cs │ │ │ │ ├── FinalizerShouldNotBeEmpty.cs │ │ │ │ ├── FindInsteadOfFirstOrDefault.cs │ │ │ │ ├── FlagsEnumWithoutInitializer.cs │ │ │ │ ├── FlagsEnumZeroMember.cs │ │ │ │ ├── ForLoopConditionAlwaysFalse.cs │ │ │ │ ├── ForLoopCounterChanged.cs │ │ │ │ ├── ForLoopCounterCondition.cs │ │ │ │ ├── ForLoopIncrementSign.cs │ │ │ │ ├── ForeachLoopExplicitConversion.cs │ │ │ │ ├── ForeachLoopExplicitConversionCodeFix.cs │ │ │ │ ├── FrameworkTypeNaming.cs │ │ │ │ ├── FunctionComplexity.cs │ │ │ │ ├── FunctionNestingDepth.cs │ │ │ │ ├── GenericInheritanceShouldNotBeRecursive.cs │ │ │ │ ├── GenericLoggerInjectionShouldMatchEnclosingType.cs │ │ │ │ ├── GenericReadonlyFieldPropertyAssignment.cs │ │ │ │ ├── GenericReadonlyFieldPropertyAssignmentCodeFix.cs │ │ │ │ ├── GenericTypeParameterEmptinessChecking.cs │ │ │ │ ├── GenericTypeParameterEmptinessCheckingCodeFix.cs │ │ │ │ ├── GenericTypeParameterInOut.cs │ │ │ │ ├── GenericTypeParameterUnused.cs │ │ │ │ ├── GenericTypeParametersRequired.cs │ │ │ │ ├── GetHashCodeEqualsOverride.cs │ │ │ │ ├── GetHashCodeMutable.cs │ │ │ │ ├── GetHashCodeMutableCodeFix.cs │ │ │ │ ├── GetTypeWithIsAssignableFrom.cs │ │ │ │ ├── GetTypeWithIsAssignableFromCodeFix.cs │ │ │ │ ├── GotoStatement.cs │ │ │ │ ├── GuardConditionOnEqualsOverride.cs │ │ │ │ ├── Hotspots/ │ │ │ │ │ ├── ClearTextProtocolsAreSensitive.cs │ │ │ │ │ ├── CommandPath.cs │ │ │ │ │ ├── ConfiguringLoggers.cs │ │ │ │ │ ├── CookieShouldBeHttpOnly.cs │ │ │ │ │ ├── CookieShouldBeSecure.cs │ │ │ │ │ ├── CreatingHashAlgorithms.cs │ │ │ │ │ ├── DeliveringDebugFeaturesInProduction.cs │ │ │ │ │ ├── DisablingCsrfProtection.cs │ │ │ │ │ ├── DisablingRequestValidation.cs │ │ │ │ │ ├── DoNotUseRandom.cs │ │ │ │ │ ├── ExecutingSqlQueries.cs │ │ │ │ │ ├── ExpandingArchives.cs │ │ │ │ │ ├── HardcodedIpAddress.cs │ │ │ │ │ ├── InsecureDeserialization.cs │ │ │ │ │ ├── PermissiveCors.cs │ │ │ │ │ ├── PubliclyWritableDirectories.cs │ │ │ │ │ ├── RequestsWithExcessiveLength.cs │ │ │ │ │ ├── SpecifyTimeoutOnRegex.cs │ │ │ │ │ ├── UnsafeCodeBlocks.cs │ │ │ │ │ └── UsingNonstandardCryptography.cs │ │ │ │ ├── IdentifiersNamedExtensionShouldBeEscaped.cs │ │ │ │ ├── IdentifiersNamedFieldShouldBeEscaped.cs │ │ │ │ ├── IfChainWithoutElse.cs │ │ │ │ ├── IfCollapsible.cs │ │ │ │ ├── ImplementIDisposableCorrectly.cs │ │ │ │ ├── ImplementISerializableCorrectly.cs │ │ │ │ ├── ImplementSerializationMethodsCorrectly.cs │ │ │ │ ├── IndentSingleLineFollowingConditional.cs │ │ │ │ ├── IndexOfCheckAgainstZero.cs │ │ │ │ ├── InfiniteRecursion.RoslynCfg.cs │ │ │ │ ├── InfiniteRecursion.SonarCfg.cs │ │ │ │ ├── InfiniteRecursion.cs │ │ │ │ ├── InheritedCollidingInterfaceMembers.cs │ │ │ │ ├── InitializeStaticFieldsInline.cs │ │ │ │ ├── InsecureContentSecurityPolicy.cs │ │ │ │ ├── InsecureEncryptionAlgorithm.cs │ │ │ │ ├── InsecureTemporaryFilesCreation.cs │ │ │ │ ├── InsteadOfAny.cs │ │ │ │ ├── InterfaceMethodsShouldBeCallableByChildTypes.cs │ │ │ │ ├── InterfacesShouldNotBeEmpty.cs │ │ │ │ ├── InvalidCastToInterface.cs │ │ │ │ ├── InvocationResolvesToOverrideWithParams.cs │ │ │ │ ├── IssueSuppression.cs │ │ │ │ ├── JSInvokableMethodsShouldBePublic.cs │ │ │ │ ├── JwtSigned.cs │ │ │ │ ├── LdapConnectionShouldBeSecure.cs │ │ │ │ ├── LineLength.cs │ │ │ │ ├── LinkedListPropertiesInsteadOfMethods.cs │ │ │ │ ├── LinkedListPropertiesInsteadOfMethodsCodeFix.cs │ │ │ │ ├── LiteralSuffixUpperCase.cs │ │ │ │ ├── LiteralSuffixUpperCaseCodeFix.cs │ │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParameters.cs │ │ │ │ ├── LockedFieldShouldBeReadonly.cs │ │ │ │ ├── LoggerFieldsShouldBePrivateStaticReadonly.cs │ │ │ │ ├── LoggerMembersNamesShouldComply.cs │ │ │ │ ├── LoggersShouldBeNamedForEnclosingType.cs │ │ │ │ ├── LoggingArgumentsShouldBePassedCorrectly.cs │ │ │ │ ├── LoopsAndLinq.cs │ │ │ │ ├── LooseFilePermissions.cs │ │ │ │ ├── LossOfFractionInDivision.cs │ │ │ │ ├── MagicNumberShouldNotBeUsed.cs │ │ │ │ ├── MarkAssemblyWithAssemblyVersionAttribute.cs │ │ │ │ ├── MarkAssemblyWithClsCompliantAttribute.cs │ │ │ │ ├── MarkAssemblyWithComVisibleAttribute.cs │ │ │ │ ├── MarkAssemblyWithNeutralResourcesLanguageAttribute.cs │ │ │ │ ├── MarkWindowsFormsMainWithStaThread.cs │ │ │ │ ├── MemberInitializedToDefault.cs │ │ │ │ ├── MemberInitializedToDefaultCodeFix.cs │ │ │ │ ├── MemberInitializerRedundant.RoslynCfg.cs │ │ │ │ ├── MemberInitializerRedundant.SonarCfg.cs │ │ │ │ ├── MemberInitializerRedundant.cs │ │ │ │ ├── MemberOverrideCallsBaseMember.cs │ │ │ │ ├── MemberOverrideCallsBaseMemberCodeFix.cs │ │ │ │ ├── MemberShadowsOuterStaticMember.cs │ │ │ │ ├── MemberShouldBeStatic.cs │ │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.cs │ │ │ │ ├── MessageTemplates/ │ │ │ │ │ ├── IMessageTemplateCheck.cs │ │ │ │ │ ├── LoggingTemplatePlaceHoldersShouldBeInOrder.cs │ │ │ │ │ ├── MessageTemplateAnalyzer.cs │ │ │ │ │ ├── MessageTemplateExtractor.cs │ │ │ │ │ ├── MessageTemplatesShouldBeCorrect.cs │ │ │ │ │ ├── NamedPlaceholdersShouldBeUnique.cs │ │ │ │ │ └── UsePascalCaseForNamedPlaceHolders.cs │ │ │ │ ├── MethodOverloadOptionalParameter.cs │ │ │ │ ├── MethodOverloadsShouldBeGrouped.cs │ │ │ │ ├── MethodOverrideAddsParams.cs │ │ │ │ ├── MethodOverrideAddsParamsCodeFix.cs │ │ │ │ ├── MethodOverrideChangedDefaultValue.cs │ │ │ │ ├── MethodOverrideChangedDefaultValueCodeFix.cs │ │ │ │ ├── MethodOverrideNoParams.cs │ │ │ │ ├── MethodOverrideNoParamsCodeFix.cs │ │ │ │ ├── MethodParameterMissingOptional.cs │ │ │ │ ├── MethodParameterMissingOptionalCodeFix.cs │ │ │ │ ├── MethodParameterUnused.cs │ │ │ │ ├── MethodParameterUnusedCodeFix.cs │ │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.cs │ │ │ │ ├── MethodShouldNotOnlyReturnConstant.cs │ │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.cs │ │ │ │ ├── MethodsShouldNotHaveTooManyLines.cs │ │ │ │ ├── MethodsShouldUseBaseTypes.cs │ │ │ │ ├── MultilineBlocksWithoutBrace.cs │ │ │ │ ├── MultipleVariableDeclaration.cs │ │ │ │ ├── MultipleVariableDeclarationCodeFix.cs │ │ │ │ ├── MutableFieldsShouldNotBe.cs │ │ │ │ ├── MutableFieldsShouldNotBePublicReadonly.cs │ │ │ │ ├── MutableFieldsShouldNotBePublicStatic.cs │ │ │ │ ├── NameOfShouldBeUsed.cs │ │ │ │ ├── NativeMethodsShouldBeWrapped.cs │ │ │ │ ├── NestedCodeBlock.cs │ │ │ │ ├── NoExceptionsInFinally.cs │ │ │ │ ├── NonAsyncTaskShouldNotReturnNull.cs │ │ │ │ ├── NonDerivedPrivateClassesShouldBeSealed.cs │ │ │ │ ├── NonFlagsEnumInBitwiseOperation.cs │ │ │ │ ├── NonFlagsEnumInBitwiseOperationCodeFix.cs │ │ │ │ ├── NormalizeStringsToUppercase.cs │ │ │ │ ├── NotAssignedPrivateMember.cs │ │ │ │ ├── NumberPatternShouldBeRegular.cs │ │ │ │ ├── ObjectCreatedDropped.cs │ │ │ │ ├── ObjectShouldBeInitializedCorrectlyBase.cs │ │ │ │ ├── ObsoleteAttributes.cs │ │ │ │ ├── OperatorOverloadsShouldHaveNamedAlternatives.cs │ │ │ │ ├── OperatorsShouldBeOverloadedConsistently.cs │ │ │ │ ├── OptionalParameter.cs │ │ │ │ ├── OptionalParameterNotPassedToBaseCall.cs │ │ │ │ ├── OptionalParameterWithDefaultValue.cs │ │ │ │ ├── OptionalParameterWithDefaultValueCodeFix.cs │ │ │ │ ├── OptionalRefOutParameter.cs │ │ │ │ ├── OptionalRefOutParameterCodeFix.cs │ │ │ │ ├── OrderByRepeated.cs │ │ │ │ ├── OrderByRepeatedCodeFix.cs │ │ │ │ ├── OverrideGetHashCodeOnOverridingEquals.cs │ │ │ │ ├── PInvokesShouldNotBeVisible.cs │ │ │ │ ├── ParameterAssignedTo.cs │ │ │ │ ├── ParameterNameMatchesOriginal.cs │ │ │ │ ├── ParameterNamesShouldNotDuplicateMethodNames.cs │ │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.cs │ │ │ │ ├── ParameterValidationInAsyncShouldBeWrapped.cs │ │ │ │ ├── ParameterValidationInYieldShouldBeWrapped.cs │ │ │ │ ├── ParametersCorrectOrder.cs │ │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttribute.cs │ │ │ │ ├── PartialMethodNoImplementation.cs │ │ │ │ ├── PasswordsShouldBeStoredCorrectly.cs │ │ │ │ ├── PointersShouldBePrivate.cs │ │ │ │ ├── PreferGuidEmpty.cs │ │ │ │ ├── PreferGuidEmptyCodeFix.cs │ │ │ │ ├── PreferJaggedArraysOverMultidimensional.cs │ │ │ │ ├── PrivateFieldUsedAsLocalVariable.cs │ │ │ │ ├── PrivateStaticMethodUsedOnlyByNestedClass.cs │ │ │ │ ├── PropertiesAccessCorrectField.cs │ │ │ │ ├── PropertiesShouldBePreferred.cs │ │ │ │ ├── PropertyGetterWithThrow.cs │ │ │ │ ├── PropertyNamesShouldNotMatchGetMethods.cs │ │ │ │ ├── PropertyToAutoProperty.cs │ │ │ │ ├── PropertyWriteOnly.cs │ │ │ │ ├── ProvideDeserializationMethodsForOptionalFields.cs │ │ │ │ ├── PublicConstantField.cs │ │ │ │ ├── PublicMethodWithMultidimensionalArray.cs │ │ │ │ ├── PureAttributeOnVoidMethod.cs │ │ │ │ ├── RedundancyInConstructorDestructorDeclaration.cs │ │ │ │ ├── RedundancyInConstructorDestructorDeclarationCodeFix.cs │ │ │ │ ├── RedundantArgument.cs │ │ │ │ ├── RedundantArgumentCodeFix.cs │ │ │ │ ├── RedundantCast.cs │ │ │ │ ├── RedundantCastCodeFix.cs │ │ │ │ ├── RedundantConditionalAroundAssignment.cs │ │ │ │ ├── RedundantConditionalAroundAssignmentCodeFix.cs │ │ │ │ ├── RedundantDeclaration.cs │ │ │ │ ├── RedundantDeclarationCodeFix.cs │ │ │ │ ├── RedundantInheritanceList.cs │ │ │ │ ├── RedundantInheritanceListCodeFix.cs │ │ │ │ ├── RedundantJumpStatement.cs │ │ │ │ ├── RedundantModifier.cs │ │ │ │ ├── RedundantModifierCodeFix.cs │ │ │ │ ├── RedundantNullCheck.cs │ │ │ │ ├── RedundantNullCheckCodeFix.cs │ │ │ │ ├── RedundantNullableTypeComparison.cs │ │ │ │ ├── RedundantParentheses.cs │ │ │ │ ├── RedundantParenthesesCodeFix.cs │ │ │ │ ├── RedundantParenthesesObjectsCreation.cs │ │ │ │ ├── RedundantPropertyNamesInAnonymousClass.cs │ │ │ │ ├── RedundantPropertyNamesInAnonymousClassCodeFix.cs │ │ │ │ ├── RedundantToArrayCall.cs │ │ │ │ ├── RedundantToArrayCallCodeFix.cs │ │ │ │ ├── RedundantToStringCall.cs │ │ │ │ ├── RedundantToStringCallCodeFix.cs │ │ │ │ ├── ReferenceEqualityCheckWhenEqualsExists.cs │ │ │ │ ├── ReferenceEqualsOnValueType.cs │ │ │ │ ├── RegularExpressions/ │ │ │ │ │ └── RegexMustHaveValidSyntax.cs │ │ │ │ ├── RequireAttributeUsageAttribute.cs │ │ │ │ ├── ReturnEmptyCollectionInsteadOfNull.cs │ │ │ │ ├── ReturnTypeNamedPartialShouldBeEscaped.cs │ │ │ │ ├── ReturnValueIgnored.cs │ │ │ │ ├── ReuseClientBase.cs │ │ │ │ ├── ReversedOperators.cs │ │ │ │ ├── RightCurlyBraceStartsLine.cs │ │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.cs │ │ │ │ ├── SelfAssignment.cs │ │ │ │ ├── SerializationConstructorsShouldBeSecured.cs │ │ │ │ ├── SetLocaleForDataTypes.cs │ │ │ │ ├── SetPropertiesInsteadOfMethods.cs │ │ │ │ ├── ShiftDynamicNotInteger.cs │ │ │ │ ├── ShouldImplementExportedInterfaces.cs │ │ │ │ ├── SingleStatementPerLine.cs │ │ │ │ ├── SpecifyIFormatProviderOrCultureInfo.cs │ │ │ │ ├── SpecifyStringComparison.cs │ │ │ │ ├── SqlKeywordsDelimitedBySpace.cs │ │ │ │ ├── StaticFieldInGenericClass.cs │ │ │ │ ├── StaticFieldInitializerOrder.cs │ │ │ │ ├── StaticFieldVisible.cs │ │ │ │ ├── StaticFieldWrittenFrom.cs │ │ │ │ ├── StaticFieldWrittenFromInstanceConstructor.cs │ │ │ │ ├── StaticFieldWrittenFromInstanceMember.cs │ │ │ │ ├── StaticSealedClassProtectedMembers.cs │ │ │ │ ├── StreamReadStatement.cs │ │ │ │ ├── StringConcatenationInLoop.cs │ │ │ │ ├── StringFormatValidator.cs │ │ │ │ ├── StringLiteralShouldNotBeDuplicated.cs │ │ │ │ ├── StringOffsetMethods.cs │ │ │ │ ├── StringOperationWithoutCulture.cs │ │ │ │ ├── StringOrIntegralTypesForIndexers.cs │ │ │ │ ├── SuppressFinalizeUseless.cs │ │ │ │ ├── SuppressFinalizeUselessCodeFix.cs │ │ │ │ ├── SwaggerActionReturnType.cs │ │ │ │ ├── SwitchCaseFallsThroughToDefault.cs │ │ │ │ ├── SwitchCaseFallsThroughToDefaultCodeFix.cs │ │ │ │ ├── SwitchCasesMinimumThree.cs │ │ │ │ ├── SwitchDefaultClauseEmpty.cs │ │ │ │ ├── SwitchDefaultClauseEmptyCodeFix.cs │ │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements.cs │ │ │ │ ├── SwitchShouldNotBeNested.cs │ │ │ │ ├── SwitchWithoutDefault.cs │ │ │ │ ├── TabCharacter.cs │ │ │ │ ├── TaskConfigureAwait.cs │ │ │ │ ├── TestClassShouldHaveTestMethod.cs │ │ │ │ ├── TestMethodShouldContainAssertion.cs │ │ │ │ ├── TestMethodShouldHaveCorrectSignature.cs │ │ │ │ ├── TestMethodShouldNotBeIgnored.cs │ │ │ │ ├── TestsShouldNotUseThreadSleep.cs │ │ │ │ ├── ThisShouldNotBeExposedFromConstructors.cs │ │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalled.cs │ │ │ │ ├── ThreadStaticNonStaticField.cs │ │ │ │ ├── ThreadStaticNonStaticFieldCodeFix.cs │ │ │ │ ├── ThreadStaticWithInitializer.cs │ │ │ │ ├── ThrowReservedExceptions.cs │ │ │ │ ├── ToStringShouldNotReturnNull.cs │ │ │ │ ├── TooManyGenericParameters.cs │ │ │ │ ├── TooManyLabelsInSwitch.cs │ │ │ │ ├── TooManyLoggingCalls.cs │ │ │ │ ├── TooManyParameters.cs │ │ │ │ ├── TrackNotImplementedException.cs │ │ │ │ ├── TryStatementsWithIdenticalCatchShouldBeMerged.cs │ │ │ │ ├── TypeExaminationOnSystemType.cs │ │ │ │ ├── TypeMemberVisibility.cs │ │ │ │ ├── TypeNamesShouldNotMatchNamespaces.cs │ │ │ │ ├── TypesShouldNotExtendOutdatedBaseTypes.cs │ │ │ │ ├── UnaryPrefixOperatorRepeated.cs │ │ │ │ ├── UnaryPrefixOperatorRepeatedCodeFix.cs │ │ │ │ ├── UnchangedLocalVariablesShouldBeConst.cs │ │ │ │ ├── UnchangedLocalVariablesShouldBeConstCodeFix.cs │ │ │ │ ├── UnconditionalJumpStatement.cs │ │ │ │ ├── UninvokedEventDeclaration.cs │ │ │ │ ├── UnnecessaryBitwiseOperation.cs │ │ │ │ ├── UnnecessaryBitwiseOperationCodeFix.cs │ │ │ │ ├── UnnecessaryMathematicalComparison.cs │ │ │ │ ├── UnnecessaryUsings.cs │ │ │ │ ├── UnnecessaryUsingsCodeFix.cs │ │ │ │ ├── UnusedPrivateMember.cs │ │ │ │ ├── UnusedPrivateMemberCodeFix.cs │ │ │ │ ├── UnusedReturnValue.cs │ │ │ │ ├── UnusedStringBuilder.cs │ │ │ │ ├── UriShouldNotBeHardcoded.cs │ │ │ │ ├── UseAwaitableMethod.cs │ │ │ │ ├── UseCharOverloadOfStringMethods.cs │ │ │ │ ├── UseCharOverloadOfStringMethodsCodeFix.cs │ │ │ │ ├── UseConstantLoggingTemplate.cs │ │ │ │ ├── UseConstantsWhereAppropriate.cs │ │ │ │ ├── UseCurlyBraces.cs │ │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.cs │ │ │ │ ├── UseFindSystemTimeZoneById.cs │ │ │ │ ├── UseGenericEventHandlerInstances.cs │ │ │ │ ├── UseGenericWithRefParameters.cs │ │ │ │ ├── UseIFormatProviderForParsingDateAndTime.cs │ │ │ │ ├── UseIndexingInsteadOfLinqMethods.cs │ │ │ │ ├── UseLambdaParameterInConcurrentDictionary.cs │ │ │ │ ├── UseNumericLiteralSeparator.cs │ │ │ │ ├── UseParamsForVariableArguments.cs │ │ │ │ ├── UseShortCircuitingOperator.cs │ │ │ │ ├── UseShortCircuitingOperatorCodeFix.cs │ │ │ │ ├── UseStringCreate.cs │ │ │ │ ├── UseStringIsNullOrEmpty.cs │ │ │ │ ├── UseTestableTimeProvider.cs │ │ │ │ ├── UseTrueForAll.cs │ │ │ │ ├── UseUnixEpoch.cs │ │ │ │ ├── UseUnixEpochCodeFix.cs │ │ │ │ ├── UseUriInsteadOfString.cs │ │ │ │ ├── UseValueParameter.cs │ │ │ │ ├── UseWhereBeforeOrderBy.cs │ │ │ │ ├── UseWhileLoopInstead.cs │ │ │ │ ├── Utilities/ │ │ │ │ │ ├── AnalysisWarningAnalyzer.cs │ │ │ │ │ ├── CopyPasteTokenAnalyzer.cs │ │ │ │ │ ├── FileMetadataAnalyzer.cs │ │ │ │ │ ├── LogAnalyzer.cs │ │ │ │ │ ├── MetricsAnalyzer.cs │ │ │ │ │ ├── SymbolReferenceAnalyzer.cs │ │ │ │ │ ├── TelemetryAnalyzer.cs │ │ │ │ │ ├── TestMethodDeclarationsAnalyzer.cs │ │ │ │ │ └── TokenTypeAnalyzer.cs │ │ │ │ ├── ValueTypeShouldImplementIEquatable.cs │ │ │ │ ├── ValuesUselesslyIncremented.cs │ │ │ │ ├── VariableShadowsField.cs │ │ │ │ ├── VariableUnused.cs │ │ │ │ ├── VirtualEventField.cs │ │ │ │ ├── VirtualEventFieldCodeFix.cs │ │ │ │ ├── WcfMissingContractAttribute.cs │ │ │ │ ├── WcfNonVoidOneWay.cs │ │ │ │ ├── WeakSslTlsProtocols.cs │ │ │ │ ├── XMLSignatureCheck.cs │ │ │ │ ├── XXE/ │ │ │ │ │ └── XmlReaderSettingsValidator.cs │ │ │ │ └── XmlExternalEntityShouldNotBeParsed.cs │ │ │ ├── SonarAnalyzer.CSharp.csproj │ │ │ ├── Syntax/ │ │ │ │ ├── Extensions/ │ │ │ │ │ ├── ExpressionSyntaxExtensions.cs │ │ │ │ │ ├── IMethodSymbolExtensions.cs │ │ │ │ │ ├── IfStatementSyntaxExtensions.cs │ │ │ │ │ ├── InvocationExpressionSyntaxExtensions.cs │ │ │ │ │ ├── SwitchSectionSyntaxExtensions.cs │ │ │ │ │ └── SyntaxNodeExtensions.cs │ │ │ │ └── Utilities/ │ │ │ │ ├── AttributeSyntaxSymbolMapping.cs │ │ │ │ ├── SymbolUsage.cs │ │ │ │ └── SymbolUsageCollector.cs │ │ │ ├── Walkers/ │ │ │ │ ├── CatchLoggingInvocationWalker.cs │ │ │ │ └── ParameterValidationInMethodWalker.cs │ │ │ ├── packages.lock.json │ │ │ └── sonarpedia.json │ │ ├── SonarAnalyzer.CSharp.Core/ │ │ │ ├── Common/ │ │ │ │ ├── DescriptorFactory.cs │ │ │ │ └── SyntaxConstants.cs │ │ │ ├── Extensions/ │ │ │ │ ├── CSharpCompilationExtensions.cs │ │ │ │ ├── IAnalyzerConfigurationExtensions.cs │ │ │ │ ├── ICompilationReportExtensions.cs │ │ │ │ ├── IFieldSymbolExtensions.cs │ │ │ │ ├── ILocalSymbolExtensions.cs │ │ │ │ ├── IMethodSymbolExtensions.cs │ │ │ │ ├── ISymbolExtensions.cs │ │ │ │ ├── ITupleOperationWrapperExtensions.cs │ │ │ │ ├── ITypeSymbolExtensions.cs │ │ │ │ ├── LanguageVersionExtensions.cs │ │ │ │ ├── SonarAnalysisContextExtensions.cs │ │ │ │ ├── SonarCompilationStartAnalysisContextExtensions.cs │ │ │ │ ├── SonarParametrizedAnalysisContextExtensions.cs │ │ │ │ └── SonarSyntaxNodeReportingContextExtensions.cs │ │ │ ├── Facade/ │ │ │ │ ├── CSharpFacade.cs │ │ │ │ └── Implementation/ │ │ │ │ ├── CSharpSyntaxFacade.cs │ │ │ │ ├── CSharpSyntaxKindFacade.cs │ │ │ │ └── CSharpTrackerFacade.cs │ │ │ ├── LiveVariableAnalysis/ │ │ │ │ └── SonarCSharpLiveVariableAnalysis.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── RegularExpressions/ │ │ │ │ └── MessageTemplatesParser.cs │ │ │ ├── SonarAnalyzer.CSharp.Core.csproj │ │ │ ├── Syntax/ │ │ │ │ ├── Extensions/ │ │ │ │ │ ├── AccessorDeclarationSyntaxExtensions.cs │ │ │ │ │ ├── ArgumentListSyntaxExtensions.cs │ │ │ │ │ ├── ArgumentSyntaxExtensions.cs │ │ │ │ │ ├── AssignmentExpressionSyntaxExtensions.cs │ │ │ │ │ ├── AttributeListSyntaxExtensions.cs │ │ │ │ │ ├── AttributeSyntaxExtensions.cs │ │ │ │ │ ├── AwaitExpressionSyntaxExtensions.cs │ │ │ │ │ ├── BaseArgumentListSyntaxExtensions.cs │ │ │ │ │ ├── BaseMethodDeclarationSyntaxExtensions.cs │ │ │ │ │ ├── BlockSyntaxExtensions.cs │ │ │ │ │ ├── CompilationUnitSyntaxExtensions.cs │ │ │ │ │ ├── ExpressionSyntaxExtensions.Roslyn.cs │ │ │ │ │ ├── ExpressionSyntaxExtensions.cs │ │ │ │ │ ├── InterpolatedStringExpressionSyntaxExtensions.cs │ │ │ │ │ ├── InvocationExpressionSyntaxExtensions.cs │ │ │ │ │ ├── LocalFunctionStatementSyntaxWrapperExtensions.cs │ │ │ │ │ ├── MemberAccessExpressionSyntaxExtensions.cs │ │ │ │ │ ├── MethodDeclarationSyntaxExtensions.cs │ │ │ │ │ ├── ObjectCreationExpressionSyntaxExtensions.cs │ │ │ │ │ ├── ParameterSyntaxExtensions.cs │ │ │ │ │ ├── PropertyDeclarationSyntaxExtensions.cs │ │ │ │ │ ├── StatementSyntaxExtensions.cs │ │ │ │ │ ├── SwitchExpressionSyntaxWrapperExtensions.cs │ │ │ │ │ ├── SwitchStatementSyntaxExtensions.cs │ │ │ │ │ ├── SyntaxNodeExtensions.Roslyn.cs │ │ │ │ │ ├── SyntaxNodeExtensionsCSharp.cs │ │ │ │ │ ├── SyntaxTokenExtensions.cs │ │ │ │ │ ├── SyntaxTokenListExtensions.cs │ │ │ │ │ ├── SyntaxTriviaExtensions.cs │ │ │ │ │ ├── TupleExpressionSyntaxExtensions.cs │ │ │ │ │ ├── TypeDeclarationSyntaxExtensions.cs │ │ │ │ │ ├── TypeSyntaxExtensions.cs │ │ │ │ │ └── VariableDesignationSyntaxWrapperExtensions.cs │ │ │ │ └── Utilities/ │ │ │ │ ├── CSharpAttributeParameterLookup.cs │ │ │ │ ├── CSharpEquivalenceChecker.cs │ │ │ │ ├── CSharpExpressionNumericConverter.cs │ │ │ │ ├── CSharpGeneratedCodeRecognizer.cs │ │ │ │ ├── CSharpMethodParameterLookup.cs │ │ │ │ ├── CSharpRemovableDeclarationCollector.cs │ │ │ │ ├── CSharpStringInterpolationConstantValueResolver.cs │ │ │ │ ├── CSharpSyntaxClassifier.cs │ │ │ │ ├── MutedSyntaxWalker.cs │ │ │ │ └── SafeCSharpSyntaxWalker.cs │ │ │ ├── Trackers/ │ │ │ │ ├── CSharpArgumentTracker.cs │ │ │ │ ├── CSharpAssignmentFinder.cs │ │ │ │ ├── CSharpBaseTypeTracker.cs │ │ │ │ ├── CSharpBuilderPatternCondition.cs │ │ │ │ ├── CSharpConstantValueFinder.cs │ │ │ │ ├── CSharpElementAccessTracker.cs │ │ │ │ ├── CSharpFieldAccessTracker.cs │ │ │ │ ├── CSharpInvocationTracker.cs │ │ │ │ ├── CSharpMethodDeclarationTracker.cs │ │ │ │ ├── CSharpObjectCreationTracker.cs │ │ │ │ ├── CSharpObjectInitializationTracker.cs │ │ │ │ └── CSharpPropertyAccessTracker.cs │ │ │ ├── Wrappers/ │ │ │ │ ├── IMethodDeclaration.cs │ │ │ │ ├── MethodDeclarationFactory.cs │ │ │ │ └── ObjectCreationFactory.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.CSharp.Styling/ │ │ │ ├── Common/ │ │ │ │ ├── DescriptorFactory.cs │ │ │ │ ├── OrderDescriptor.cs │ │ │ │ └── StylingAnalyzer.cs │ │ │ ├── Extensions/ │ │ │ │ ├── LambdaExpressionSyntaxExtensions.cs │ │ │ │ ├── MemberDeclarationSyntaxExtensions.cs │ │ │ │ └── SyntaxNodeExtensions.cs │ │ │ ├── Rules/ │ │ │ │ ├── AllArgumentsOnSameLine.cs │ │ │ │ ├── AllParametersBase.cs │ │ │ │ ├── AllParametersOnSameColumn.cs │ │ │ │ ├── AllParametersOnSameLine.cs │ │ │ │ ├── ArrowLocation.cs │ │ │ │ ├── AvoidArrangeActAssertComment.cs │ │ │ │ ├── AvoidDeconstruction.cs │ │ │ │ ├── AvoidGet.cs │ │ │ │ ├── AvoidListForEach.cs │ │ │ │ ├── AvoidNullable.cs │ │ │ │ ├── AvoidPrimaryConstructor.cs │ │ │ │ ├── AvoidUnusedInterpolation.cs │ │ │ │ ├── AvoidValueTuple.cs │ │ │ │ ├── AvoidVarPattern.cs │ │ │ │ ├── FieldOrdering.cs │ │ │ │ ├── FileScopeNamespace.cs │ │ │ │ ├── HelperInTypeName.cs │ │ │ │ ├── IndentArgument.cs │ │ │ │ ├── IndentBase.cs │ │ │ │ ├── IndentInvocation.cs │ │ │ │ ├── IndentOperator.cs │ │ │ │ ├── IndentRawString.cs │ │ │ │ ├── IndentTernary.cs │ │ │ │ ├── InitializerLine.cs │ │ │ │ ├── LambdaParameterName.cs │ │ │ │ ├── LocalFunctionLocation.cs │ │ │ │ ├── MemberAccessLine.cs │ │ │ │ ├── MemberVisibilityOrdering.cs │ │ │ │ ├── MethodExpressionBodyLine.cs │ │ │ │ ├── MoveMethodToDedicatedExtensionClass.cs │ │ │ │ ├── NamespaceName.cs │ │ │ │ ├── NullPatternMatching.cs │ │ │ │ ├── OperatorLocation.cs │ │ │ │ ├── PropertyOrdering.cs │ │ │ │ ├── ProtectedFieldsCase.cs │ │ │ │ ├── SeparateDeclarations.cs │ │ │ │ ├── TermaryLine.cs │ │ │ │ ├── TypeMemberOrdering.cs │ │ │ │ ├── UseDifferentMember.cs │ │ │ │ ├── UseField.cs │ │ │ │ ├── UseInnermostRegistrationContext.cs │ │ │ │ ├── UseLinqExtensions.cs │ │ │ │ ├── UseNullInsteadOfDefault.cs │ │ │ │ ├── UsePositiveLogic.cs │ │ │ │ ├── UseRawString.cs │ │ │ │ ├── UseRegexSafeIsMatch.cs │ │ │ │ ├── UseShortName.cs │ │ │ │ └── UseVar.cs │ │ │ ├── SonarAnalyzer.CSharp.Styling.csproj │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.Core/ │ │ │ ├── AnalysisContext/ │ │ │ │ ├── IAnalysisContext.cs │ │ │ │ ├── IReport.cs │ │ │ │ ├── IReportingContext.cs │ │ │ │ ├── IssueReporter.cs │ │ │ │ ├── ReportingContext.cs │ │ │ │ ├── ShouldAnalyzeTreeCache.cs │ │ │ │ ├── SonarAnalysisContext.cs │ │ │ │ ├── SonarAnalysisContextBase.cs │ │ │ │ ├── SonarCodeBlockReportingContext.cs │ │ │ │ ├── SonarCodeBlockStartAnalysisContext.cs │ │ │ │ ├── SonarCodeFixContext.cs │ │ │ │ ├── SonarCompilationReportingContext.cs │ │ │ │ ├── SonarCompilationStartAnalysisContext.cs │ │ │ │ ├── SonarParametrizedAnalysisContext.cs │ │ │ │ ├── SonarSemanticModelReportingContext.cs │ │ │ │ ├── SonarSymbolReportingContext.cs │ │ │ │ ├── SonarSymbolStartAnalysisContext.cs │ │ │ │ ├── SonarSyntaxNodeReportingContext.cs │ │ │ │ └── SonarSyntaxTreeReportingContext.cs │ │ │ ├── Analyzers/ │ │ │ │ ├── DiagnosticDescriptorFactory.cs │ │ │ │ ├── HotspotDiagnosticAnalyzer.cs │ │ │ │ ├── ParametrizedDiagnosticAnalyzer.cs │ │ │ │ ├── SonarCodeFix.cs │ │ │ │ ├── SonarDiagnosticAnalyzer.cs │ │ │ │ └── TrackerHotspotDiagnosticAnalyzer.cs │ │ │ ├── Common/ │ │ │ │ ├── AnalyzerAdditionalFile.cs │ │ │ │ ├── AnalyzerConfiguration.cs │ │ │ │ ├── AnalyzerLanguage.cs │ │ │ │ ├── BidirectionalDictionary.cs │ │ │ │ ├── Constants.cs │ │ │ │ ├── Conversions.cs │ │ │ │ ├── DisjointSets.cs │ │ │ │ ├── DocumentBasedFixAllProvider.cs │ │ │ │ ├── HashCode.cs │ │ │ │ ├── IAnalyzerConfiguration.cs │ │ │ │ ├── IRuleLoader.cs │ │ │ │ ├── ISafeSyntaxWalker.cs │ │ │ │ ├── IsExternalInit.cs │ │ │ │ ├── MultiValueDictionary.cs │ │ │ │ ├── NaturalLanguageDetector.cs │ │ │ │ ├── NodeAndModel.cs │ │ │ │ ├── NodeAndSymbol.cs │ │ │ │ ├── NodeSymbolAndModel.cs │ │ │ │ ├── Pair.cs │ │ │ │ ├── PropertyType.cs │ │ │ │ ├── RuleDescriptor.cs │ │ │ │ ├── RuleLoader.cs │ │ │ │ ├── RuleParameterAttribute.cs │ │ │ │ ├── SecondaryLocation.cs │ │ │ │ ├── ShannonEntropy.cs │ │ │ │ ├── SourceScope.cs │ │ │ │ ├── TokenAndModel.cs │ │ │ │ ├── TopLevelStatements.cs │ │ │ │ ├── UnexpectedLanguageException.cs │ │ │ │ └── UnexpectedValueException.cs │ │ │ ├── Configuration/ │ │ │ │ ├── AnalysisConfig.cs │ │ │ │ ├── AnalysisConfigReader.cs │ │ │ │ ├── ConfigSetting.cs │ │ │ │ ├── FilesToAnalyzeProvider.cs │ │ │ │ ├── ParameterLoader.cs │ │ │ │ ├── ProjectConfig.cs │ │ │ │ ├── ProjectConfigReader.cs │ │ │ │ ├── ProjectType.cs │ │ │ │ ├── ProjectTypeCache.cs │ │ │ │ ├── SonarLintXml.cs │ │ │ │ └── SonarLintXmlReader.cs │ │ │ ├── Extensions/ │ │ │ │ ├── AnalyzerOptionsExtensions.cs │ │ │ │ ├── CompilationExtensions.cs │ │ │ │ ├── DiagnosticDescriptorExtensions.cs │ │ │ │ ├── DictionaryExtensions.cs │ │ │ │ ├── HashSetExtensions.cs │ │ │ │ ├── IAnalysisContextExtensions.cs │ │ │ │ ├── ICompilationReportExtensions.cs │ │ │ │ ├── IEnumerableExtensions.cs │ │ │ │ ├── ITreeReportExtensions.cs │ │ │ │ ├── IXmlLineInfoExtensions.cs │ │ │ │ ├── RegexExtensions.cs │ │ │ │ ├── SonarAnalysisContextExtensions.cs │ │ │ │ ├── StackExtensions.cs │ │ │ │ ├── StringExtensions.cs │ │ │ │ ├── XAttributeExtensions.cs │ │ │ │ └── XElementExtensions.cs │ │ │ ├── Facade/ │ │ │ │ ├── ILanguageFacade.cs │ │ │ │ ├── ISyntaxKindFacade.cs │ │ │ │ ├── ITrackerFacade.cs │ │ │ │ └── SyntaxFacade.cs │ │ │ ├── Json/ │ │ │ │ ├── JsonException.cs │ │ │ │ ├── JsonNode.cs │ │ │ │ ├── JsonSerializer.cs │ │ │ │ ├── JsonWalker.cs │ │ │ │ └── Parsing/ │ │ │ │ ├── Kind.cs │ │ │ │ ├── LexicalAnalyzer.cs │ │ │ │ ├── Symbol.cs │ │ │ │ └── SyntaxAnalyzer.cs │ │ │ ├── Metrics/ │ │ │ │ ├── CognitiveComplexity.cs │ │ │ │ ├── FileComments.cs │ │ │ │ └── MetricsBase.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── Protobuf/ │ │ │ │ └── AnalyzerReport.proto │ │ │ ├── RegularExpressions/ │ │ │ │ ├── NamingPatterns.cs │ │ │ │ ├── RegexContext.cs │ │ │ │ └── WildcardPatternMatcher.cs │ │ │ ├── Rules/ │ │ │ │ ├── AllBranchesShouldNotHaveSameImplementationBase.cs │ │ │ │ ├── AlwaysSetDateTimeKindBase.cs │ │ │ │ ├── ArrayPassedAsParamsBase.cs │ │ │ │ ├── AspNet/ │ │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutesBase.cs │ │ │ │ │ └── RouteTemplateShouldNotStartWithSlashBase.cs │ │ │ │ ├── AvoidDateTimeNowForBenchmarkingBase.cs │ │ │ │ ├── AvoidUnsealedAttributesBase.cs │ │ │ │ ├── BeginInvokePairedWithEndInvokeBase.cs │ │ │ │ ├── BinaryOperationWithIdenticalExpressionsBase.cs │ │ │ │ ├── BooleanCheckInvertedBase.cs │ │ │ │ ├── BooleanLiteralUnnecessaryBase.cs │ │ │ │ ├── BypassingAccessibilityBase.cs │ │ │ │ ├── CatchRethrowBase.cs │ │ │ │ ├── CertificateValidationCheckBase.cs │ │ │ │ ├── CheckFileLicenseBase.cs │ │ │ │ ├── ClassNamedExceptionBase.cs │ │ │ │ ├── ClassNotInstantiatableBase.cs │ │ │ │ ├── ClassShouldNotBeEmptyBase.cs │ │ │ │ ├── CognitiveComplexityBase.cs │ │ │ │ ├── CollectionEmptinessCheckingBase.cs │ │ │ │ ├── CommentKeywordBase.cs │ │ │ │ ├── CommentsShouldNotBeEmptyBase.cs │ │ │ │ ├── ConditionalStructureSameConditionBase.cs │ │ │ │ ├── ConditionalStructureSameImplementationBase.cs │ │ │ │ ├── ConstructorArgumentValueShouldExistBase.cs │ │ │ │ ├── DangerousGetHandleShouldNotBeCalledBase.cs │ │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase.cs │ │ │ │ ├── DateTimeFormatShouldNotBeHardcodedBase.cs │ │ │ │ ├── DebuggerDisplayUsesExistingMembersBase.cs │ │ │ │ ├── DeclareTypesInNamespacesBase.cs │ │ │ │ ├── DoNotCallInsecureSecurityAlgorithmBase.cs │ │ │ │ ├── DoNotCallMethodsBase.cs │ │ │ │ ├── DoNotCheckZeroSizeCollectionBase.cs │ │ │ │ ├── DoNotHardcodeBase.cs │ │ │ │ ├── DoNotHardcodeCredentialsBase.cs │ │ │ │ ├── DoNotHardcodeSecretsBase.cs │ │ │ │ ├── DoNotInstantiateSharedClassesBase.cs │ │ │ │ ├── DoNotLockOnSharedResourceBase.cs │ │ │ │ ├── DoNotLockWeakIdentityObjectsBase.cs │ │ │ │ ├── DoNotNestTernaryOperatorsBase.cs │ │ │ │ ├── DoNotOverwriteCollectionElementsBase.cs │ │ │ │ ├── DoNotThrowFromDestructorsBase.cs │ │ │ │ ├── DoNotUseDateTimeNowBase.cs │ │ │ │ ├── EmptyMethodBase.cs │ │ │ │ ├── EmptyNestedBlockBase.cs │ │ │ │ ├── EncryptionAlgorithmsShouldBeSecureBase.cs │ │ │ │ ├── EnumNameHasEnumSuffixBase.cs │ │ │ │ ├── EnumNameShouldFollowRegexBase.cs │ │ │ │ ├── ExceptionsShouldBePublicBase.cs │ │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs │ │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsedBase.cs │ │ │ │ ├── ExpressionComplexityBase.cs │ │ │ │ ├── ExtensionMethodShouldNotExtendObjectBase.cs │ │ │ │ ├── FieldShadowsParentFieldBase.cs │ │ │ │ ├── FieldShouldNotBePublicBase.cs │ │ │ │ ├── FileLinesBase.cs │ │ │ │ ├── FindInsteadOfFirstOrDefaultBase.cs │ │ │ │ ├── FlagsEnumWithoutInitializerBase.cs │ │ │ │ ├── FlagsEnumZeroMemberBase.cs │ │ │ │ ├── FunctionComplexityBase.cs │ │ │ │ ├── FunctionNestingDepthBase.cs │ │ │ │ ├── GenericInheritanceShouldNotBeRecursiveBase.cs │ │ │ │ ├── GotoStatementBase.cs │ │ │ │ ├── Hotspots/ │ │ │ │ │ ├── CommandPathBase.cs │ │ │ │ │ ├── ConfiguringLoggersBase.cs │ │ │ │ │ ├── CreatingHashAlgorithmsBase.cs │ │ │ │ │ ├── DeliveringDebugFeaturesInProductionBase.cs │ │ │ │ │ ├── DisablingRequestValidationBase.cs │ │ │ │ │ ├── ExecutingSqlQueriesBase.cs │ │ │ │ │ ├── ExpandingArchivesBase.cs │ │ │ │ │ ├── HardcodedIpAddressBase.cs │ │ │ │ │ ├── PubliclyWritableDirectoriesBase.cs │ │ │ │ │ ├── RequestsWithExcessiveLengthBase.cs │ │ │ │ │ ├── SpecifyTimeoutOnRegexBase.cs │ │ │ │ │ └── UsingNonstandardCryptographyBase.cs │ │ │ │ ├── IfChainWithoutElseBase.cs │ │ │ │ ├── IfCollapsibleBase.cs │ │ │ │ ├── ImplementSerializationMethodsCorrectlyBase.cs │ │ │ │ ├── IndexOfCheckAgainstZeroBase.cs │ │ │ │ ├── InsecureEncryptionAlgorithmBase.cs │ │ │ │ ├── InsecureTemporaryFilesCreationBase.cs │ │ │ │ ├── InsteadOfAnyBase.cs │ │ │ │ ├── InvalidCastToInterfaceBase.cs │ │ │ │ ├── JwtSignedBase.cs │ │ │ │ ├── LineLengthBase.cs │ │ │ │ ├── LinkedListPropertiesInsteadOfMethodsBase.cs │ │ │ │ ├── LooseFilePermissionsBase.cs │ │ │ │ ├── LooseFilePermissionsConfig.cs │ │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeBase.cs │ │ │ │ ├── MarkAssemblyWithAttributeBase.cs │ │ │ │ ├── MarkAssemblyWithClsCompliantAttributeBase.cs │ │ │ │ ├── MarkAssemblyWithComVisibleAttributeBase.cs │ │ │ │ ├── MarkWindowsFormsMainWithStaThreadBase.cs │ │ │ │ ├── MethodOverloadsShouldBeGroupedBase.cs │ │ │ │ ├── MethodParameterUnusedBase.cs │ │ │ │ ├── MethodsShouldNotHaveIdenticalImplementationsBase.cs │ │ │ │ ├── MethodsShouldNotHaveTooManyLinesBase.cs │ │ │ │ ├── MultipleVariableDeclarationBase.cs │ │ │ │ ├── MultipleVariableDeclarationCodeFixBase.cs │ │ │ │ ├── NameOfShouldBeUsedBase.cs │ │ │ │ ├── NoExceptionsInFinallyBase.cs │ │ │ │ ├── NonAsyncTaskShouldNotReturnNullBase.cs │ │ │ │ ├── ObsoleteAttributesBase.cs │ │ │ │ ├── OptionalParameterBase.cs │ │ │ │ ├── OptionalParameterNotPassedToBaseCallBase.cs │ │ │ │ ├── ParameterAssignedToBase.cs │ │ │ │ ├── ParameterNameMatchesOriginalBase.cs │ │ │ │ ├── ParametersCorrectOrderBase.cs │ │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttributeBase.cs │ │ │ │ ├── PreferGuidEmptyBase.cs │ │ │ │ ├── PropertiesAccessCorrectFieldBase.cs │ │ │ │ ├── PropertyGetterWithThrowBase.cs │ │ │ │ ├── PropertyWriteOnlyBase.cs │ │ │ │ ├── ProvideDeserializationMethodsForOptionalFieldsBase.cs │ │ │ │ ├── PublicConstantFieldBase.cs │ │ │ │ ├── PublicMethodWithMultidimensionalArrayBase.cs │ │ │ │ ├── PureAttributeOnVoidMethodBase.cs │ │ │ │ ├── RedundantNullCheckBase.cs │ │ │ │ ├── RedundantParenthesesBase.cs │ │ │ │ ├── RegularExpressions/ │ │ │ │ │ └── RegexMustHaveValidSyntaxBase.cs │ │ │ │ ├── ReversedOperatorsBase.cs │ │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalledBase.cs │ │ │ │ ├── SelfAssignmentBase.cs │ │ │ │ ├── SetPropertiesInsteadOfMethodsBase.cs │ │ │ │ ├── ShiftDynamicNotIntegerBase.cs │ │ │ │ ├── ShouldImplementExportedInterfacesBase.cs │ │ │ │ ├── SingleStatementPerLineBase.cs │ │ │ │ ├── StringConcatenationInLoopBase.cs │ │ │ │ ├── StringLiteralShouldNotBeDuplicatedBase.cs │ │ │ │ ├── SwitchCasesMinimumThreeBase.cs │ │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatementsBase.cs │ │ │ │ ├── SwitchShouldNotBeNestedBase.cs │ │ │ │ ├── SwitchWithoutDefaultBase.cs │ │ │ │ ├── TabCharacterBase.cs │ │ │ │ ├── TestsShouldNotUseThreadSleepBase.cs │ │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalledBase.cs │ │ │ │ ├── ThrowReservedExceptionsBase.cs │ │ │ │ ├── ToStringShouldNotReturnNullBase.cs │ │ │ │ ├── TooManyLabelsInSwitchBase.cs │ │ │ │ ├── TooManyParametersBase.cs │ │ │ │ ├── UnaryPrefixOperatorRepeatedBase.cs │ │ │ │ ├── UnconditionalJumpStatementBase.cs │ │ │ │ ├── UnnecessaryBitwiseOperationBase.cs │ │ │ │ ├── UnnecessaryBitwiseOperationCodeFixBase.cs │ │ │ │ ├── UnusedStringBuilderBase.cs │ │ │ │ ├── UriShouldNotBeHardcodedBase.cs │ │ │ │ ├── UseCharOverloadOfStringMethodsBase.cs │ │ │ │ ├── UseDateTimeOffsetInsteadOfDateTimeBase.cs │ │ │ │ ├── UseFindSystemTimeZoneByIdBase.cs │ │ │ │ ├── UseIFormatProviderForParsingDateAndTimeBase.cs │ │ │ │ ├── UseIndexingInsteadOfLinqMethodsBase.cs │ │ │ │ ├── UseLambdaParameterInConcurrentDictionaryBase.cs │ │ │ │ ├── UseShortCircuitingOperatorBase.cs │ │ │ │ ├── UseShortCircuitingOperatorCodeFixBase.cs │ │ │ │ ├── UseTestableTimeProviderBase.cs │ │ │ │ ├── UseTrueForAllBase.cs │ │ │ │ ├── UseUnixEpochBase.cs │ │ │ │ ├── UseUnixEpochCodeFixBase.cs │ │ │ │ ├── UseWhereBeforeOrderByBase.cs │ │ │ │ ├── Utilities/ │ │ │ │ │ ├── AnalysisWarningAnalyzerBase.cs │ │ │ │ │ ├── CopyPasteTokenAnalyzerBase.cs │ │ │ │ │ ├── FileMetadataAnalyzerBase.cs │ │ │ │ │ ├── LogAnalyzerBase.cs │ │ │ │ │ ├── MethodDeclarationInfoComparer.cs │ │ │ │ │ ├── MetricsAnalyzerBase.cs │ │ │ │ │ ├── SymbolReferenceAnalyzerBase.cs │ │ │ │ │ ├── TelemetryAnalyzerBase.cs │ │ │ │ │ ├── TestMethodDeclarationsAnalyzerBase.cs │ │ │ │ │ ├── TokenTypeAnalyzerBase.cs │ │ │ │ │ └── UtilityAnalyzerBase.cs │ │ │ │ ├── ValueTypeShouldImplementIEquatableBase.cs │ │ │ │ ├── VariableUnusedBase.cs │ │ │ │ ├── WcfNonVoidOneWayBase.cs │ │ │ │ └── WeakSslTlsProtocolsBase.cs │ │ │ ├── Semantics/ │ │ │ │ ├── Extensions/ │ │ │ │ │ ├── IMethodSymbolExtensions.cs │ │ │ │ │ ├── INamedTypeSymbolExtensions.cs │ │ │ │ │ ├── INamespaceSymbolExtensions.cs │ │ │ │ │ ├── IParameterSymbolExtensions.cs │ │ │ │ │ ├── IPropertySymbolExtensions.cs │ │ │ │ │ ├── ISymbolExtensions.Roslyn.cs │ │ │ │ │ ├── ISymbolExtensions.cs │ │ │ │ │ ├── ITypeParameterSymbolExtensions.cs │ │ │ │ │ ├── ITypeSymbolExtensions.cs │ │ │ │ │ ├── KnownAssemblyExtensions.cs │ │ │ │ │ ├── SemanticModelExtensions.cs │ │ │ │ │ └── SymbolInfoExtensions.cs │ │ │ │ ├── KnownAssembly.Predicates.cs │ │ │ │ ├── KnownAssembly.cs │ │ │ │ ├── KnownMethods.cs │ │ │ │ ├── KnownType.Implementation.cs │ │ │ │ ├── KnownType.cs │ │ │ │ └── NetFrameworkVersionProvider.cs │ │ │ ├── SonarAnalyzer.Core.csproj │ │ │ ├── Syntax/ │ │ │ │ ├── Extensions/ │ │ │ │ │ ├── AccessibilityExtensions.cs │ │ │ │ │ ├── AttributeDataExtensions.cs │ │ │ │ │ ├── BinaryOperatorKindExtensions.cs │ │ │ │ │ ├── ComparisonKindExtensions.cs │ │ │ │ │ ├── CountComparisonResultExtensions.cs │ │ │ │ │ ├── FileLinePositionSpanExtensions.cs │ │ │ │ │ ├── LinePositionExtensions.cs │ │ │ │ │ ├── LocationExtensions.cs │ │ │ │ │ ├── SyntaxNodeExtensions.Roslyn.cs │ │ │ │ │ ├── SyntaxNodeExtensions.cs │ │ │ │ │ ├── SyntaxTokenExtensions.cs │ │ │ │ │ ├── SyntaxTreeExtensions.cs │ │ │ │ │ └── SyntaxTriviaExtensions.cs │ │ │ │ └── Utilities/ │ │ │ │ ├── AccessorAccess.cs │ │ │ │ ├── ComparisonKind.cs │ │ │ │ ├── CountComparisionResult.cs │ │ │ │ ├── EquivalenceChecker.cs │ │ │ │ ├── ExpressionNumericConverterBase.cs │ │ │ │ ├── GeneratedCodeRecognizer.cs │ │ │ │ ├── IExpressionNumericConverter.cs │ │ │ │ ├── MethodParameterLookupBase.cs │ │ │ │ ├── RemovableDeclarationCollectorBase.cs │ │ │ │ ├── StringInterpolationConstantValueResolver.cs │ │ │ │ └── VisualIndentComparer.cs │ │ │ ├── Trackers/ │ │ │ │ ├── ArgumentContext.cs │ │ │ │ ├── ArgumentDescriptor.cs │ │ │ │ ├── ArgumentTracker.cs │ │ │ │ ├── AssignmentFinder.cs │ │ │ │ ├── BaseContext.cs │ │ │ │ ├── BaseTypeContext.cs │ │ │ │ ├── BaseTypeTracker.cs │ │ │ │ ├── BuilderPatternCondition.cs │ │ │ │ ├── BuilderPatternDescriptor.cs │ │ │ │ ├── ConstantValueFinder.cs │ │ │ │ ├── ElementAccessContext.cs │ │ │ │ ├── ElementAccessTracker.cs │ │ │ │ ├── FieldAccessContext.cs │ │ │ │ ├── FieldAccessTracker.cs │ │ │ │ ├── InvocationContext.cs │ │ │ │ ├── InvocationTracker.cs │ │ │ │ ├── MemberDescriptor.cs │ │ │ │ ├── MethodDeclarationContext.cs │ │ │ │ ├── MethodDeclarationTracker.cs │ │ │ │ ├── ObjectCreationContext.cs │ │ │ │ ├── ObjectCreationTracker.cs │ │ │ │ ├── PropertyAccessContext.cs │ │ │ │ ├── PropertyAccessTracker.cs │ │ │ │ ├── SyntaxBaseContext.cs │ │ │ │ ├── SyntaxTrackerBase.cs │ │ │ │ ├── TrackerBase.cs │ │ │ │ └── TrackerInput.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.Shared/ │ │ │ ├── LoggingFrameworkMethods.cs │ │ │ ├── SonarAnalyzer.Shared.projitems │ │ │ ├── SonarAnalyzer.Shared.shproj │ │ │ └── Syntax/ │ │ │ └── Extensions/ │ │ │ ├── MemberAccessExpressionSyntaxExtensionsShared.cs │ │ │ ├── StatementSyntaxExtensionsShared.cs │ │ │ ├── SyntaxNodeExtensionsShared.cs │ │ │ └── SyntaxTokenExtensionsShared.cs │ │ ├── SonarAnalyzer.ShimLayer/ │ │ │ ├── Common/ │ │ │ │ ├── ISyntaxWrapper.cs │ │ │ │ └── SyntaxNodeTypes.cs │ │ │ ├── PartialTypes/ │ │ │ │ ├── BaseNamespaceDeclarationSyntaxWrapper.cs │ │ │ │ ├── BaseObjectCreationExpressionSyntaxWrapper.cs │ │ │ │ └── CommonForEachStatementSyntaxWrapper.cs │ │ │ ├── SonarAnalyzer.ShimLayer.csproj │ │ │ ├── TemporaryLightUp/ │ │ │ │ ├── LightupHelpers.cs │ │ │ │ ├── SeparatedSyntaxListWrapper`1.cs │ │ │ │ ├── SyntaxWrapper`1.cs │ │ │ │ └── TryGetValueAccessor`3.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.ShimLayer.CodeGeneration/ │ │ │ ├── GeneratorSyntaxExtensions.cs │ │ │ ├── LICENSE │ │ │ ├── OperationLightupGenerator.cs │ │ │ ├── RoslynHashCode.cs │ │ │ ├── SonarAnalyzer.ShimLayer.CodeGeneration.csproj │ │ │ ├── SyntaxLightupGenerator.cs │ │ │ ├── XElementExtensions.cs │ │ │ ├── XmlSyntaxFactory.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.ShimLayer.Generator/ │ │ │ ├── Factory.cs │ │ │ ├── Model/ │ │ │ │ ├── MemberDescriptor.cs │ │ │ │ ├── ModelBuilder.cs │ │ │ │ ├── StrategyModel.cs │ │ │ │ ├── TypeDescriptor.cs │ │ │ │ └── TypeLoader.cs │ │ │ ├── ShimLayerGenerator.cs │ │ │ ├── SonarAnalyzer.ShimLayer.Generator.csproj │ │ │ ├── Strategies/ │ │ │ │ ├── IOperationStrategy.cs │ │ │ │ ├── NewEnumStrategy.cs │ │ │ │ ├── NoChangeStrategy.cs │ │ │ │ ├── PartialEnumStrategy.cs │ │ │ │ ├── SeparatedSyntaxListStrategy.cs │ │ │ │ ├── SkipStrategy.cs │ │ │ │ ├── Strategy.cs │ │ │ │ ├── SyntaxNodeExtendStrategy.cs │ │ │ │ └── SyntaxNodeWrapStrategy.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.ShimLayer.Lightup/ │ │ │ ├── AnalysisContext/ │ │ │ │ ├── CompilationStartAnalysisContextExtensions.cs │ │ │ │ └── SymbolStartAnalysisContextWrapper.cs │ │ │ ├── AnalyzerConfigOptionsProviderWrapper.cs │ │ │ ├── AnalyzerConfigOptionsWrapper.cs │ │ │ ├── AnalyzerOptionsExtensions.cs │ │ │ ├── ArgumentSyntaxExtensions.cs │ │ │ ├── BaseMethodDeclarationSyntaxExtensions.cs │ │ │ ├── CSharp7.md │ │ │ ├── CSharp71.md │ │ │ ├── CSharp72.md │ │ │ ├── CSharp73.md │ │ │ ├── CSharp8.md │ │ │ ├── IFieldSymbolExtensions.cs │ │ │ ├── INamedTypeSymbolExtensions.cs │ │ │ ├── ISyntaxWrapper`1.cs │ │ │ ├── ITypeParameterSymbolExtensions.cs │ │ │ ├── ITypeSymbolExtensions.cs │ │ │ ├── LICENSE │ │ │ ├── LightupHelpers.cs │ │ │ ├── OperationInterfaces.xml │ │ │ ├── README.md │ │ │ ├── SeparatedSyntaxListWrapper`1.cs │ │ │ ├── Sonar/ │ │ │ │ ├── CaptureId.cs │ │ │ │ ├── CompilationOptionsWrapper.cs │ │ │ │ ├── IEventSymbolExtensions.cs │ │ │ │ ├── IMethodSymbolExtensions.cs │ │ │ │ ├── INamedTypeSymbolExtensions.Sonar.cs │ │ │ │ ├── IOperationWrapper.cs │ │ │ │ ├── IOperationWrapperSonar.cs │ │ │ │ ├── IPropertySymbolExtensions.cs │ │ │ │ ├── ISymbolNullableExtensions.cs │ │ │ │ ├── ITypeSymbolExtension.cs │ │ │ │ ├── NullabilityInfo.cs │ │ │ │ ├── SyntaxTreeOptionsProviderWrapper.cs │ │ │ │ └── TypeInfoExtensions.cs │ │ │ ├── SonarAnalyzer.ShimLayer.Lightup.csproj │ │ │ ├── StatementSyntaxExtensions.cs │ │ │ ├── Syntax.xml │ │ │ ├── SyntaxFactoryEx.cs │ │ │ ├── SyntaxFactsEx.cs │ │ │ ├── SyntaxWrapper`1.cs │ │ │ ├── TryGetValueAccessor`3.cs │ │ │ ├── WrapperHelper.cs │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.SourceGenerators/ │ │ │ ├── RuleCatalogGenerator.cs │ │ │ ├── SonarAnalyzer.SourceGenerators.csproj │ │ │ └── packages.lock.json │ │ ├── SonarAnalyzer.VisualBasic/ │ │ │ ├── Metrics/ │ │ │ │ ├── VisualBasicCognitiveComplexityMetric.cs │ │ │ │ ├── VisualBasicExecutableLinesMetric.cs │ │ │ │ └── VisualBasicMetrics.cs │ │ │ ├── Properties/ │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── Rules/ │ │ │ │ ├── AllBranchesShouldNotHaveSameImplementation.cs │ │ │ │ ├── AlwaysSetDateTimeKind.cs │ │ │ │ ├── ArrayCreationLongSyntax.cs │ │ │ │ ├── ArrayCreationLongSyntaxCodeFix.cs │ │ │ │ ├── ArrayDesignatorOnVariable.cs │ │ │ │ ├── ArrayDesignatorOnVariableCodeFix.cs │ │ │ │ ├── ArrayInitializationMultipleStatements.cs │ │ │ │ ├── ArrayPassedAsParams.cs │ │ │ │ ├── AspNet/ │ │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.cs │ │ │ │ │ └── RouteTemplateShouldNotStartWithSlash.cs │ │ │ │ ├── AvoidDateTimeNowForBenchmarking.cs │ │ │ │ ├── AvoidUnsealedAttributes.cs │ │ │ │ ├── BeginInvokePairedWithEndInvoke.cs │ │ │ │ ├── BinaryOperationWithIdenticalExpressions.cs │ │ │ │ ├── BooleanCheckInverted.cs │ │ │ │ ├── BooleanLiteralUnnecessary.cs │ │ │ │ ├── BypassingAccessibility.cs │ │ │ │ ├── CatchRethrow.cs │ │ │ │ ├── CertificateValidationCheck.cs │ │ │ │ ├── CheckFileLicense.cs │ │ │ │ ├── ClassNamedException.cs │ │ │ │ ├── ClassNotInstantiatable.cs │ │ │ │ ├── ClassShouldNotBeEmpty.cs │ │ │ │ ├── CognitiveComplexity.cs │ │ │ │ ├── CollectionEmptinessChecking.cs │ │ │ │ ├── CommentKeyword.cs │ │ │ │ ├── CommentLineEnd.cs │ │ │ │ ├── CommentsShouldNotBeEmpty.cs │ │ │ │ ├── ConditionalStructureSameCondition.cs │ │ │ │ ├── ConditionalStructureSameImplementation.cs │ │ │ │ ├── ConstructorArgumentValueShouldExist.cs │ │ │ │ ├── DangerousGetHandleShouldNotBeCalled.cs │ │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.cs │ │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.cs │ │ │ │ ├── DebuggerDisplayUsesExistingMembers.cs │ │ │ │ ├── DeclareTypesInNamespaces.cs │ │ │ │ ├── DoNotCheckZeroSizeCollection.cs │ │ │ │ ├── DoNotHardcodeCredentials.cs │ │ │ │ ├── DoNotHardcodeSecrets.cs │ │ │ │ ├── DoNotInstantiateSharedClasses.cs │ │ │ │ ├── DoNotLockOnSharedResource.cs │ │ │ │ ├── DoNotLockWeakIdentityObjects.cs │ │ │ │ ├── DoNotNestTernaryOperators.cs │ │ │ │ ├── DoNotOverwriteCollectionElements.cs │ │ │ │ ├── DoNotThrowFromDestructors.cs │ │ │ │ ├── DoNotUseByVal.cs │ │ │ │ ├── DoNotUseByValCodeFix.cs │ │ │ │ ├── DoNotUseDateTimeNow.cs │ │ │ │ ├── DoNotUseIIf.cs │ │ │ │ ├── DoNotUseIIfCodeFix.cs │ │ │ │ ├── EmptyMethod.cs │ │ │ │ ├── EmptyNestedBlock.cs │ │ │ │ ├── EncryptionAlgorithmsShouldBeSecure.cs │ │ │ │ ├── EndStatementUsage.cs │ │ │ │ ├── EnumNameHasEnumSuffix.cs │ │ │ │ ├── EventNameContainsBeforeOrAfter.cs │ │ │ │ ├── ExceptionsShouldBePublic.cs │ │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.cs │ │ │ │ ├── ExitStatementUsage.cs │ │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.cs │ │ │ │ ├── ExpressionComplexity.cs │ │ │ │ ├── ExtensionMethodShouldNotExtendObject.cs │ │ │ │ ├── FieldShadowsParentField.cs │ │ │ │ ├── FieldShouldNotBePublic.cs │ │ │ │ ├── FileLines.cs │ │ │ │ ├── FindInsteadOfFirstOrDefault.cs │ │ │ │ ├── FlagsEnumWithoutInitializer.cs │ │ │ │ ├── FlagsEnumZeroMember.cs │ │ │ │ ├── FunctionComplexity.cs │ │ │ │ ├── FunctionNestingDepth.cs │ │ │ │ ├── GenericInheritanceShouldNotBeRecursive.cs │ │ │ │ ├── GotoStatement.cs │ │ │ │ ├── Hotspots/ │ │ │ │ │ ├── CommandPath.cs │ │ │ │ │ ├── ConfiguringLoggers.cs │ │ │ │ │ ├── CreatingHashAlgorithms.cs │ │ │ │ │ ├── DeliveringDebugFeaturesInProduction.cs │ │ │ │ │ ├── DisablingRequestValidation.cs │ │ │ │ │ ├── ExecutingSqlQueries.cs │ │ │ │ │ ├── ExpandingArchives.cs │ │ │ │ │ ├── HardcodedIpAddress.cs │ │ │ │ │ ├── PubliclyWritableDirectories.cs │ │ │ │ │ ├── RequestsWithExcessiveLength.cs │ │ │ │ │ ├── SpecifyTimeoutOnRegex.cs │ │ │ │ │ └── UsingNonstandardCryptography.cs │ │ │ │ ├── IfChainWithoutElse.cs │ │ │ │ ├── IfCollapsible.cs │ │ │ │ ├── ImplementSerializationMethodsCorrectly.cs │ │ │ │ ├── IndexOfCheckAgainstZero.cs │ │ │ │ ├── IndexedPropertyWithMultipleParameters.cs │ │ │ │ ├── InsecureEncryptionAlgorithm.cs │ │ │ │ ├── InsecureTemporaryFilesCreation.cs │ │ │ │ ├── InsteadOfAny.cs │ │ │ │ ├── InvalidCastToInterface.cs │ │ │ │ ├── JwtSigned.cs │ │ │ │ ├── LineContinuation.cs │ │ │ │ ├── LineLength.cs │ │ │ │ ├── LinkedListPropertiesInsteadOfMethods.cs │ │ │ │ ├── LooseFilePermissions.cs │ │ │ │ ├── MarkAssemblyWithAssemblyVersionAttribute.cs │ │ │ │ ├── MarkAssemblyWithClsCompliantAttribute.cs │ │ │ │ ├── MarkAssemblyWithComVisibleAttribute.cs │ │ │ │ ├── MarkWindowsFormsMainWithStaThread.cs │ │ │ │ ├── MethodOverloadsShouldBeGrouped.cs │ │ │ │ ├── MethodParameterUnused.cs │ │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.cs │ │ │ │ ├── MethodsShouldNotHaveTooManyLines.cs │ │ │ │ ├── MultipleVariableDeclaration.cs │ │ │ │ ├── MultipleVariableDeclarationCodeFix.cs │ │ │ │ ├── NameOfShouldBeUsed.cs │ │ │ │ ├── Naming/ │ │ │ │ │ ├── ClassName.cs │ │ │ │ │ ├── EnumNameShouldFollowRegex.cs │ │ │ │ │ ├── EnumerationValueName.cs │ │ │ │ │ ├── EventHandlerName.cs │ │ │ │ │ ├── EventName.cs │ │ │ │ │ ├── FieldNameChecker.cs │ │ │ │ │ ├── FunctionName.cs │ │ │ │ │ ├── InterfaceName.cs │ │ │ │ │ ├── LocalVariableName.cs │ │ │ │ │ ├── NamespaceName.cs │ │ │ │ │ ├── ParameterName.cs │ │ │ │ │ ├── PrivateConstantFieldName.cs │ │ │ │ │ ├── PrivateFieldName.cs │ │ │ │ │ ├── PrivateSharedReadonlyFieldName.cs │ │ │ │ │ ├── PropertyName.cs │ │ │ │ │ ├── PublicConstantFieldName.cs │ │ │ │ │ ├── PublicFieldName.cs │ │ │ │ │ ├── PublicSharedReadonlyFieldName.cs │ │ │ │ │ └── TypeParameterName.cs │ │ │ │ ├── NegatedIsExpression.cs │ │ │ │ ├── NegatedIsExpressionCodeFix.cs │ │ │ │ ├── NoExceptionsInFinally.cs │ │ │ │ ├── NonAsyncTaskShouldNotReturnNull.cs │ │ │ │ ├── ObsoleteAttributes.cs │ │ │ │ ├── OnErrorStatement.cs │ │ │ │ ├── OptionExplicitOn.cs │ │ │ │ ├── OptionStrictOn.cs │ │ │ │ ├── OptionalParameter.cs │ │ │ │ ├── OptionalParameterNotPassedToBaseCall.cs │ │ │ │ ├── ParameterAssignedTo.cs │ │ │ │ ├── ParameterNameMatchesOriginal.cs │ │ │ │ ├── ParametersCorrectOrder.cs │ │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttribute.cs │ │ │ │ ├── PreferGuidEmpty.cs │ │ │ │ ├── PropertiesAccessCorrectField.cs │ │ │ │ ├── PropertyGetterWithThrow.cs │ │ │ │ ├── PropertyWithArrayType.cs │ │ │ │ ├── PropertyWriteOnly.cs │ │ │ │ ├── ProvideDeserializationMethodsForOptionalFields.cs │ │ │ │ ├── PublicConstantField.cs │ │ │ │ ├── PublicMethodWithMultidimensionalArray.cs │ │ │ │ ├── PureAttributeOnVoidMethod.cs │ │ │ │ ├── RedundantExitSelect.cs │ │ │ │ ├── RedundantNullCheck.cs │ │ │ │ ├── RedundantParentheses.cs │ │ │ │ ├── RegularExpressions/ │ │ │ │ │ └── RegexMustHaveValidSyntax.cs │ │ │ │ ├── ReversedOperators.cs │ │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.cs │ │ │ │ ├── SelfAssignment.cs │ │ │ │ ├── SetPropertiesInsteadOfMethods.cs │ │ │ │ ├── ShiftDynamicNotInteger.cs │ │ │ │ ├── ShouldImplementExportedInterfaces.cs │ │ │ │ ├── SimpleDoLoop.cs │ │ │ │ ├── SingleStatementPerLine.cs │ │ │ │ ├── StringConcatenationInLoop.cs │ │ │ │ ├── StringConcatenationWithPlus.cs │ │ │ │ ├── StringConcatenationWithPlusCodeFix.cs │ │ │ │ ├── StringLiteralShouldNotBeDuplicated.cs │ │ │ │ ├── SwitchCasesMinimumThree.cs │ │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements.cs │ │ │ │ ├── SwitchShouldNotBeNested.cs │ │ │ │ ├── SwitchWithoutDefault.cs │ │ │ │ ├── TabCharacter.cs │ │ │ │ ├── TestsShouldNotUseThreadSleep.cs │ │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalled.cs │ │ │ │ ├── ThrowReservedExceptions.cs │ │ │ │ ├── ToStringShouldNotReturnNull.cs │ │ │ │ ├── TooManyLabelsInSwitch.cs │ │ │ │ ├── TooManyParameters.cs │ │ │ │ ├── UnaryPrefixOperatorRepeated.cs │ │ │ │ ├── UnconditionalJumpStatement.cs │ │ │ │ ├── UnnecessaryBitwiseOperation.cs │ │ │ │ ├── UnnecessaryBitwiseOperationCodeFix.cs │ │ │ │ ├── UnsignedTypesUsage.cs │ │ │ │ ├── UnusedStringBuilder.cs │ │ │ │ ├── UriShouldNotBeHardcoded.cs │ │ │ │ ├── UseCharOverloadOfStringMethods.cs │ │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.cs │ │ │ │ ├── UseFindSystemTimeZoneById.cs │ │ │ │ ├── UseIFormatProviderForParsingDateAndTime.cs │ │ │ │ ├── UseIndexingInsteadOfLinqMethods.cs │ │ │ │ ├── UseLambdaParameterInConcurrentDictionary.cs │ │ │ │ ├── UseReturnStatement.cs │ │ │ │ ├── UseShortCircuitingOperator.cs │ │ │ │ ├── UseShortCircuitingOperatorCodeFix.cs │ │ │ │ ├── UseTestableTimeProvider.cs │ │ │ │ ├── UseTrueForAll.cs │ │ │ │ ├── UseUnixEpoch.cs │ │ │ │ ├── UseUnixEpochCodeFix.cs │ │ │ │ ├── UseWhereBeforeOrderBy.cs │ │ │ │ ├── UseWithStatement.cs │ │ │ │ ├── Utilities/ │ │ │ │ │ ├── AnalysisWarningAnalyzer.cs │ │ │ │ │ ├── CopyPasteTokenAnalyzer.cs │ │ │ │ │ ├── FileMetadataAnalyzer.cs │ │ │ │ │ ├── LogAnalyzer.cs │ │ │ │ │ ├── MetricsAnalyzer.cs │ │ │ │ │ ├── SymbolReferenceAnalyzer.cs │ │ │ │ │ ├── TelemetryAnalyzer.cs │ │ │ │ │ ├── TestMethodDeclarationsAnalyzer.cs │ │ │ │ │ └── TokenTypeAnalyzer.cs │ │ │ │ ├── ValueTypeShouldImplementIEquatable.cs │ │ │ │ ├── VariableUnused.cs │ │ │ │ ├── WcfNonVoidOneWay.cs │ │ │ │ └── WeakSslTlsProtocols.cs │ │ │ ├── SonarAnalyzer.VisualBasic.csproj │ │ │ ├── packages.lock.json │ │ │ └── sonarpedia.json │ │ └── SonarAnalyzer.VisualBasic.Core/ │ │ ├── Common/ │ │ │ └── DescriptorFactory.cs │ │ ├── Extensions/ │ │ │ ├── ICompilationReportExtensions.cs │ │ │ ├── ISymbolExtensions.cs │ │ │ ├── SonarAnalysisContextExtensions.cs │ │ │ ├── SonarCompilationStartAnalysisContextExtensions.cs │ │ │ ├── SonarParametrizedAnalysisContextExtensions.cs │ │ │ └── SonarSyntaxNodeReportingContextExtensions.cs │ │ ├── Facade/ │ │ │ ├── Implementation/ │ │ │ │ ├── VisualBasicSyntaxFacade.cs │ │ │ │ ├── VisualBasicSyntaxKindFacade.cs │ │ │ │ └── VisualBasicTrackerFacade.cs │ │ │ └── VisualBasicFacade.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── SonarAnalyzer.VisualBasic.Core.csproj │ │ ├── Syntax/ │ │ │ ├── Extensions/ │ │ │ │ ├── ArgumentListSyntaxExtensions.cs │ │ │ │ ├── ArgumentSyntaxExtensions.cs │ │ │ │ ├── AttributeSyntaxExtensions.cs │ │ │ │ ├── ExpressionSyntaxExtensions.Roslyn.cs │ │ │ │ ├── ExpressionSyntaxExtensions.cs │ │ │ │ ├── InterpolatedStringExpressionSyntaxExtensions.cs │ │ │ │ ├── InvocationExpressionSyntaxExtensions.cs │ │ │ │ ├── MemberAccessExpressionSyntaxExtensions.cs │ │ │ │ ├── MethodBlockBaseSyntaxExtensions.cs │ │ │ │ ├── MethodBlockSyntaxExtensions.cs │ │ │ │ ├── ObjectCreationExpressionSyntaxExtensions.cs │ │ │ │ ├── StatementSyntaxExtensions.cs │ │ │ │ ├── SyntaxNodeExtensions.Roslyn.cs │ │ │ │ ├── SyntaxNodeExtensionsVisualBasic.cs │ │ │ │ ├── SyntaxTokenExtensions.cs │ │ │ │ ├── SyntaxTriviaExtensions.cs │ │ │ │ └── VisualBasicCompilationExtensions.cs │ │ │ └── Utilities/ │ │ │ ├── SafeVisualBasicSyntaxWalker.cs │ │ │ ├── VisualBasicAttributeParameterLookup.cs │ │ │ ├── VisualBasicEquivalenceChecker.cs │ │ │ ├── VisualBasicExpressionNumericConverter.cs │ │ │ ├── VisualBasicGeneratedCodeRecognizer.cs │ │ │ ├── VisualBasicMethodParameterLookup.cs │ │ │ ├── VisualBasicRemovableDeclarationCollector.cs │ │ │ ├── VisualBasicStringInterpolationConstantValueResolver.cs │ │ │ └── VisualBasicSyntaxClassifier.cs │ │ ├── Trackers/ │ │ │ ├── VisualBasicArgumentTracker.cs │ │ │ ├── VisualBasicAssignmentFinder.cs │ │ │ ├── VisualBasicBaseTypeTracker.cs │ │ │ ├── VisualBasicBuilderPatternCondition.cs │ │ │ ├── VisualBasicConstantValueFinder.cs │ │ │ ├── VisualBasicElementAccessTracker.cs │ │ │ ├── VisualBasicFieldAccessTracker.cs │ │ │ ├── VisualBasicInvocationTracker.cs │ │ │ ├── VisualBasicMethodDeclarationTracker.cs │ │ │ ├── VisualBasicObjectCreationTracker.cs │ │ │ └── VisualBasicPropertyAccessTracker.cs │ │ └── packages.lock.json │ ├── stylecop.json │ └── tests/ │ ├── Directory.Build.targets │ ├── FrameworkMocks/ │ │ └── src/ │ │ ├── Directory.Build.targets │ │ ├── Mocks.sln │ │ ├── Mscorlib3.5Mock/ │ │ │ ├── Debugger.cs │ │ │ ├── mscorlib.csproj │ │ │ └── packages.lock.json │ │ ├── Mscorlib4.0Mock/ │ │ │ ├── Debugger.cs │ │ │ ├── mscorlib.csproj │ │ │ └── packages.lock.json │ │ ├── Mscorlib4.0MockWithIo/ │ │ │ ├── Debugger.cs │ │ │ ├── UnmanagedMemoryStream.cs │ │ │ ├── mscorlib.csproj │ │ │ └── packages.lock.json │ │ └── Mscorlib4.8Mock/ │ │ ├── Debugger.cs │ │ ├── UnmanagedMemoryStream.cs │ │ ├── mscorlib.csproj │ │ └── packages.lock.json │ ├── SonarAnalyzer.CSharp.Core.Test/ │ │ ├── Extensions/ │ │ │ ├── BaseArgumentListSyntaxExtensionsTest.cs │ │ │ ├── BaseMethodDeclarationSyntaxExtensionsTest.cs │ │ │ ├── ISymbolExtensionsTest.cs │ │ │ ├── TypeDeclarationSyntaxExtensionsTest.cs │ │ │ └── TypeSyntaxExtensionsTest.cs │ │ ├── Facade/ │ │ │ ├── CSharpFacadeTest.cs │ │ │ └── Implementation/ │ │ │ └── CSharpSyntaxFacadeTest.cs │ │ ├── RegularExpressions/ │ │ │ └── MessageTemplateParserTest.cs │ │ ├── SonarAnalyzer.CSharp.Core.Test.csproj │ │ ├── Syntax/ │ │ │ ├── Extensions/ │ │ │ │ ├── ArgumentSyntaxExtensionsTest.cs │ │ │ │ ├── AssignmentExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── AttributeDataExtensionsTest.cs │ │ │ │ ├── AttributeSyntaxExtensionsTest.cs │ │ │ │ ├── AwaitExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── BlockSyntaxExtensionsTest.cs │ │ │ │ ├── DiagnosticDescriptorExtensionsTest.cs │ │ │ │ ├── ExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── ITupleOperationWrapperExtensionsTest.cs │ │ │ │ ├── InterpolatedStringExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── InvocationExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── ObjectCreationExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── PatternSyntaxWrapperExtensionsTest.cs │ │ │ │ ├── PropertyDeclarationSyntaxExtensionsTest.cs │ │ │ │ ├── StatementSyntaxExtensionsTest.cs │ │ │ │ ├── SyntaxNodeExtensionsCSharpTest.cs │ │ │ │ ├── SyntaxTokenExtensionsTest.cs │ │ │ │ ├── SyntaxTreeExtensionsTest.cs │ │ │ │ ├── TupleExpressionSyntaxExtensionsTest.cs │ │ │ │ ├── TypeExtensionsTest.cs │ │ │ │ └── VariableDesignationSyntaxWrapperTest.cs │ │ │ └── Utilities/ │ │ │ └── SafeCSharpSyntaxWalkerTest.cs │ │ ├── Trackers/ │ │ │ └── FieldAccessTrackerTest.cs │ │ ├── Wrappers/ │ │ │ ├── MethodDeclarationFactoryTest.cs │ │ │ └── ObjectCreationFactoryTest.cs │ │ └── packages.lock.json │ ├── SonarAnalyzer.CSharp.Styling.Test/ │ │ ├── Common/ │ │ │ ├── StylingRuleTest.cs │ │ │ └── StylingVerifierBuilder.cs │ │ ├── Rules/ │ │ │ ├── AllArgumentsOnSameLineTest.cs │ │ │ ├── AllParametersOnSameColumnTest.cs │ │ │ ├── AllParametersOnSameLineTest.cs │ │ │ ├── ArrowLocationTest.cs │ │ │ ├── AvoidArrangeActAssertCommentTest.cs │ │ │ ├── AvoidDeconstructionTest.cs │ │ │ ├── AvoidGetTest.cs │ │ │ ├── AvoidListForEachTest.cs │ │ │ ├── AvoidNullableTest.cs │ │ │ ├── AvoidPrimaryConstructorTest.cs │ │ │ ├── AvoidUnusedInterpolationTest.cs │ │ │ ├── AvoidValueTupleTest.cs │ │ │ ├── AvoidVarPatternTest.cs │ │ │ ├── FieldOrderingTest.cs │ │ │ ├── FileScopeNamespaceTest.cs │ │ │ ├── HelperInTypeNameTest.cs │ │ │ ├── IndentArgumentTest.cs │ │ │ ├── IndentInvocationTest.cs │ │ │ ├── IndentOperatorTest.cs │ │ │ ├── IndentRawStringTest.cs │ │ │ ├── IndentTernaryTest.cs │ │ │ ├── InitializerLineTest.cs │ │ │ ├── LambdaParameterNameTest.cs │ │ │ ├── LocalFunctionLocationTest.cs │ │ │ ├── MemberAccessLineTest.cs │ │ │ ├── MemberVisibilityOrderingTest.cs │ │ │ ├── MethodExpressionBodyLineTest.cs │ │ │ ├── MoveMethodToDedicatedExtensionClassTest.cs │ │ │ ├── NamespaceNameTest.cs │ │ │ ├── NullPatternMatchingTest.cs │ │ │ ├── OperatorLocationTest.cs │ │ │ ├── PropertyOrderingTest.cs │ │ │ ├── ProtectedFieldsCaseTest.cs │ │ │ ├── SeparateDeclarationsTest.cs │ │ │ ├── TernaryLineTest.cs │ │ │ ├── TypeMemberOrderingTest.cs │ │ │ ├── UseDifferentMemberTest.cs │ │ │ ├── UseFieldTest.cs │ │ │ ├── UseInnermostRegistrationContextTest.cs │ │ │ ├── UseLinqExtensionsTest.cs │ │ │ ├── UseNullInsteadOfDefaultTest.cs │ │ │ ├── UsePositiveLogicTest.cs │ │ │ ├── UseRawStringTest.cs │ │ │ ├── UseRegexSafeIsMatchTest.cs │ │ │ ├── UseShortNameTest.cs │ │ │ └── UseVarTest.cs │ │ ├── SonarAnalyzer.CSharp.Styling.Test.csproj │ │ ├── TestCases/ │ │ │ ├── AllArgumentsOnSameLine.cs │ │ │ ├── AllParametersOnSameColumn.cs │ │ │ ├── AllParametersOnSameLine.cs │ │ │ ├── ArrowLocation.cs │ │ │ ├── AvoidArrangeActAssertComment.cs │ │ │ ├── AvoidDeconstruction.TopLevelStatements.cs │ │ │ ├── AvoidDeconstruction.cs │ │ │ ├── AvoidGet.cs │ │ │ ├── AvoidListForEach.cs │ │ │ ├── AvoidNullable.cs │ │ │ ├── AvoidPrimaryConstructor.cs │ │ │ ├── AvoidUnusedInterpolation.cs │ │ │ ├── AvoidValueTuple.cs │ │ │ ├── AvoidVarPattern.cs │ │ │ ├── FieldOrdering.cs │ │ │ ├── FileScopeNamespace.Compliant.cs │ │ │ ├── FileScopeNamespace.cs │ │ │ ├── HelperInTypeName.cs │ │ │ ├── IndentArgument.cs │ │ │ ├── IndentInvocation.cs │ │ │ ├── IndentOperator.cs │ │ │ ├── IndentRawString.cs │ │ │ ├── IndentTernary.cs │ │ │ ├── InitializerLine.cs │ │ │ ├── LambdaParameterName.cs │ │ │ ├── LocalFunctionLocation.TopLevelStatements.cs │ │ │ ├── LocalFunctionLocation.cs │ │ │ ├── MemberAccessLine.cs │ │ │ ├── MemberVisibilityOrdering.cs │ │ │ ├── MethodExpressionBodyLine.cs │ │ │ ├── MoveMethodToDedicatedExtensionClass.cs │ │ │ ├── NullPatternMatching.cs │ │ │ ├── OperatorLocation.cs │ │ │ ├── PropertyOrdering.cs │ │ │ ├── ProtectedFieldsCase.cs │ │ │ ├── SeparateDeclarations.cs │ │ │ ├── TernaryLine.cs │ │ │ ├── TypeMemberOrdering.cs │ │ │ ├── UseDifferentMember.cs │ │ │ ├── UseField.cs │ │ │ ├── UseInnermostRegistrationContext.cs │ │ │ ├── UseLinqExtensions.cs │ │ │ ├── UseNullInsteadOfDefault.cs │ │ │ ├── UsePositiveLogic.cs │ │ │ ├── UseRawString.cs │ │ │ ├── UseRegexSafeIsMatch.cs │ │ │ ├── UseShortName.cs │ │ │ └── UseVar.cs │ │ └── packages.lock.json │ ├── SonarAnalyzer.Core.Test/ │ │ ├── AnalysisContext/ │ │ │ ├── IssueReporterTest.cs │ │ │ ├── SonarCodeBlockReportingContextTest.cs │ │ │ ├── SonarCodeBlockStartAnalysisContextTest.cs │ │ │ ├── SonarCodeFixContextTest.cs │ │ │ ├── SonarCompilationReportingContextTest.cs │ │ │ ├── SonarCompilationStartAnalysisContextTest.cs │ │ │ ├── SonarSemanticModelReportingContextTest.cs │ │ │ ├── SonarSymbolReportingContextTest.cs │ │ │ ├── SonarSyntaxNodeReportingContextTest.cs │ │ │ └── SonarSyntaxTreeReportingContextTest.cs │ │ ├── Analyzers/ │ │ │ └── DiagnosticDescriptorFactoryTest.cs │ │ ├── Common/ │ │ │ ├── AnalyzerAdditionalFileTest.cs │ │ │ ├── AnalyzerConfigurationTest.cs │ │ │ ├── AnalyzerLanguageTest.cs │ │ │ ├── BidirectionalDictionaryTest.cs │ │ │ ├── DisjointSetsTest.cs │ │ │ ├── FixAllProviders/ │ │ │ │ └── DocumentBasedFixAllProviderTest.cs │ │ │ ├── HashCodeTest.cs │ │ │ ├── MultiValueDictionaryTest.cs │ │ │ ├── NaturalLanguageDetectorTest.cs │ │ │ ├── RuleLoaderTest.cs │ │ │ ├── ShannonEntropyTest.cs │ │ │ └── UnexpectedLanguageExceptionTest.cs │ │ ├── Configuration/ │ │ │ ├── AnalysisConfigReaderTest.cs │ │ │ ├── FilesToAnalyzeProviderTest.cs │ │ │ ├── ProjectConfigReaderTest.cs │ │ │ ├── ProjectTypeCacheTest.cs │ │ │ ├── SonarLintXmlReaderTest.cs │ │ │ └── SonarLintXmlTest.cs │ │ ├── Extensions/ │ │ │ ├── BasicBlockExtensionsTest.cs │ │ │ ├── CompilationExtensionsTest.cs │ │ │ ├── ControlFlowGraphExtensionsTest.cs │ │ │ ├── DictionaryExtensionsTest.cs │ │ │ ├── IEnumerableExtensionsTest.cs │ │ │ ├── IXmlLineInfoExtensionsTest.cs │ │ │ ├── RegexExtensionsTest.cs │ │ │ └── XAttributeExtensionsTest.cs │ │ ├── Json/ │ │ │ ├── JsonNodeTest.cs │ │ │ ├── JsonSerializerTest.cs │ │ │ ├── JsonWalkerTest.cs │ │ │ ├── LexicalAnalyzerTest.cs │ │ │ └── SyntaxAnalyzerTest.cs │ │ ├── RegularExpressions/ │ │ │ ├── RegexContextTest.cs │ │ │ └── WildcardPatternMatcherTest.cs │ │ ├── Semantics/ │ │ │ ├── Extensions/ │ │ │ │ ├── IMethodSymbolExtensionsTest.cs │ │ │ │ ├── INamedTypeSymbolExtensionsTest.cs │ │ │ │ ├── INamespaceSymbolExtensionsTest.cs │ │ │ │ ├── IParameterSymbolExtensionsTest.cs │ │ │ │ ├── IPropertySymbolExtensionsTest.cs │ │ │ │ ├── ISymbolExtensionsTest.cs │ │ │ │ └── ITypeSymbolExtensionsTest.cs │ │ │ ├── KnownAssemblyTest.cs │ │ │ ├── KnownMethodsTest.cs │ │ │ ├── KnownTypeTest.cs │ │ │ └── NetFrameworkVersionProviderTest.cs │ │ ├── SonarAnalyzer.Core.Test.csproj │ │ ├── Syntax/ │ │ │ ├── Extensions/ │ │ │ │ ├── ComparisonKindExtensionsTest.cs │ │ │ │ ├── CountComparisonResultExtensionsTest.cs │ │ │ │ ├── LocationExtensionsTest.cs │ │ │ │ ├── SyntaxNodeExtensionsTest.cs │ │ │ │ └── SyntaxTokenExtensionsTest.cs │ │ │ └── Utilities/ │ │ │ ├── GeneratedCodeRecognizerTest.cs │ │ │ └── VisualIndentComparerTest.cs │ │ ├── TestResources/ │ │ │ ├── FilesToAnalyze/ │ │ │ │ ├── EmptyFilesToAnalyze.txt │ │ │ │ ├── FilesToAnalyze.txt │ │ │ │ └── FilesToAnalyzeWithInvalidValues.txt │ │ │ ├── SonarLintXml/ │ │ │ │ ├── All_properties_cs/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── All_properties_vbnet/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── Duplicated_Properties/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── Incorrect_value_type/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── Missing_properties/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── Partially_missing_properties/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ └── PropertiesCSharpTrueVbnetFalse/ │ │ │ │ └── SonarLint.xml │ │ │ └── SonarProjectConfig/ │ │ │ ├── Invalid_DifferentClassName/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── Invalid_DifferentNamespace/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── Invalid_Xml/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingAnalysisConfigPath/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingFilesToAnalyzePath/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingOutPath/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingProjectPath/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingProjectType/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── MissingTargetFramework/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── Path_MixedSeparators/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── Path_Unix/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ ├── Path_Windows/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ └── UnexpectedProjectTypeValue/ │ │ │ └── SonarProjectConfig.xml │ │ └── packages.lock.json │ ├── SonarAnalyzer.Memory.Test/ │ │ ├── DotMemorySetupTest.cs │ │ ├── MSTestSettings.cs │ │ ├── Registrations/ │ │ │ └── SonarSyntaxNodeReportingContextMemoryTest.cs │ │ ├── RunMemoryTest.ps1 │ │ ├── SonarAnalyzer.Memory.Test.csproj │ │ └── packages.lock.json │ ├── SonarAnalyzer.ShimLayer.Generator.Test/ │ │ ├── FactoryTest.cs │ │ ├── Model/ │ │ │ ├── ModelBuilderTest.cs │ │ │ └── TypeLoaderTest.cs │ │ ├── Snapshots/ │ │ │ ├── Snap#AccessorDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#AliasQualifiedNameSyntax.g.cs.verified.cs │ │ │ ├── Snap#AllowsConstraintClauseSyntax.g.cs.verified.cs │ │ │ ├── Snap#AllowsConstraintSyntax.g.cs.verified.cs │ │ │ ├── Snap#AnonymousFunctionExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#AnonymousMethodExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#ArgumentKind.g.cs.verified.cs │ │ │ ├── Snap#ArgumentSyntax.g.cs.verified.cs │ │ │ ├── Snap#ArrayTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#BaseExpressionColonSyntax.g.cs.verified.cs │ │ │ ├── Snap#BaseMethodDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#BaseNamespaceDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#BaseObjectCreationExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#BaseParameterSyntax.g.cs.verified.cs │ │ │ ├── Snap#BasicBlockKind.g.cs.verified.cs │ │ │ ├── Snap#BinaryOperatorKind.g.cs.verified.cs │ │ │ ├── Snap#BinaryPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#BlockSyntax.g.cs.verified.cs │ │ │ ├── Snap#BranchKind.g.cs.verified.cs │ │ │ ├── Snap#BreakStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#CaseKind.g.cs.verified.cs │ │ │ ├── Snap#CasePatternSwitchLabelSyntax.g.cs.verified.cs │ │ │ ├── Snap#CheckedStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ClassDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#ClassOrStructConstraintSyntax.g.cs.verified.cs │ │ │ ├── Snap#CollectionElementSyntax.g.cs.verified.cs │ │ │ ├── Snap#CollectionExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#CommonForEachStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ConstantPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#ConstructorDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#ContinueStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ControlFlowBranchSemantics.g.cs.verified.cs │ │ │ ├── Snap#ControlFlowConditionKind.g.cs.verified.cs │ │ │ ├── Snap#ControlFlowRegionKind.g.cs.verified.cs │ │ │ ├── Snap#ConversionOperatorDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#ConversionOperatorMemberCrefSyntax.g.cs.verified.cs │ │ │ ├── Snap#CrefParameterSyntax.g.cs.verified.cs │ │ │ ├── Snap#DeclarationExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#DeclarationPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#DefaultConstraintSyntax.g.cs.verified.cs │ │ │ ├── Snap#DestructorDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#DiscardDesignationSyntax.g.cs.verified.cs │ │ │ ├── Snap#DiscardPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#DoStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#EmptyStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#EnumMemberDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#EventDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExpressionColonSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExpressionElementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExpressionOrPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExpressionStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExtensionBlockDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#ExtensionMemberCrefSyntax.g.cs.verified.cs │ │ │ ├── Snap#FieldExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#FileScopedNamespaceDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#FixedStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ForEachStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ForEachVariableStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#ForStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerCallingConventionSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerParameterListSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerParameterSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerUnmanagedCallingConventionListSyntax.g.cs.verified.cs │ │ │ ├── Snap#FunctionPointerUnmanagedCallingConventionSyntax.g.cs.verified.cs │ │ │ ├── Snap#GeneratedKind.g.cs.verified.cs │ │ │ ├── Snap#GenericNameSyntax.g.cs.verified.cs │ │ │ ├── Snap#GlobalStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#GotoStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#IAddressOfOperation.g.cs.verified.cs │ │ │ ├── Snap#IAnonymousFunctionOperation.g.cs.verified.cs │ │ │ ├── Snap#IAnonymousObjectCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IArgumentOperation.g.cs.verified.cs │ │ │ ├── Snap#IArrayCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IArrayElementReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IArrayInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#IAttributeOperation.g.cs.verified.cs │ │ │ ├── Snap#IAwaitOperation.g.cs.verified.cs │ │ │ ├── Snap#IBinaryOperation.g.cs.verified.cs │ │ │ ├── Snap#IBinaryPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IBlockOperation.g.cs.verified.cs │ │ │ ├── Snap#IBranchOperation.g.cs.verified.cs │ │ │ ├── Snap#ICaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#ICatchClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#ICaughtExceptionOperation.g.cs.verified.cs │ │ │ ├── Snap#ICoalesceAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#ICoalesceOperation.g.cs.verified.cs │ │ │ ├── Snap#ICollectionElementInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#ICollectionExpressionOperation.g.cs.verified.cs │ │ │ ├── Snap#ICompoundAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#IConditionalAccessInstanceOperation.g.cs.verified.cs │ │ │ ├── Snap#IConditionalAccessOperation.g.cs.verified.cs │ │ │ ├── Snap#IConditionalOperation.g.cs.verified.cs │ │ │ ├── Snap#IConstantPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IConstructorBodyOperation.g.cs.verified.cs │ │ │ ├── Snap#IConversionOperation.g.cs.verified.cs │ │ │ ├── Snap#IDeclarationExpressionOperation.g.cs.verified.cs │ │ │ ├── Snap#IDeclarationPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IDeconstructionAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#IDefaultCaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#IDefaultValueOperation.g.cs.verified.cs │ │ │ ├── Snap#IDelegateCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IDiscardOperation.g.cs.verified.cs │ │ │ ├── Snap#IDiscardPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IDynamicIndexerAccessOperation.g.cs.verified.cs │ │ │ ├── Snap#IDynamicInvocationOperation.g.cs.verified.cs │ │ │ ├── Snap#IDynamicMemberReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IDynamicObjectCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IEmptyOperation.g.cs.verified.cs │ │ │ ├── Snap#IEndOperation.g.cs.verified.cs │ │ │ ├── Snap#IEventAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#IEventReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IExpressionStatementOperation.g.cs.verified.cs │ │ │ ├── Snap#IFieldInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IFieldReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IFlowAnonymousFunctionOperation.g.cs.verified.cs │ │ │ ├── Snap#IFlowCaptureOperation.g.cs.verified.cs │ │ │ ├── Snap#IFlowCaptureReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IForEachLoopOperation.g.cs.verified.cs │ │ │ ├── Snap#IForLoopOperation.g.cs.verified.cs │ │ │ ├── Snap#IForToLoopOperation.g.cs.verified.cs │ │ │ ├── Snap#IFunctionPointerInvocationOperation.g.cs.verified.cs │ │ │ ├── Snap#IImplicitIndexerReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IIncrementOrDecrementOperation.g.cs.verified.cs │ │ │ ├── Snap#IInlineArrayAccessOperation.g.cs.verified.cs │ │ │ ├── Snap#IInstanceReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringAdditionOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringAppendOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringContentOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringHandlerArgumentPlaceholderOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringHandlerCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolatedStringTextOperation.g.cs.verified.cs │ │ │ ├── Snap#IInterpolationOperation.g.cs.verified.cs │ │ │ ├── Snap#IInvalidOperation.g.cs.verified.cs │ │ │ ├── Snap#IInvocationOperation.g.cs.verified.cs │ │ │ ├── Snap#IIsNullOperation.g.cs.verified.cs │ │ │ ├── Snap#IIsPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IIsTypeOperation.g.cs.verified.cs │ │ │ ├── Snap#ILabeledOperation.g.cs.verified.cs │ │ │ ├── Snap#IListPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#ILiteralOperation.g.cs.verified.cs │ │ │ ├── Snap#ILocalFunctionOperation.g.cs.verified.cs │ │ │ ├── Snap#ILocalReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#ILockOperation.g.cs.verified.cs │ │ │ ├── Snap#ILoopOperation.g.cs.verified.cs │ │ │ ├── Snap#IMemberInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IMemberReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IMethodBodyBaseOperation.g.cs.verified.cs │ │ │ ├── Snap#IMethodBodyOperation.g.cs.verified.cs │ │ │ ├── Snap#IMethodReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#INameOfOperation.g.cs.verified.cs │ │ │ ├── Snap#INegatedPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IObjectCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#IObjectOrCollectionInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IOmittedArgumentOperation.g.cs.verified.cs │ │ │ ├── Snap#IOperation.g.cs.verified.cs │ │ │ ├── Snap#IParameterInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IParameterReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IParenthesizedOperation.g.cs.verified.cs │ │ │ ├── Snap#IPatternCaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#IPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IPropertyInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IPropertyReferenceOperation.g.cs.verified.cs │ │ │ ├── Snap#IPropertySubpatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IRaiseEventOperation.g.cs.verified.cs │ │ │ ├── Snap#IRangeCaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#IRangeOperation.g.cs.verified.cs │ │ │ ├── Snap#IReDimClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#IReDimOperation.g.cs.verified.cs │ │ │ ├── Snap#IRecursivePatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IRelationalCaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#IRelationalPatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IReturnOperation.g.cs.verified.cs │ │ │ ├── Snap#ISimpleAssignmentOperation.g.cs.verified.cs │ │ │ ├── Snap#ISingleValueCaseClauseOperation.g.cs.verified.cs │ │ │ ├── Snap#ISizeOfOperation.g.cs.verified.cs │ │ │ ├── Snap#ISlicePatternOperation.g.cs.verified.cs │ │ │ ├── Snap#ISpreadOperation.g.cs.verified.cs │ │ │ ├── Snap#IStaticLocalInitializationSemaphoreOperation.g.cs.verified.cs │ │ │ ├── Snap#IStopOperation.g.cs.verified.cs │ │ │ ├── Snap#ISwitchCaseOperation.g.cs.verified.cs │ │ │ ├── Snap#ISwitchExpressionArmOperation.g.cs.verified.cs │ │ │ ├── Snap#ISwitchExpressionOperation.g.cs.verified.cs │ │ │ ├── Snap#ISwitchOperation.g.cs.verified.cs │ │ │ ├── Snap#ISymbolInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IThrowOperation.g.cs.verified.cs │ │ │ ├── Snap#ITranslatedQueryOperation.g.cs.verified.cs │ │ │ ├── Snap#ITryOperation.g.cs.verified.cs │ │ │ ├── Snap#ITupleBinaryOperation.g.cs.verified.cs │ │ │ ├── Snap#ITupleOperation.g.cs.verified.cs │ │ │ ├── Snap#ITypeOfOperation.g.cs.verified.cs │ │ │ ├── Snap#ITypeParameterObjectCreationOperation.g.cs.verified.cs │ │ │ ├── Snap#ITypePatternOperation.g.cs.verified.cs │ │ │ ├── Snap#IUnaryOperation.g.cs.verified.cs │ │ │ ├── Snap#IUsingDeclarationOperation.g.cs.verified.cs │ │ │ ├── Snap#IUsingOperation.g.cs.verified.cs │ │ │ ├── Snap#IUtf8StringOperation.g.cs.verified.cs │ │ │ ├── Snap#IVariableDeclarationGroupOperation.g.cs.verified.cs │ │ │ ├── Snap#IVariableDeclarationOperation.g.cs.verified.cs │ │ │ ├── Snap#IVariableDeclaratorOperation.g.cs.verified.cs │ │ │ ├── Snap#IVariableInitializerOperation.g.cs.verified.cs │ │ │ ├── Snap#IWhileLoopOperation.g.cs.verified.cs │ │ │ ├── Snap#IWithOperation.g.cs.verified.cs │ │ │ ├── Snap#IdentifierNameSyntax.g.cs.verified.cs │ │ │ ├── Snap#IfStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#IgnoredDirectiveTriviaSyntax.g.cs.verified.cs │ │ │ ├── Snap#ImplicitObjectCreationExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#ImplicitStackAllocArrayCreationExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#IncrementalGeneratorOutputKind.g.cs.verified.cs │ │ │ ├── Snap#IncrementalStepRunReason.g.cs.verified.cs │ │ │ ├── Snap#InstanceReferenceKind.g.cs.verified.cs │ │ │ ├── Snap#InstrumentationKind.g.cs.verified.cs │ │ │ ├── Snap#InterfaceDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#InterpolatedStringArgumentPlaceholderKind.g.cs.verified.cs │ │ │ ├── Snap#IsPatternExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#LabeledStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#LambdaExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#LanguageVersion.g.cs.verified.cs │ │ │ ├── Snap#LineDirectivePositionSyntax.g.cs.verified.cs │ │ │ ├── Snap#LineOrSpanDirectiveTriviaSyntax.g.cs.verified.cs │ │ │ ├── Snap#LineSpanDirectiveTriviaSyntax.g.cs.verified.cs │ │ │ ├── Snap#ListPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#LocalDeclarationStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#LocalFunctionStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#LockStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#LoopKind.g.cs.verified.cs │ │ │ ├── Snap#MemberDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#MetadataImportOptions.g.cs.verified.cs │ │ │ ├── Snap#MethodKind.g.cs.verified.cs │ │ │ ├── Snap#NameColonSyntax.g.cs.verified.cs │ │ │ ├── Snap#NameSyntax.g.cs.verified.cs │ │ │ ├── Snap#NamespaceDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#NullableAnnotation.g.cs.verified.cs │ │ │ ├── Snap#NullableContext.g.cs.verified.cs │ │ │ ├── Snap#NullableContextOptions.g.cs.verified.cs │ │ │ ├── Snap#NullableDirectiveTriviaSyntax.g.cs.verified.cs │ │ │ ├── Snap#NullableFlowState.g.cs.verified.cs │ │ │ ├── Snap#NullableTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#OmittedTypeArgumentSyntax.g.cs.verified.cs │ │ │ ├── Snap#OperationKind.g.cs.verified.cs │ │ │ ├── Snap#OperatorDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#OperatorMemberCrefSyntax.g.cs.verified.cs │ │ │ ├── Snap#ParenthesizedLambdaExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#ParenthesizedPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#ParenthesizedVariableDesignationSyntax.g.cs.verified.cs │ │ │ ├── Snap#PatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#Platform.g.cs.verified.cs │ │ │ ├── Snap#PointerTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#PositionalPatternClauseSyntax.g.cs.verified.cs │ │ │ ├── Snap#PredefinedTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#PrimaryConstructorBaseTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#PropertyPatternClauseSyntax.g.cs.verified.cs │ │ │ ├── Snap#QualifiedNameSyntax.g.cs.verified.cs │ │ │ ├── Snap#RangeExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#RecordDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#RecursivePatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#RefExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#RefKind.g.cs.verified.cs │ │ │ ├── Snap#RefStructConstraintSyntax.g.cs.verified.cs │ │ │ ├── Snap#RefTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#RelationalPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#ReturnStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#RuntimeCapability.g.cs.verified.cs │ │ │ ├── Snap#SarifVersion.g.cs.verified.cs │ │ │ ├── Snap#ScopedKind.g.cs.verified.cs │ │ │ ├── Snap#ScopedTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#SemanticEditKind.g.cs.verified.cs │ │ │ ├── Snap#SemanticModelOptions.g.cs.verified.cs │ │ │ ├── Snap#ShebangDirectiveTriviaSyntax.g.cs.verified.cs │ │ │ ├── Snap#SimpleLambdaExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#SimpleNameSyntax.g.cs.verified.cs │ │ │ ├── Snap#SingleVariableDesignationSyntax.g.cs.verified.cs │ │ │ ├── Snap#SlicePatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#SpecialType.g.cs.verified.cs │ │ │ ├── Snap#SpreadElementSyntax.g.cs.verified.cs │ │ │ ├── Snap#StackAllocArrayCreationExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#StatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#StructDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#SubpatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#SwitchExpressionArmSyntax.g.cs.verified.cs │ │ │ ├── Snap#SwitchExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#SwitchStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#SymbolDisplayLocalOptions.g.cs.verified.cs │ │ │ ├── Snap#SymbolDisplayMemberOptions.g.cs.verified.cs │ │ │ ├── Snap#SymbolDisplayMiscellaneousOptions.g.cs.verified.cs │ │ │ ├── Snap#SymbolDisplayParameterOptions.g.cs.verified.cs │ │ │ ├── Snap#SymbolDisplayPartKind.g.cs.verified.cs │ │ │ ├── Snap#SymbolKind.g.cs.verified.cs │ │ │ ├── Snap#SyntaxKind.g.cs.verified.cs │ │ │ ├── Snap#ThrowExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#ThrowStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#TryStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#TupleElementSyntax.g.cs.verified.cs │ │ │ ├── Snap#TupleExpressionSyntax.g.cs.verified.cs │ │ │ ├── Snap#TupleTypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#TypeDeclarationSyntax.g.cs.verified.cs │ │ │ ├── Snap#TypeKind.g.cs.verified.cs │ │ │ ├── Snap#TypePatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#TypeSyntax.g.cs.verified.cs │ │ │ ├── Snap#UnaryOperatorKind.g.cs.verified.cs │ │ │ ├── Snap#UnaryPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#UnsafeStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#UsingDirectiveSyntax.g.cs.verified.cs │ │ │ ├── Snap#UsingStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#VarPatternSyntax.g.cs.verified.cs │ │ │ ├── Snap#VariableDesignationSyntax.g.cs.verified.cs │ │ │ ├── Snap#WhenClauseSyntax.g.cs.verified.cs │ │ │ ├── Snap#WhileStatementSyntax.g.cs.verified.cs │ │ │ ├── Snap#WithExpressionSyntax.g.cs.verified.cs │ │ │ └── Snap#YieldStatementSyntax.g.cs.verified.cs │ │ ├── SonarAnalyzer.ShimLayer.Generator.Test.csproj │ │ ├── Strategies/ │ │ │ ├── NewEnumStrategyTest.cs │ │ │ ├── NoChangeStrategyTest.cs │ │ │ ├── PartialEnumStrategyTest.cs │ │ │ ├── SkipStrategyTest.cs │ │ │ ├── SyntaxNodeExtendStrategyTest.cs │ │ │ └── SyntaxNodeWrapStrategyTest.cs │ │ └── packages.lock.json │ ├── SonarAnalyzer.Test/ │ │ ├── AnalysisContext/ │ │ │ ├── SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs │ │ │ ├── SonarAnalysisContextBaseTest.cs │ │ │ ├── SonarAnalysisContextTest.Parametrized.cs │ │ │ ├── SonarAnalysisContextTest.Register.cs │ │ │ └── SonarAnalysisContextTest.cs │ │ ├── CFG/ │ │ │ ├── CfgSerializer/ │ │ │ │ └── DotWriterTest.cs │ │ │ ├── Extensions/ │ │ │ │ └── IOperationExtensionsTest.cs │ │ │ ├── Roslyn/ │ │ │ │ ├── BasicBlockTest.cs │ │ │ │ ├── CaptureIdTest.cs │ │ │ │ ├── CfgAllPathValidatorTest.cs │ │ │ │ ├── ControlFlowBranchTest.cs │ │ │ │ ├── ControlFlowRegionTest.cs │ │ │ │ ├── RoslynCfgSerializerTest.cs │ │ │ │ ├── RoslynControlFlowGraphTest.cs │ │ │ │ └── RoslynLvaSerializerTest.cs │ │ │ └── Sonar/ │ │ │ ├── BlockIdProviderTest.cs │ │ │ ├── SonarCfgSerializerTest.cs │ │ │ └── SonarControlFlowGraphTest.cs │ │ ├── Common/ │ │ │ ├── ConcurrentExecutionTest.cs │ │ │ ├── MetricsTest.cs │ │ │ ├── ParameterLoaderTest.cs │ │ │ ├── RuleCatalogTest.cs │ │ │ ├── SecurityHotspotTest.cs │ │ │ ├── UnchangedFilesTest.cs │ │ │ ├── UniqueQueueTest.cs │ │ │ └── XUnitVersions.cs │ │ ├── Extensions/ │ │ │ ├── ICompilationReportExtensionsTests.cs │ │ │ ├── StringExtensionsTest.cs │ │ │ ├── SyntaxNodeExtensionsTest.cs │ │ │ └── SyntaxTokenExtensionsSharedTest.cs │ │ ├── LiveVariableAnalysis/ │ │ │ ├── RoslynLiveVariableAnalysisTest.FlowCaptureOperation.cs │ │ │ ├── RoslynLiveVariableAnalysisTest.LocalFunction.cs │ │ │ ├── RoslynLiveVariableAnalysisTest.TryCatchFinally.cs │ │ │ ├── RoslynLiveVariableAnalysisTest.cs │ │ │ └── SonarLiveVariableAnalysisTest.cs │ │ ├── Metrics/ │ │ │ ├── CSharpExecutableLinesMetricTest.cs │ │ │ └── VisualBasicExecutableLinesMetricTest.cs │ │ ├── Operations/ │ │ │ └── Utilities/ │ │ │ ├── OperationExecutionOrderTest.cs │ │ │ └── OperationFinderTest.cs │ │ ├── Rules/ │ │ │ ├── AbstractClassToInterfaceTest.cs │ │ │ ├── AbstractTypesShouldNotHaveConstructorsTest.cs │ │ │ ├── AllBranchesShouldNotHaveSameImplementationTest.cs │ │ │ ├── AlwaysSetDateTimeKindTest.cs │ │ │ ├── AnonymousDelegateEventUnsubscribeTest.cs │ │ │ ├── ArgumentSpecifiedForCallerInfoParameterTest.cs │ │ │ ├── ArrayCovarianceTest.cs │ │ │ ├── ArrayCreationLongSyntaxTest.cs │ │ │ ├── ArrayDesignatorOnVariableTest.cs │ │ │ ├── ArrayInitializationMultipleStatementsTest.cs │ │ │ ├── ArrayPassedAsParamsTest.cs │ │ │ ├── AspNet/ │ │ │ │ ├── AnnotateApiActionsWithHttpVerbTest.cs │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromControllerTest.cs │ │ │ │ ├── AvoidUnderPostingTest.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutesTest.cs │ │ │ │ ├── CallModelStateIsValidTest.cs │ │ │ │ ├── ControllersHaveMixedResponsibilitiesTest.cs │ │ │ │ ├── ControllersReuseClientTest.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlashTest.cs │ │ │ │ ├── SpecifyRouteAttributeTest.cs │ │ │ │ └── UseAspNetModelBindingTest.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrderTest.cs │ │ │ ├── AssertionsShouldBeCompleteTest.cs │ │ │ ├── AssignmentInsideSubExpressionTest.cs │ │ │ ├── AsyncAwaitIdentifierTest.cs │ │ │ ├── AsyncVoidMethodTest.cs │ │ │ ├── AvoidDateTimeNowForBenchmarkingTest.cs │ │ │ ├── AvoidExcessiveClassCouplingTest.cs │ │ │ ├── AvoidExcessiveInheritanceTest.cs │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazorTest.cs │ │ │ ├── AvoidUnsealedAttributesTest.cs │ │ │ ├── BeginInvokePairedWithEndInvokeTest.cs │ │ │ ├── BinaryOperationWithIdenticalExpressionsTest.cs │ │ │ ├── BlazorQueryParameterRoutableComponentTest.cs │ │ │ ├── BooleanCheckInvertedTest.cs │ │ │ ├── BooleanLiteralUnnecessaryTest.cs │ │ │ ├── BreakOutsideSwitchTest.cs │ │ │ ├── BypassingAccessibilityTest.cs │ │ │ ├── CallToAsyncMethodShouldNotBeBlockingTest.cs │ │ │ ├── CallerInformationParametersShouldBeLastTest.cs │ │ │ ├── CastConcreteTypeToInterfaceTest.cs │ │ │ ├── CastShouldNotBeDuplicatedTest.cs │ │ │ ├── CatchEmptyTest.cs │ │ │ ├── CatchRethrowTest.cs │ │ │ ├── CertificateValidationCheckTest.cs │ │ │ ├── CheckArgumentExceptionTest.cs │ │ │ ├── CheckFileLicenseTest.cs │ │ │ ├── ClassAndMethodNameTest.cs │ │ │ ├── ClassNamedExceptionTest.cs │ │ │ ├── ClassNotInstantiatableTest.cs │ │ │ ├── ClassShouldNotBeEmptyTest.cs │ │ │ ├── ClassWithEqualityShouldImplementIEquatableTest.cs │ │ │ ├── ClassWithOnlyStaticMemberTest.cs │ │ │ ├── CloudNative/ │ │ │ │ ├── AzureFunctionsCatchExceptionsTest.cs │ │ │ │ ├── AzureFunctionsLogFailuresTest.cs │ │ │ │ ├── AzureFunctionsReuseClientsTest.cs │ │ │ │ ├── AzureFunctionsStatelessTest.cs │ │ │ │ └── DurableEntityInterfaceRestrictionsTest.cs │ │ │ ├── CognitiveComplexityTest.cs │ │ │ ├── CollectionEmptinessCheckingTest.cs │ │ │ ├── CollectionPropertiesShouldBeReadOnlyTest.cs │ │ │ ├── CollectionQuerySimplificationTest.cs │ │ │ ├── CollectionsShouldImplementGenericInterfaceTest.cs │ │ │ ├── CommentKeywordTest.cs │ │ │ ├── CommentLineEndTest.cs │ │ │ ├── CommentedOutCodeTest.cs │ │ │ ├── CommentsShouldNotBeEmptyTest.cs │ │ │ ├── ComparableInterfaceImplementationTest.cs │ │ │ ├── CompareNaNTest.cs │ │ │ ├── ConditionalSimplificationTest.cs │ │ │ ├── ConditionalStructureSameConditionTest.cs │ │ │ ├── ConditionalStructureSameImplementationTest.cs │ │ │ ├── ConditionalsShouldStartOnNewLineTest.cs │ │ │ ├── ConditionalsWithSameConditionTest.cs │ │ │ ├── ConstructorArgumentValueShouldExistTest.cs │ │ │ ├── ConstructorOverridableCallTest.cs │ │ │ ├── ConsumeValueTaskCorrectlyTest.cs │ │ │ ├── ControlCharacterInStringTest.cs │ │ │ ├── CryptographicKeyShouldNotBeTooShortTest.cs │ │ │ ├── DangerousGetHandleShouldNotBeCalledTest.cs │ │ │ ├── DatabasePasswordsShouldBeSecureTest.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyTest.cs │ │ │ ├── DateTimeFormatShouldNotBeHardcodedTest.cs │ │ │ ├── DeadStoresTest.cs │ │ │ ├── DebugAssertHasNoSideEffectsTest.cs │ │ │ ├── DebuggerDisplayUsesExistingMembersTest.cs │ │ │ ├── DeclareEventHandlersCorrectlyTest.cs │ │ │ ├── DeclareTypesInNamespacesTest.cs │ │ │ ├── DefaultSectionShouldBeFirstOrLastTest.cs │ │ │ ├── DelegateSubtractionTest.cs │ │ │ ├── DisposableMemberInNonDisposableClassTest.cs │ │ │ ├── DisposableNotDisposedTest.cs │ │ │ ├── DisposableReturnedFromUsingTest.cs │ │ │ ├── DisposableTypesNeedFinalizersTest.cs │ │ │ ├── DisposeFromDisposeTest.cs │ │ │ ├── DisposeNotImplementingDisposeTest.cs │ │ │ ├── DoNotCallAssemblyGetExecutingAssemblyTest.cs │ │ │ ├── DoNotCallAssemblyLoadInvalidMethodsTest.cs │ │ │ ├── DoNotCallExitMethodsTest.cs │ │ │ ├── DoNotCallGCCollectMethodTest.cs │ │ │ ├── DoNotCallGCSuppressFinalizeTest.cs │ │ │ ├── DoNotCatchNullReferenceExceptionTest.cs │ │ │ ├── DoNotCatchSystemExceptionTest.cs │ │ │ ├── DoNotCheckZeroSizeCollectionTest.cs │ │ │ ├── DoNotCopyArraysInPropertiesTest.cs │ │ │ ├── DoNotDecreaseMemberVisibilityTest.cs │ │ │ ├── DoNotExposeListTTest.cs │ │ │ ├── DoNotHardcodeCredentialsTest.cs │ │ │ ├── DoNotHardcodeSecretsTest.cs │ │ │ ├── DoNotHideBaseClassMethodsTest.cs │ │ │ ├── DoNotInstantiateSharedClassesTest.cs │ │ │ ├── DoNotLockOnSharedResourceTest.cs │ │ │ ├── DoNotLockWeakIdentityObjectsTest.cs │ │ │ ├── DoNotMarkEnumsWithFlagsTest.cs │ │ │ ├── DoNotNestTernaryOperatorsTest.cs │ │ │ ├── DoNotNestTypesInArgumentsTest.cs │ │ │ ├── DoNotOverloadOperatorEqualTest.cs │ │ │ ├── DoNotOverwriteCollectionElementsTest.cs │ │ │ ├── DoNotShiftByZeroOrIntSizeTest.cs │ │ │ ├── DoNotTestThisWithIsOperatorTest.cs │ │ │ ├── DoNotThrowFromDestructorsTest.cs │ │ │ ├── DoNotUseByValTest.cs │ │ │ ├── DoNotUseCollectionInItsOwnMethodCallsTest.cs │ │ │ ├── DoNotUseDateTimeNowTest.cs │ │ │ ├── DoNotUseIIfTest.cs │ │ │ ├── DoNotUseLiteralBoolInAssertionsTest.cs │ │ │ ├── DoNotUseOutRefParametersTest.cs │ │ │ ├── DoNotWriteToStandardOutputTest.cs │ │ │ ├── DontMixIncrementOrDecrementWithOtherOperatorsTest.cs │ │ │ ├── DontUseTraceSwitchLevelsTest.cs │ │ │ ├── DontUseTraceWriteTest.cs │ │ │ ├── EmptyMethodTest.cs │ │ │ ├── EmptyNamespaceTest.cs │ │ │ ├── EmptyNestedBlockTest.cs │ │ │ ├── EmptyStatementTest.cs │ │ │ ├── EncryptionAlgorithmsShouldBeSecureTest.cs │ │ │ ├── EndStatementUsageTest.cs │ │ │ ├── EnumNameHasEnumSuffixTest.cs │ │ │ ├── EnumNameShouldFollowRegexTest.cs │ │ │ ├── EnumStorageNeedsToBeInt32Test.cs │ │ │ ├── EnumerableSumInUncheckedTest.cs │ │ │ ├── EnumerationValueNameTest.cs │ │ │ ├── EnumsShouldNotBeNamedReservedTest.cs │ │ │ ├── EqualityOnFloatingPointTest.cs │ │ │ ├── EqualityOnModulusTest.cs │ │ │ ├── EquatableClassShouldBeSealedTest.cs │ │ │ ├── EscapeLambdaParameterTypeNamedScopedTest.cs │ │ │ ├── EventHandlerDelegateShouldHaveProperArgumentsTest.cs │ │ │ ├── EventHandlerNameTest.cs │ │ │ ├── EventNameContainsBeforeOrAfterTest.cs │ │ │ ├── EventNameTest.cs │ │ │ ├── ExceptionRethrowTest.cs │ │ │ ├── ExceptionShouldNotBeThrownFromUnexpectedMethodsTest.cs │ │ │ ├── ExceptionsNeedStandardConstructorsTest.cs │ │ │ ├── ExceptionsShouldBeLoggedOrThrownTest.cs │ │ │ ├── ExceptionsShouldBeLoggedTest.cs │ │ │ ├── ExceptionsShouldBePublicTest.cs │ │ │ ├── ExceptionsShouldBeUsedTest.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs │ │ │ ├── ExitStatementUsageTest.cs │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsedTest.cs │ │ │ ├── ExpressionComplexityTest.cs │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespaceTest.cs │ │ │ ├── ExtensionMethodShouldNotExtendObjectTest.cs │ │ │ ├── FieldShadowsParentFieldTest.cs │ │ │ ├── FieldShouldBeReadonlyTest.cs │ │ │ ├── FieldShouldNotBePublicTest.cs │ │ │ ├── FieldsShouldBeEncapsulatedInPropertiesTest.cs │ │ │ ├── FileLinesTest.cs │ │ │ ├── FileShouldEndWithEmptyNewLineTest.cs │ │ │ ├── FinalizerShouldNotBeEmptyTest.cs │ │ │ ├── FindInsteadOfFirstOrDefaultTest.cs │ │ │ ├── FlagsEnumWithoutInitializerTest.cs │ │ │ ├── FlagsEnumZeroMemberTest.cs │ │ │ ├── ForLoopConditionAlwaysFalseTest.cs │ │ │ ├── ForLoopCounterChangedTest.cs │ │ │ ├── ForLoopCounterConditionTest.cs │ │ │ ├── ForLoopIncrementSignTest.cs │ │ │ ├── ForeachLoopExplicitConversionTest.cs │ │ │ ├── FrameworkTypeNamingTest.cs │ │ │ ├── FunctionComplexityTest.cs │ │ │ ├── FunctionNameTest.cs │ │ │ ├── FunctionNestingDepthTest.cs │ │ │ ├── GenericInheritanceShouldNotBeRecursiveTest.cs │ │ │ ├── GenericLoggerInjectionShouldMatchEnclosingTypeTest.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignmentTest.cs │ │ │ ├── GenericTypeParameterEmptinessCheckingTest.cs │ │ │ ├── GenericTypeParameterInOutTest.cs │ │ │ ├── GenericTypeParameterUnusedTest.cs │ │ │ ├── GenericTypeParametersRequiredTest.cs │ │ │ ├── GetHashCodeEqualsOverrideTest.cs │ │ │ ├── GetHashCodeMutableTest.cs │ │ │ ├── GetTypeWithIsAssignableFromTest.cs │ │ │ ├── GotoStatementTest.cs │ │ │ ├── GuardConditionOnEqualsOverrideTest.cs │ │ │ ├── Hotspots/ │ │ │ │ ├── ClearTextProtocolsAreSensitiveTest.cs │ │ │ │ ├── CommandPathTest.cs │ │ │ │ ├── ConfiguringLoggersTest.cs │ │ │ │ ├── CookieShouldBeHttpOnlyTest.cs │ │ │ │ ├── CookieShouldBeSecureTest.cs │ │ │ │ ├── CreatingHashAlgorithmsTest.cs │ │ │ │ ├── DeliveringDebugFeaturesInProductionTest.cs │ │ │ │ ├── DisablingCsrfProtectionTest.cs │ │ │ │ ├── DisablingRequestValidationTest.cs │ │ │ │ ├── DoNotUseRandomTest.cs │ │ │ │ ├── ExecutingSqlQueriesTest.cs │ │ │ │ ├── ExpandingArchivesTest.cs │ │ │ │ ├── HardcodedIpAddressTest.cs │ │ │ │ ├── InsecureDeserializationTest.cs │ │ │ │ ├── PermissiveCorsTest.cs │ │ │ │ ├── PubliclyWritableDirectoriesTest.cs │ │ │ │ ├── RequestsWithExcessiveLengthTest.cs │ │ │ │ ├── UnsafeCodeBlocksTest.cs │ │ │ │ └── UsingNonstandardCryptographyTest.cs │ │ │ ├── IdentifiersNamedExtensionShouldBeEscapedTest.cs │ │ │ ├── IdentifiersNamedFieldShouldBeEscapedTest.cs │ │ │ ├── IfChainWithoutElseTest.cs │ │ │ ├── IfCollapsibleTest.cs │ │ │ ├── ImplementIDisposableCorrectlyTest.cs │ │ │ ├── ImplementISerializableCorrectlyTest.cs │ │ │ ├── ImplementSerializationMethodsCorrectlyTest.cs │ │ │ ├── IndentSingleLineFollowingConditionalTest.cs │ │ │ ├── IndexOfCheckAgainstZeroTest.cs │ │ │ ├── IndexedPropertyWithMultipleParametersTest.cs │ │ │ ├── InfiniteRecursionTest.cs │ │ │ ├── InheritedCollidingInterfaceMembersTest.cs │ │ │ ├── InitializeStaticFieldsInlineTest.cs │ │ │ ├── InsecureContentSecurityPolicyTest.cs │ │ │ ├── InsecureEncryptionAlgorithmTest.cs │ │ │ ├── InsecureTemporaryFilesCreationTest.cs │ │ │ ├── InsteadOfAnyTest.cs │ │ │ ├── InterfaceMethodsShouldBeCallableByChildTypesTest.cs │ │ │ ├── InterfaceNameTest.cs │ │ │ ├── InterfacesShouldNotBeEmptyTest.cs │ │ │ ├── InvalidCastToInterfaceTest.cs │ │ │ ├── InvocationResolvesToOverrideWithParamsTest.cs │ │ │ ├── IssueSuppressionTest.cs │ │ │ ├── JSInvokableMethodsShouldBePublicTest.cs │ │ │ ├── JwtSignedTest.cs │ │ │ ├── LdapConnectionShouldBeSecureTest.cs │ │ │ ├── LineContinuationTest.cs │ │ │ ├── LineLengthTest.cs │ │ │ ├── LinkedListPropertiesInsteadOfMethodsTest.cs │ │ │ ├── LiteralSuffixUpperCaseTest.cs │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParametersTest.cs │ │ │ ├── LocalVariableNameTest.cs │ │ │ ├── LockedFieldShouldBeReadonlyTest.cs │ │ │ ├── LoggerFieldsShouldBePrivateStaticReadonlyTest.cs │ │ │ ├── LoggerMembersNamesShouldComplyTest.cs │ │ │ ├── LoggersShouldBeNamedForEnclosingTypeTest.cs │ │ │ ├── LoggingArgumentsShouldBePassedCorrectlyTest.cs │ │ │ ├── LoggingTemplatePlaceHoldersShouldBeInOrderTest.cs │ │ │ ├── LoopsAndLinqTest.cs │ │ │ ├── LooseFilePermissionsTest.cs │ │ │ ├── LossOfFractionInDivisionTest.cs │ │ │ ├── MagicNumberShouldNotBeUsedTest.cs │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeTest.cs │ │ │ ├── MarkAssemblyWithAttributeUsageAttributeTest.cs │ │ │ ├── MarkAssemblyWithClsCompliantAttributeTest.cs │ │ │ ├── MarkAssemblyWithComVisibleAttributeTest.cs │ │ │ ├── MarkAssemblyWithNeutralResourcesLanguageAttributeTest.cs │ │ │ ├── MarkWindowsFormsMainWithStaThreadTest.cs │ │ │ ├── MemberInitializedToDefaultTest.cs │ │ │ ├── MemberInitializerRedundantTest.cs │ │ │ ├── MemberOverrideCallsBaseMemberTest.cs │ │ │ ├── MemberShadowsOuterStaticMemberTest.cs │ │ │ ├── MemberShouldBeStaticTest.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributesTest.cs │ │ │ ├── MessageTemplatesShouldBeCorrectTest.cs │ │ │ ├── MethodOverloadOptionalParameterTest.cs │ │ │ ├── MethodOverloadsShouldBeGroupedTest.cs │ │ │ ├── MethodOverrideAddsParamsTest.cs │ │ │ ├── MethodOverrideChangedDefaultValueTest.cs │ │ │ ├── MethodOverrideNoParamsTest.cs │ │ │ ├── MethodParameterMissingOptionalTest.cs │ │ │ ├── MethodParameterUnusedTest.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicityTest.cs │ │ │ ├── MethodShouldNotOnlyReturnConstantTest.cs │ │ │ ├── MethodsShouldNotHaveIdenticalImplementationsTest.cs │ │ │ ├── MethodsShouldNotHaveTooManyLinesTest.cs │ │ │ ├── MethodsShouldUseBaseTypesTest.cs │ │ │ ├── MultilineBlocksWithoutBraceTest.cs │ │ │ ├── MultipleVariableDeclarationTest.cs │ │ │ ├── MutableFieldsShouldNotBePublicReadonlyTest.cs │ │ │ ├── MutableFieldsShouldNotBePublicStaticTest.cs │ │ │ ├── NameOfShouldBeUsedTest.cs │ │ │ ├── NamedPlaceholdersShouldBeUniqueTest.cs │ │ │ ├── NamespaceNameTest.cs │ │ │ ├── NativeMethodsShouldBeWrappedTest.cs │ │ │ ├── NegatedIsExpressionTest.cs │ │ │ ├── NestedCodeBlockTest.cs │ │ │ ├── NoExceptionsInFinallyTest.cs │ │ │ ├── NonAsyncTaskShouldNotReturnNullTest.cs │ │ │ ├── NonDerivedPrivateClassesShouldBeSealedTest.cs │ │ │ ├── NonFlagsEnumInBitwiseOperationTest.cs │ │ │ ├── NormalizeStringsToUppercaseTest.cs │ │ │ ├── NotAssignedPrivateMemberTest.cs │ │ │ ├── NumberPatternShouldBeRegularTest.cs │ │ │ ├── ObjectCreatedDroppedTest.cs │ │ │ ├── ObsoleteAttributesTest.cs │ │ │ ├── OnErrorStatementTest.cs │ │ │ ├── OperatorOverloadsShouldHaveNamedAlternativesTest.cs │ │ │ ├── OperatorsShouldBeOverloadedConsistentlyTest.cs │ │ │ ├── OptionExplicitOnTest.cs │ │ │ ├── OptionStrictOnTest.cs │ │ │ ├── OptionalParameterNotPassedToBaseCallTest.cs │ │ │ ├── OptionalParameterTest.cs │ │ │ ├── OptionalParameterWithDefaultValueTest.cs │ │ │ ├── OptionalRefOutParameterTest.cs │ │ │ ├── OrderByRepeatedTest.cs │ │ │ ├── OverrideGetHashCodeOnOverridingEqualsTest.cs │ │ │ ├── PInvokesShouldNotBeVisibleTest.cs │ │ │ ├── ParameterAssignedToTest.cs │ │ │ ├── ParameterNameMatchesOriginalTest.cs │ │ │ ├── ParameterNameTest.cs │ │ │ ├── ParameterNamesShouldNotDuplicateMethodNamesTest.cs │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraintTest.cs │ │ │ ├── ParameterValidationInAsyncShouldBeWrappedTest.cs │ │ │ ├── ParameterValidationInYieldShouldBeWrappedTest.cs │ │ │ ├── ParametersCorrectOrderTest.cs │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttributeTest.cs │ │ │ ├── PartialMethodNoImplementationTest.cs │ │ │ ├── PasswordsShouldBeStoredCorrectlyTest.cs │ │ │ ├── PointersShouldBePrivateTest.cs │ │ │ ├── PreferGuidEmptyTest.cs │ │ │ ├── PreferJaggedArraysOverMultidimensionalTest.cs │ │ │ ├── PrivateConstantFieldNameTest.cs │ │ │ ├── PrivateFieldNameTest.cs │ │ │ ├── PrivateFieldUsedAsLocalVariableTest.cs │ │ │ ├── PrivateSharedReadonlyFieldNameTest.cs │ │ │ ├── PrivateStaticMethodUsedOnlyByNestedClassTest.cs │ │ │ ├── PropertiesAccessCorrectFieldTest.cs │ │ │ ├── PropertiesShouldBePreferredTest.cs │ │ │ ├── PropertyGetterWithThrowTest.cs │ │ │ ├── PropertyNameTest.cs │ │ │ ├── PropertyNamesShouldNotMatchGetMethodsTest.cs │ │ │ ├── PropertyToAutoPropertyTest.cs │ │ │ ├── PropertyWithArrayTypeTest.cs │ │ │ ├── PropertyWriteOnlyTest.cs │ │ │ ├── ProvideDeserializationMethodsForOptionalFieldsTest.cs │ │ │ ├── PublicConstantFieldNameTest.cs │ │ │ ├── PublicConstantFieldTest.cs │ │ │ ├── PublicFieldNameTest.cs │ │ │ ├── PublicMethodWithMultidimensionalArrayTest.cs │ │ │ ├── PublicSharedReadonlyFieldNameTest.cs │ │ │ ├── PureAttributeOnVoidMethodTest.cs │ │ │ ├── RedundancyInConstructorDestructorDeclarationTest.cs │ │ │ ├── RedundantArgumentTest.cs │ │ │ ├── RedundantCastTest.cs │ │ │ ├── RedundantConditionalAroundAssignmentTest.cs │ │ │ ├── RedundantDeclarationTest.cs │ │ │ ├── RedundantExitSelectTest.cs │ │ │ ├── RedundantInheritanceListTest.cs │ │ │ ├── RedundantJumpStatementTest.cs │ │ │ ├── RedundantModifierTest.cs │ │ │ ├── RedundantNullCheckTest.cs │ │ │ ├── RedundantNullableTypeComparisonTest.cs │ │ │ ├── RedundantParenthesesObjectCreationTest.cs │ │ │ ├── RedundantParenthesesTest.cs │ │ │ ├── RedundantPropertyNamesInAnonymousClassTest.cs │ │ │ ├── RedundantToArrayCallTest.cs │ │ │ ├── RedundantToStringCallTest.cs │ │ │ ├── ReferenceEqualityCheckWhenEqualsExistsTest.cs │ │ │ ├── ReferenceEqualsOnValueTypeTest.cs │ │ │ ├── RegularExpressions/ │ │ │ │ └── RegexMustHaveValidSyntaxTest.cs │ │ │ ├── RequireAttributeUsageAttributeTest.cs │ │ │ ├── ReturnEmptyCollectionInsteadOfNullTest.cs │ │ │ ├── ReturnTypeNamedPartialShouldBeEscapedTest.cs │ │ │ ├── ReturnValueIgnoredTest.cs │ │ │ ├── ReversedOperatorsTest.cs │ │ │ ├── RightCurlyBraceStartsLineTest.cs │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalledTest.cs │ │ │ ├── SelfAssignmentTest.cs │ │ │ ├── SerializationConstructorsShouldBeSecuredTest.cs │ │ │ ├── SetLocaleForDataTypesTest.cs │ │ │ ├── SetPropertiesInsteadOfMethodsTest.cs │ │ │ ├── ShiftDynamicNotIntegerTest.cs │ │ │ ├── ShouldImplementExportedInterfacesTest.cs │ │ │ ├── SimpleDoLoopTest.cs │ │ │ ├── SingleStatementPerLineTest.cs │ │ │ ├── SpecifyIFormatProviderOrCultureInfoTest.cs │ │ │ ├── SpecifyStringComparisonTest.cs │ │ │ ├── SpecifyTimeoutOnRegexTest.cs │ │ │ ├── SqlKeywordsDelimitedBySpaceTest.cs │ │ │ ├── StaticFieldInGenericClassTest.cs │ │ │ ├── StaticFieldInitializerOrderTest.cs │ │ │ ├── StaticFieldVisibleTest.cs │ │ │ ├── StaticFieldWrittenFromInstanceConstructorTest.cs │ │ │ ├── StaticFieldWrittenFromInstanceMemberTest.cs │ │ │ ├── StaticSealedClassProtectedMembersTest.cs │ │ │ ├── StreamReadStatementTest.cs │ │ │ ├── StringConcatenationInLoopTest.cs │ │ │ ├── StringConcatenationWithPlusTest.cs │ │ │ ├── StringFormatValidatorTest.cs │ │ │ ├── StringLiteralShouldNotBeDuplicatedTest.cs │ │ │ ├── StringOffsetMethodsTest.cs │ │ │ ├── StringOperationWithoutCultureTest.cs │ │ │ ├── StringOrIntegralTypesForIndexersTest.cs │ │ │ ├── SuppressFinalizeUselessTest.cs │ │ │ ├── SwaggerActionReturnTypeTest.cs │ │ │ ├── SwitchCaseFallsThroughToDefaultTest.cs │ │ │ ├── SwitchCasesMinimumThreeTest.cs │ │ │ ├── SwitchDefaultClauseEmptyTest.cs │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatementsTest.cs │ │ │ ├── SwitchShouldNotBeNestedTest.cs │ │ │ ├── SwitchWithoutDefaultTest.cs │ │ │ ├── TabCharacterTest.cs │ │ │ ├── TaskConfigureAwaitTest.cs │ │ │ ├── TestClassShouldHaveTestMethodTest.cs │ │ │ ├── TestMethodShouldContainAssertionTest.cs │ │ │ ├── TestMethodShouldHaveCorrectSignatureTest.cs │ │ │ ├── TestMethodShouldNotBeIgnoredTest.cs │ │ │ ├── TestsShouldNotUseThreadSleepTest.cs │ │ │ ├── ThisShouldNotBeExposedFromConstructorsTest.cs │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalledTest.cs │ │ │ ├── ThreadStaticNonStaticFieldTest.cs │ │ │ ├── ThreadStaticWithInitializerTest.cs │ │ │ ├── ThrowReservedExceptionsTest.cs │ │ │ ├── ToStringShouldNotReturnNullTest.cs │ │ │ ├── TooManyGenericParametersTest.cs │ │ │ ├── TooManyLabelsInSwitchTest.cs │ │ │ ├── TooManyLoggingCallsTest.cs │ │ │ ├── TooManyParametersTest.cs │ │ │ ├── TrackNotImplementedExceptionTest.cs │ │ │ ├── TryStatementsWithIdenticalCatchShouldBeMergedTest.cs │ │ │ ├── TypeExaminationOnSystemTypeTest.cs │ │ │ ├── TypeMemberVisibilityTest.cs │ │ │ ├── TypeNamesShouldNotMatchNamespacesTest.cs │ │ │ ├── TypeParameterNameTest.cs │ │ │ ├── TypesShouldNotExtendOutdatedBaseTypesTest.cs │ │ │ ├── UnaryPrefixOperatorRepeatedTest.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConstTest.cs │ │ │ ├── UnconditionalJumpStatementTest.cs │ │ │ ├── UninvokedEventDeclarationTest.cs │ │ │ ├── UnnecessaryBitwiseOperationTest.cs │ │ │ ├── UnnecessaryMathematicalComparisonTest.cs │ │ │ ├── UnnecessaryUsingsTest.cs │ │ │ ├── UnsignedTypesUsageTest.cs │ │ │ ├── UnusedPrivateMemberTest.Constructors.cs │ │ │ ├── UnusedPrivateMemberTest.Fields.cs │ │ │ ├── UnusedPrivateMemberTest.Methods.cs │ │ │ ├── UnusedPrivateMemberTest.Properties.cs │ │ │ ├── UnusedPrivateMemberTest.Types.cs │ │ │ ├── UnusedPrivateMemberTest.cs │ │ │ ├── UnusedReturnValueTest.cs │ │ │ ├── UnusedStringBuilderTest.cs │ │ │ ├── UriShouldNotBeHardcodedTest.cs │ │ │ ├── UseAwaitableMethodTest.cs │ │ │ ├── UseCharOverloadOfStringMethodsTest.cs │ │ │ ├── UseConstantLoggingTemplateTest.cs │ │ │ ├── UseConstantsWhereAppropriateTest.cs │ │ │ ├── UseCurlyBracesTest.cs │ │ │ ├── UseDateTimeOffsetInsteadOfDateTimeTest.cs │ │ │ ├── UseFindSystemTimeZoneByIdTest.cs │ │ │ ├── UseGenericEventHandlerInstancesTest.cs │ │ │ ├── UseGenericWithRefParametersTest.cs │ │ │ ├── UseIFormatProviderForParsingDateAndTimeTest.cs │ │ │ ├── UseIndexingInsteadOfLinqMethodsTest.cs │ │ │ ├── UseLambdaParameterInConcurrentDictionaryTest.cs │ │ │ ├── UseNumericLiteralSeparatorTest.cs │ │ │ ├── UseParamsForVariableArgumentsTest.cs │ │ │ ├── UsePascalCaseForNamedPlaceHoldersTest.cs │ │ │ ├── UseReturnStatementTest.cs │ │ │ ├── UseShortCircuitingOperatorTest.cs │ │ │ ├── UseStringCreateTest.cs │ │ │ ├── UseStringIsNullOrEmptyTest.cs │ │ │ ├── UseTestableTimeProviderTest.cs │ │ │ ├── UseTrueForAllTest.cs │ │ │ ├── UseUnixEpochTest.cs │ │ │ ├── UseUriInsteadOfStringTest.cs │ │ │ ├── UseValueParameterTest.cs │ │ │ ├── UseWhereBeforeOrderByTest.cs │ │ │ ├── UseWhileLoopInsteadTest.cs │ │ │ ├── UseWithStatementTest.cs │ │ │ ├── Utilities/ │ │ │ │ ├── AnalysisWarningAnalyzerTest.cs │ │ │ │ ├── CopyPasteTokenAnalyzerTest.cs │ │ │ │ ├── FileMetadataAnalyzerTest.cs │ │ │ │ ├── LogAnalyzerTest.cs │ │ │ │ ├── MethodDeclarationInfoComparerTest.cs │ │ │ │ ├── MetricsAnalyzerTest.cs │ │ │ │ ├── SymbolReferenceAnalyzerTest.cs │ │ │ │ ├── TelemetryAnalyzerTest.cs │ │ │ │ ├── TestMethodDeclarationsAnalyzerTest.cs │ │ │ │ ├── TokenTypeAnalyzerTest.Classifier.cs │ │ │ │ ├── TokenTypeAnalyzerTest.ClassifierTestHarness.cs │ │ │ │ ├── TokenTypeAnalyzerTest.cs │ │ │ │ └── UtilityAnalyzerBaseTest.cs │ │ │ ├── ValueTypeShouldImplementIEquatableTest.cs │ │ │ ├── ValuesUselesslyIncrementedTest.cs │ │ │ ├── VariableShadowsFieldTest.cs │ │ │ ├── VariableUnusedTest.cs │ │ │ ├── VirtualEventFieldTest.cs │ │ │ ├── WcfMissingContractAttributeTest.cs │ │ │ ├── WcfNonVoidOneWayTest.cs │ │ │ ├── WeakSslTlsProtocolsTest.cs │ │ │ ├── XMLSignatureCheckTest.cs │ │ │ └── XmlExternalEntityShouldNotBeParsedTest.cs │ │ ├── SonarAnalyzer.Test.csproj │ │ ├── Syntax/ │ │ │ ├── Extensions/ │ │ │ │ ├── IfStatementSyntaxExtensionsTest.cs │ │ │ │ ├── SwitchSectionSyntaxExtensionsTest.cs │ │ │ │ └── SyntaxNodeExtensionsTest.CSharp.cs │ │ │ └── Utilities/ │ │ │ ├── EquivalenceCheckerTest.cs │ │ │ ├── MethodParameterLookupTest.cs │ │ │ ├── RemovableDeclarationCollectorTest.cs │ │ │ ├── SymbolUsageCollectorTest.cs │ │ │ └── SyntaxClassifierTest.cs │ │ ├── TestCases/ │ │ │ ├── AbstractClassToInterface.Latest.Partial.cs │ │ │ ├── AbstractClassToInterface.Latest.cs │ │ │ ├── AbstractClassToInterface.cs │ │ │ ├── AbstractTypesShouldNotHaveConstructors.Latest.Partial.cs │ │ │ ├── AbstractTypesShouldNotHaveConstructors.Latest.cs │ │ │ ├── AbstractTypesShouldNotHaveConstructors.TopLevelStatements.cs │ │ │ ├── AbstractTypesShouldNotHaveConstructors.cs │ │ │ ├── AllBranchesShouldNotHaveSameImplementation.CSharp9.cs │ │ │ ├── AllBranchesShouldNotHaveSameImplementation.cs │ │ │ ├── AllBranchesShouldNotHaveSameImplementation.vb │ │ │ ├── AlwaysSetDateTimeKind.cs │ │ │ ├── AlwaysSetDateTimeKind.vb │ │ │ ├── AnonymousDelegateEventUnsubscribe.Latest.cs │ │ │ ├── AnonymousDelegateEventUnsubscribe.cs │ │ │ ├── AppSettings/ │ │ │ │ ├── DatabasePasswordsShouldBeSecure/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ ├── ArrayInside/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── ConnectionStringComment/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── EmptyArray/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── EmptyFile/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── Null/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── PropertyKinds/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ ├── ValueKind/ │ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ │ └── WrongStructure/ │ │ │ │ │ │ └── appsettings.json │ │ │ │ │ └── Values/ │ │ │ │ │ └── appsettings.json │ │ │ │ ├── DoNotHardcodeCredentials/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── AppSettings.json │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── AppSettings.json │ │ │ │ │ └── Valid/ │ │ │ │ │ ├── AppSettings.Custom.json │ │ │ │ │ ├── AppSettings.Development.json │ │ │ │ │ ├── AppSettings.Production.json │ │ │ │ │ ├── AppSettings.json │ │ │ │ │ └── OtherFile.json │ │ │ │ └── DoNotHardcodeSecrets/ │ │ │ │ ├── Corrupt/ │ │ │ │ │ └── AppSettings.json │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ └── AppSettings.json │ │ │ │ └── Valid/ │ │ │ │ ├── AppSettings.Custom.json │ │ │ │ ├── AppSettings.Development.json │ │ │ │ ├── AppSettings.Production.json │ │ │ │ ├── AppSettings.json │ │ │ │ └── OtherFile.json │ │ │ ├── ArgumentSpecifiedForCallerInfoParameter.Latest.cs │ │ │ ├── ArgumentSpecifiedForCallerInfoParameter.cs │ │ │ ├── ArrayCovariance.CSharp9.cs │ │ │ ├── ArrayCovariance.cs │ │ │ ├── ArrayCreationLongSyntax.Fixed.vb │ │ │ ├── ArrayCreationLongSyntax.vb │ │ │ ├── ArrayDesignatorOnVariable.Fixed.vb │ │ │ ├── ArrayDesignatorOnVariable.vb │ │ │ ├── ArrayInitializationMultipleStatements.vb │ │ │ ├── ArrayPassedAsParams.Latest.cs │ │ │ ├── ArrayPassedAsParams.cs │ │ │ ├── ArrayPassedAsParams.vb │ │ │ ├── AspNet/ │ │ │ │ ├── AnnotateApiActionsWithHttpVerb.cs │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromController.cs │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.Fixed.cs │ │ │ │ ├── ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.cs │ │ │ │ ├── AvoidUnderPosting.AutogeneratedModel.cs │ │ │ │ ├── AvoidUnderPosting.Latest.Partial.cs │ │ │ │ ├── AvoidUnderPosting.Latest.cs │ │ │ │ ├── AvoidUnderPosting.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.vb │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2AndAbove.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.Latest.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.vb │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.Latest.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.vb │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.Latest.cs │ │ │ │ ├── BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.vb │ │ │ │ ├── CallModelStateIsValid.AutogeneratedController.cs │ │ │ │ ├── CallModelStateIsValid.Latest.cs │ │ │ │ ├── CallModelStateIsValid.cs │ │ │ │ ├── ControllerReuseClient.CSharp12.cs │ │ │ │ ├── ControllersHaveMixedResponsibilities.Latest.Partial.cs │ │ │ │ ├── ControllersHaveMixedResponsibilities.Latest.cs │ │ │ │ ├── ControllersReuseClient.CSharp9.cs │ │ │ │ ├── ControllersReuseClient.Csharp8.cs │ │ │ │ ├── ControllersReuseClient.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNet4x.PartialAutogenerated.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNet4x.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNet4x.vb │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNetCore.CSharp12.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNetCore.PartialAutogenerated.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNetCore.cs │ │ │ │ ├── RouteTemplateShouldNotStartWithSlash.AspNetCore.vb │ │ │ │ ├── SpecifyRouteAttribute.CSharp12.cs │ │ │ │ └── UseAspNetModelBinding_AspNetCore_Latest.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.MsTest.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.NUnit.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.NUnit4.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.Xunit.cs │ │ │ ├── AssertionArgsShouldBePassedInCorrectOrder.XunitV3.cs │ │ │ ├── AssertionsShouldBeComplete.AllFrameworks.cs │ │ │ ├── AssertionsShouldBeComplete.FluentAssertions.CSharp7.cs │ │ │ ├── AssertionsShouldBeComplete.FluentAssertions.Latest.cs │ │ │ ├── AssertionsShouldBeComplete.NFluent.Latest.cs │ │ │ ├── AssertionsShouldBeComplete.NFluent.cs │ │ │ ├── AssertionsShouldBeComplete.NSubstitute.cs │ │ │ ├── AssignmentInsideSubExpression.Latest.cs │ │ │ ├── AssignmentInsideSubExpression.TopLevelStatements.cs │ │ │ ├── AssignmentInsideSubExpression.cs │ │ │ ├── AsyncAwaitIdentifier.Latest.cs │ │ │ ├── AsyncAwaitIdentifier.cs │ │ │ ├── AsyncVoidMethod.Latest.cs │ │ │ ├── AsyncVoidMethod.MsTestTestFramework.cs │ │ │ ├── AsyncVoidMethod.VsUtFramework.cs │ │ │ ├── AsyncVoidMethod.cs │ │ │ ├── AvoidDateTimeNowForBenchmarking.cs │ │ │ ├── AvoidDateTimeNowForBenchmarking.vb │ │ │ ├── AvoidExcessiveClassCoupling.Latest.cs │ │ │ ├── AvoidExcessiveClassCoupling.cs │ │ │ ├── AvoidExcessiveInheritance_CustomValues.Records.cs │ │ │ ├── AvoidExcessiveInheritance_CustomValues.cs │ │ │ ├── AvoidExcessiveInheritance_DefaultValues.Concurrent.cs │ │ │ ├── AvoidExcessiveInheritance_DefaultValues.FileScopedTypes.cs │ │ │ ├── AvoidExcessiveInheritance_DefaultValues.Records.Concurrent.cs │ │ │ ├── AvoidExcessiveInheritance_DefaultValues.Records.cs │ │ │ ├── AvoidExcessiveInheritance_DefaultValues.cs │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.RenderFragment.razor │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.RenderFragmentConsumer.razor │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.cs │ │ │ ├── AvoidLambdaExpressionInLoopsInBlazor.razor │ │ │ ├── AvoidUnsealedAttributes.cs │ │ │ ├── AvoidUnsealedAttributes.vb │ │ │ ├── BeginInvokePairedWithEndInvoke.Latest.Partial.cs │ │ │ ├── BeginInvokePairedWithEndInvoke.Latest.cs │ │ │ ├── BeginInvokePairedWithEndInvoke.Partial.vb │ │ │ ├── BeginInvokePairedWithEndInvoke.cs │ │ │ ├── BeginInvokePairedWithEndInvoke.vb │ │ │ ├── BinaryOperationWithIdenticalExpressions.CSharpLatest.cs │ │ │ ├── BinaryOperationWithIdenticalExpressions.cs │ │ │ ├── BinaryOperationWithIdenticalExpressions.vb │ │ │ ├── BlazorQueryParameterRoutableComponent.Compliant.cs │ │ │ ├── BlazorQueryParameterRoutableComponent.Latest.Partial.1.razor.cs │ │ │ ├── BlazorQueryParameterRoutableComponent.Latest.Partial.2.razor.cs │ │ │ ├── BlazorQueryParameterRoutableComponent.Latest.Partial.razor │ │ │ ├── BlazorQueryParameterRoutableComponent.NoRoute.razor │ │ │ ├── BlazorQueryParameterRoutableComponent.Noncompliant.cs │ │ │ ├── BlazorQueryParameterRoutableComponent.razor │ │ │ ├── BooleanCheckInverted.Fixed.Batch.cs │ │ │ ├── BooleanCheckInverted.Fixed.cs │ │ │ ├── BooleanCheckInverted.Latest.cs │ │ │ ├── BooleanCheckInverted.cs │ │ │ ├── BooleanCheckInverted.vb │ │ │ ├── BooleanLiteralUnnecessary.Fixed.cs │ │ │ ├── BooleanLiteralUnnecessary.Latest.Fixed.cs │ │ │ ├── BooleanLiteralUnnecessary.Latest.cs │ │ │ ├── BooleanLiteralUnnecessary.cs │ │ │ ├── BooleanLiteralUnnecessary.vb │ │ │ ├── BreakOutsideSwitch.cs │ │ │ ├── BypassingAccessibility.Latest.cs │ │ │ ├── BypassingAccessibility.cs │ │ │ ├── BypassingAccessibility.vb │ │ │ ├── CallToAsyncMethodShouldNotBeBlocking.Latest.cs │ │ │ ├── CallToAsyncMethodShouldNotBeBlocking.TopLevelStatements.cs │ │ │ ├── CallToAsyncMethodShouldNotBeBlocking.cs │ │ │ ├── CallerInformationParametersShouldBeLast.Latest.Partial.cs │ │ │ ├── CallerInformationParametersShouldBeLast.Latest.cs │ │ │ ├── CallerInformationParametersShouldBeLast.cs │ │ │ ├── CallerInformationParametersShouldBeLastInvalidSyntax.cs │ │ │ ├── CastConcreteTypeToInterface.cs │ │ │ ├── CastShouldNotBeDuplicated.Latest.cs │ │ │ ├── CastShouldNotBeDuplicated.cs │ │ │ ├── CastShouldNotBeDuplicated.cshtml │ │ │ ├── CatchEmpty.cs │ │ │ ├── CatchRethrow.Fixed.cs │ │ │ ├── CatchRethrow.cs │ │ │ ├── CatchRethrow.vb │ │ │ ├── CertificateValidationCheck.Latest.Partial.cs │ │ │ ├── CertificateValidationCheck.Latest.cs │ │ │ ├── CertificateValidationCheck.TopLevelStatements.cs │ │ │ ├── CertificateValidationCheck.cs │ │ │ ├── CertificateValidationCheck.vb │ │ │ ├── CheckArgumentException.Latest.cs │ │ │ ├── CheckArgumentException.TopLevelStatements.cs │ │ │ ├── CheckArgumentException.cs │ │ │ ├── CheckFileLicense_CSharp9.Fixed.cs │ │ │ ├── CheckFileLicense_CSharp9.cs │ │ │ ├── CheckFileLicense_ComplexRegex.cs │ │ │ ├── CheckFileLicense_Compliant.vb │ │ │ ├── CheckFileLicense_DefaultValues.Fixed.cs │ │ │ ├── CheckFileLicense_DefaultValues.cs │ │ │ ├── CheckFileLicense_EmptyFile.cs │ │ │ ├── CheckFileLicense_ForcingEmptyLinesKo.cs │ │ │ ├── CheckFileLicense_ForcingEmptyLinesOk.cs │ │ │ ├── CheckFileLicense_MultiLineLicenseStartWithNamespace.cs │ │ │ ├── CheckFileLicense_MultiLineLicenseStartWithUsing.cs │ │ │ ├── CheckFileLicense_MultiSingleLineLicenseStartWithAdditionalComment.cs │ │ │ ├── CheckFileLicense_MultiSingleLineLicenseStartWithAdditionalCommentOnSameLine.cs │ │ │ ├── CheckFileLicense_MultiSingleLineLicenseStartWithNamespace.cs │ │ │ ├── CheckFileLicense_NoLicenseStartWithNamespace.Fixed.cs │ │ │ ├── CheckFileLicense_NoLicenseStartWithNamespace.cs │ │ │ ├── CheckFileLicense_NoLicenseStartWithUsing.Fixed.cs │ │ │ ├── CheckFileLicense_NoLicenseStartWithUsing.cs │ │ │ ├── CheckFileLicense_NonCompliant.vb │ │ │ ├── CheckFileLicense_OutdatedLicenseStartWithNamespace.Fixed.cs │ │ │ ├── CheckFileLicense_OutdatedLicenseStartWithNamespace.cs │ │ │ ├── CheckFileLicense_OutdatedLicenseStartWithUsing.Fixed.cs │ │ │ ├── CheckFileLicense_OutdatedLicenseStartWithUsing.cs │ │ │ ├── CheckFileLicense_SingleLineLicenseStartWithNamespace.cs │ │ │ ├── CheckFileLicense_SingleLineLicenseStartWithUsing.cs │ │ │ ├── CheckFileLicense_YearDifference.Fixed.cs │ │ │ ├── CheckFileLicense_YearDifference.cs │ │ │ ├── ClassAndMethodName.MethodName.Latest.Partial.cs │ │ │ ├── ClassAndMethodName.MethodName.Latest.cs │ │ │ ├── ClassAndMethodName.MethodName.Partial.cs │ │ │ ├── ClassAndMethodName.MethodName.cs │ │ │ ├── ClassAndMethodName.Partial.cs │ │ │ ├── ClassAndMethodName.Tests.cs │ │ │ ├── ClassAndMethodName.TopLevelStatement.Test.cs │ │ │ ├── ClassAndMethodName.TopLevelStatement.cs │ │ │ ├── ClassAndMethodName.cs │ │ │ ├── ClassAndMethodName.vb │ │ │ ├── ClassNamedException.Interop.cs │ │ │ ├── ClassNamedException.Interop.vb │ │ │ ├── ClassNamedException.Latest.cs │ │ │ ├── ClassNamedException.cs │ │ │ ├── ClassNamedException.vb │ │ │ ├── ClassNotInstantiatable.Latest.Partial.cs │ │ │ ├── ClassNotInstantiatable.Latest.cs │ │ │ ├── ClassNotInstantiatable.cs │ │ │ ├── ClassNotInstantiatable.vb │ │ │ ├── ClassShouldNotBeEmpty.Inheritance.cs │ │ │ ├── ClassShouldNotBeEmpty.Inheritance.vb │ │ │ ├── ClassShouldNotBeEmpty.Latest.Partial.cs │ │ │ ├── ClassShouldNotBeEmpty.Latest.cs │ │ │ ├── ClassShouldNotBeEmpty.cs │ │ │ ├── ClassShouldNotBeEmpty.vb │ │ │ ├── ClassWithEqualityShouldImplementIEquatable.Latest.cs │ │ │ ├── ClassWithEqualityShouldImplementIEquatable.cs │ │ │ ├── ClassWithOnlyStaticMember.Latest.Partial.cs │ │ │ ├── ClassWithOnlyStaticMember.Latest.cs │ │ │ ├── ClassWithOnlyStaticMember.TopLevelStatements.cs │ │ │ ├── ClassWithOnlyStaticMember.cs │ │ │ ├── CloudNative/ │ │ │ │ ├── AzureFunctionsCatchExceptions.cs │ │ │ │ ├── AzureFunctionsLogFailures.cs │ │ │ │ ├── AzureFunctionsReuseClients.ArmClient.cs │ │ │ │ ├── AzureFunctionsReuseClients.CosmosClient.cs │ │ │ │ ├── AzureFunctionsReuseClients.DocumentClient.cs │ │ │ │ ├── AzureFunctionsReuseClients.HttpClient.CSharp9.cs │ │ │ │ ├── AzureFunctionsReuseClients.HttpClient.cs │ │ │ │ ├── AzureFunctionsReuseClients.ServiceBusV5.cs │ │ │ │ ├── AzureFunctionsReuseClients.ServiceBusV7.cs │ │ │ │ ├── AzureFunctionsReuseClients.Storage.cs │ │ │ │ ├── AzureFunctionsStateless.Latest.Partial.cs │ │ │ │ ├── AzureFunctionsStateless.Latest.cs │ │ │ │ ├── AzureFunctionsStateless.cs │ │ │ │ ├── DurableEntityInterfaceRestrictions.CSharp11.cs │ │ │ │ └── DurableEntityInterfaceRestrictions.cs │ │ │ ├── CognitiveComplexity.Latest.Partial.cs │ │ │ ├── CognitiveComplexity.Latest.cs │ │ │ ├── CognitiveComplexity.cs │ │ │ ├── CognitiveComplexity.vb │ │ │ ├── CollectionEmptinessChecking.Fixed.cs │ │ │ ├── CollectionEmptinessChecking.Latest.cs │ │ │ ├── CollectionEmptinessChecking.cs │ │ │ ├── CollectionEmptinessChecking.vb │ │ │ ├── CollectionPropertiesShouldBeReadOnly.Latest.cs │ │ │ ├── CollectionPropertiesShouldBeReadOnly.cs │ │ │ ├── CollectionPropertiesShouldBeReadOnly.razor │ │ │ ├── CollectionPropertiesShouldBeReadOnly.razor.cs │ │ │ ├── CollectionQuerySimplification.Latest.cs │ │ │ ├── CollectionQuerySimplification.NetFx.cs │ │ │ ├── CollectionQuerySimplification.TopLevelStatements.cs │ │ │ ├── CollectionQuerySimplification.cs │ │ │ ├── CollectionsShouldImplementGenericInterface.CSharp10.cs │ │ │ ├── CollectionsShouldImplementGenericInterface.CSharp9.cs │ │ │ ├── CollectionsShouldImplementGenericInterface.cs │ │ │ ├── CommentFixme.cs │ │ │ ├── CommentFixme.vb │ │ │ ├── CommentLineEnd.vb │ │ │ ├── CommentTodo.cs │ │ │ ├── CommentTodo.vb │ │ │ ├── CommentedOutCode.MultiLine.Fixed.cs │ │ │ ├── CommentedOutCode.MultiLine.ToFix.cs │ │ │ ├── CommentedOutCode.SingleLine.Fixed.cs │ │ │ ├── CommentedOutCode.SingleLine.ToFix.cs │ │ │ ├── CommentedOutCode.cs │ │ │ ├── CommentedOutCode_Nonconcurrent.cs │ │ │ ├── CommentsShouldNotBeEmpty.cs │ │ │ ├── CommentsShouldNotBeEmpty.vb │ │ │ ├── ComparableInterfaceImplementation.Latest.cs │ │ │ ├── ComparableInterfaceImplementation.cs │ │ │ ├── CompareNaN.Latest.cs │ │ │ ├── CompareNaN.cs │ │ │ ├── ConditionalSimplification.BeforeCSharp8.Fixed.cs │ │ │ ├── ConditionalSimplification.BeforeCSharp8.cs │ │ │ ├── ConditionalSimplification.CSharp8.Fixed.cs │ │ │ ├── ConditionalSimplification.CSharp8.cs │ │ │ ├── ConditionalSimplification.FromCSharp10.Fixed.cs │ │ │ ├── ConditionalSimplification.FromCSharp10.cs │ │ │ ├── ConditionalSimplification.FromCSharp8.Fixed.cs │ │ │ ├── ConditionalSimplification.FromCSharp8.cs │ │ │ ├── ConditionalSimplification.FromCSharp9.Fixed.cs │ │ │ ├── ConditionalSimplification.FromCSharp9.cs │ │ │ ├── ConditionalStructureSameCondition.CSharp10.cs │ │ │ ├── ConditionalStructureSameCondition.CSharp9.cs │ │ │ ├── ConditionalStructureSameCondition.cs │ │ │ ├── ConditionalStructureSameCondition.vb │ │ │ ├── ConditionalStructureSameImplementation_If.Latest.cs │ │ │ ├── ConditionalStructureSameImplementation_If.cs │ │ │ ├── ConditionalStructureSameImplementation_If.vb │ │ │ ├── ConditionalStructureSameImplementation_Switch.Latest.cs │ │ │ ├── ConditionalStructureSameImplementation_Switch.cs │ │ │ ├── ConditionalStructureSameImplementation_Switch.vb │ │ │ ├── ConditionalsShouldStartOnNewLine.Latest.cs │ │ │ ├── ConditionalsShouldStartOnNewLine.cs │ │ │ ├── ConditionalsWithSameCondition.CSharp10.cs │ │ │ ├── ConditionalsWithSameCondition.CSharp9.TopLevelStatements.cs │ │ │ ├── ConditionalsWithSameCondition.CSharp9.cs │ │ │ ├── ConditionalsWithSameCondition.cs │ │ │ ├── ConstructorArgumentValueShouldExist.Latest.Partial.cs │ │ │ ├── ConstructorArgumentValueShouldExist.Latest.cs │ │ │ ├── ConstructorArgumentValueShouldExist.cs │ │ │ ├── ConstructorArgumentValueShouldExist.vb │ │ │ ├── ConstructorOverridableCall.Latest.Partial.cs │ │ │ ├── ConstructorOverridableCall.Latest.cs │ │ │ ├── ConstructorOverridableCall.Nancy.cs │ │ │ ├── ConstructorOverridableCall.cs │ │ │ ├── ConsumeValueTaskCorrectly.Latest.cs │ │ │ ├── ConsumeValueTaskCorrectly.cs │ │ │ ├── ControlCharacterInString.Latest.cs │ │ │ ├── ControlCharacterInString.cs │ │ │ ├── CryptographicKeyShouldNotBeTooShort.BeforeNet7.cs │ │ │ ├── CryptographicKeyShouldNotBeTooShort.Latest.cs │ │ │ ├── CryptographicKeyShouldNotBeTooShort.cs │ │ │ ├── DangerousGetHandleShouldNotBeCalled.CSharp9.cs │ │ │ ├── DangerousGetHandleShouldNotBeCalled.cs │ │ │ ├── DangerousGetHandleShouldNotBeCalled.vb │ │ │ ├── DatabasePasswordsShouldBeSecure.Latest.cs │ │ │ ├── DatabasePasswordsShouldBeSecure.Net5.cs │ │ │ ├── DatabasePasswordsShouldBeSecure.NetCore31.cs │ │ │ ├── DatabasePasswordsShouldBeSecure.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.EntityFrameworkCore.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.EntityFrameworkCore.vb │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.FluentApi.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.FluentApi.vb │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.Latest.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.NoReferenceToEntityFramework.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.NoReferenceToEntityFramework.vb │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.cs │ │ │ ├── DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.vb │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.Latest.cs │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.Net.vb │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.cs │ │ │ ├── DateTimeFormatShouldNotBeHardcoded.vb │ │ │ ├── DeadStores.Latest.cs │ │ │ ├── DeadStores.RoslynCfg.cs │ │ │ ├── DeadStores.SonarCfg.cs │ │ │ ├── DebugAssertHasNoSideEffects.Latest.cs │ │ │ ├── DebugAssertHasNoSideEffects.cs │ │ │ ├── DebuggerDisplayUsesExistingMembers.Latest.cs │ │ │ ├── DebuggerDisplayUsesExistingMembers.cs │ │ │ ├── DebuggerDisplayUsesExistingMembers.vb │ │ │ ├── DeclareEventHandlersCorrectly.cs │ │ │ ├── DeclareTypesInNamespaces.FileScopedNamespace.cs │ │ │ ├── DeclareTypesInNamespaces.Latest.cs │ │ │ ├── DeclareTypesInNamespaces.TopLevelStatements.Partial.cs │ │ │ ├── DeclareTypesInNamespaces.TopLevelStatements.cs │ │ │ ├── DeclareTypesInNamespaces.cs │ │ │ ├── DeclareTypesInNamespaces.vb │ │ │ ├── DeclareTypesInNamespaces2.cs │ │ │ ├── DeclareTypesInNamespaces2.vb │ │ │ ├── DefaultSectionShouldBeFirstOrLast.CSharp9.cs │ │ │ ├── DefaultSectionShouldBeFirstOrLast.cs │ │ │ ├── DelegateSubtraction.Latest.cs │ │ │ ├── DelegateSubtraction.TopLevelStatements.cs │ │ │ ├── DelegateSubtraction.cs │ │ │ ├── DisposableMemberInNonDisposableClass.CSharp9.cs │ │ │ ├── DisposableMemberInNonDisposableClass.NetCore.cs │ │ │ ├── DisposableMemberInNonDisposableClass.cs │ │ │ ├── DisposableNotDisposed.ILogger.cs │ │ │ ├── DisposableNotDisposed.Latest.cs │ │ │ ├── DisposableNotDisposed.TopLevelStatements.cs │ │ │ ├── DisposableNotDisposed.cs │ │ │ ├── DisposableReturnedFromUsing.Latest.cs │ │ │ ├── DisposableReturnedFromUsing.TopLevelStatements.cs │ │ │ ├── DisposableReturnedFromUsing.cs │ │ │ ├── DisposableTypesNeedFinalizers.CSharp11.cs │ │ │ ├── DisposableTypesNeedFinalizers.CSharp9.cs │ │ │ ├── DisposableTypesNeedFinalizers.cs │ │ │ ├── DisposeFromDispose.CSharp10.cs │ │ │ ├── DisposeFromDispose.CSharp7_2.cs │ │ │ ├── DisposeFromDispose.CSharp8.cs │ │ │ ├── DisposeFromDispose.CSharp9.Part1.cs │ │ │ ├── DisposeFromDispose.CSharp9.Part2.cs │ │ │ ├── DisposeNotImplementingDispose.Latest.Partial.cs │ │ │ ├── DisposeNotImplementingDispose.Latest.cs │ │ │ ├── DisposeNotImplementingDispose.TopLevelStatements.cs │ │ │ ├── DisposeNotImplementingDispose.cs │ │ │ ├── DoNotCallAssemblyGetExecutingAssembly.CSharp9.cs │ │ │ ├── DoNotCallAssemblyGetExecutingAssembly.cs │ │ │ ├── DoNotCallAssemblyLoadInvalidMethods.CSharp9.cs │ │ │ ├── DoNotCallAssemblyLoadInvalidMethods.Evidence.cs │ │ │ ├── DoNotCallAssemblyLoadInvalidMethods.cs │ │ │ ├── DoNotCallExitMethods.CSharp9.cs │ │ │ ├── DoNotCallExitMethods.cs │ │ │ ├── DoNotCallGCCollectMethod.Latest.cs │ │ │ ├── DoNotCallGCCollectMethod.cs │ │ │ ├── DoNotCallGCCollectMethodAD0001.cs │ │ │ ├── DoNotCallGCSuppressFinalize.Net5.cs │ │ │ ├── DoNotCallGCSuppressFinalize.NetCore.cs │ │ │ ├── DoNotCallGCSuppressFinalize.cs │ │ │ ├── DoNotCatchNullReferenceException.CSharp9.cs │ │ │ ├── DoNotCatchNullReferenceException.cs │ │ │ ├── DoNotCatchSystemException.CSharp10.cs │ │ │ ├── DoNotCatchSystemException.CSharp9.cs │ │ │ ├── DoNotCatchSystemException.cs │ │ │ ├── DoNotCheckZeroSizeCollection.Latest.cs │ │ │ ├── DoNotCheckZeroSizeCollection.cs │ │ │ ├── DoNotCheckZeroSizeCollection.vb │ │ │ ├── DoNotCopyArraysInProperties.Latest.cs │ │ │ ├── DoNotCopyArraysInProperties.cs │ │ │ ├── DoNotDecreaseMemberVisibility.Concurrent.cs │ │ │ ├── DoNotDecreaseMemberVisibility.Latest.Partial.cs │ │ │ ├── DoNotDecreaseMemberVisibility.Latest.cs │ │ │ ├── DoNotDecreaseMemberVisibility.cs │ │ │ ├── DoNotExposeListT.Latest.cs │ │ │ ├── DoNotExposeListT.cs │ │ │ ├── DoNotHardcodeCredentials.CustomValues.cs │ │ │ ├── DoNotHardcodeCredentials.CustomValues.vb │ │ │ ├── DoNotHardcodeCredentials.DefaultValues.Latest.cs │ │ │ ├── DoNotHardcodeCredentials.DefaultValues.cs │ │ │ ├── DoNotHardcodeCredentials.DefaultValues.vb │ │ │ ├── DoNotHardcodeCredentials.SecureString.cs │ │ │ ├── DoNotHardcodeCredentials.SecureString.vb │ │ │ ├── DoNotHardcodeSecrets.Latest.cs │ │ │ ├── DoNotHardcodeSecrets.cs │ │ │ ├── DoNotHardcodeSecrets.vb │ │ │ ├── DoNotHideBaseClassMethods.Concurrent.cs │ │ │ ├── DoNotHideBaseClassMethods.Latest.cs │ │ │ ├── DoNotHideBaseClassMethods.cs │ │ │ ├── DoNotInstantiateSharedClasses.cs │ │ │ ├── DoNotInstantiateSharedClasses.vb │ │ │ ├── DoNotLockOnSharedResource.Latest.cs │ │ │ ├── DoNotLockOnSharedResource.cs │ │ │ ├── DoNotLockOnSharedResource.vb │ │ │ ├── DoNotLockWeakIdentityObjects.Latest.cs │ │ │ ├── DoNotLockWeakIdentityObjects.cs │ │ │ ├── DoNotLockWeakIdentityObjects.vb │ │ │ ├── DoNotMarkEnumsWithFlags.cs │ │ │ ├── DoNotNestTernaryOperators.cs │ │ │ ├── DoNotNestTernaryOperators.vb │ │ │ ├── DoNotNestTypesInArguments.Latest.cs │ │ │ ├── DoNotNestTypesInArguments.Latest.partial.cs │ │ │ ├── DoNotNestTypesInArguments.cs │ │ │ ├── DoNotOverloadOperatorEqual.Latest.cs │ │ │ ├── DoNotOverloadOperatorEqual.cs │ │ │ ├── DoNotOverwriteCollectionElements.Latest.cs │ │ │ ├── DoNotOverwriteCollectionElements.cs │ │ │ ├── DoNotOverwriteCollectionElements.vb │ │ │ ├── DoNotShiftByZeroOrIntSize.CSharp10.cs │ │ │ ├── DoNotShiftByZeroOrIntSize.CSharp11.cs │ │ │ ├── DoNotShiftByZeroOrIntSize.CSharp9.cs │ │ │ ├── DoNotShiftByZeroOrIntSize.cs │ │ │ ├── DoNotTestThisWithIsOperator.CSharp10.cs │ │ │ ├── DoNotTestThisWithIsOperator.CSharp11.cs │ │ │ ├── DoNotTestThisWithIsOperator.CSharp9.cs │ │ │ ├── DoNotTestThisWithIsOperator.cs │ │ │ ├── DoNotThrowFromDestructors.CSharp9.cs │ │ │ ├── DoNotThrowFromDestructors.cs │ │ │ ├── DoNotThrowFromDestructors.vb │ │ │ ├── DoNotUseByVal.Fixed.vb │ │ │ ├── DoNotUseByVal.vb │ │ │ ├── DoNotUseCollectionInItsOwnMethodCalls.cs │ │ │ ├── DoNotUseDateTimeNow.cs │ │ │ ├── DoNotUseDateTimeNow.vb │ │ │ ├── DoNotUseIIf.Fixed.vb │ │ │ ├── DoNotUseIIf.vb │ │ │ ├── DoNotUseLiteralBoolInAssertions.MsTest.cs │ │ │ ├── DoNotUseLiteralBoolInAssertions.NUnit.cs │ │ │ ├── DoNotUseLiteralBoolInAssertions.NUnit4.cs │ │ │ ├── DoNotUseLiteralBoolInAssertions.Xunit.cs │ │ │ ├── DoNotUseLiteralBoolInAssertions.XunitV3.cs │ │ │ ├── DoNotUseOutRefParameters.CSharp11.cs │ │ │ ├── DoNotUseOutRefParameters.CSharp9.cs │ │ │ ├── DoNotUseOutRefParameters.cs │ │ │ ├── DoNotWriteToStandardOutput.cs │ │ │ ├── DoNotWriteToStandardOutput_Conditionals1.cs │ │ │ ├── DoNotWriteToStandardOutput_Conditionals2.cs │ │ │ ├── DontMixIncrementOrDecrementWithOtherOperators.Latest.cs │ │ │ ├── DontMixIncrementOrDecrementWithOtherOperators.cs │ │ │ ├── DontUseTraceSwitchLevels.cs │ │ │ ├── DontUseTraceWrite.cs │ │ │ ├── EmptyMethod.CSharp9.Comment.Fixed.cs │ │ │ ├── EmptyMethod.CSharp9.Throw.Fixed.cs │ │ │ ├── EmptyMethod.CSharp9.cs │ │ │ ├── EmptyMethod.Comment.Fixed.cs │ │ │ ├── EmptyMethod.Latest.cs │ │ │ ├── EmptyMethod.OverrideVirtual.cs │ │ │ ├── EmptyMethod.OverrideVirtual.vb │ │ │ ├── EmptyMethod.Throw.Fixed.cs │ │ │ ├── EmptyMethod.WithoutClosingBracket.Comment.Fixed.cs │ │ │ ├── EmptyMethod.WithoutClosingBracket.cs │ │ │ ├── EmptyMethod.cs │ │ │ ├── EmptyMethod.vb │ │ │ ├── EmptyNamespace.CSharp10.Empty.cs │ │ │ ├── EmptyNamespace.CSharp10.Fixed.cs │ │ │ ├── EmptyNamespace.CSharp10.NotEmpty.cs │ │ │ ├── EmptyNamespace.Fixed.Batch.cs │ │ │ ├── EmptyNamespace.Fixed.cs │ │ │ ├── EmptyNamespace.cs │ │ │ ├── EmptyNestedBlock.Latest.cs │ │ │ ├── EmptyNestedBlock.cs │ │ │ ├── EmptyNestedBlock.vb │ │ │ ├── EmptyNestedBlock2.cs │ │ │ ├── EmptyStatement.Fixed.cs │ │ │ ├── EmptyStatement.Latest.cs │ │ │ ├── EmptyStatement.TopLevelStatements.Fixed.cs │ │ │ ├── EmptyStatement.TopLevelStatements.cs │ │ │ ├── EmptyStatement.cs │ │ │ ├── EncryptionAlgorithmsShouldBeSecure.cs │ │ │ ├── EncryptionAlgorithmsShouldBeSecure.vb │ │ │ ├── EncryptionAlgorithmsShouldBeSecure_NetStandard21.Net48.vb │ │ │ ├── EncryptionAlgorithmsShouldBeSecure_NetStandard21.Net7.vb │ │ │ ├── EncryptionAlgorithmsShouldBeSecure_NetStandard21.cs │ │ │ ├── EncryptionAlgorithmsShouldBeSecure_NetStandard21.vb │ │ │ ├── EndStatementUsage.vb │ │ │ ├── EnumNameHasEnumSuffix.cs │ │ │ ├── EnumNameHasEnumSuffix.vb │ │ │ ├── EnumNameShouldFollowRegex.cs │ │ │ ├── EnumNameShouldFollowRegex.vb │ │ │ ├── EnumStorageNeedsToBeInt32.cs │ │ │ ├── EnumerableSumInUnchecked.CSharp11.cs │ │ │ ├── EnumerableSumInUnchecked.CSharp9.cs │ │ │ ├── EnumerableSumInUnchecked.cs │ │ │ ├── EnumerationValueName.vb │ │ │ ├── EnumsShouldNotBeNamedReserved.cs │ │ │ ├── EqualityOnFloatingPoint.CSharp11.cs │ │ │ ├── EqualityOnFloatingPoint.cs │ │ │ ├── EqualityOnModulus.CSharp11.cs │ │ │ ├── EqualityOnModulus.CSharp9.cs │ │ │ ├── EqualityOnModulus.cs │ │ │ ├── EquatableClassShouldBeSealed.CSharp9.cs │ │ │ ├── EquatableClassShouldBeSealed.cs │ │ │ ├── EscapeLambdaParameterTypeNamedScoped.CSharp11-13.cs │ │ │ ├── EscapeLambdaParameterTypeNamedScoped.Latest.cs │ │ │ ├── EscapeLambdaParameterTypeNamedScoped.TopLevelStatements.CSharp11-13.cs │ │ │ ├── EscapeLambdaParameterTypeNamedScoped.TopLevelStatements.Latest.cs │ │ │ ├── EventHandlerDelegateShouldHaveProperArguments.Latest.Partial.cs │ │ │ ├── EventHandlerDelegateShouldHaveProperArguments.Latest.cs │ │ │ ├── EventHandlerDelegateShouldHaveProperArguments.cs │ │ │ ├── EventHandlerName.vb │ │ │ ├── EventName.vb │ │ │ ├── EventNameContainsBeforeOrAfter.vb │ │ │ ├── ExceptionRethrow.Fixed.cs │ │ │ ├── ExceptionRethrow.cs │ │ │ ├── ExceptionShouldNotBeThrownFromUnexpectedMethods.Latest.Partial.cs │ │ │ ├── ExceptionShouldNotBeThrownFromUnexpectedMethods.Latest.cs │ │ │ ├── ExceptionShouldNotBeThrownFromUnexpectedMethods.cs │ │ │ ├── ExceptionsNeedStandardConstructors.cs │ │ │ ├── ExceptionsShouldBeLogged.cs │ │ │ ├── ExceptionsShouldBeLoggedOrThrown.cs │ │ │ ├── ExceptionsShouldBePublic.cs │ │ │ ├── ExceptionsShouldBePublic.vb │ │ │ ├── ExceptionsShouldBeUsed.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.net48.cs │ │ │ ├── ExcludeFromCodeCoverageAttributesNeedJustification.vb │ │ │ ├── ExitStatementUsage.vb │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.MsTest.cs │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.MsTest.vb │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.NUnit.cs │ │ │ ├── ExpectedExceptionAttributeShouldNotBeUsed.NUnit.vb │ │ │ ├── ExpressionComplexity.CSharp10.cs │ │ │ ├── ExpressionComplexity.CSharp11.cs │ │ │ ├── ExpressionComplexity.CSharp9.cs │ │ │ ├── ExpressionComplexity.cs │ │ │ ├── ExpressionComplexity.vb │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespace.CSharp10.cs │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespace.CSharp9.cs │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespace.GeneratedCode.cs │ │ │ ├── ExtensionMethodShouldBeInSeparateNamespace.cs │ │ │ ├── ExtensionMethodShouldNotExtendObject.cs │ │ │ ├── ExtensionMethodShouldNotExtendObject.vb │ │ │ ├── FieldShadowsParentField.CSharp9.cs │ │ │ ├── FieldShadowsParentField.cs │ │ │ ├── FieldShadowsParentField.vb │ │ │ ├── FieldShouldBeReadonly.Fixed.cs │ │ │ ├── FieldShouldBeReadonly.Latest.cs │ │ │ ├── FieldShouldBeReadonly.cs │ │ │ ├── FieldShouldNotBePublic.CSharp9.cs │ │ │ ├── FieldShouldNotBePublic.cs │ │ │ ├── FieldShouldNotBePublic.vb │ │ │ ├── FieldsShouldBeEncapsulatedInProperties.CSharp12.cs │ │ │ ├── FieldsShouldBeEncapsulatedInProperties.CSharp9.cs │ │ │ ├── FieldsShouldBeEncapsulatedInProperties.Unity3D.cs │ │ │ ├── FieldsShouldBeEncapsulatedInProperties.cs │ │ │ ├── FieldsShouldNotDifferByCapitalization.CSharp9.cs │ │ │ ├── FieldsShouldNotDifferByCapitalization.cs │ │ │ ├── FieldsShouldNotDifferByCapitalization.vb │ │ │ ├── FileLines20.cs │ │ │ ├── FileLines20.vb │ │ │ ├── FileLines9.cs │ │ │ ├── FileLines9.vb │ │ │ ├── FileShouldEndWithEmptyNewLine_EmptyFile.cs │ │ │ ├── FileShouldEndWithEmptyNewLine_EmptyLine.cs │ │ │ ├── FileShouldEndWithEmptyNewLine_NoEmptyLine.cs │ │ │ ├── FinalizerShouldNotBeEmpty.CSharp9.cs │ │ │ ├── FinalizerShouldNotBeEmpty.cs │ │ │ ├── FindInsteadOfFirstOrDefault.Array.cs │ │ │ ├── FindInsteadOfFirstOrDefault.Immutable.cs │ │ │ ├── FindInsteadOfFirstOrDefault.Net.cs │ │ │ ├── FindInsteadOfFirstOrDefault.Net.vb │ │ │ ├── FindInsteadOfFirstOrDefault.cs │ │ │ ├── FindInsteadOfFirstOrDefault.vb │ │ │ ├── FlagsEnumWithoutInitializer.cs │ │ │ ├── FlagsEnumWithoutInitializer.vb │ │ │ ├── FlagsEnumZeroMember.cs │ │ │ ├── FlagsEnumZeroMember.vb │ │ │ ├── ForLoopConditionAlwaysFalse.CSharp11.cs │ │ │ ├── ForLoopConditionAlwaysFalse.CSharp9.cs │ │ │ ├── ForLoopConditionAlwaysFalse.cs │ │ │ ├── ForLoopCounterChanged.Latest.cs │ │ │ ├── ForLoopCounterChanged.cs │ │ │ ├── ForLoopCounterCondition.cs │ │ │ ├── ForLoopIncrementSign.cs │ │ │ ├── ForeachLoopExplicitConversion.Fixed.cs │ │ │ ├── ForeachLoopExplicitConversion.Latest.Fixed.cs │ │ │ ├── ForeachLoopExplicitConversion.Latest.cs │ │ │ ├── ForeachLoopExplicitConversion.cs │ │ │ ├── FrameworkTypeNaming.cs │ │ │ ├── FunctionComplexity.Latest.cs │ │ │ ├── FunctionComplexity.cs │ │ │ ├── FunctionComplexity.vb │ │ │ ├── FunctionName.vb │ │ │ ├── FunctionNestingDepth.CSharp9.cs │ │ │ ├── FunctionNestingDepth.cs │ │ │ ├── FunctionNestingDepth.vb │ │ │ ├── GenericInheritanceShouldNotBeRecursive.CSharp9.cs │ │ │ ├── GenericInheritanceShouldNotBeRecursive.cs │ │ │ ├── GenericInheritanceShouldNotBeRecursive.vb │ │ │ ├── GenericLoggerInjectionShouldMatchEnclosingType.Latest.cs │ │ │ ├── GenericLoggerInjectionShouldMatchEnclosingType.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignment.AddConstraint.Fixed.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignment.Latest.Remove.Fixed.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignment.Latest.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignment.Remove.Fixed.cs │ │ │ ├── GenericReadonlyFieldPropertyAssignment.cs │ │ │ ├── GenericTypeParameterEmptinessChecking.Fixed.cs │ │ │ ├── GenericTypeParameterEmptinessChecking.Latest.cs │ │ │ ├── GenericTypeParameterEmptinessChecking.cs │ │ │ ├── GenericTypeParameterInOut.Latest.cs │ │ │ ├── GenericTypeParameterInOut.cs │ │ │ ├── GenericTypeParameterUnused.Latest.Partial.cs │ │ │ ├── GenericTypeParameterUnused.Latest.cs │ │ │ ├── GenericTypeParameterUnused.Partial.cs │ │ │ ├── GenericTypeParameterUnused.cs │ │ │ ├── GenericTypeParametersRequired.cs │ │ │ ├── GetHashCodeEqualsOverride.Latest.cs │ │ │ ├── GetHashCodeEqualsOverride.cs │ │ │ ├── GetHashCodeMutable.Fixed.cs │ │ │ ├── GetHashCodeMutable.Latest.cs │ │ │ ├── GetHashCodeMutable.cs │ │ │ ├── GetTypeWithIsAssignableFrom.Fixed.Batch.cs │ │ │ ├── GetTypeWithIsAssignableFrom.Fixed.cs │ │ │ ├── GetTypeWithIsAssignableFrom.Latest.Fixed.cs │ │ │ ├── GetTypeWithIsAssignableFrom.Latest.cs │ │ │ ├── GetTypeWithIsAssignableFrom.cs │ │ │ ├── GotoStatement.cs │ │ │ ├── GotoStatement.vb │ │ │ ├── GuardConditionOnEqualsOverride.cs │ │ │ ├── Hotspots/ │ │ │ │ ├── ClearTextProtocolsAreSensitive.Latest.cs │ │ │ │ ├── ClearTextProtocolsAreSensitive.NetFramework.cs │ │ │ │ ├── ClearTextProtocolsAreSensitive.TopLevelStatements.cs │ │ │ │ ├── ClearTextProtocolsAreSensitive.cs │ │ │ │ ├── CommandPath.Latest.cs │ │ │ │ ├── CommandPath.cs │ │ │ │ ├── CommandPath.vb │ │ │ │ ├── ConfiguringLoggers_AspNetCore.cs │ │ │ │ ├── ConfiguringLoggers_AspNetCore.vb │ │ │ │ ├── ConfiguringLoggers_AspNetCore6.cs │ │ │ │ ├── ConfiguringLoggers_Log4Net.cs │ │ │ │ ├── ConfiguringLoggers_Log4Net.vb │ │ │ │ ├── ConfiguringLoggers_NLog.cs │ │ │ │ ├── ConfiguringLoggers_NLog.vb │ │ │ │ ├── ConfiguringLoggers_Serilog.cs │ │ │ │ ├── ConfiguringLoggers_Serilog.vb │ │ │ │ ├── CookieShouldBeHttpOnly.Latest.cs │ │ │ │ ├── CookieShouldBeHttpOnly.TopLevelStatements.cs │ │ │ │ ├── CookieShouldBeHttpOnly.cs │ │ │ │ ├── CookieShouldBeHttpOnly_Nancy.cs │ │ │ │ ├── CookieShouldBeHttpOnly_WithWebConfig.cs │ │ │ │ ├── CookieShouldBeSecure.Latest.cs │ │ │ │ ├── CookieShouldBeSecure.TopLevelStatements.cs │ │ │ │ ├── CookieShouldBeSecure.cs │ │ │ │ ├── CookieShouldBeSecure_Nancy.cs │ │ │ │ ├── CookieShouldBeSecure_WithWebConfig.cs │ │ │ │ ├── CreatingHashAlgorithms.Latest.cs │ │ │ │ ├── CreatingHashAlgorithms.NET.vb │ │ │ │ ├── CreatingHashAlgorithms.NetFramework.cs │ │ │ │ ├── CreatingHashAlgorithms.NetFramework.vb │ │ │ │ ├── CreatingHashAlgorithms.cs │ │ │ │ ├── CreatingHashAlgorithms.vb │ │ │ │ ├── DeliveringDebugFeaturesInProduction.Net7.cs │ │ │ │ ├── DeliveringDebugFeaturesInProduction.NetCore2.cs │ │ │ │ ├── DeliveringDebugFeaturesInProduction.NetCore2.vb │ │ │ │ ├── DeliveringDebugFeaturesInProduction.NetCore3.cs │ │ │ │ ├── DeliveringDebugFeaturesInProduction.NetCore3.vb │ │ │ │ ├── DisablingCsrfProtection.Latest.cs │ │ │ │ ├── DisablingRequestValidation.cs │ │ │ │ ├── DisablingRequestValidation.vb │ │ │ │ ├── DoNotUseRandom.cs │ │ │ │ ├── ExecutingSqlQueries.AzureCosmos.cs │ │ │ │ ├── ExecutingSqlQueries.Dapper.cs │ │ │ │ ├── ExecutingSqlQueries.EF6.cs │ │ │ │ ├── ExecutingSqlQueries.EntityFrameworkCore2.cs │ │ │ │ ├── ExecutingSqlQueries.EntityFrameworkCore2.vb │ │ │ │ ├── ExecutingSqlQueries.EntityFrameworkCoreLatest.cs │ │ │ │ ├── ExecutingSqlQueries.EntityFrameworkCoreLatest.vb │ │ │ │ ├── ExecutingSqlQueries.Latest.cs │ │ │ │ ├── ExecutingSqlQueries.MicrosoftDataSqlClient.cs │ │ │ │ ├── ExecutingSqlQueries.MySqlData.cs │ │ │ │ ├── ExecutingSqlQueries.NHibernate.cs │ │ │ │ ├── ExecutingSqlQueries.Net46.MonoSqlLite.cs │ │ │ │ ├── ExecutingSqlQueries.Net46.MonoSqlLite.vb │ │ │ │ ├── ExecutingSqlQueries.Net46.cs │ │ │ │ ├── ExecutingSqlQueries.Net46.vb │ │ │ │ ├── ExecutingSqlQueries.OracleManagedDataAccess.cs │ │ │ │ ├── ExecutingSqlQueries.OrmLite.cs │ │ │ │ ├── ExpandingArchives.Latest.cs │ │ │ │ ├── ExpandingArchives.cs │ │ │ │ ├── ExpandingArchives.vb │ │ │ │ ├── HardcodedIpAddress.Latest.cs │ │ │ │ ├── HardcodedIpAddress.cs │ │ │ │ ├── HardcodedIpAddress.vb │ │ │ │ ├── InsecureDeserialization.Latest.Partial.cs │ │ │ │ ├── InsecureDeserialization.Latest.cs │ │ │ │ ├── InsecureDeserialization.cs │ │ │ │ ├── PermissiveCors.Latest.cs │ │ │ │ ├── PermissiveCors.NetFramework.cs │ │ │ │ ├── PubliclyWritableDirectories.Latest.cs │ │ │ │ ├── PubliclyWritableDirectories.cs │ │ │ │ ├── PubliclyWritableDirectories.vb │ │ │ │ ├── RequestsWithExcessiveLength.Latest.cs │ │ │ │ ├── RequestsWithExcessiveLength.cs │ │ │ │ ├── RequestsWithExcessiveLength.vb │ │ │ │ ├── RequestsWithExcessiveLength_CustomValues.cs │ │ │ │ ├── RequestsWithExcessiveLength_CustomValues.vb │ │ │ │ ├── SpecifyTimeoutOnRegex.DefaultMatchTimeout.cs │ │ │ │ ├── SpecifyTimeoutOnRegex.Latest.cs │ │ │ │ ├── SpecifyTimeoutOnRegex.cs │ │ │ │ ├── SpecifyTimeoutOnRegex.vb │ │ │ │ ├── UnsafeCodeBlocks.cs │ │ │ │ ├── UsingNonstandardCryptography.CSharp10.cs │ │ │ │ ├── UsingNonstandardCryptography.CSharp12.cs │ │ │ │ ├── UsingNonstandardCryptography.CSharp9.cs │ │ │ │ ├── UsingNonstandardCryptography.cs │ │ │ │ └── UsingNonstandardCryptography.vb │ │ │ ├── IdentifiersNamedExtensionShouldBeEscaped.BeforeCSharp14.cs │ │ │ ├── IdentifiersNamedExtensionShouldBeEscaped.Latest.cs │ │ │ ├── IdentifiersNamedFieldShouldBeEscaped.BeforeCSharp14.cs │ │ │ ├── IdentifiersNamedFieldShouldBeEscaped.BeforeCSharp14.partial.cs │ │ │ ├── IdentifiersNamedFieldShouldBeEscaped.CSharp9-13.cs │ │ │ ├── IdentifiersNamedFieldShouldBeEscaped.Latest.cs │ │ │ ├── IfChainWithoutElse.cs │ │ │ ├── IfChainWithoutElse.vb │ │ │ ├── IfCollapsible.cs │ │ │ ├── IfCollapsible.vb │ │ │ ├── ImplementIDisposableCorrectly.AbstractClass.cs │ │ │ ├── ImplementIDisposableCorrectly.Latest.cs │ │ │ ├── ImplementIDisposableCorrectly.cs │ │ │ ├── ImplementIDisposableCorrectlyPartial1.cs │ │ │ ├── ImplementIDisposableCorrectlyPartial2.cs │ │ │ ├── ImplementISerializableCorrectly.Latest.Partial.cs │ │ │ ├── ImplementISerializableCorrectly.Latest.cs │ │ │ ├── ImplementISerializableCorrectly.cs │ │ │ ├── ImplementSerializationMethodsCorrectly.Latest.cs │ │ │ ├── ImplementSerializationMethodsCorrectly.cs │ │ │ ├── ImplementSerializationMethodsCorrectly.vb │ │ │ ├── IndentSingleLineFollowingConditional.Latest.cs │ │ │ ├── IndentSingleLineFollowingConditional.cs │ │ │ ├── IndexOfCheckAgainstZero.Latest.cs │ │ │ ├── IndexOfCheckAgainstZero.cs │ │ │ ├── IndexOfCheckAgainstZero.vb │ │ │ ├── IndexedPropertyWithMultipleParameters.vb │ │ │ ├── InfiniteRecursion.RoslynCfg.Latest.cs │ │ │ ├── InfiniteRecursion.RoslynCfg.cs │ │ │ ├── InfiniteRecursion.SonarCfg.cs │ │ │ ├── InheritedCollidingInterfaceMembers.AnotherFile.cs │ │ │ ├── InheritedCollidingInterfaceMembers.DifferentFile.cs │ │ │ ├── InheritedCollidingInterfaceMembers.Latest.cs │ │ │ ├── InheritedCollidingInterfaceMembers.cs │ │ │ ├── InitializeStaticFieldsInline.cs │ │ │ ├── InsecureContentSecurityPolicy.Latest.cs │ │ │ ├── InsecureContentSecurityPolicy.cs │ │ │ ├── InsecureEncryptionAlgorithm.Latest.cs │ │ │ ├── InsecureEncryptionAlgorithm.Latest.vb │ │ │ ├── InsecureEncryptionAlgorithm.cs │ │ │ ├── InsecureEncryptionAlgorithm.vb │ │ │ ├── InsecureTemporaryFilesCreation.Latest.cs │ │ │ ├── InsecureTemporaryFilesCreation.cs │ │ │ ├── InsecureTemporaryFilesCreation.vb │ │ │ ├── InsteadOfAny.EntityFramework.Core.cs │ │ │ ├── InsteadOfAny.EntityFramework.Core.vb │ │ │ ├── InsteadOfAny.EntityFramework.Framework.cs │ │ │ ├── InsteadOfAny.Latest.cs │ │ │ ├── InsteadOfAny.cs │ │ │ ├── InsteadOfAny.vb │ │ │ ├── InterfaceMethodsShouldBeCallableByChildTypes.CSharp9.cs │ │ │ ├── InterfaceMethodsShouldBeCallableByChildTypes.cs │ │ │ ├── InterfaceName.vb │ │ │ ├── InterfacesShouldNotBeEmpty.cs │ │ │ ├── InvalidCastToInterface.Latest.cs │ │ │ ├── InvalidCastToInterface.cs │ │ │ ├── InvalidCastToInterface.vb │ │ │ ├── InvocationResolvesToOverrideWithParams.Latest.cs │ │ │ ├── InvocationResolvesToOverrideWithParams.TopLevelStatements.cs │ │ │ ├── InvocationResolvesToOverrideWithParams.cs │ │ │ ├── IssueSuppression.CSharp10.cs │ │ │ ├── IssueSuppression.CSharp9.cs │ │ │ ├── IssueSuppression.cs │ │ │ ├── IssueSuppression2.cs │ │ │ ├── JSInvokableMethodsShouldBePublic.Latest.cs │ │ │ ├── JSInvokableMethodsShouldBePublic.cs │ │ │ ├── JSInvokableMethodsShouldBePublic.razor │ │ │ ├── JSInvokableMethodsShouldBePublic.razor.cs │ │ │ ├── JwtSigned.Extensions.cs │ │ │ ├── JwtSigned.Extensions.vb │ │ │ ├── JwtSigned.Latest.cs │ │ │ ├── JwtSigned.cs │ │ │ ├── JwtSigned.vb │ │ │ ├── LaunchSettings/ │ │ │ │ ├── DoNotHardcodeCredentials/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── launchSettings.json │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── launchSettings.json │ │ │ │ │ └── Valid/ │ │ │ │ │ └── launchSettings.json │ │ │ │ └── DoNotHardcodeSecrets/ │ │ │ │ ├── Corrupt/ │ │ │ │ │ └── launchSettings.json │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ └── launchSettings.json │ │ │ │ └── Valid/ │ │ │ │ └── launchSettings.json │ │ │ ├── LdapConnectionShouldBeSecure.Latest.cs │ │ │ ├── LdapConnectionShouldBeSecure.cs │ │ │ ├── LineContinuation.vb │ │ │ ├── LineLength.cs │ │ │ ├── LineLength.vb │ │ │ ├── LinkedListPropertiesInsteadOfMethods.Fixed.cs │ │ │ ├── LinkedListPropertiesInsteadOfMethods.cs │ │ │ ├── LinkedListPropertiesInsteadOfMethods.vb │ │ │ ├── LiteralSuffixUpperCase.Fixed.cs │ │ │ ├── LiteralSuffixUpperCase.Latest.cs │ │ │ ├── LiteralSuffixUpperCase.cs │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParameters.CSharp10.cs │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParameters.CSharp11.cs │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParameters.CSharp9.cs │ │ │ ├── LiteralsShouldNotBePassedAsLocalizedParameters.cs │ │ │ ├── LocalVariableName.vb │ │ │ ├── LockedFieldShouldBeReadonly.Latest.cs │ │ │ ├── LockedFieldShouldBeReadonly.cs │ │ │ ├── LoggerFieldsShouldBePrivateStaticReadonly.cs │ │ │ ├── LoggersShouldBeNamedForEnclosingType.NLog.cs │ │ │ ├── LoggersShouldBeNamedForEnclosingType.cs │ │ │ ├── LoggingArgumentsShouldBePassedCorrectly.cs │ │ │ ├── LoggingTemplatePlaceHoldersShouldBeInOrder.cs │ │ │ ├── LoopsAndLinq.Latest.cs │ │ │ ├── LoopsAndLinq.cs │ │ │ ├── LooseFilePermissions.Unix.cs │ │ │ ├── LooseFilePermissions.Unix.vb │ │ │ ├── LooseFilePermissions.Windows.CSharp10.cs │ │ │ ├── LooseFilePermissions.Windows.CSharp11.cs │ │ │ ├── LooseFilePermissions.Windows.CSharp12.cs │ │ │ ├── LooseFilePermissions.Windows.CSharp9.cs │ │ │ ├── LooseFilePermissions.Windows.cs │ │ │ ├── LooseFilePermissions.Windows.vb │ │ │ ├── LossOfFractionInDivision.Latest.cs │ │ │ ├── LossOfFractionInDivision.cs │ │ │ ├── MagicNumberShouldNotBeUsed.CSharp11.cs │ │ │ ├── MagicNumberShouldNotBeUsed.cs │ │ │ ├── MarkAssemblyWithAssemblyVersionAttribute.cs │ │ │ ├── MarkAssemblyWithAssemblyVersionAttribute.vb │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeNoncompliant.cs │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeNoncompliant.vb │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeRazor.cs │ │ │ ├── MarkAssemblyWithAssemblyVersionAttributeRazor.vb │ │ │ ├── MarkAssemblyWithClsCompliantAttribute.cs │ │ │ ├── MarkAssemblyWithClsCompliantAttribute.vb │ │ │ ├── MarkAssemblyWithClsCompliantAttributeNoncompliant.cs │ │ │ ├── MarkAssemblyWithClsCompliantAttributeNoncompliant.vb │ │ │ ├── MarkAssemblyWithComVisibleAttribute.cs │ │ │ ├── MarkAssemblyWithComVisibleAttribute.vb │ │ │ ├── MarkAssemblyWithComVisibleAttributeNoncompliant.cs │ │ │ ├── MarkAssemblyWithComVisibleAttributeNoncompliant.vb │ │ │ ├── MarkAssemblyWithNeutralResourcesLanguageAttribute.cs │ │ │ ├── MarkAssemblyWithNeutralResourcesLanguageAttributeNonCompliant.cs │ │ │ ├── MarkAssemblyWithNeutralResourcesLanguageAttributeNoncompliant.Invalid.cs │ │ │ ├── MarkWindowsFormsMainWithStaThread.cs │ │ │ ├── MarkWindowsFormsMainWithStaThread.vb │ │ │ ├── MarkWindowsFormsMainWithStaThread_NoWindowsForms.cs │ │ │ ├── MarkWindowsFormsMainWithStaThread_NoWindowsForms.vb │ │ │ ├── MemberInitializedToDefault.CSharp10.cs │ │ │ ├── MemberInitializedToDefault.CSharp11.Fixed.cs │ │ │ ├── MemberInitializedToDefault.CSharp11.cs │ │ │ ├── MemberInitializedToDefault.CSharp8.cs │ │ │ ├── MemberInitializedToDefault.CSharp9.cs │ │ │ ├── MemberInitializedToDefault.Fixed.cs │ │ │ ├── MemberInitializedToDefault.cs │ │ │ ├── MemberInitializerRedundant.Fixed.cs │ │ │ ├── MemberInitializerRedundant.Latest.Fixed.cs │ │ │ ├── MemberInitializerRedundant.Latest.cs │ │ │ ├── MemberInitializerRedundant.RoslynCfg.FlowCaptureBug.cs │ │ │ ├── MemberInitializerRedundant.cs │ │ │ ├── MemberOverrideCallsBaseMember.Fixed.cs │ │ │ ├── MemberOverrideCallsBaseMember.Latest.Fixed.cs │ │ │ ├── MemberOverrideCallsBaseMember.Latest.cs │ │ │ ├── MemberOverrideCallsBaseMember.cs │ │ │ ├── MemberShadowsOuterStaticMember.Latest.cs │ │ │ ├── MemberShadowsOuterStaticMember.cs │ │ │ ├── MemberShouldBeStatic.Latest.Partial.cs │ │ │ ├── MemberShouldBeStatic.Latest.cs │ │ │ ├── MemberShouldBeStatic.WinForms.cs │ │ │ ├── MemberShouldBeStatic.Xaml.cs │ │ │ ├── MemberShouldBeStatic.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.AssemblyLevel.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.Latest.Partial.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.Latest.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.Partial.cs │ │ │ ├── MemberShouldNotHaveConflictingTransparencyAttributes.cs │ │ │ ├── MessageTemplatesShouldBeCorrect.Latest.cs │ │ │ ├── MessageTemplatesShouldBeCorrect.cs │ │ │ ├── MethodOverloadOptionalParameter.Latest.Partial.cs │ │ │ ├── MethodOverloadOptionalParameter.Latest.cs │ │ │ ├── MethodOverloadOptionalParameter.cs │ │ │ ├── MethodOverloadsShouldBeGrouped.Latest.cs │ │ │ ├── MethodOverloadsShouldBeGrouped.cs │ │ │ ├── MethodOverloadsShouldBeGrouped.vb │ │ │ ├── MethodOverrideAddsParams.Fixed.cs │ │ │ ├── MethodOverrideAddsParams.Latest.cs │ │ │ ├── MethodOverrideAddsParams.cs │ │ │ ├── MethodOverrideChangedDefaultValue.Latest.Fixed.cs │ │ │ ├── MethodOverrideChangedDefaultValue.Latest.cs │ │ │ ├── MethodOverrideChangedDefaultValue.Remove.Fixed.cs │ │ │ ├── MethodOverrideChangedDefaultValue.Synchronize.Fixed.Batch.cs │ │ │ ├── MethodOverrideChangedDefaultValue.Synchronize.Fixed.cs │ │ │ ├── MethodOverrideChangedDefaultValue.cs │ │ │ ├── MethodOverrideNoParams.Fixed.cs │ │ │ ├── MethodOverrideNoParams.Latest.cs │ │ │ ├── MethodOverrideNoParams.cs │ │ │ ├── MethodParameterMissingOptional.Fixed.cs │ │ │ ├── MethodParameterMissingOptional.Latest.cs │ │ │ ├── MethodParameterMissingOptional.TopLevelStatements.cs │ │ │ ├── MethodParameterMissingOptional.cs │ │ │ ├── MethodParameterUnused.Latest.cs │ │ │ ├── MethodParameterUnused.RoslynCfg.Fixed.cs │ │ │ ├── MethodParameterUnused.RoslynCfg.cs │ │ │ ├── MethodParameterUnused.SonarCfg.cs │ │ │ ├── MethodParameterUnused.vb │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.CSharp11.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.CSharp8.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.MVC.Core.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.MVC.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.MsTest.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.NUnit.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.Xunit.cs │ │ │ ├── MethodShouldBeNamedAccordingToSynchronicity.cs │ │ │ ├── MethodShouldNotOnlyReturnConstant.Latest.cs │ │ │ ├── MethodShouldNotOnlyReturnConstant.cs │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.Latest.cs │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.TopLevelStatements.cs │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.cs │ │ │ ├── MethodsShouldNotHaveIdenticalImplementations.vb │ │ │ ├── MethodsShouldNotHaveTooManyLines.LocalFunctions.CSharp9.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines.LocalFunctions.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines_CustomValues.CSharp10.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines_CustomValues.CSharp9.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines_CustomValues.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines_CustomValues.vb │ │ │ ├── MethodsShouldNotHaveTooManyLines_DefaultValues.cs │ │ │ ├── MethodsShouldNotHaveTooManyLines_DefaultValues.vb │ │ │ ├── MethodsShouldUseBaseTypes.AspControllers.cs │ │ │ ├── MethodsShouldUseBaseTypes.CSharp8.cs │ │ │ ├── MethodsShouldUseBaseTypes.CSharp9.cs │ │ │ ├── MethodsShouldUseBaseTypes.Concurrent.cs │ │ │ ├── MethodsShouldUseBaseTypes.cs │ │ │ ├── MultilineBlocksWithoutBrace.Latest.cs │ │ │ ├── MultilineBlocksWithoutBrace.cs │ │ │ ├── MultipleVariableDeclaration.Fixed.cs │ │ │ ├── MultipleVariableDeclaration.Fixed.vb │ │ │ ├── MultipleVariableDeclaration.WrongIndentation.Fixed.cs │ │ │ ├── MultipleVariableDeclaration.WrongIndentation.cs │ │ │ ├── MultipleVariableDeclaration.cs │ │ │ ├── MultipleVariableDeclaration.vb │ │ │ ├── MutableFieldsShouldNotBePublicReadonly.Latest.cs │ │ │ ├── MutableFieldsShouldNotBePublicReadonly.cs │ │ │ ├── MutableFieldsShouldNotBePublicStatic.Latest.cs │ │ │ ├── MutableFieldsShouldNotBePublicStatic.cs │ │ │ ├── NameOfShouldBeUsed.CSharp11.cs │ │ │ ├── NameOfShouldBeUsed.cs │ │ │ ├── NameOfShouldBeUsed.vb │ │ │ ├── NamedPlaceholdersShouldBeUnique.cs │ │ │ ├── NamespaceName.vb │ │ │ ├── NativeMethodsShouldBeWrapped.Latest.SourceGenerator.cs │ │ │ ├── NativeMethodsShouldBeWrapped.Latest.cs │ │ │ ├── NativeMethodsShouldBeWrapped.TopLevelStatements.cs │ │ │ ├── NativeMethodsShouldBeWrapped.cs │ │ │ ├── NegatedIsExpression.Fixed.vb │ │ │ ├── NegatedIsExpression.vb │ │ │ ├── NestedCodeBlock.Latest.cs │ │ │ ├── NestedCodeBlock.TopLevelStatements.cs │ │ │ ├── NestedCodeBlock.cs │ │ │ ├── NoExceptionsInFinally.cs │ │ │ ├── NoExceptionsInFinally.vb │ │ │ ├── NonAsyncTaskShouldNotReturnNull.Latest.Partial.cs │ │ │ ├── NonAsyncTaskShouldNotReturnNull.Latest.cs │ │ │ ├── NonAsyncTaskShouldNotReturnNull.cs │ │ │ ├── NonAsyncTaskShouldNotReturnNull.vb │ │ │ ├── NonDerivedPrivateClassesShouldBeSealed.Latest.cs │ │ │ ├── NonDerivedPrivateClassesShouldBeSealed.cs │ │ │ ├── NonDerivedPrivateClassesShouldBeSealed_PartialClass.cs │ │ │ ├── NonFlagsEnumInBitwiseOperation.Fixed.cs │ │ │ ├── NonFlagsEnumInBitwiseOperation.cs │ │ │ ├── NormalizeStringsToUppercase.CSharp11.cs │ │ │ ├── NormalizeStringsToUppercase.cs │ │ │ ├── NotAssignedPrivateMember.Latest.Partial.cs │ │ │ ├── NotAssignedPrivateMember.Latest.cs │ │ │ ├── NotAssignedPrivateMember.cs │ │ │ ├── NotAssignedPrivateMember.razor │ │ │ ├── NotAssignedPrivateMember.razor.cs │ │ │ ├── NumberPatternShouldBeRegular.CSharp9.cs │ │ │ ├── NumberPatternShouldBeRegular.cs │ │ │ ├── ObjectCreatedDropped.Latest.cs │ │ │ ├── ObjectCreatedDropped.cs │ │ │ ├── ObsoleteAttributesNeedExplanation.Latest.cs │ │ │ ├── ObsoleteAttributesNeedExplanation.VB14.vb │ │ │ ├── ObsoleteAttributesNeedExplanation.cs │ │ │ ├── ObsoleteAttributesNeedExplanation.vb │ │ │ ├── OnErrorStatement.vb │ │ │ ├── OperatorOverloadsShouldHaveNamedAlternatives.CSharp11.cs │ │ │ ├── OperatorOverloadsShouldHaveNamedAlternatives.CSharp9.cs │ │ │ ├── OperatorOverloadsShouldHaveNamedAlternatives.cs │ │ │ ├── OperatorsShouldBeOverloadedConsistently.Latest.cs │ │ │ ├── OperatorsShouldBeOverloadedConsistently.cs │ │ │ ├── OptionalParameter.CSharp10.cs │ │ │ ├── OptionalParameter.CSharp11.cs │ │ │ ├── OptionalParameter.Web.cs │ │ │ ├── OptionalParameter.cs │ │ │ ├── OptionalParameter.vb │ │ │ ├── OptionalParameterNotPassedToBaseCall.Latest.cs │ │ │ ├── OptionalParameterNotPassedToBaseCall.cs │ │ │ ├── OptionalParameterNotPassedToBaseCall.vb │ │ │ ├── OptionalParameterWithDefaultValue.Fixed.cs │ │ │ ├── OptionalParameterWithDefaultValue.Latest.cs │ │ │ ├── OptionalParameterWithDefaultValue.cs │ │ │ ├── OptionalRefOutParameter.Fixed.cs │ │ │ ├── OptionalRefOutParameter.Latest.cs │ │ │ ├── OptionalRefOutParameter.TopLevelStatements.cs │ │ │ ├── OptionalRefOutParameter.cs │ │ │ ├── OrderByRepeated.Fixed.cs │ │ │ ├── OrderByRepeated.cs │ │ │ ├── OverrideGetHashCodeOnOverridingEquals.Latest.cs │ │ │ ├── OverrideGetHashCodeOnOverridingEquals.cs │ │ │ ├── PInvokesShouldNotBeVisible.CSharp11.cs │ │ │ ├── PInvokesShouldNotBeVisible.CSharp9.cs │ │ │ ├── PInvokesShouldNotBeVisible.cs │ │ │ ├── ParameterAssignedTo.Latest.cs │ │ │ ├── ParameterAssignedTo.cs │ │ │ ├── ParameterAssignedTo.vb │ │ │ ├── ParameterName.CustomPattern.vb │ │ │ ├── ParameterName.vb │ │ │ ├── ParameterNameMatchesOriginal.Latest.Partial.cs │ │ │ ├── ParameterNameMatchesOriginal.Latest.cs │ │ │ ├── ParameterNameMatchesOriginal.cs │ │ │ ├── ParameterNameMatchesOriginal.vb │ │ │ ├── ParameterNamesShouldNotDuplicateMethodNames.cs │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.Conversion.razor │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.Latest.cs │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.Partial.razor │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.Partial.razor.cs │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.cs │ │ │ ├── ParameterTypeShouldMatchRouteTypeConstraint.razor │ │ │ ├── ParameterValidationInAsyncShouldBeWrapped.CSharp10.cs │ │ │ ├── ParameterValidationInAsyncShouldBeWrapped.CSharp11.cs │ │ │ ├── ParameterValidationInAsyncShouldBeWrapped.cs │ │ │ ├── ParameterValidationInYieldShouldBeWrapped.Latest.cs │ │ │ ├── ParameterValidationInYieldShouldBeWrapped.cs │ │ │ ├── ParametersCorrectOrder.Latest.cs │ │ │ ├── ParametersCorrectOrder.cs │ │ │ ├── ParametersCorrectOrder.vb │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttribute.Latest.cs │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttribute.cs │ │ │ ├── PartCreationPolicyShouldBeUsedWithExportAttribute.vb │ │ │ ├── PartialMethodNoImplementation.Latest.Partial.cs │ │ │ ├── PartialMethodNoImplementation.Latest.cs │ │ │ ├── PartialMethodNoImplementation.cs │ │ │ ├── PasswordsShouldBeStoredCorrectly.Core.cs │ │ │ ├── PointersShouldBePrivate.CSharp11.cs │ │ │ ├── PointersShouldBePrivate.CSharp9.cs │ │ │ ├── PointersShouldBePrivate.cs │ │ │ ├── PreferGuidEmpty.Fixed.cs │ │ │ ├── PreferGuidEmpty.Latest.cs │ │ │ ├── PreferGuidEmpty.cs │ │ │ ├── PreferGuidEmpty.vb │ │ │ ├── PreferJaggedArraysOverMultidimensional.Latest.cs │ │ │ ├── PreferJaggedArraysOverMultidimensional.cs │ │ │ ├── PrivateConstantFieldName.vb │ │ │ ├── PrivateFieldName.vb │ │ │ ├── PrivateFieldUsedAsLocalVariable.Latest.cs │ │ │ ├── PrivateFieldUsedAsLocalVariable.cs │ │ │ ├── PrivateSharedReadonlyFieldName.vb │ │ │ ├── PrivateStaticMethodUsedOnlyByNestedClass.CSharpLatest.cs │ │ │ ├── PrivateStaticMethodUsedOnlyByNestedClass.cs │ │ │ ├── PropertiesAccessCorrectField.Latest.Partial.cs │ │ │ ├── PropertiesAccessCorrectField.Latest.cs │ │ │ ├── PropertiesAccessCorrectField.NetFramework.cs │ │ │ ├── PropertiesAccessCorrectField.NetFramework.vb │ │ │ ├── PropertiesAccessCorrectField.cs │ │ │ ├── PropertiesAccessCorrectField.vb │ │ │ ├── PropertiesShouldBePreferred.CSharp10.cs │ │ │ ├── PropertiesShouldBePreferred.CSharp11.cs │ │ │ ├── PropertiesShouldBePreferred.CSharp9.cs │ │ │ ├── PropertiesShouldBePreferred.cs │ │ │ ├── PropertyGetterWithThrow.Latest.Partial.cs │ │ │ ├── PropertyGetterWithThrow.Latest.cs │ │ │ ├── PropertyGetterWithThrow.cs │ │ │ ├── PropertyGetterWithThrow.vb │ │ │ ├── PropertyName.vb │ │ │ ├── PropertyNamesShouldNotMatchGetMethods.Latest.Partial1.g.cs │ │ │ ├── PropertyNamesShouldNotMatchGetMethods.Latest.Partial2.cs │ │ │ ├── PropertyNamesShouldNotMatchGetMethods.Latest.cs │ │ │ ├── PropertyNamesShouldNotMatchGetMethods.cs │ │ │ ├── PropertyToAutoProperty.Latest.cs │ │ │ ├── PropertyToAutoProperty.cs │ │ │ ├── PropertyWithArrayType.vb │ │ │ ├── PropertyWriteOnly.Latest.Partial.cs │ │ │ ├── PropertyWriteOnly.Latest.cs │ │ │ ├── PropertyWriteOnly.cs │ │ │ ├── PropertyWriteOnly.vb │ │ │ ├── ProvideDeserializationMethodsForOptionalFields.Latest.cs │ │ │ ├── ProvideDeserializationMethodsForOptionalFields.cs │ │ │ ├── ProvideDeserializationMethodsForOptionalFields.vb │ │ │ ├── PublicConstantField.CSharp10.cs │ │ │ ├── PublicConstantField.CSharp9.cs │ │ │ ├── PublicConstantField.cs │ │ │ ├── PublicConstantField.vb │ │ │ ├── PublicConstantFieldName.vb │ │ │ ├── PublicFieldName.vb │ │ │ ├── PublicMethodWithMultidimensionalArray.Latest.Partial.cs │ │ │ ├── PublicMethodWithMultidimensionalArray.Latest.cs │ │ │ ├── PublicMethodWithMultidimensionalArray.cs │ │ │ ├── PublicMethodWithMultidimensionalArray.vb │ │ │ ├── PublicSharedReadonlyFieldName.vb │ │ │ ├── PureAttributeOnVoidMethod.CSharp7.cs │ │ │ ├── PureAttributeOnVoidMethod.Latest.cs │ │ │ ├── PureAttributeOnVoidMethod.TopLevelStatements.cs │ │ │ ├── PureAttributeOnVoidMethod.cs │ │ │ ├── PureAttributeOnVoidMethod.vb │ │ │ ├── RedundancyInConstructorDestructorDeclaration.BaseCall.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp10.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp10.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp11.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp11.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp12.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp9.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.CSharp9.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.Constructor.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.Destructor.Fixed.cs │ │ │ ├── RedundancyInConstructorDestructorDeclaration.cs │ │ │ ├── RedundantArgument.Latest.cs │ │ │ ├── RedundantArgument.Named.Fixed.cs │ │ │ ├── RedundantArgument.NoNamed.Fixed.cs │ │ │ ├── RedundantArgument.TopLevelStatements.cs │ │ │ ├── RedundantArgument.cs │ │ │ ├── RedundantCast.Fixed.cs │ │ │ ├── RedundantCast.Latest.cs │ │ │ ├── RedundantCast.cs │ │ │ ├── RedundantConditionalAroundAssignment.Fixed.cs │ │ │ ├── RedundantConditionalAroundAssignment.Latest.Fixed.cs │ │ │ ├── RedundantConditionalAroundAssignment.Latest.cs │ │ │ ├── RedundantConditionalAroundAssignment.cs │ │ │ ├── RedundantDeclaration.ArraySize.Fixed.cs │ │ │ ├── RedundantDeclaration.ArrayType.Fixed.cs │ │ │ ├── RedundantDeclaration.CSharp10.Fixed.cs │ │ │ ├── RedundantDeclaration.CSharp10.cs │ │ │ ├── RedundantDeclaration.CSharp12.ArraySize.Fixed.cs │ │ │ ├── RedundantDeclaration.CSharp12.LambdaParameterType.Fixed.cs │ │ │ ├── RedundantDeclaration.CSharp12.cs │ │ │ ├── RedundantDeclaration.CSharp9.Fixed.cs │ │ │ ├── RedundantDeclaration.CSharp9.cs │ │ │ ├── RedundantDeclaration.DelegateParameterList.Fixed.cs │ │ │ ├── RedundantDeclaration.ExplicitDelegate.Fixed.cs │ │ │ ├── RedundantDeclaration.ExplicitNullable.Fixed.cs │ │ │ ├── RedundantDeclaration.LambdaParameterType.Fixed.cs │ │ │ ├── RedundantDeclaration.ObjectInitializer.Fixed.cs │ │ │ ├── RedundantDeclaration.cs │ │ │ ├── RedundantExitSelect.vb │ │ │ ├── RedundantInheritanceList.CSharp10.Fixed.cs │ │ │ ├── RedundantInheritanceList.CSharp10.cs │ │ │ ├── RedundantInheritanceList.CSharp9.Fixed.cs │ │ │ ├── RedundantInheritanceList.CSharp9.cs │ │ │ ├── RedundantInheritanceList.Fixed.cs │ │ │ ├── RedundantInheritanceList.cs │ │ │ ├── RedundantJumpStatement.Latest.cs │ │ │ ├── RedundantJumpStatement.TopLevelStatements.cs │ │ │ ├── RedundantJumpStatement.cs │ │ │ ├── RedundantModifier.Fixed.Checked.cs │ │ │ ├── RedundantModifier.Fixed.Partial.cs │ │ │ ├── RedundantModifier.Fixed.Sealed.cs │ │ │ ├── RedundantModifier.Fixed.Unsafe.cs │ │ │ ├── RedundantModifier.Latest.Fixed.Checked.cs │ │ │ ├── RedundantModifier.Latest.Fixed.Partial.cs │ │ │ ├── RedundantModifier.Latest.Fixed.Sealed.cs │ │ │ ├── RedundantModifier.Latest.Fixed.Unsafe.cs │ │ │ ├── RedundantModifier.Latest.Partial.cs │ │ │ ├── RedundantModifier.Latest.cs │ │ │ ├── RedundantModifier.Preprocessor.Net.Partial.cs │ │ │ ├── RedundantModifier.Preprocessor.Net.cs │ │ │ ├── RedundantModifier.Preprocessor.NetFx.cs │ │ │ ├── RedundantModifier.Preprocessor.cs │ │ │ ├── RedundantModifier.cs │ │ │ ├── RedundantNullCheck.CSharp10.Fixed.cs │ │ │ ├── RedundantNullCheck.CSharp10.cs │ │ │ ├── RedundantNullCheck.CSharp11.cs │ │ │ ├── RedundantNullCheck.CSharp9.Fixed.cs │ │ │ ├── RedundantNullCheck.CSharp9.cs │ │ │ ├── RedundantNullCheck.Fixed.Batch.cs │ │ │ ├── RedundantNullCheck.Fixed.cs │ │ │ ├── RedundantNullCheck.cs │ │ │ ├── RedundantNullCheck.vb │ │ │ ├── RedundantNullableTypeComparison.cs │ │ │ ├── RedundantParentheses.vb │ │ │ ├── RedundantParenthesesExpression.cs │ │ │ ├── RedundantParenthesesObjectCreation.CSharp10.cs │ │ │ ├── RedundantParenthesesObjectCreation.CSharp11.cs │ │ │ ├── RedundantParenthesesObjectCreation.CSharp9.cs │ │ │ ├── RedundantParenthesesObjectCreation.Fixed.cs │ │ │ ├── RedundantParenthesesObjectCreation.cs │ │ │ ├── RedundantPropertyNamesInAnonymousClass.Fixed.cs │ │ │ ├── RedundantPropertyNamesInAnonymousClass.Latest.Partial.cs │ │ │ ├── RedundantPropertyNamesInAnonymousClass.Latest.cs │ │ │ ├── RedundantPropertyNamesInAnonymousClass.cs │ │ │ ├── RedundantToArrayCall.CSharp11.Fixed.cs │ │ │ ├── RedundantToArrayCall.CSharp11.cs │ │ │ ├── RedundantToArrayCall.Fixed.cs │ │ │ ├── RedundantToArrayCall.cs │ │ │ ├── RedundantToStringCall.CSharp10.cs │ │ │ ├── RedundantToStringCall.CSharp11.cs │ │ │ ├── RedundantToStringCall.CSharp9.cs │ │ │ ├── RedundantToStringCall.Fixed.cs │ │ │ ├── RedundantToStringCall.cs │ │ │ ├── ReferenceEqualityCheckWhenEqualsExists.cs │ │ │ ├── ReferenceEqualityCheckWhenEqualsExists2.cs │ │ │ ├── ReferenceEqualsOnValueType.cs │ │ │ ├── RegularExpressions/ │ │ │ │ ├── RegexMustHaveValidSyntax.Latest.cs │ │ │ │ ├── RegexMustHaveValidSyntax.cs │ │ │ │ └── RegexMustHaveValidSyntax.vb │ │ │ ├── RemoveObsoleteCode.Latest.cs │ │ │ ├── RemoveObsoleteCode.cs │ │ │ ├── RemoveObsoleteCode.vb │ │ │ ├── RequireAttributeUsageAttribute.CSharp11.cs │ │ │ ├── RequireAttributeUsageAttribute.cs │ │ │ ├── Resources/ │ │ │ │ ├── AnotherResources.Designer.cs │ │ │ │ ├── AnotherResources.resx │ │ │ │ ├── SomeResources.Designer.cs │ │ │ │ └── SomeResources.resx │ │ │ ├── ReturnEmptyCollectionInsteadOfNull.Latest.cs │ │ │ ├── ReturnEmptyCollectionInsteadOfNull.TopLevelStatements.cs │ │ │ ├── ReturnEmptyCollectionInsteadOfNull.cs │ │ │ ├── ReturnTypeNamedPartialShouldBeEscaped.CSharp8-13.cs │ │ │ ├── ReturnTypeNamedPartialShouldBeEscaped.Latest.cs │ │ │ ├── ReturnTypeNamedPartialShouldBeEscaped.TopLevelStatements.CSharp9-13.cs │ │ │ ├── ReturnTypeNamedPartialShouldBeEscaped.TopLevelStatements.Latest.cs │ │ │ ├── ReturnValueIgnored.Latest.cs │ │ │ ├── ReturnValueIgnored.TopLevelStatements.cs │ │ │ ├── ReturnValueIgnored.cs │ │ │ ├── ReversedOperators.Latest.cs │ │ │ ├── ReversedOperators.TopLevelStatements.cs │ │ │ ├── ReversedOperators.cs │ │ │ ├── ReversedOperators.vb │ │ │ ├── RightCurlyBraceStartsLine.cs │ │ │ ├── RuleFailure/ │ │ │ │ ├── InvalidSyntax.cs │ │ │ │ ├── InvalidSyntax.vb │ │ │ │ ├── PerformanceTestCases.cs │ │ │ │ ├── SpecialCases.cs │ │ │ │ └── SpecialCases.vb │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.CSharp11.cs │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.CSharp12.cs │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.cs │ │ │ ├── SecurityPInvokeMethodShouldNotBeCalled.vb │ │ │ ├── SelfAssignment.Latest.Partial.cs │ │ │ ├── SelfAssignment.Latest.cs │ │ │ ├── SelfAssignment.TopLevelStatements.cs │ │ │ ├── SelfAssignment.cs │ │ │ ├── SelfAssignment.vb │ │ │ ├── SerializationConstructorsShouldBeSecured.CSharp9.cs │ │ │ ├── SerializationConstructorsShouldBeSecured.cs │ │ │ ├── SerializationConstructorsShouldBeSecured_NoAssemblyAttribute.cs │ │ │ ├── SerializationConstructorsShouldBeSecured_Part1.cs │ │ │ ├── SerializationConstructorsShouldBeSecured_Part2.cs │ │ │ ├── SetLocaleForDataTypes.CSharp10.cs │ │ │ ├── SetLocaleForDataTypes.CSharp9.cs │ │ │ ├── SetLocaleForDataTypes.cs │ │ │ ├── SetPropertiesInsteadOfMethods.Latest.cs │ │ │ ├── SetPropertiesInsteadOfMethods.cs │ │ │ ├── SetPropertiesInsteadOfMethods.vb │ │ │ ├── ShiftDynamicNotInteger.CSharp11.cs │ │ │ ├── ShiftDynamicNotInteger.CSharp9.cs │ │ │ ├── ShiftDynamicNotInteger.cs │ │ │ ├── ShiftDynamicNotInteger.vb │ │ │ ├── ShiftDynamicNotInteger2.vb │ │ │ ├── ShouldImplementExportedInterfaces.CSharp9.cs │ │ │ ├── ShouldImplementExportedInterfaces.System.Composition.cs │ │ │ ├── ShouldImplementExportedInterfaces.cs │ │ │ ├── ShouldImplementExportedInterfaces.vb │ │ │ ├── ShouldImplementExportedInterfaces_Part1.cs │ │ │ ├── ShouldImplementExportedInterfaces_Part2.cs │ │ │ ├── SimpleDoLoop.vb │ │ │ ├── SingleStatementPerLine.CSharp9.cs │ │ │ ├── SingleStatementPerLine.cs │ │ │ ├── SingleStatementPerLine.vb │ │ │ ├── SingleStatementPerLine2.vb │ │ │ ├── SpecifyIFormatProviderOrCultureInfo.Latest.cs │ │ │ ├── SpecifyIFormatProviderOrCultureInfo.cs │ │ │ ├── SpecifyStringComparison.cs │ │ │ ├── SqlKeywordsDelimitedBySpace.CSharp10.FileScopedNamespaceDeclaration.cs │ │ │ ├── SqlKeywordsDelimitedBySpace.CSharp10.GlobalUsing.cs │ │ │ ├── SqlKeywordsDelimitedBySpace.CSharp10.GlobalUsingConsumer.cs │ │ │ ├── SqlKeywordsDelimitedBySpace.Latest.cs │ │ │ ├── SqlKeywordsDelimitedBySpace.cs │ │ │ ├── SqlKeywordsDelimitedBySpace_DefaultNamespace.cs │ │ │ ├── SqlKeywordsDelimitedBySpace_InsideNamespace.cs │ │ │ ├── StaticFieldInGenericClass.Latest.Partial.cs │ │ │ ├── StaticFieldInGenericClass.Latest.cs │ │ │ ├── StaticFieldInGenericClass.cs │ │ │ ├── StaticFieldInitializerOrder.Latest.Partial.cs │ │ │ ├── StaticFieldInitializerOrder.Latest.cs │ │ │ ├── StaticFieldInitializerOrder.Partial.cs │ │ │ ├── StaticFieldInitializerOrder.cs │ │ │ ├── StaticFieldVisible.Latest.cs │ │ │ ├── StaticFieldVisible.cs │ │ │ ├── StaticFieldWrittenFromInstanceConstructor.Latest.Partial.cs │ │ │ ├── StaticFieldWrittenFromInstanceConstructor.Latest.cs │ │ │ ├── StaticFieldWrittenFromInstanceConstructor.cs │ │ │ ├── StaticFieldWrittenFromInstanceMember.Latest.cs │ │ │ ├── StaticFieldWrittenFromInstanceMember.TopLevelStatements.cs │ │ │ ├── StaticFieldWrittenFromInstanceMember.cs │ │ │ ├── StaticSealedClassProtectedMembers.Latest.Partial.cs │ │ │ ├── StaticSealedClassProtectedMembers.Latest.cs │ │ │ ├── StaticSealedClassProtectedMembers.cs │ │ │ ├── StreamReadStatement.Latest.cs │ │ │ ├── StreamReadStatement.cs │ │ │ ├── StringConcatenationInLoop.Latest.cs │ │ │ ├── StringConcatenationInLoop.cs │ │ │ ├── StringConcatenationInLoop.vb │ │ │ ├── StringConcatenationWithPlus.Fixed.vb │ │ │ ├── StringConcatenationWithPlus.vb │ │ │ ├── StringFormatValidator.EdgeCases.cs │ │ │ ├── StringFormatValidator.Latest.cs │ │ │ ├── StringFormatValidator.RuntimeExceptionFree.CSharp11.cs │ │ │ ├── StringFormatValidator.RuntimeExceptionFree.cs │ │ │ ├── StringFormatValidator.TypoFree.CSharp11.cs │ │ │ ├── StringFormatValidator.TypoFree.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.Dapper.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.Dapper.vb │ │ │ ├── StringLiteralShouldNotBeDuplicated.Latest.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.Partial.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.WithTopLevelStatements.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated.vb │ │ │ ├── StringLiteralShouldNotBeDuplicated_Attributes.cs │ │ │ ├── StringLiteralShouldNotBeDuplicated_Attributes.vb │ │ │ ├── StringOffsetMethods.Latest.cs │ │ │ ├── StringOffsetMethods.cs │ │ │ ├── StringOperationWithoutCulture.CSharp10.cs │ │ │ ├── StringOperationWithoutCulture.CSharp11.cs │ │ │ ├── StringOperationWithoutCulture.cs │ │ │ ├── StringOrIntegralTypesForIndexers.Latest.Partial.cs │ │ │ ├── StringOrIntegralTypesForIndexers.Latest.cs │ │ │ ├── StringOrIntegralTypesForIndexers.cs │ │ │ ├── SuppressFinalizeUseless.CSharp9.cs │ │ │ ├── SuppressFinalizeUseless.Fixed.cs │ │ │ ├── SuppressFinalizeUseless.cs │ │ │ ├── SwaggerActionReturnType.cs │ │ │ ├── SwitchCaseFallsThroughToDefault.Fixed.cs │ │ │ ├── SwitchCaseFallsThroughToDefault.TopLevelStatements.cs │ │ │ ├── SwitchCaseFallsThroughToDefault.cs │ │ │ ├── SwitchCasesMinimumThree.cs │ │ │ ├── SwitchCasesMinimumThree.vb │ │ │ ├── SwitchDefaultClauseEmpty.Fixed.cs │ │ │ ├── SwitchDefaultClauseEmpty.cs │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements_CustomValue.cs │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements_CustomValue.vb │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements_DefaultValue.cs │ │ │ ├── SwitchSectionShouldNotHaveTooManyStatements_DefaultValue.vb │ │ │ ├── SwitchShouldNotBeNested.cs │ │ │ ├── SwitchShouldNotBeNested.vb │ │ │ ├── SwitchWithoutDefault.cs │ │ │ ├── SwitchWithoutDefault.vb │ │ │ ├── SyntaxWalker_InsufficientExecutionStackException.cs │ │ │ ├── SyntaxWalker_InsufficientExecutionStackException.vb │ │ │ ├── TabCharacter.cs │ │ │ ├── TabCharacter.vb │ │ │ ├── TaskConfigureAwait.NetCore.cs │ │ │ ├── TaskConfigureAwait.NetFx.cs │ │ │ ├── TestClassShouldHaveTestMethod.Latest.cs │ │ │ ├── TestClassShouldHaveTestMethod.MsTest.cs │ │ │ ├── TestClassShouldHaveTestMethod.NUnit.cs │ │ │ ├── TestClassShouldHaveTestMethod.NUnit3.cs │ │ │ ├── TestClassShouldHaveTestMethod.NUnit4.cs │ │ │ ├── TestMethodShouldContainAssertion.Custom.Common.cs │ │ │ ├── TestMethodShouldContainAssertion.Custom.V3.cs │ │ │ ├── TestMethodShouldContainAssertion.Latest.cs │ │ │ ├── TestMethodShouldContainAssertion.Moq.cs │ │ │ ├── TestMethodShouldContainAssertion.MsTest.AnotherFile.cs │ │ │ ├── TestMethodShouldContainAssertion.MsTest.Common.cs │ │ │ ├── TestMethodShouldContainAssertion.MsTest.Latest.cs │ │ │ ├── TestMethodShouldContainAssertion.MsTest.V3.cs │ │ │ ├── TestMethodShouldContainAssertion.NUnit.FsCheck.cs │ │ │ ├── TestMethodShouldContainAssertion.NUnit.cs │ │ │ ├── TestMethodShouldContainAssertion.NUnit4.cs │ │ │ ├── TestMethodShouldContainAssertion.SourceGenerators.cs │ │ │ ├── TestMethodShouldContainAssertion.XUnit.FsCheck.cs │ │ │ ├── TestMethodShouldContainAssertion.Xunit.Legacy.cs │ │ │ ├── TestMethodShouldContainAssertion.Xunit.cs │ │ │ ├── TestMethodShouldContainAssertion.XunitV3.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.Latest.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.Misc.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.MsTest.Legacy.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.MsTest.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.NUnit.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.Xunit.Legacy.cs │ │ │ ├── TestMethodShouldHaveCorrectSignature.Xunit.cs │ │ │ ├── TestMethodShouldNotBeIgnored.Latest.cs │ │ │ ├── TestMethodShouldNotBeIgnored.MsTest.cs │ │ │ ├── TestMethodShouldNotBeIgnored.NUnit.V2.cs │ │ │ ├── TestMethodShouldNotBeIgnored.NUnit.cs │ │ │ ├── TestMethodShouldNotBeIgnored.Xunit.cs │ │ │ ├── TestMethodShouldNotBeIgnored.Xunit.v1.cs │ │ │ ├── TestsShouldNotUseThreadSleep.cs │ │ │ ├── TestsShouldNotUseThreadSleep.vb │ │ │ ├── ThisShouldNotBeExposedFromConstructors.CSharp10.cs │ │ │ ├── ThisShouldNotBeExposedFromConstructors.CSharp9.cs │ │ │ ├── ThisShouldNotBeExposedFromConstructors.cs │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalled.cs │ │ │ ├── ThreadResumeOrSuspendShouldNotBeCalled.vb │ │ │ ├── ThreadStaticNonStaticField.Fixed.cs │ │ │ ├── ThreadStaticNonStaticField.Latest.Fixed.cs │ │ │ ├── ThreadStaticNonStaticField.Latest.cs │ │ │ ├── ThreadStaticNonStaticField.cs │ │ │ ├── ThreadStaticWithInitializer.Latest.cs │ │ │ ├── ThreadStaticWithInitializer.cs │ │ │ ├── ThrowReservedExceptions.Latest.cs │ │ │ ├── ThrowReservedExceptions.cs │ │ │ ├── ThrowReservedExceptions.vb │ │ │ ├── ToStringShouldNotReturnNull.Latest.cs │ │ │ ├── ToStringShouldNotReturnNull.TopLevelStatements.cs │ │ │ ├── ToStringShouldNotReturnNull.cs │ │ │ ├── ToStringShouldNotReturnNull.vb │ │ │ ├── TooManyGenericParameters.CustomValues.cs │ │ │ ├── TooManyGenericParameters.DefaultValues.cs │ │ │ ├── TooManyGenericParameters.Latest.cs │ │ │ ├── TooManyGenericParameters.TopLevelStatements.cs │ │ │ ├── TooManyLabelsInSwitch.Latest.cs │ │ │ ├── TooManyLabelsInSwitch.cs │ │ │ ├── TooManyLabelsInSwitch.vb │ │ │ ├── TooManyLoggingCalls.Castle.Core.Logging.cs │ │ │ ├── TooManyLoggingCalls.Log4Net.cs │ │ │ ├── TooManyLoggingCalls.Microsoft.Extensions.Logging.cs │ │ │ ├── TooManyLoggingCalls.NLog.cs │ │ │ ├── TooManyLoggingCalls.Serilog.cs │ │ │ ├── TooManyLoggingCalls.cs │ │ │ ├── TooManyParameters_CustomValues.Latest.cs │ │ │ ├── TooManyParameters_CustomValues.TopLevelStatements.cs │ │ │ ├── TooManyParameters_CustomValues.cs │ │ │ ├── TooManyParameters_CustomValues.vb │ │ │ ├── TooManyParameters_DefaultValues.cs │ │ │ ├── TooManyParameters_DefaultValues.vb │ │ │ ├── TrackNotImplementedException.CSharp11.cs │ │ │ ├── TrackNotImplementedException.cs │ │ │ ├── TryStatementsWithIdenticalCatchShouldBeMerged.cs │ │ │ ├── TypeExaminationOnSystemType.TopLevelStatements.cs │ │ │ ├── TypeExaminationOnSystemType.cs │ │ │ ├── TypeMemberVisibility.Latest.Partial.cs │ │ │ ├── TypeMemberVisibility.Latest.cs │ │ │ ├── TypeMemberVisibility.cs │ │ │ ├── TypeNamesShouldNotMatchNamespaces.CSharp10.cs │ │ │ ├── TypeNamesShouldNotMatchNamespaces.CSharp9.cs │ │ │ ├── TypeNamesShouldNotMatchNamespaces.cs │ │ │ ├── TypeParameterName.vb │ │ │ ├── TypeParameterName_CustomRegex.vb │ │ │ ├── TypesShouldNotExtendOutdatedBaseTypes.cs │ │ │ ├── UnaryPrefixOperatorRepeated.Fixed.cs │ │ │ ├── UnaryPrefixOperatorRepeated.Latest.cs │ │ │ ├── UnaryPrefixOperatorRepeated.TopLevelStatements.cs │ │ │ ├── UnaryPrefixOperatorRepeated.cs │ │ │ ├── UnaryPrefixOperatorRepeated.vb │ │ │ ├── UnchangedLocalVariablesShouldBeConst.Fixed.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConst.Latest.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConst.ToFix.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConst.TopLevelStatements.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConst.cs │ │ │ ├── UnchangedLocalVariablesShouldBeConst.cshtml.ide.g.cs │ │ │ ├── UnconditionalJumpStatement.Latest.cs │ │ │ ├── UnconditionalJumpStatement.cs │ │ │ ├── UnconditionalJumpStatement.vb │ │ │ ├── UninvokedEventDeclaration.Latest.Partial.cs │ │ │ ├── UninvokedEventDeclaration.Latest.cs │ │ │ ├── UninvokedEventDeclaration.cs │ │ │ ├── UnnecessaryBitwiseOperation.Fixed.cs │ │ │ ├── UnnecessaryBitwiseOperation.Fixed.vb │ │ │ ├── UnnecessaryBitwiseOperation.Latest.cs │ │ │ ├── UnnecessaryBitwiseOperation.TopLevelStatements.cs │ │ │ ├── UnnecessaryBitwiseOperation.cs │ │ │ ├── UnnecessaryBitwiseOperation.vb │ │ │ ├── UnnecessaryMathematicalComparison.Latest.cs │ │ │ ├── UnnecessaryMathematicalComparison.cs │ │ │ ├── UnnecessaryUsings.CSharp10.Consumer.cs │ │ │ ├── UnnecessaryUsings.CSharp10.FileScopedNamespace.Fixed.cs │ │ │ ├── UnnecessaryUsings.CSharp10.FileScopedNamespace.cs │ │ │ ├── UnnecessaryUsings.CSharp10.Global.cs │ │ │ ├── UnnecessaryUsings.CSharp12.cs │ │ │ ├── UnnecessaryUsings.CSharp9.cs │ │ │ ├── UnnecessaryUsings.Fixed.Batch.cs │ │ │ ├── UnnecessaryUsings.Fixed.cs │ │ │ ├── UnnecessaryUsings.InheritedPropertyBase.cs │ │ │ ├── UnnecessaryUsings.InheritedPropertyChild.cs │ │ │ ├── UnnecessaryUsings.InheritedPropertyUsage.cs │ │ │ ├── UnnecessaryUsings.TupleDeconstruct.NetCore.cs │ │ │ ├── UnnecessaryUsings.TupleDeconstruct.NetFx.cs │ │ │ ├── UnnecessaryUsings.cs │ │ │ ├── UnnecessaryUsings2.cs │ │ │ ├── UnnecessaryUsingsFNRepro.cs │ │ │ ├── UnsignedTypesUsage.vb │ │ │ ├── UnusedPrivateMember.CalledFromGenerated.cs │ │ │ ├── UnusedPrivateMember.Fixed.Batch.cs │ │ │ ├── UnusedPrivateMember.Fixed.cs │ │ │ ├── UnusedPrivateMember.Generated.cs │ │ │ ├── UnusedPrivateMember.Latest.Partial.cs │ │ │ ├── UnusedPrivateMember.Latest.cs │ │ │ ├── UnusedPrivateMember.Performance.cs │ │ │ ├── UnusedPrivateMember.TopLevelStatements.cs │ │ │ ├── UnusedPrivateMember.cs │ │ │ ├── UnusedPrivateMember.part1.cs │ │ │ ├── UnusedPrivateMember.part2.cs │ │ │ ├── UnusedReturnValue.Latest.cs │ │ │ ├── UnusedReturnValue.Partial.cs │ │ │ ├── UnusedReturnValue.TopLevelStatements.cs │ │ │ ├── UnusedReturnValue.cs │ │ │ ├── UnusedStringBuilder.Latest.cs │ │ │ ├── UnusedStringBuilder.cs │ │ │ ├── UnusedStringBuilder.vb │ │ │ ├── UriShouldNotBeHardcoded.AspNet.cs │ │ │ ├── UriShouldNotBeHardcoded.AspNetCore.cs │ │ │ ├── UriShouldNotBeHardcoded.Exceptions.cs │ │ │ ├── UriShouldNotBeHardcoded.Latest.cs │ │ │ ├── UriShouldNotBeHardcoded.cs │ │ │ ├── UriShouldNotBeHardcoded.vb │ │ │ ├── UseAwaitableMethod.Latest.cs │ │ │ ├── UseAwaitableMethod.Moq.cs │ │ │ ├── UseAwaitableMethod.TopLevelStatements.cs │ │ │ ├── UseAwaitableMethod.cs │ │ │ ├── UseAwaitableMethod_DbDataReader.cs │ │ │ ├── UseAwaitableMethod_EF.cs │ │ │ ├── UseAwaitableMethod_FluentValidation.cs │ │ │ ├── UseAwaitableMethod_MongoDBDriver.cs │ │ │ ├── UseAwaitableMethod_Sockets.cs │ │ │ ├── UseCharOverloadOfStringMethods.Fixed.cs │ │ │ ├── UseCharOverloadOfStringMethods.Framework.cs │ │ │ ├── UseCharOverloadOfStringMethods.Framework.vb │ │ │ ├── UseCharOverloadOfStringMethods.Latest.cs │ │ │ ├── UseCharOverloadOfStringMethods.ToFix.cs │ │ │ ├── UseCharOverloadOfStringMethods.cs │ │ │ ├── UseCharOverloadOfStringMethods.vb │ │ │ ├── UseConstantLoggingTemplate.cs │ │ │ ├── UseConstantsWhereAppropriate.CSharp11.cs │ │ │ ├── UseConstantsWhereAppropriate.cs │ │ │ ├── UseCurlyBraces.CSharp7.cs │ │ │ ├── UseCurlyBraces.cs │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.CSharp9.cs │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.Net.vb │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.cs │ │ │ ├── UseDateTimeOffsetInsteadOfDateTime.vb │ │ │ ├── UseFindSystemTimeZoneById.Net.cs │ │ │ ├── UseFindSystemTimeZoneById.Net.vb │ │ │ ├── UseFindSystemTimeZoneById.cs │ │ │ ├── UseFindSystemTimeZoneById.vb │ │ │ ├── UseGenericEventHandlerInstances.CSharp11.cs │ │ │ ├── UseGenericEventHandlerInstances.CSharp9.cs │ │ │ ├── UseGenericEventHandlerInstances.cs │ │ │ ├── UseGenericWithRefParameters.cs │ │ │ ├── UseIFormatProviderForParsingDateAndTime.cs │ │ │ ├── UseIFormatProviderForParsingDateAndTime.vb │ │ │ ├── UseIndexingInsteadOfLinqMethods.cs │ │ │ ├── UseIndexingInsteadOfLinqMethods.vb │ │ │ ├── UseLambdaParameterInConcurrentDictionary.CSharp8.cs │ │ │ ├── UseLambdaParameterInConcurrentDictionary.vb │ │ │ ├── UseNumericLiteralSeparator.CSharp9.cs │ │ │ ├── UseNumericLiteralSeparator.cs │ │ │ ├── UseParamsForVariableArguments.Latest.Partial.cs │ │ │ ├── UseParamsForVariableArguments.Latest.cs │ │ │ ├── UseParamsForVariableArguments.cs │ │ │ ├── UsePascalCaseForNamedPlaceHolders.Latest.cs │ │ │ ├── UsePascalCaseForNamedPlaceHolders.cs │ │ │ ├── UseReturnStatement.vb │ │ │ ├── UseShortCircuitingOperator.Fixed.cs │ │ │ ├── UseShortCircuitingOperator.Fixed.vb │ │ │ ├── UseShortCircuitingOperator.Latest.cs │ │ │ ├── UseShortCircuitingOperator.TopLevelStatements.Fixed.cs │ │ │ ├── UseShortCircuitingOperator.TopLevelStatements.cs │ │ │ ├── UseShortCircuitingOperator.cs │ │ │ ├── UseShortCircuitingOperator.vb │ │ │ ├── UseStringCreate.CSharp10.cs │ │ │ ├── UseStringCreate.cs │ │ │ ├── UseStringIsNullOrEmpty.CSharp10.cs │ │ │ ├── UseStringIsNullOrEmpty.CSharp11.cs │ │ │ ├── UseStringIsNullOrEmpty.cs │ │ │ ├── UseTestableTimeProvider.cs │ │ │ ├── UseTestableTimeProvider.vb │ │ │ ├── UseTrueForAll.Immutable.cs │ │ │ ├── UseTrueForAll.cs │ │ │ ├── UseTrueForAll.vb │ │ │ ├── UseUnixEpoch.CSharp9.Fixed.cs │ │ │ ├── UseUnixEpoch.CSharp9.cs │ │ │ ├── UseUnixEpoch.Fixed.cs │ │ │ ├── UseUnixEpoch.Fixed.vb │ │ │ ├── UseUnixEpoch.Framework.cs │ │ │ ├── UseUnixEpoch.Framework.vb │ │ │ ├── UseUnixEpoch.cs │ │ │ ├── UseUnixEpoch.vb │ │ │ ├── UseUriInsteadOfString.Latest.Partial.cs │ │ │ ├── UseUriInsteadOfString.Latest.cs │ │ │ ├── UseUriInsteadOfString.TopLevelStatements.cs │ │ │ ├── UseUriInsteadOfString.cs │ │ │ ├── UseValueParameter.Latest.Partial.cs │ │ │ ├── UseValueParameter.Latest.cs │ │ │ ├── UseValueParameter.cs │ │ │ ├── UseWhereBeforeOrderBy.cs │ │ │ ├── UseWhereBeforeOrderBy.vb │ │ │ ├── UseWhileLoopInstead.cs │ │ │ ├── UseWithStatement.vb │ │ │ ├── Utilities/ │ │ │ │ ├── CopyPasteTokenAnalyzer/ │ │ │ │ │ ├── Duplicated.Csharp10.cs │ │ │ │ │ ├── Duplicated.cs │ │ │ │ │ ├── DuplicatedDifferentLiterals.cs │ │ │ │ │ ├── Razor.cshtml │ │ │ │ │ ├── Razor.razor │ │ │ │ │ ├── Unique.CSharp11.cs │ │ │ │ │ ├── Unique.CSharp12.cs │ │ │ │ │ ├── Unique.cs │ │ │ │ │ └── Unique.vb │ │ │ │ ├── FileMetadataAnalyzer/ │ │ │ │ │ ├── Razor.cshtml │ │ │ │ │ ├── Razor.razor │ │ │ │ │ ├── TEMPORARYGENERATEDFILE_class.cs │ │ │ │ │ ├── autogenerated_comment.cs │ │ │ │ │ ├── autogenerated_comment2.cs │ │ │ │ │ ├── class.designer.cs │ │ │ │ │ ├── class.g.cs │ │ │ │ │ ├── class.g.something.cs │ │ │ │ │ ├── class.generated.cs │ │ │ │ │ ├── class_generated.cs │ │ │ │ │ ├── compiler_generated.cs │ │ │ │ │ ├── compiler_generated_attr.cs │ │ │ │ │ ├── debugger_non_user_code.cs │ │ │ │ │ ├── debugger_non_user_code_attr.cs │ │ │ │ │ ├── generated_code_attr.cs │ │ │ │ │ ├── generated_code_attr2.cs │ │ │ │ │ ├── generated_code_attr_local_function.cs │ │ │ │ │ ├── generated_region.cs │ │ │ │ │ ├── generated_region_2.cs │ │ │ │ │ └── normal_file.cs │ │ │ │ ├── LogAnalyzer/ │ │ │ │ │ ├── GeneratedByContent.cs │ │ │ │ │ ├── GeneratedByContent.vb │ │ │ │ │ ├── GeneratedByName.generated.cs │ │ │ │ │ ├── GeneratedByName.generated.vb │ │ │ │ │ ├── Generated_cshtml.g.cs │ │ │ │ │ ├── Generated_razor.g.cs │ │ │ │ │ ├── Normal.cs │ │ │ │ │ ├── Normal.vb │ │ │ │ │ ├── Second.cs │ │ │ │ │ └── Second.vb │ │ │ │ ├── MethodDeclarationsAnalyzer/ │ │ │ │ │ ├── TestMethodDeclarations.Generated.g.cs │ │ │ │ │ ├── TestMethodDeclarations.GlobalNamespace.cs │ │ │ │ │ ├── TestMethodDeclarations.GlobalNamespace.vb │ │ │ │ │ ├── TestMethodDeclarations.NoMethods.cs │ │ │ │ │ ├── TestMethodDeclarations.NoMethods.vb │ │ │ │ │ ├── TestMethodDeclarations.Partial.cs │ │ │ │ │ ├── TestMethodDeclarations.Partial.vb │ │ │ │ │ ├── TestMethodDeclarations.cs │ │ │ │ │ └── TestMethodDeclarations.vb │ │ │ │ ├── MetricsAnalyzer/ │ │ │ │ │ ├── AllMetrics.cs │ │ │ │ │ ├── Component.razor │ │ │ │ │ ├── Metrics.Latest.cs │ │ │ │ │ ├── Razor.cshtml │ │ │ │ │ ├── Razor.razor │ │ │ │ │ └── _Imports.razor │ │ │ │ ├── SymbolReferenceAnalyzer/ │ │ │ │ │ ├── Event.cs │ │ │ │ │ ├── Event.vb │ │ │ │ │ ├── ExtensionKeyword.cs │ │ │ │ │ ├── Field.EscapedNonKeyword.cs │ │ │ │ │ ├── Field.EscapedNonKeyword.vb │ │ │ │ │ ├── Field.EscapedSequences.cs │ │ │ │ │ ├── Field.ReservedKeyword.cs │ │ │ │ │ ├── Field.ReservedKeyword.vb │ │ │ │ │ ├── Field.cs │ │ │ │ │ ├── Field.vb │ │ │ │ │ ├── LocalFunction.cs │ │ │ │ │ ├── Method.cs │ │ │ │ │ ├── Method.vb │ │ │ │ │ ├── Method_Partial1.cs │ │ │ │ │ ├── Method_Partial2.cs │ │ │ │ │ ├── MissingDeclaration.cs │ │ │ │ │ ├── NamedType.ReservedKeyword.cs │ │ │ │ │ ├── NamedType.ReservedKeyword.vb │ │ │ │ │ ├── NamedType.cs │ │ │ │ │ ├── NamedType.vb │ │ │ │ │ ├── Parameter.ReservedKeyword.cs │ │ │ │ │ ├── Parameter.ReservedKeyword.vb │ │ │ │ │ ├── Parameter.cs │ │ │ │ │ ├── Parameter.vb │ │ │ │ │ ├── PartialConstructor.Partial.cs │ │ │ │ │ ├── PartialConstructor.cs │ │ │ │ │ ├── PartialEvent.Partial.cs │ │ │ │ │ ├── PartialEvent.cs │ │ │ │ │ ├── PrimaryConstructor.cs │ │ │ │ │ ├── Property.FieldKeyword.cs │ │ │ │ │ ├── Property.cs │ │ │ │ │ ├── Property.vb │ │ │ │ │ ├── Property_Partial1.cs │ │ │ │ │ ├── Property_Partial2.cs │ │ │ │ │ ├── Razor.cshtml │ │ │ │ │ ├── Razor.razor │ │ │ │ │ ├── Razor.razor.cs │ │ │ │ │ ├── RazorComponent.razor │ │ │ │ │ ├── Setter.cs │ │ │ │ │ ├── ToDo.cs │ │ │ │ │ ├── TokenThreshold.cs │ │ │ │ │ ├── Tuples.cs │ │ │ │ │ ├── Tuples.vb │ │ │ │ │ ├── TypeParameter.cs │ │ │ │ │ └── TypeParameter.vb │ │ │ │ └── TokenTypeAnalyzer/ │ │ │ │ ├── IdentifierTokenThreshold.cs │ │ │ │ ├── Identifiers.cs │ │ │ │ ├── Identifiers.vb │ │ │ │ ├── Razor.cshtml │ │ │ │ ├── Razor.razor │ │ │ │ ├── Tokens.Latest.cs │ │ │ │ ├── Tokens.cs │ │ │ │ ├── Tokens.vb │ │ │ │ ├── Trivia.cs │ │ │ │ └── Trivia.vb │ │ │ ├── ValueTypeShouldImplementIEquatable.CSharp10.cs │ │ │ ├── ValueTypeShouldImplementIEquatable.cs │ │ │ ├── ValueTypeShouldImplementIEquatable.vb │ │ │ ├── ValuesUselesslyIncremented.Latest.cs │ │ │ ├── ValuesUselesslyIncremented.TopLevelStatements.cs │ │ │ ├── ValuesUselesslyIncremented.cs │ │ │ ├── VariableShadowsField.Latest.Partial.cs │ │ │ ├── VariableShadowsField.Latest.cs │ │ │ ├── VariableShadowsField.TopLevelStatements.cs │ │ │ ├── VariableShadowsField.cs │ │ │ ├── VariableUnused.Latest.cs │ │ │ ├── VariableUnused.TopLevelStatements.cs │ │ │ ├── VariableUnused.cs │ │ │ ├── VariableUnused.vb │ │ │ ├── VirtualEventField.Fixed.cs │ │ │ ├── VirtualEventField.Latest.Fixed.cs │ │ │ ├── VirtualEventField.Latest.Partial.cs │ │ │ ├── VirtualEventField.Latest.cs │ │ │ ├── VirtualEventField.cs │ │ │ ├── WcfMissingContractAttribute.cs │ │ │ ├── WcfNonVoidOneWay.cs │ │ │ ├── WcfNonVoidOneWay.vb │ │ │ ├── WeakSslTlsProtocols.CSharp12.cs │ │ │ ├── WeakSslTlsProtocols.cs │ │ │ ├── WeakSslTlsProtocols.vb │ │ │ ├── WebConfig/ │ │ │ │ ├── CookieShouldBeHttpOnly/ │ │ │ │ │ ├── ConfigWithoutAttribute/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── Formatting/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── HttpOnlyCookiesConfig/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── NonHttpOnlyCookiesConfig/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── UnrelatedConfig/ │ │ │ │ │ └── Web.config │ │ │ │ ├── CookieShouldBeSecure/ │ │ │ │ │ ├── ConfigWithoutAttribute/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── Formatting/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── NonSecureCookieConfig/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── SecureCookieConfig/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── UnrelatedConfig/ │ │ │ │ │ └── Web.config │ │ │ │ ├── DatabasePasswordsShouldBeSecure/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── ExternalConfig/ │ │ │ │ │ │ ├── Web.config │ │ │ │ │ │ └── external.config │ │ │ │ │ ├── Formatting/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── Values/ │ │ │ │ │ └── Web.config │ │ │ │ ├── DisablingRequestValidation/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── EdgeValues/ │ │ │ │ │ │ ├── 3.9/ │ │ │ │ │ │ │ └── Web.config │ │ │ │ │ │ ├── 5.6/ │ │ │ │ │ │ │ └── Web.config │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── Formatting/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── LowerCase/ │ │ │ │ │ │ └── web.config │ │ │ │ │ ├── MultipleFiles/ │ │ │ │ │ │ ├── SubFolder/ │ │ │ │ │ │ │ └── Web.config │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── TransformCustom/ │ │ │ │ │ │ └── Web.Custom.config │ │ │ │ │ ├── TransformDebug/ │ │ │ │ │ │ └── Web.Debug.config │ │ │ │ │ ├── TransformRelease/ │ │ │ │ │ │ └── Web.Release.config │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── Values/ │ │ │ │ │ └── Web.config │ │ │ │ ├── DoNotHardcodeCredentials/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── Valid/ │ │ │ │ │ ├── Web.Custom.config │ │ │ │ │ ├── Web.Debug.config │ │ │ │ │ ├── Web.Release.config │ │ │ │ │ └── Web.config │ │ │ │ ├── DoNotHardcodeSecrets/ │ │ │ │ │ ├── Corrupt/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ │ └── Web.config │ │ │ │ │ └── Valid/ │ │ │ │ │ ├── Web.Custom.config │ │ │ │ │ ├── Web.Debug.config │ │ │ │ │ ├── Web.Release.config │ │ │ │ │ └── Web.config │ │ │ │ └── RequestsWithExcessiveLength/ │ │ │ │ ├── Corrupt/ │ │ │ │ │ └── Web.config │ │ │ │ ├── UnexpectedContent/ │ │ │ │ │ └── Web.config │ │ │ │ └── Values/ │ │ │ │ ├── ContentLength/ │ │ │ │ │ └── Web.config │ │ │ │ ├── ContentLength_Compliant/ │ │ │ │ │ └── Web.config │ │ │ │ ├── CornerCases/ │ │ │ │ │ └── Web.config │ │ │ │ ├── DefaultSettings/ │ │ │ │ │ └── Web.config │ │ │ │ ├── EmptySystemWeb/ │ │ │ │ │ └── Web.config │ │ │ │ ├── EmptySystemWebServer/ │ │ │ │ │ └── Web.config │ │ │ │ ├── InvalidConfig/ │ │ │ │ │ └── Web.config │ │ │ │ ├── NoSystemWeb/ │ │ │ │ │ └── Web.config │ │ │ │ ├── NoSystemWebServer/ │ │ │ │ │ └── Web.config │ │ │ │ ├── RequestAndContentLength/ │ │ │ │ │ └── Web.config │ │ │ │ ├── RequestLength/ │ │ │ │ │ └── Web.config │ │ │ │ ├── SmallValues/ │ │ │ │ │ └── Web.config │ │ │ │ └── ValidValues/ │ │ │ │ └── Web.config │ │ │ ├── XMLSignatureCheck.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_AlwaysSafe.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XPathDocument_CSharp9.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XPathDocument_Net35.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XPathDocument_Net4.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XPathDocument_Net452.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp10.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp9.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument_Net35.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument_Net4.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlDocument_UnknownFrameworkVersion.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_CSharp9.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_ExternalParameter.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_Net35.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_Net4.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_Net452.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlReader_ParameterProvider.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlTextReader.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp10.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp9.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlTextReader_Net35.cs │ │ │ ├── XmlExternalEntityShouldNotBeParsed_XmlTextReader_Net4.cs │ │ │ └── XmlExternalEntityShouldNotBeParsed_XmlTextReader_UnknownFrameworkVersion.cs │ │ ├── TestResources/ │ │ │ ├── ProjectOutFolderPath.txt │ │ │ ├── SonarLintXml/ │ │ │ │ ├── All_properties_cs/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ ├── All_properties_vbnet/ │ │ │ │ │ └── SonarLint.xml │ │ │ │ └── Invalid_Xml/ │ │ │ │ └── SonarLint.xml │ │ │ └── SonarProjectConfig/ │ │ │ ├── Path_Unix/ │ │ │ │ └── SonarProjectConfig.xml │ │ │ └── Path_Windows/ │ │ │ └── SonarProjectConfig.xml │ │ ├── TestSuiteInitialization.cs │ │ ├── Trackers/ │ │ │ ├── ArgumentTrackerTest.cs │ │ │ ├── BaseTypeTrackerTest.cs │ │ │ ├── BuilderPatternConditionTest.cs │ │ │ ├── BuilderPatternDescriptorTest.cs │ │ │ ├── ConstantValueFinderTest.cs │ │ │ ├── ElementAccessTrackerTest.cs │ │ │ ├── InvocationTrackerTest.cs │ │ │ ├── MemberDescriptorTest.cs │ │ │ ├── MethodDeclarationTrackerTest.cs │ │ │ ├── ObjectCreationTrackerTest.cs │ │ │ └── PropertyAccessTrackerTest.cs │ │ ├── Wrappers/ │ │ │ ├── INamedTypeSymbolExtensionsTests.cs │ │ │ ├── IOperationWrapperSonarTest.cs │ │ │ ├── IPropertySymbolExtensionTest.cs │ │ │ ├── ISymbolNullableExtensionsTest.cs │ │ │ ├── RegisterSymbolStartActionWrapperTest.cs │ │ │ └── TypeInfoExtensionsTest.cs │ │ └── packages.lock.json │ ├── SonarAnalyzer.TestFramework/ │ │ ├── Analyzers/ │ │ │ ├── DummyAnalyzer.cs │ │ │ ├── DummyAnalyzerWithLocation.cs │ │ │ ├── DummyCodeFix.cs │ │ │ ├── DummyUtilityAnalyzer.cs │ │ │ ├── TestAnalyzer.cs │ │ │ └── TestGeneratedCodeRecognizer.cs │ │ ├── Build/ │ │ │ ├── LanguageOptions.cs │ │ │ ├── ProjectBuilder.cs │ │ │ ├── SnippetCompiler.cs │ │ │ └── SolutionBuilder.cs │ │ ├── Common/ │ │ │ ├── AnalysisScaffolding.cs │ │ │ ├── AssertIgnoreScope.cs │ │ │ ├── CurrentCultureScope.cs │ │ │ ├── EditorConfigGenerator.cs │ │ │ ├── EnvironmentVariableScope.cs │ │ │ ├── FixAllDiagnosticProvider.cs │ │ │ ├── LogTester.cs │ │ │ ├── Paths.cs │ │ │ ├── SdkPathProvider.cs │ │ │ ├── TestCompiler.cs │ │ │ ├── TestConstants.cs │ │ │ ├── TestEnvironment.cs │ │ │ └── TestFiles.cs │ │ ├── Extensions/ │ │ │ ├── CompilationExtensions.cs │ │ │ ├── DiagnosticDescriptorExtensions.cs │ │ │ ├── StringAssertionsExtensions.cs │ │ │ ├── SyntaxTreeExtensions.cs │ │ │ └── TypeExtensions.cs │ │ ├── MetadataReferences/ │ │ │ ├── AspNetCoreMetadataReference.cs │ │ │ ├── CoreMetadataReference.cs │ │ │ ├── FrameworkMetadataReference.cs │ │ │ ├── MetadataReferenceFacade.cs │ │ │ ├── MetadataReferenceFactory.cs │ │ │ ├── NuGetMetadataFactory.Package.cs │ │ │ ├── NuGetMetadataFactory.cs │ │ │ ├── NuGetMetadataReference.cs │ │ │ ├── NugetPackageVersions.cs │ │ │ └── WindowsDesktopMetadataReference.cs │ │ ├── Packaging/ │ │ │ ├── RuleTypeMappingCS.cs │ │ │ └── RuleTypeMappingVB.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── SonarAnalyzer.TestFramework.csproj │ │ ├── Verification/ │ │ │ ├── CodeFixVerifier.cs │ │ │ ├── DiagnosticVerifier.cs │ │ │ ├── DiagnosticVerifierException.cs │ │ │ ├── IssueValidation/ │ │ │ │ ├── CompilationIssues.cs │ │ │ │ ├── FileContent.cs │ │ │ │ ├── IssueLocation.cs │ │ │ │ ├── IssueLocationCollector.cs │ │ │ │ ├── IssueLocationPair.cs │ │ │ │ └── VerificationMessage.cs │ │ │ ├── SuppressionHandler.cs │ │ │ ├── Verifier.cs │ │ │ └── VerifierBuilder.cs │ │ └── packages.lock.json │ ├── SonarAnalyzer.TestFramework.Test/ │ │ ├── Analyzers/ │ │ │ └── TestGeneratedCodeRecognizerTest.cs │ │ ├── Build/ │ │ │ ├── LanguageOptionsTest.cs │ │ │ ├── ProjectBuilderTest.cs │ │ │ └── SnippetCompilerTest.cs │ │ ├── Common/ │ │ │ ├── EditorConfigGeneratorTest.cs │ │ │ ├── EnvironmentVariableScopeTest.cs │ │ │ ├── LogTesterTest.cs │ │ │ ├── SdkPathProviderTest.cs │ │ │ ├── TestCompilerTest.cs │ │ │ └── TestFilesTest.cs │ │ ├── Extensions/ │ │ │ └── CompilationExtensionsTest.cs │ │ ├── MetadataReferences/ │ │ │ ├── NuGetMetadataFactoryTest.cs │ │ │ └── NugetPackageVersionsTest.cs │ │ ├── SonarAnalyzer.TestFramework.Test.csproj │ │ ├── TestCases/ │ │ │ ├── DiagnosticVerifierException.Concurrent.cs │ │ │ ├── DiagnosticVerifierException.File1.cs │ │ │ ├── DiagnosticVerifierException.File2.cs │ │ │ ├── DiagnosticsVerifier/ │ │ │ │ ├── ExpectedIssuesNotRaised.cs │ │ │ │ └── ExpectedIssuesNotRaised2.cs │ │ │ ├── Dummy.SecondaryLocation.CSharp10.razor │ │ │ ├── Dummy.SecondaryLocation.cshtml │ │ │ ├── Dummy.SecondaryLocation.razor │ │ │ ├── Dummy.cshtml │ │ │ ├── Dummy.razor │ │ │ ├── DummyExpressions.CSharp10.razor │ │ │ ├── DummyExpressions.cshtml │ │ │ ├── DummyExpressions.razor │ │ │ ├── ProjectBuilder.AddDocument.cs │ │ │ ├── ProjectBuilder.AddDocument.cshtml │ │ │ ├── ProjectBuilder.AddDocument.razor │ │ │ ├── ProjectBuilder.AddDocument.vb │ │ │ ├── ProjectBuilder.AddDocument.vbhtml │ │ │ ├── Verifier/ │ │ │ │ └── Verifier.BasePath.cs │ │ │ ├── Verifier.BasePathAssertFails.cs │ │ │ └── VerifyCodeFix.Empty.cs │ │ ├── Verification/ │ │ │ ├── CodeFixProviderTest.cs │ │ │ ├── DiagnosticVerifierExceptionTest.cs │ │ │ ├── DiagnosticVerifierTest.cs │ │ │ ├── IssueValidation/ │ │ │ │ ├── CompilationIssuesTest.cs │ │ │ │ ├── IssueLocationCollectorTest.ExpectedIssueLocations.cs │ │ │ │ ├── IssueLocationCollectorTest.FindIssueLocations.cs │ │ │ │ ├── IssueLocationCollectorTest.FindPreciseIssueLocations.cs │ │ │ │ ├── IssueLocationCollectorTest.MergeLocations.cs │ │ │ │ ├── IssueLocationCollectorTest.cs │ │ │ │ ├── IssueLocationPairTest.cs │ │ │ │ └── IssueLocationTest.cs │ │ │ ├── VerifierBuilderTest.cs │ │ │ └── VerifierTest.cs │ │ └── packages.lock.json │ └── SonarAnalyzer.VisualBasic.Core.Test/ │ ├── Extensions/ │ │ └── ISymbolExtensionsTest.cs │ ├── Facade/ │ │ ├── Implementation/ │ │ │ └── VisualBasicSyntaxFacadeTest.cs │ │ └── VisualBasicFacadeTest.cs │ ├── SonarAnalyzer.VisualBasic.Core.Test.csproj │ ├── Syntax/ │ │ ├── Extensions/ │ │ │ ├── ExpressionSyntaxExtensionsTest.cs │ │ │ ├── InterpolatedStringExpressionSyntaxExtensionsTest.cs │ │ │ ├── InvocationExpressionSyntaxExtensionsTest.cs │ │ │ ├── ObjectCreationExpressionSyntaxExtensionsTest.cs │ │ │ ├── SyntaxNodeExtensionsVisualBasicTest.cs │ │ │ └── SyntaxTokenExtensionsTest.cs │ │ └── Utilities/ │ │ └── SafeVisualBasicSyntaxWalkerTest.cs │ ├── Trackers/ │ │ └── FieldAccessTrackerTest.cs │ └── packages.lock.json ├── azure-pipelines.yml ├── docs/ │ ├── code-of-conduct.md │ ├── coding-style.md │ ├── contributing-analyzer.md │ ├── contributing-plugin.md │ ├── issues.md │ ├── regenerate-lock-files.md │ └── verifier-syntax.md ├── global.json ├── pom.xml ├── scripts/ │ ├── build/ │ │ ├── build-utils.ps1 │ │ └── store-azp-variables.ps1 │ ├── rspec/ │ │ ├── CopyTestCasesFromRspec.ps1 │ │ ├── README.md │ │ ├── rspec-templates/ │ │ │ ├── Rule.Base.cs │ │ │ ├── Rule.CS.cs │ │ │ ├── Rule.VB.cs │ │ │ ├── Test.CS.cs │ │ │ ├── Test.VB.cs │ │ │ ├── TestCase.CS.cs │ │ │ ├── TestCase.VB.vb │ │ │ └── TestMethod.VB.cs │ │ ├── rspec.ps1 │ │ └── tests/ │ │ └── CopyTestCasesFromRspec.Tests.ps1 │ ├── set-version.ps1 │ └── utils.ps1 ├── sonar-csharp-core/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ ├── sonar/ │ │ │ └── plugins/ │ │ │ └── csharpenterprise/ │ │ │ └── api/ │ │ │ ├── ProfileRegistrar.java │ │ │ └── package-info.java │ │ └── sonarsource/ │ │ └── csharp/ │ │ └── core/ │ │ ├── CSharpCoreExtensions.java │ │ ├── CSharpCorePluginMetadata.java │ │ ├── CSharpFileCacheSensor.java │ │ ├── CSharpLanguageConfiguration.java │ │ ├── CSharpPropertyDefinitions.java │ │ ├── CSharpSonarWayProfile.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── sonarsource/ │ │ └── csharp/ │ │ └── core/ │ │ ├── CSharpCoreExtensionsTest.java │ │ ├── CSharpCorePluginMetadataTest.java │ │ ├── CSharpFileCacheSensorTest.java │ │ ├── CSharpLanguageConfigurationTest.java │ │ ├── CSharpPropertyDefinitionsTest.java │ │ ├── CSharpSonarWayProfileTest.java │ │ ├── CSharpTest.java │ │ └── TestCSharpMetadata.java │ └── resources/ │ └── CSharpSonarWayProfileTest/ │ └── Sonar_way_profile.json ├── sonar-csharp-plugin/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── sonar/ │ │ └── plugins/ │ │ └── csharp/ │ │ ├── CSharpPlugin.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── sonar/ │ │ └── plugins/ │ │ └── csharp/ │ │ ├── CSharpPluginTest.java │ │ ├── CSharpRulesDefinitionTest.java │ │ └── CSharpSonarWayProfileTest.java │ ├── resources/ │ │ └── Program.cs │ └── scripts/ │ ├── echo.bat │ ├── echo.sh │ ├── forever.bat │ └── forever.sh ├── sonar-dotnet-core/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ ├── sonar/ │ │ │ │ └── plugins/ │ │ │ │ └── dotnet/ │ │ │ │ └── tests/ │ │ │ │ ├── FileService.java │ │ │ │ ├── NUnitTestResults.java │ │ │ │ ├── NUnitTestResultsParser.java │ │ │ │ ├── ParseErrorException.java │ │ │ │ ├── PathSuffixPredicate.java │ │ │ │ ├── ScannerFileService.java │ │ │ │ ├── UnitTestConfiguration.java │ │ │ │ ├── UnitTestResultParser.java │ │ │ │ ├── UnitTestResults.java │ │ │ │ ├── UnitTestResultsAggregator.java │ │ │ │ ├── UnitTestResultsImportSensor.java │ │ │ │ ├── VisualStudioTestResultParser.java │ │ │ │ ├── VisualStudioTestResults.java │ │ │ │ ├── WildcardPatternFileProvider.java │ │ │ │ ├── XUnitTestResults.java │ │ │ │ ├── XUnitTestResultsParser.java │ │ │ │ ├── XmlParserHelper.java │ │ │ │ ├── XmlTestReportParser.java │ │ │ │ ├── coverage/ │ │ │ │ │ ├── BranchCoverage.java │ │ │ │ │ ├── CoberturaReportParser.java │ │ │ │ │ ├── ConditionData.java │ │ │ │ │ ├── Coverage.java │ │ │ │ │ ├── CoverageAggregator.java │ │ │ │ │ ├── CoverageCache.java │ │ │ │ │ ├── CoverageConfiguration.java │ │ │ │ │ ├── CoverageParser.java │ │ │ │ │ ├── CoverageReportImportSensor.java │ │ │ │ │ ├── DotCoverReportParser.java │ │ │ │ │ ├── DotCoverReportsAggregator.java │ │ │ │ │ ├── NCover3ReportParser.java │ │ │ │ │ ├── OpenCoverReportParser.java │ │ │ │ │ ├── SequencePoint.java │ │ │ │ │ ├── VisualStudioCoverageXmlReportParser.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── sonarsource/ │ │ │ └── dotnet/ │ │ │ └── shared/ │ │ │ ├── CallableUtils.java │ │ │ ├── LazyCallException.java │ │ │ ├── PropertyUtils.java │ │ │ ├── StringUtils.java │ │ │ ├── package-info.java │ │ │ ├── plugins/ │ │ │ │ ├── AbstractLanguageConfiguration.java │ │ │ │ ├── AbstractPropertyDefinitions.java │ │ │ │ ├── AbstractSonarWayProfile.java │ │ │ │ ├── CodeCoverageProvider.java │ │ │ │ ├── DotNetRulesDefinition.java │ │ │ │ ├── EncodingPerFile.java │ │ │ │ ├── GlobalProtobufFileProcessor.java │ │ │ │ ├── HashProvider.java │ │ │ │ ├── MethodDeclarationsCollector.java │ │ │ │ ├── ModuleConfiguration.java │ │ │ │ ├── PluginMetadata.java │ │ │ │ ├── ProjectTypeCollector.java │ │ │ │ ├── ProtobufDataImporter.java │ │ │ │ ├── RealPathProvider.java │ │ │ │ ├── ReportPathCollector.java │ │ │ │ ├── RoslynDataImporter.java │ │ │ │ ├── RoslynReport.java │ │ │ │ ├── RoslynRules.java │ │ │ │ ├── SarifParserCallbackImpl.java │ │ │ │ ├── SensorContextUtils.java │ │ │ │ ├── TelemetryCollector.java │ │ │ │ ├── UnitTestResultsProvider.java │ │ │ │ ├── filters/ │ │ │ │ │ ├── GeneratedFileFilter.java │ │ │ │ │ ├── WrongEncodingFileFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── protobuf/ │ │ │ │ │ ├── CPDTokensImporter.java │ │ │ │ │ ├── FileMetadataImporter.java │ │ │ │ │ ├── HighlightImporter.java │ │ │ │ │ ├── LogImporter.java │ │ │ │ │ ├── MethodDeclarationsImporter.java │ │ │ │ │ ├── MetricsImporter.java │ │ │ │ │ ├── ProtobufImporter.java │ │ │ │ │ ├── RawProtobufImporter.java │ │ │ │ │ ├── SymbolRefsImporter.java │ │ │ │ │ ├── TelemetryAggregator.java │ │ │ │ │ ├── TelemetryImporter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── sensors/ │ │ │ │ │ ├── AbstractFileCacheSensor.java │ │ │ │ │ ├── AnalysisWarningsSensor.java │ │ │ │ │ ├── DotNetSensor.java │ │ │ │ │ ├── FileTypeSensor.java │ │ │ │ │ ├── LogSensor.java │ │ │ │ │ ├── MethodDeclarationsSensor.java │ │ │ │ │ ├── PropertiesSensor.java │ │ │ │ │ ├── TelemetryJsonProcessor.java │ │ │ │ │ ├── TelemetryJsonProjectCollector.java │ │ │ │ │ ├── TelemetryJsonSensor.java │ │ │ │ │ ├── TelemetryProcessor.java │ │ │ │ │ ├── TelemetrySensor.java │ │ │ │ │ └── package-info.java │ │ │ │ └── telemetryjson/ │ │ │ │ ├── TelemetryJsonAggregator.java │ │ │ │ ├── TelemetryJsonCollector.java │ │ │ │ ├── TelemetryJsonParser.java │ │ │ │ ├── TelemetryUtils.java │ │ │ │ └── package-info.java │ │ │ └── sarif/ │ │ │ ├── Location.java │ │ │ ├── SarifParser.java │ │ │ ├── SarifParser01And04.java │ │ │ ├── SarifParser10.java │ │ │ ├── SarifParserCallback.java │ │ │ ├── SarifParserFactory.java │ │ │ └── package-info.java │ │ └── protobuf/ │ │ └── .gitignore │ └── test/ │ ├── java/ │ │ └── org/ │ │ ├── sonar/ │ │ │ └── plugins/ │ │ │ └── dotnet/ │ │ │ └── tests/ │ │ │ ├── NUnitTestResultsParserTest.java │ │ │ ├── PathSuffixPredicateTest.java │ │ │ ├── ScannerFileServiceTest.java │ │ │ ├── UnitTestResultsAggregatorTest.java │ │ │ ├── UnitTestResultsImportSensorTest.java │ │ │ ├── VisualStudioTestResultParserTest.java │ │ │ ├── VstsUtils.java │ │ │ ├── WildcardPatternFileProviderTest.java │ │ │ ├── XUnitTestResultParserTest.java │ │ │ ├── XmlParserHelperTest.java │ │ │ └── coverage/ │ │ │ ├── BranchCoverageTest.java │ │ │ ├── CoberturaReportParserTest.java │ │ │ ├── ConditionDataTest.java │ │ │ ├── CoverageAggregatorTest.java │ │ │ ├── CoverageCacheTest.java │ │ │ ├── CoverageReportImportSensorTest.java │ │ │ ├── CoverageTest.java │ │ │ ├── DotCoverReportParserTest.java │ │ │ ├── DotCoverReportsAggregatorTest.java │ │ │ ├── NCover3ReportParserTest.java │ │ │ ├── OpenCoverReportParserTest.java │ │ │ └── VisualStudioCoverageXmlReportParserTest.java │ │ └── sonarsource/ │ │ └── dotnet/ │ │ └── shared/ │ │ ├── CallableUtilsTests.java │ │ ├── plugins/ │ │ │ ├── AbstractLanguageConfigurationTest.java │ │ │ ├── AbstractPropertyDefinitionsTest.java │ │ │ ├── AbstractSonarWayProfileTest.java │ │ │ ├── CodeCoverageProviderTest.java │ │ │ ├── DotNetRulesDefinitionTest.java │ │ │ ├── EncodingPerFileTest.java │ │ │ ├── GeneratedFileFilterTest.java │ │ │ ├── GlobalProtobufFileProcessorTest.java │ │ │ ├── HashProviderTest.java │ │ │ ├── MethodDeclarationsSensorTest.java │ │ │ ├── ModuleConfigurationTest.java │ │ │ ├── ProjectTypeCollectorTest.java │ │ │ ├── ProtobufDataImporterTest.java │ │ │ ├── RealPathProviderTest.java │ │ │ ├── ReportPathCollectorTest.java │ │ │ ├── RoslynDataImporterTest.java │ │ │ ├── RoslynRulesTest.java │ │ │ ├── SensorContextUtilsTest.java │ │ │ ├── UnitTestResultsProviderTest.java │ │ │ ├── WrongEncodingFileFilterTest.java │ │ │ ├── protobuf/ │ │ │ │ ├── CPDTokensImporterTest.java │ │ │ │ ├── FileMetadataImporterTest.java │ │ │ │ ├── HighlightImporterTest.java │ │ │ │ ├── LogImporterTest.java │ │ │ │ ├── MethodDeclarationsImporterTest.java │ │ │ │ ├── MetricsImporterTest.java │ │ │ │ ├── RazorImporterTestBase.java │ │ │ │ ├── RazorMetricsImporterTest.java │ │ │ │ ├── RazorSymbolRefsImporterTest.java │ │ │ │ ├── SymbolRefsImporterTest.java │ │ │ │ ├── TelemetryAggregatorLanguageVersionTest.java │ │ │ │ └── TelemetryImporterTest.java │ │ │ ├── sensors/ │ │ │ │ ├── AbstractFileCacheSensorTest.java │ │ │ │ ├── AnalysisWarningsSensorTest.java │ │ │ │ ├── DotNetSensorTest.java │ │ │ │ ├── FileTypeSensorTest.java │ │ │ │ ├── LogSensorTest.java │ │ │ │ ├── PropertiesSensorTest.java │ │ │ │ ├── TelemetryJsonProcessorTest.java │ │ │ │ ├── TelemetryJsonProjectSensorTest.java │ │ │ │ ├── TelemetryJsonSensorTest.java │ │ │ │ ├── TelemetryProcessorTest.java │ │ │ │ └── TelemetrySensorTest.java │ │ │ ├── telemetryjson/ │ │ │ │ ├── TelemetryJsonAggregatorTest.java │ │ │ │ ├── TelemetryJsonCollectorTest.java │ │ │ │ ├── TelemetryJsonParserTest.java │ │ │ │ └── TelemetryUtilsTest.java │ │ │ └── testutils/ │ │ │ ├── AutoDeletingTempFile.java │ │ │ ├── FileUtils.java │ │ │ └── ProtobufFilterTool.java │ │ └── sarif/ │ │ ├── SarifParser01And04Test.java │ │ ├── SarifParser10Test.java │ │ ├── SarifParserCallbackImplTest.java │ │ └── SarifParserFactoryTest.java │ └── resources/ │ ├── AbstractSonarWayProfile/ │ │ └── Sonar_way_profile.json │ ├── Directory.Build.targets │ ├── DotNetRulesDefinitionTest/ │ │ ├── Rules.json │ │ ├── S100.html │ │ ├── S100.json │ │ ├── S1111.html │ │ ├── S1111.json │ │ ├── S1112.html │ │ ├── S1112.json │ │ ├── S1113.html │ │ ├── S1113.json │ │ ├── S1114.html │ │ ├── S1114.json │ │ ├── S1115.html │ │ ├── S1115.json │ │ ├── S1116.html │ │ ├── S1116.json │ │ ├── S1117.html │ │ ├── S1117.json │ │ ├── S2115.html │ │ ├── S2115.json │ │ ├── S4502.html │ │ ├── S4502.json │ │ └── Sonar_way_profile.json │ ├── HashProvider/ │ │ ├── Ansi.cs │ │ ├── CodeNoBom.cs │ │ ├── CodeWithBom.cs │ │ ├── EmptyNoBom.cs │ │ ├── EmptyWithBom.cs │ │ ├── Utf16.cs │ │ └── Utf8.cs │ ├── LogSensorTest/ │ │ └── log.pb │ ├── MethodDeclarationsSensorTest/ │ │ ├── ReadMe.md │ │ ├── TestMethodImport/ │ │ │ ├── TestMethodImport.Tests/ │ │ │ │ ├── TestBase.cs │ │ │ │ ├── TestClass.cs │ │ │ │ ├── TestMethodImport.Tests.csproj │ │ │ │ └── packages.lock.json │ │ │ └── TestMethodImport.sln │ │ └── protobuf-files/ │ │ └── test-method-declarations.pb │ ├── Program.cs │ ├── ProtobufImporterTest/ │ │ ├── Program.cs │ │ ├── README.md │ │ ├── custom-log.pb │ │ ├── file-metadata.pb │ │ ├── invalid-encoding.pb │ │ ├── metrics.pb │ │ ├── symrefs.pb │ │ ├── token-cpd.pb │ │ ├── token-type.pb │ │ └── unknown-log.pb │ ├── RazorProtobufImporter/ │ │ ├── ReadMe.md │ │ ├── Roslyn 4.10/ │ │ │ ├── file-metadata.pb │ │ │ ├── global.json │ │ │ ├── log.pb │ │ │ ├── metrics.pb │ │ │ ├── symrefs.pb │ │ │ ├── telemetry.pb │ │ │ ├── test-method-declarations.pb │ │ │ ├── token-cpd.pb │ │ │ └── token-type.pb │ │ ├── Roslyn 4.9/ │ │ │ ├── file-metadata.pb │ │ │ ├── global.json │ │ │ ├── log.pb │ │ │ ├── metrics.pb │ │ │ ├── symrefs.pb │ │ │ ├── telemetry.pb │ │ │ ├── test-method-declarations.pb │ │ │ ├── token-cpd.pb │ │ │ └── token-type.pb │ │ └── WebProject/ │ │ ├── BlazorWebAssembly.csproj │ │ ├── Cases.razor │ │ ├── OverlapSymbolReferences.razor │ │ ├── Program.cs │ │ ├── _Imports.razor │ │ └── packages.lock.json │ ├── RoslynDataImporterTest/ │ │ ├── roslyn-report-empty.json │ │ ├── roslyn-report-invalid-location.json │ │ └── roslyn-report.json │ ├── RoslynProfileExporterTest/ │ │ ├── empty_string_value.xml │ │ ├── mixed.xml │ │ ├── no_rules.xml │ │ └── only_sonarlint.xml │ ├── RoslynRulesTest/ │ │ └── Rules.json │ ├── SarifParserTest/ │ │ ├── v0_1.json │ │ ├── v0_1_empty_issues.json │ │ ├── v0_1_empty_no_issues.json │ │ ├── v0_4.json │ │ ├── v0_4_empty_no_results.json │ │ ├── v0_4_empty_no_runLogs.json │ │ ├── v0_4_empty_results.json │ │ ├── v0_4_empty_runLogs.json │ │ ├── v0_4_file_level_issue.json │ │ ├── v0_4_secondary_locations.json │ │ ├── v0_4_secondary_locations_no_messages.json │ │ ├── v1_0.json │ │ ├── v1_0_another.json │ │ ├── v1_0_empty.json │ │ ├── v1_0_empty_location.json │ │ ├── v1_0_escaping.json │ │ ├── v1_0_execution_flow.json │ │ ├── v1_0_execution_flow_no_secondary_locations.json │ │ ├── v1_0_file_level_issue.json │ │ ├── v1_0_file_level_issue_with_execution_flow.json │ │ ├── v1_0_file_name_with_illegal_char.json │ │ ├── v1_0_invalid_execution_flow_value.json │ │ ├── v1_0_no_execution_flow.json │ │ ├── v1_0_no_location.json │ │ ├── v1_0_no_message.json │ │ ├── v1_0_region_with_length.json │ │ ├── v1_0_relative_paths.json │ │ ├── v1_0_same_start_end_location.json │ │ ├── v1_0_secondary_locations.json │ │ ├── v1_0_secondary_locations_messages.json │ │ └── v1_0_suppressed.json │ ├── TelemetryJsonSensorTest/ │ │ ├── 0/ │ │ │ └── Telemetry.json │ │ ├── 1/ │ │ │ └── Telemetry.json │ │ ├── Telemetry.Other.json │ │ └── Telemetry.S4NET.json │ ├── TelemetrySensorTest/ │ │ ├── 0/ │ │ │ └── telemetry.pb │ │ ├── 1/ │ │ │ └── telemetry.pb │ │ └── Readme.md │ ├── analysisWarnings/ │ │ ├── AnalysisWarnings.AutoScan.json │ │ └── AnalysisWarnings.Scanner.json │ ├── cobertura/ │ │ ├── absolute_path_no_sources.xml │ │ ├── absolute_path_with_sources.xml │ │ ├── branch_jump_conditions.xml │ │ ├── branch_malformed_condition_coverage.xml │ │ ├── branch_mixed_conditions.xml │ │ ├── branch_no_condition_coverage_attr.xml │ │ ├── branch_no_conditions.xml │ │ ├── branch_switch_condition.xml │ │ ├── branch_unresolved_file.xml │ │ ├── empty_source_tag.xml │ │ ├── invalid_path.xml │ │ ├── invalid_root.xml │ │ ├── line_coverage.xml │ │ ├── line_coverage_unresolved_file.xml │ │ ├── multiple_classes_same_filename.xml │ │ ├── relative_path_no_sources.xml │ │ ├── relative_path_with_sources.xml │ │ ├── source_with_nested_elements.xml │ │ └── valid_empty.xml │ ├── dotcover/ │ │ ├── invalid_path.html │ │ ├── no_highlight.html │ │ ├── no_script.html │ │ ├── no_title.html │ │ ├── no_title_end.html │ │ ├── title_nested_tag.html │ │ ├── title_swapped.html │ │ ├── valid.html │ │ ├── valid_big.html │ │ └── valid_multiple_sequence_points_per_line.html │ ├── dotcover_aggregator/ │ │ ├── empty_folder/ │ │ │ └── src/ │ │ │ └── .gitignore │ │ ├── empty_folder.html │ │ ├── foo.bar/ │ │ │ └── src/ │ │ │ ├── 1.html │ │ │ ├── 2.html │ │ │ └── nosource.html │ │ ├── foo.bar.html │ │ ├── no_extension │ │ ├── no_sources.html │ │ └── not_html.html │ ├── ncover3/ │ │ ├── invalid_path.nccov │ │ ├── invalid_root.nccov │ │ ├── no_version.nccov │ │ ├── one_file.nccov │ │ ├── valid.nccov │ │ └── wrong_version.nccov │ ├── nunit/ │ │ ├── invalid_root.xml │ │ ├── invalid_test_outcome.xml │ │ ├── readme.md │ │ ├── test_name_not_mapped.xml │ │ ├── valid_comma_in_double.xml │ │ ├── valid_inheritance.xml │ │ ├── valid_no_execution_time.xml │ │ ├── valid_nunit2.xml │ │ └── valid_nunit3.xml │ ├── opencover/ │ │ ├── code_tested_by_multiple_projects.xml │ │ ├── coverage_branches.xml │ │ ├── deterministic_source_paths.xml │ │ ├── invalid_file_id.xml │ │ ├── invalid_path.xml │ │ ├── invalid_root.xml │ │ ├── missing_start_line.xml │ │ ├── one_class.xml │ │ ├── switch_expression_multiple_test_projects_1.xml │ │ ├── switch_expression_multiple_test_projects_2.xml │ │ ├── valid.xml │ │ ├── valid_case_multiple_sequence_points_per_line.xml │ │ └── wrong_start_line.xml │ ├── samples/ │ │ ├── ReadMe.md │ │ ├── csharp/ │ │ │ └── Calculator/ │ │ │ ├── Calculator/ │ │ │ │ ├── Calculator.cs │ │ │ │ ├── Calculator.csproj │ │ │ │ └── packages.lock.json │ │ │ ├── Calculator.MSTest/ │ │ │ │ ├── Calculator.MSTest.csproj │ │ │ │ ├── CalculatorTests.cs │ │ │ │ ├── MSTestSettings.cs │ │ │ │ └── packages.lock.json │ │ │ ├── Calculator.NUnit3/ │ │ │ │ ├── Calculator.NUnit3.csproj │ │ │ │ ├── CalculatorTests.cs │ │ │ │ └── packages.lock.json │ │ │ ├── Calculator.NUnit4/ │ │ │ │ ├── Calculator.NUnit4.csproj │ │ │ │ ├── CalculatorTests.cs │ │ │ │ └── packages.lock.json │ │ │ ├── Calculator.sln │ │ │ └── Calculator.xUnit/ │ │ │ ├── Calculator.xUnit.csproj │ │ │ ├── CalculatorTests.cs │ │ │ └── packages.lock.json │ │ └── vbnet/ │ │ └── Calculator/ │ │ ├── Calculator/ │ │ │ ├── Calculator.vb │ │ │ └── Calculator.vbproj │ │ ├── Calculator.MSTest/ │ │ │ ├── Calculator.MSTest.vbproj │ │ │ ├── CalculatorTests.vb │ │ │ └── MSTestSettings.vb │ │ ├── Calculator.NUnit3/ │ │ │ ├── Calculator.NUnit3.vbproj │ │ │ └── CalculatorTests.vb │ │ ├── Calculator.NUnit4/ │ │ │ ├── Calculator.NUnit4.vbproj │ │ │ └── CalculatorTests.vb │ │ ├── Calculator.sln │ │ └── Calculator.xUnit/ │ │ ├── Calculator.xUnit.vbproj │ │ └── CalculatorTests.vb │ ├── visualstudio_coverage_xml/ │ │ ├── deterministic_source_paths.coveragexml │ │ ├── getter_setter.coveragexml │ │ ├── getter_setter_multiple_per_line.coveragexml │ │ ├── invalid_path.coveragexml │ │ ├── invalid_root.coveragexml │ │ ├── no_ranges.coveragexml │ │ ├── valid.coveragexml │ │ ├── valid_complex_case.coveragexml │ │ └── wrong_covered.coveragexml │ ├── visualstudio_test_results/ │ │ ├── invalid_character.trx │ │ ├── invalid_dates.trx │ │ ├── invalid_test_outcome.trx │ │ ├── multiple_runs_same_test.trx │ │ ├── nunitproject_with_vs_logger.trx │ │ ├── projects/ │ │ │ └── TestReport/ │ │ │ ├── TestReport/ │ │ │ │ ├── Test1.cs │ │ │ │ ├── TestReport.csproj │ │ │ │ └── packages.lock.json │ │ │ └── TestReport.sln │ │ ├── readme.md │ │ ├── test_name_not_mapped.trx │ │ ├── test_result_no_test_method.trx │ │ └── valid.trx │ ├── xml_parser_helper/ │ │ ├── invalid_prolog.txt │ │ └── valid.xml │ └── xunit/ │ ├── ReadMe.md │ ├── invalid_root.xml │ ├── invalid_test_outcome.xml │ ├── test_name_not_mapped.xml │ ├── valid.xml │ ├── valid_data_attribute.xml │ ├── valid_generic_methods_csharp.xml │ ├── valid_generic_methods_vbnet.xml │ └── valid_no_execution_time.xml ├── sonar-vbnet-core/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ ├── sonar/ │ │ │ └── plugins/ │ │ │ └── vbnetenterprise/ │ │ │ └── api/ │ │ │ ├── ProfileRegistrar.java │ │ │ └── package-info.java │ │ └── sonarsource/ │ │ └── vbnet/ │ │ └── core/ │ │ ├── VbNetCoreExtensions.java │ │ ├── VbNetCorePluginMetadata.java │ │ ├── VbNetFileCacheSensor.java │ │ ├── VbNetLanguageConfiguration.java │ │ ├── VbNetPropertyDefinitions.java │ │ ├── VbNetSonarWayProfile.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── sonarsource/ │ │ └── vbnet/ │ │ └── core/ │ │ ├── TestVbNetMetadata.java │ │ ├── VbNetCoreExtensionsTest.java │ │ ├── VbNetCorePluginMetadataTest.java │ │ ├── VbNetFileCacheSensorTest.java │ │ ├── VbNetLanguageConfigurationTest.java │ │ ├── VbNetPropertyDefinitionsTest.java │ │ ├── VbNetSonarWayProfileTest.java │ │ └── VbNetTest.java │ └── resources/ │ └── VbNetSonarWayProfileTest/ │ └── Sonar_way_profile.json └── sonar-vbnet-plugin/ ├── README.md ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── org/ │ └── sonar/ │ └── plugins/ │ └── vbnet/ │ ├── VbNetPlugin.java │ └── package-info.java └── test/ ├── java/ │ └── org/ │ └── sonar/ │ └── plugins/ │ └── vbnet/ │ ├── VbNetPluginTest.java │ ├── VbNetRulesDefinitionTest.java │ └── VbNetSonarWayProfileTest.java ├── resources/ │ └── Program.cs └── scripts/ ├── echo.bat ├── echo.sh ├── forever.bat └── forever.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # Editor configuration, see http://editorconfig.org # Visual studio supported code style syntax https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference # Visual studio supported naming convention syntax https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-naming-conventions # Undocumented https://kent-boogaart.com/blog/editorconfig-reference-for-c-developers # Undocumented CS options https://github.com/dotnet/roslyn/blob/master/src/Workspaces/CSharp/Portable/Formatting/CSharpFormattingOptions.cs # Undocumented .NET options https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs # top-most EditorConfig file, hierarchy search will stop in this file root = true # ---------------------------------------------------------------------------------------------------------------------- # General settings # ---------------------------------------------------------------------------------------------------------------------- # Don't use tabs for indentation. [*] indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) [*.md] trim_trailing_whitespace = false end_of_line = lf # Code files [*.{cs,csx,vb,vbx}] charset = utf-8-bom indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf max_line_length = 200 # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # JSON and YML files [*.{json,yml}] indent_size = 2 # Scripting files [*.{ps1,bat,cmd}] indent_size = 4 # Java code files [*.java] indent_size = 2 # Pom files [pom.xml] indent_size = 2 # ---------------------------------------------------------------------------------------------------------------------- # Coding styles # ---------------------------------------------------------------------------------------------------------------------- # Dotnet code style settings: [*.{cs,vb}] tab_width = 4 # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_property = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_event = false:warning # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning # Suggest more modern language features when available dotnet_style_coalesce_expression = true:warning dotnet_style_collection_initializer = true:warning dotnet_style_prefer_collection_expression = when_types_loosely_match:warning dotnet_style_explicit_tuple_names = true:warning dotnet_style_namespace_match_folder = true:warning # This is activated only on production code below via IDE0130 settings dotnet_style_null_propagation = true:warning dotnet_style_object_initializer = true:warning dotnet_style_operator_placement_when_wrapping = beginning_of_line:warning dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:warning dotnet_style_prefer_conditional_expression_over_return = false:silent dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning dotnet_style_prefer_simplified_interpolation = true:warning dotnet_style_prefer_simplified_boolean_expressions = true:warning dotnet_style_readonly_field = true:warning # Parameter preferences dotnet_code_quality_unused_parameters = non_public:warning # Parentheses dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent # CSharp code style settings: [*.cs] csharp_prefer_braces = true:warning # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:warning csharp_style_var_when_type_is_apparent = true:warning csharp_style_var_elsewhere = true:warning # Prefer expression-body csharp_style_expression_bodied_methods = true:warning csharp_style_expression_bodied_constructors = true:warning csharp_style_expression_bodied_operators = true:warning csharp_style_expression_bodied_properties = true:warning csharp_style_expression_bodied_indexers = true:warning csharp_style_expression_bodied_accessors = true:warning csharp_style_expression_bodied_lambdas = true:warning csharp_style_expression_bodied_local_functions = true:warning # Suggest more modern language features when available csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_inlined_variable_declaration = true:warning csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true:warning csharp_style_throw_expression = true:warning csharp_style_conditional_delegate_call = true:warning # IDE0055 configuration # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_between_query_expression_clauses = true # Indent csharp_indent_case_contents = true csharp_indent_switch_labels = true csharp_indent_labels = flush_left csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents_when_block = true # Spaces csharp_space_after_cast = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_before_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_before_comma = false csharp_space_after_dot = false csharp_space_before_dot = false csharp_space_after_semicolon_in_for_statement = true csharp_space_before_semicolon_in_for_statement = false # Extra space before equals sign DOES MATTER https://github.com/dotnet/roslyn/issues/20355 csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = false csharp_space_before_open_square_brackets = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_square_brackets = false # Wrapping csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true # End of IDE0055 configuration csharp_using_directive_placement = outside_namespace:warning csharp_prefer_simple_using_statement = true:warning csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_method_group_conversion = true:warning csharp_style_prefer_top_level_statements = true:warning csharp_style_prefer_primary_constructors = false:suggestion csharp_prefer_system_threading_lock = true:warning csharp_style_prefer_null_check_over_type_check = true:warning csharp_style_prefer_local_over_anonymous_function = true:warning csharp_style_prefer_index_operator = false:silent csharp_style_prefer_range_operator = false:silent csharp_style_implicit_object_creation_when_type_is_apparent = true:warning csharp_style_prefer_tuple_swap = false:silent csharp_style_prefer_unbound_generic_type_in_nameof = true:warning csharp_style_prefer_utf8_string_literals = true:warning csharp_style_unused_value_assignment_preference = unused_local_variable:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_prefer_static_local_function = true:warning csharp_prefer_static_anonymous_function = true:warning csharp_style_prefer_readonly_struct = true:warning csharp_style_prefer_readonly_struct_member = true:warning csharp_style_prefer_switch_expression = true:warning csharp_style_prefer_pattern_matching = true:warning csharp_style_prefer_not_pattern = true:warning csharp_style_prefer_extended_property_pattern = true:warning csharp_style_prefer_implicitly_typed_lambda_expression = true:warning # ---------------------------------------------------------------------------------------------------------------------- # Naming conventions # ---------------------------------------------------------------------------------------------------------------------- # ORDERING DOES MATTER!!! # Naming conventions should be ordered from most-specific to least-specific in the .editorconfig file. # The first rule encountered that can be applied is the only rule that is applied. [*.{cs,vb}] # Naming rules dotnet_naming_rule.interface_must_start_with_i.severity = warning dotnet_naming_rule.interface_must_start_with_I.symbols = interface_types dotnet_naming_rule.interface_must_start_with_i.style = I_style dotnet_naming_rule.variables_must_be_camel_style.severity = warning dotnet_naming_rule.variables_must_be_camel_style.symbols = parameter_types dotnet_naming_rule.variables_must_be_camel_style.style = camel_style dotnet_naming_rule.types_should_be_pascal_case.severity = warning dotnet_naming_rule.types_should_be_pascal_case.symbols = types dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case # Symbol specifications dotnet_naming_symbols.interface_types.applicable_kinds = interface dotnet_naming_symbols.interface_types.applicable_accessibilities = * dotnet_naming_symbols.parameter_types.applicable_kinds = parameter dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected # Naming styles dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.camel_style.capitalization = camel_case dotnet_naming_style.I_style.required_prefix = I dotnet_naming_style.I_style.capitalization = pascal_case # ---------------------------------------------------------------------------------------------------------------------- # Rules # ---------------------------------------------------------------------------------------------------------------------- dotnet_diagnostic.CS7035.severity = none # CS7035: it expects the build number to fit in 16 bits, our build numbers are bigger https://github.com/dotnet/roslyn/issues/17024#issuecomment-1669503201 dotnet_diagnostic.CA1822.severity = warning # Increase visibility for Member 'xxx' does not access instance data and can be marked as static dotnet_diagnostic.RS2008.severity = none # Enable analyzer release tracking - we don't use the release tracking analyzer dotnet_diagnostic.RS1036.severity = none # A project containing analyzers or source generators should specify the property 'true' - we're intentionally violating a lot of those types dotnet_diagnostic.JSON002.severity = none # Probable JSON string detected, noisy in UTs dotnet_analyzer_diagnostic.category-Style.severity = warning # Default severity for analyzer diagnostics with category 'Style' dotnet_diagnostic.IDE0008.severity = none # Use explicit type instead of var dotnet_diagnostic.IDE0009.severity = none # Add this or Me qualification dotnet_diagnostic.IDE0010.severity = none # Add missing cases to switch statement dotnet_diagnostic.IDE0017.severity = none # Use object initializers dotnet_diagnostic.IDE0046.severity = none # Use conditional expression for return dotnet_diagnostic.IDE0047.severity = none # Remove unnecessary parentheses dotnet_diagnostic.IDE0048.severity = none # Add parentheses for clarity dotnet_diagnostic.IDE0056.severity = none # Use index operator dotnet_diagnostic.IDE0057.severity = none # Use range operator dotnet_diagnostic.IDE0058.severity = none # Remove unused expression value dotnet_diagnostic.IDE0059.severity = none # Remove unnecessary value assignment dotnet_diagnostic.IDE0072.severity = none # Add missing cases to switch expression dotnet_diagnostic.IDE0073.severity = none # Use file header dotnet_diagnostic.IDE0160.severity = none # Use block-scoped namespace dotnet_diagnostic.IDE0180.severity = none # Use tuple to swap values dotnet_diagnostic.IDE0211.severity = none # Convert to 'Program.Main' style program dotnet_diagnostic.IDE0220.severity = none # Add explicit cast in foreach loop dotnet_diagnostic.IDE0290.severity = none # Use primary constructor resharper_convert_to_primary_constructor_highlighting = none dotnet_diagnostic.IDE0303.severity = none # Use collection expression for Create() dotnet_diagnostic.IDE0304.severity = none # Use collection expression for builder dotnet_diagnostic.IDE0305.severity = none # Use collection expression for fluent dotnet_diagnostic.IDE2006.severity = none # Blank line not allowed after arrow expression clause token # ---------------------------------------------------------------------------------------------------------------------- # SyleCop.Analyzers rules - note that the URLs below are for tag 1.1.118 # ---------------------------------------------------------------------------------------------------------------------- # Spacing Rules (SA1000-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/SpacingRules.md dotnet_diagnostic.SA1000.severity = warning dotnet_diagnostic.SA1001.severity = warning dotnet_diagnostic.SA1002.severity = warning dotnet_diagnostic.SA1003.severity = warning dotnet_diagnostic.SA1004.severity = warning dotnet_diagnostic.SA1005.severity = warning dotnet_diagnostic.SA1006.severity = warning dotnet_diagnostic.SA1007.severity = warning dotnet_diagnostic.SA1008.severity = warning dotnet_diagnostic.SA1009.severity = warning dotnet_diagnostic.SA1010.severity = warning dotnet_diagnostic.SA1011.severity = warning dotnet_diagnostic.SA1012.severity = none # noisy on collection initializers dotnet_diagnostic.SA1013.severity = none # noisy on collection initializers dotnet_diagnostic.SA1014.severity = warning dotnet_diagnostic.SA1015.severity = warning dotnet_diagnostic.SA1016.severity = warning dotnet_diagnostic.SA1017.severity = warning dotnet_diagnostic.SA1018.severity = warning dotnet_diagnostic.SA1019.severity = warning dotnet_diagnostic.SA1020.severity = warning dotnet_diagnostic.SA1021.severity = warning dotnet_diagnostic.SA1022.severity = warning dotnet_diagnostic.SA1023.severity = warning dotnet_diagnostic.SA1024.severity = warning dotnet_diagnostic.SA1025.severity = none # noisy on aligned comments dotnet_diagnostic.SA1026.severity = warning dotnet_diagnostic.SA1027.severity = none # RSPEC-105 dotnet_diagnostic.SA1028.severity = warning # Readability Rules (SA1100-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/DOCUMENTATION.md dotnet_diagnostic.SA1100.severity = warning dotnet_diagnostic.SA1101.severity = none # Doesn't match our coding style, we don't use "this." when not neded dotnet_diagnostic.SA1102.severity = warning dotnet_diagnostic.SA1103.severity = warning dotnet_diagnostic.SA1104.severity = warning dotnet_diagnostic.SA1105.severity = warning dotnet_diagnostic.SA1106.severity = warning dotnet_diagnostic.SA1107.severity = warning dotnet_diagnostic.SA1108.severity = none # Noisy for short comments on short lines (if, for, foreach) dotnet_diagnostic.SA1109.severity = warning dotnet_diagnostic.SA1110.severity = warning dotnet_diagnostic.SA1111.severity = warning dotnet_diagnostic.SA1112.severity = warning dotnet_diagnostic.SA1113.severity = warning dotnet_diagnostic.SA1114.severity = none # Prevents putting comment before first member in ImmutableArray.Create dotnet_diagnostic.SA1115.severity = warning dotnet_diagnostic.SA1116.severity = none # Waste of new lines in simple scenarios dotnet_diagnostic.SA1117.severity = none # Waste of new lines in simple scenarios dotnet_diagnostic.SA1118.severity = none # Noisy in UTs dotnet_diagnostic.SA1119.severity = warning dotnet_diagnostic.SA1120.severity = warning dotnet_diagnostic.SA1121.severity = warning dotnet_diagnostic.SA1122.severity = warning dotnet_diagnostic.SA1123.severity = warning dotnet_diagnostic.SA1124.severity = none # We need regions sometimes dotnet_diagnostic.SA1125.severity = warning dotnet_diagnostic.SA1126.severity = none # Deprecated / not implemented rule dotnet_diagnostic.SA1127.severity = none # Noisy for single-line method declarations dotnet_diagnostic.SA1128.severity = none # Doesn't match our code base dotnet_diagnostic.SA1129.severity = warning dotnet_diagnostic.SA1130.severity = warning dotnet_diagnostic.SA1131.severity = warning dotnet_diagnostic.SA1132.severity = warning dotnet_diagnostic.SA1133.severity = warning dotnet_diagnostic.SA1134.severity = warning dotnet_diagnostic.SA1135.severity = none # Noisy for generics dotnet_diagnostic.SA1136.severity = warning dotnet_diagnostic.SA1137.severity = warning dotnet_diagnostic.SA1138.severity = none # Deprecated / not implemented rule dotnet_diagnostic.SA1139.severity = warning # Ordering Rules (SA1200-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/OrderingRules.md dotnet_diagnostic.SA1200.severity = warning dotnet_diagnostic.SA1201.severity = none # Doesn't match our coding style (properties before constructor) dotnet_diagnostic.SA1202.severity = warning dotnet_diagnostic.SA1203.severity = warning dotnet_diagnostic.SA1204.severity = none # Doesn't match our coding style for private static methods dotnet_diagnostic.SA1205.severity = warning dotnet_diagnostic.SA1206.severity = warning dotnet_diagnostic.SA1207.severity = warning dotnet_diagnostic.SA1208.severity = warning dotnet_diagnostic.SA1209.severity = warning dotnet_diagnostic.SA1210.severity = warning dotnet_diagnostic.SA1211.severity = warning dotnet_diagnostic.SA1212.severity = warning dotnet_diagnostic.SA1213.severity = warning dotnet_diagnostic.SA1214.severity = warning dotnet_diagnostic.SA1215.severity = warning dotnet_diagnostic.SA1216.severity = warning dotnet_diagnostic.SA1217.severity = warning # Naming Rules (SA1300-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/NamingRules.md dotnet_diagnostic.SA1300.severity = warning dotnet_diagnostic.SA1301.severity = warning dotnet_diagnostic.SA1302.severity = warning dotnet_diagnostic.SA1303.severity = warning dotnet_diagnostic.SA1304.severity = warning dotnet_diagnostic.SA1305.severity = none # Noisy for other prefixes csFileName, orCondition dotnet_diagnostic.SA1306.severity = warning dotnet_diagnostic.SA1307.severity = warning dotnet_diagnostic.SA1308.severity = warning dotnet_diagnostic.SA1309.severity = warning dotnet_diagnostic.SA1310.severity = warning dotnet_diagnostic.SA1311.severity = warning dotnet_diagnostic.SA1312.severity = warning dotnet_diagnostic.SA1313.severity = warning dotnet_diagnostic.SA1314.severity = warning # Maintainability Rules (SA1400-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/MaintainabilityRules.md dotnet_diagnostic.SA1400.severity = warning dotnet_diagnostic.SA1401.severity = none # We have better rules S2357 and S1104 dotnet_diagnostic.SA1402.severity = none # we use the pattern of keeping 2 base classes in the same file to split generic from non-generic logic dotnet_diagnostic.SA1403.severity = warning dotnet_diagnostic.SA1404.severity = warning dotnet_diagnostic.SA1405.severity = warning dotnet_diagnostic.SA1406.severity = warning dotnet_diagnostic.SA1407.severity = none # very noisy on hash calculations; can lead to less readable code dotnet_diagnostic.SA1408.severity = warning dotnet_diagnostic.SA1409.severity = none # Deprecated / not implemented rule dotnet_diagnostic.SA1410.severity = warning dotnet_diagnostic.SA1411.severity = warning dotnet_diagnostic.SA1412.severity = warning dotnet_diagnostic.SA1413.severity = none # we do not want this dotnet_diagnostic.SA1414.severity = warning # Layout Rules (SA1500-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/LayoutRules.md dotnet_diagnostic.SA1500.severity = warning dotnet_diagnostic.SA1501.severity = warning dotnet_diagnostic.SA1502.severity = none # noisy on empty constructors calling base dotnet_diagnostic.SA1503.severity = warning dotnet_diagnostic.SA1504.severity = warning dotnet_diagnostic.SA1505.severity = warning dotnet_diagnostic.SA1506.severity = warning dotnet_diagnostic.SA1507.severity = warning dotnet_diagnostic.SA1508.severity = warning dotnet_diagnostic.SA1509.severity = warning dotnet_diagnostic.SA1510.severity = warning dotnet_diagnostic.SA1511.severity = warning dotnet_diagnostic.SA1512.severity = warning dotnet_diagnostic.SA1513.severity = none # on short methods, it does not apply dotnet_diagnostic.SA1514.severity = warning dotnet_diagnostic.SA1515.severity = none # we do not respect this dotnet_diagnostic.SA1516.severity = none # we do not respect this for fields, properties and abstract members dotnet_diagnostic.SA1517.severity = warning dotnet_diagnostic.SA1518.severity = warning dotnet_diagnostic.SA1519.severity = warning dotnet_diagnostic.SA1520.severity = warning # Documentation Rules (SA1600-) https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1.1.118/documentation/DocumentationRules.md # We don't require code documentation, however, when we do add it, we want it to be valid. dotnet_diagnostic.SA1600.severity = none dotnet_diagnostic.SA1601.severity = none dotnet_diagnostic.SA1602.severity = none dotnet_diagnostic.SA1603.severity = warning dotnet_diagnostic.SA1604.severity = none dotnet_diagnostic.SA1605.severity = none dotnet_diagnostic.SA1606.severity = warning dotnet_diagnostic.SA1607.severity = warning dotnet_diagnostic.SA1608.severity = none dotnet_diagnostic.SA1609.severity = none dotnet_diagnostic.SA1610.severity = none dotnet_diagnostic.SA1611.severity = none dotnet_diagnostic.SA1612.severity = none # noisy when method has 3 parameters and only one has documentation dotnet_diagnostic.SA1613.severity = warning dotnet_diagnostic.SA1614.severity = warning dotnet_diagnostic.SA1615.severity = none dotnet_diagnostic.SA1616.severity = warning dotnet_diagnostic.SA1617.severity = warning dotnet_diagnostic.SA1618.severity = none dotnet_diagnostic.SA1619.severity = none dotnet_diagnostic.SA1620.severity = warning dotnet_diagnostic.SA1621.severity = warning dotnet_diagnostic.SA1622.severity = warning dotnet_diagnostic.SA1623.severity = none dotnet_diagnostic.SA1624.severity = none dotnet_diagnostic.SA1625.severity = none dotnet_diagnostic.SA1626.severity = none dotnet_diagnostic.SA1627.severity = warning dotnet_diagnostic.SA1628.severity = warning dotnet_diagnostic.SA1629.severity = warning dotnet_diagnostic.SA1630.severity = none dotnet_diagnostic.SA1631.severity = none dotnet_diagnostic.SA1632.severity = none dotnet_diagnostic.SA1633.severity = none dotnet_diagnostic.SA1634.severity = none dotnet_diagnostic.SA1635.severity = none dotnet_diagnostic.SA1636.severity = none dotnet_diagnostic.SA1637.severity = none dotnet_diagnostic.SA1638.severity = none dotnet_diagnostic.SA1639.severity = none dotnet_diagnostic.SA1640.severity = none dotnet_diagnostic.SA1641.severity = none dotnet_diagnostic.SA1642.severity = none dotnet_diagnostic.SA1643.severity = none dotnet_diagnostic.SA1644.severity = none dotnet_diagnostic.SA1645.severity = none dotnet_diagnostic.SA1646.severity = none dotnet_diagnostic.SA1647.severity = none dotnet_diagnostic.SA1648.severity = none dotnet_diagnostic.SA1649.severity = none dotnet_diagnostic.SA1650.severity = none dotnet_diagnostic.SA1651.severity = none dotnet_diagnostic.SA1652.severity = none # Alternative Rules (SX0000-) dotnet_diagnostic.SX1101.severity = none dotnet_diagnostic.SX1309.severity = none dotnet_diagnostic.SX1309S.severity = none # IDE0130 Change namespace to match folder structure [**.Test/**/*.cs] # Do not raise in UTs, as we use .Test suffix after the whole namespace dotnet_diagnostic.IDE0130.severity = none resharper_check_namespace_highlighting = none [*.Roslyn.cs] # Do not raise on files copied from Roslyn repository dotnet_diagnostic.IDE0130.severity = none resharper_check_namespace_highlighting = none [**/Rules/*/*.cs] # Do not raise on files nested inside Rules (like Rules/AspNet/xxx.cs), as those are for logical separation and don't need a dedicated namespace dotnet_diagnostic.IDE0130.severity = none resharper_check_namespace_highlighting = none [**/ITs.JsonParser/**] # Do not raise in ITs.JsonParser as it is not production code. dotnet_diagnostic.T0003.severity = none # Do not use ValueTuple in the production code due to missing System.ValueTuple.dll. ================================================ FILE: .gitattributes ================================================ *.cs text eol=lf *.vb text eol=lf *.ps1 text eol=lf *.verified.cs text eol=lf working-tree-encoding=UTF-8 packages.lock.json text eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ .github/CODEOWNERS @SonarSource/quality-dotnet-squad ================================================ FILE: .github/GitHub.shproj ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/1-FalsePositive.yml ================================================ name: Report False Positive description: Analysis is raising an incorrect issue. title: "Fix Sxxxx FP: " body: - type: textarea attributes: label: Description description: Explain the issue and context, why it should not be raised, and do not forget to mention the rule ID. placeholder: Explain the issue and context, why it should not be raised, and do not forget to mention the rule ID. validations: required: true - type: textarea attributes: label: Reproducer description: Minimal code snippet which reproduces the problem. value: | ``` ``` validations: required: true - type: input attributes: label: Product and Version description: What is the product name and version that you are using? SonarQube Cloud, SonarQube Server, SonarQube for Visual Studio, NuGet, etc. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/2-FalseNegative.yml ================================================ name: Report False Negative description: Analysis is not raising an issue where it should. title: "Fix Sxxxx FN: " body: - type: textarea attributes: label: Description description: Explain the context, why the issue should be raised, and do not forget to mention rule ID. placeholder: Explain the context, why the issue should be raised, and do not forget to mention rule ID. validations: required: true - type: textarea attributes: label: Reproducer description: Minimal code snippet which reproduces the problem. value: | ``` ``` validations: required: true - type: input attributes: label: Product and Version description: What is the product name and version that you are using? SonarQube Cloud, SonarQube Server, SonarQube for Visual Studio, NuGet, etc. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/3-AD0001.yml ================================================ name: Report AD0001 description: Analysis is throwing an AD0001 error. title: "Fix AD0001: " body: - type: textarea attributes: label: Description placeholder: Explain the issue you are facing, including the full error message. validations: required: true - type: textarea attributes: label: Reproducer description: Minimal code snippet which reproduces the problem, if possible. value: | ``` ``` validations: required: false # Optional for AD0001 as those can be hard to find but important to report - type: input attributes: label: Product and Version description: What is the product name and version that you are using? SonarQube Cloud, SonarQube Server, SonarQube for Visual Studio, NuGet, etc. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/4-NewRule.yml ================================================ name: Suggest New Rule description: Suggest an idea for a new rule that does not exist yet. title: "New Rule Idea: " body: - type: markdown attributes: value: | Take a look at [existing New Rule ideas](https://github.com/SonarSource/sonar-dotnet/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Rule%20Idea%22&page=1) to avoid duplications. Leave a comment on rule ideas that you'd like to be implemented. - type: textarea attributes: label: Description description: Explain why the rule is needed and what it should detect. placeholder: Write rule description validations: required: true - type: textarea attributes: label: Noncompliant code snippet description: Minimal code snippet which illustrates what the issue should detect. value: | ``` ``` validations: required: true - type: textarea attributes: label: Compliant code snippet description: Minimal code snippet which illustrates the expected fixed code. value: | ``` ``` validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Request support or report a bug using SonarQube Cloud. url: https://community.sonarsource.com/c/sc/9 about: Community Forum - SonarQube Cloud. - name: Request support or report a bug using SonarQube Server / Community Build. url: https://community.sonarsource.com/c/sq/10 about: Community Forum - SonarQube Server / Community Build. - name: Request support or report a bug using SonarQube for IDE. url: https://community.sonarsource.com/c/sl/11 about: Community Forum - SonarQube for IDE. ================================================ FILE: .github/workflows/LabelIssue.yml ================================================ name: Issue labeled on: issues: types: ["labeled"] jobs: CreateJiraIssue_job: name: Create Jira issue runs-on: github-ubuntu-latest-s permissions: id-token: write issues: write steps: - id: secrets uses: SonarSource/vault-action-wrapper@v3 with: secrets: | development/kv/data/jira user | JIRA_USER; development/kv/data/jira token | JIRA_TOKEN; - uses: sonarsource/gh-action-lt-backlog/ImportIssue@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} jira-user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }} jira-token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }} jira-project: NET ================================================ FILE: .github/workflows/SlackNotification.yml ================================================ --- name: Slack Notifications on: workflow_dispatch: # for testing check_suite: types: [completed] jobs: notify: runs-on: github-ubuntu-latest-s # Public GitHub hosted runner required, Self-Hosted runners do not support Docker-in-Docker permissions: id-token: write checks: read if: github.event.check_suite.head_branch == 'master' && !contains(fromJson('["SUCCESS", "NEUTRAL", "SKIPPED"]'), github.event.check_suite.conclusion) steps: - name: Vault Secrets id: secrets uses: SonarSource/vault-action-wrapper@v3 with: secrets: development/kv/data/slack token | SLACK_TOKEN; - name: Check run details id: failedRun env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: pwsh run: | $SuccessConclusions = @("SUCCESS", "NEUTRAL", "SKIPPED") $Event = Get-Content -Path $env:GITHUB_EVENT_PATH | ConvertFrom-Json -Depth 100 $Request = gh api $Event.check_suite.check_runs_url $FailedRuns = $Request | ConvertFrom-Json | select -ExpandProperty check_runs | where { $SuccessConclusions -NotContains $_.conclusion } | foreach { "* [$($_.name)]($($_.details_url))" } $OFS = [Environment]::NewLine # https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.5#ofs $EOF = (New-Guid).Guid # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#multiline-strings $Message=@" message<<$EOF $($Event.check_suite.app.name ?? 'Pipeline') in [$($Event.repository.name)]($($Event.repository.html_url)) failed ($($Event.check_suite.conclusion)) on $($Event.check_suite.head_branch): $($Event.check_suite.head_commit.message -replace '[\n\r].*') $FailedRuns $EOF "@ $Message $Message >> $env:GITHUB_OUTPUT - name: Slack Notification uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 if: always() env: SLACK_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_TOKEN }} SLACK_CHANNEL: squad-dotnet # for testing: notification_tester SLACK_TITLE: Build failed SLACK_MESSAGE: ${{ steps.failedRun.outputs.message }} SLACK_USERNAME: NotifierBot SLACK_COLOR: danger MSG_MINIMAL: true SLACKIFY_MARKDOWN: true SLACK_FOOTER: ' ' SLACK_MSG_AUTHOR: ' ' SLACK_ICON_EMOJI: dotnet ================================================ FILE: .gitignore ================================================ # Maven target/ # IntelliJ IDEA *.iws *.iml *.ipr .idea/ # Eclipse .classpath .project .settings # ---- Mac OS X .DS_Store Icon? # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # ---- Windows # Windows image file caches Thumbs.db # Folder config file Desktop.ini # User-specific files + Visual Studio files *.suo *.user *.sln.docstates *.psess *.vsp .vs/ # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ x64/ bld/ [Bb]in/ [Oo]bj/ # NuGet packages/ *.nupkg # Roslyn *.sln.ide/ # Sonar .sonar/ .sonarqube/ # Product of its\projects\ScannerCli .scannerwork/ # MSTest test results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # rule-api temp artifacts *.restext .generated/ # Analyzer binaries analyzers/packaging/binaries/ analyzers/packaging/internal/ # Actual SARIF files /private/analyzers/its/actual/ # MSBuild & other output /private/analyzers/its/output/ # Temporary diff folder /private/analyzers/its/diff/ /its/projects/WebApplication/.scannerwork/report-task.txt /its/projects/WebApplication/.scannerwork/.sonar_lock # ITs.JsonParser expects to be run from "sonar-dotnet-enterprise/private/analyzers/its". # Feel free to configure your local launchSettings.json. analyzers/src/ITs.JsonParser/Properties/launchSettings.json # Claude Code .claude/* CLAUDE.local.md # Codex .codex # Sonar Code Context .sonar-code-context #Verify https://github.com/VerifyTests/Verify/blob/main/docs/wiz/Windows_VisualStudio_Cli_MSTest_AzureDevOps.md#conventions *.received.* ================================================ FILE: .globalconfig ================================================ # top-most GlobalConfig file, hierarchy search will stop in this file root = true # Issues produced from a source generators needs to be in global config, see https://github.com/dotnet/roslyn/issues/81479 dotnet_diagnostic.CS8784.severity = error # Do not hide root cause for: Generator 'xxx' failed to initialize. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'xxx' with message 'xxx' dotnet_diagnostic.CS8785.severity = error # Do not hide root cause for: Generator 'xxx' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'xxx' with message 'xxx' ================================================ FILE: LICENSE.txt ================================================ SONAR Source-Available License v1.0 Last Updated November 13, 2024 1. DEFINITIONS "Agreement" means this Sonar Source-Available License v1.0 "Competing" means marketing a product or service as a substitute for the functionality or value of SonarQube. A product or service may compete regardless of how it is designed or deployed. For example, a product or service may compete even if it provides its functionality via any kind of interface (including services, libraries, or plug-ins), even if it is ported to a different platform or programming language, and even if it is provided free of charge. "Contribution" means: a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution "originates" from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. "Contributor" means any person or entity that Distributes the Program. "Derivative Works" shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. "Distribute" means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. "Licensed Patents" mean patent claims licensable by a Contributor that are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Modified Works" shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity, any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. "Non-competitive Purpose" means any purpose except for (a) providing to others any product or service that includes or offers the same or substantially similar functionality as SonarQube, (b) Competing with SonarQube, and/or (c) employing, using, or engaging artificial intelligence technology that is not part of the Program to ingest, interpret, analyze, train on, or interact with the data provided by the Program, or to engage with the Program in any manner. "Notices" means any legal statements or attributions included with the Program, including, without limitation, statements concerning copyright, patent, trademark, disclaimers of warranty, or limitations of liability "Program" means the Contributions Distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including Contributors. "SonarQube" means an open-source or commercial edition of software offered by SonarSource that is branded "SonarQube". "SonarSource" means SonarSource SA, a Swiss company registered in Switzerland under UID No. CHE-114.587.664. "Source Code" means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license, for any Non-competitive Purpose, to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents, for any Non-competitive Purpose, to make, use, sell, offer to sell, import, and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations that include the Contribution. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third-party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS 3.1 If a Contributor Distributes the Program in any form, then the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and inform Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and 3.2 When the Program is Distributed as Source Code: a) it must be made available under this Agreement, and b) a copy of this Agreement must be included with each copy of the Program. 3.3 Contributors may not remove or alter any Notices contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate Notices. 4. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 5. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient’s patent(s), then such Recipient’s rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient’s rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient’s rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient’s obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel, or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. ================================================ FILE: NOTICE.txt ================================================ Copyright (C) SonarSource Sàrl mailto:info AT sonarsource DOT com This product includes software developed at SonarSource (https://sonarsource.com/). See LICENSE.txt file for details of the applicable license. For further legal information, see https://sonarsource.com/legal/ ================================================ FILE: README.md ================================================ # Code Quality and Security for C\# and VB.NET [![Build Status](https://dev.azure.com/sonarsource/DotNetTeam%20Project/_apis/build/status/Sonar.Net?branchName=master)](https://dev.azure.com/sonarsource/DotNetTeam%20Project/_build/latest?definitionId=77&branchName=master) |Product|Quality Gate|Coverage| |:--:|:--:|:--:| |Analyzer|[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=sonaranalyzer-dotnet&metric=alert_status)](https://sonarcloud.io/dashboard?id=sonaranalyzer-dotnet)|[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=sonaranalyzer-dotnet&metric=coverage)](https://sonarcloud.io/component_measures?id=sonaranalyzer-dotnet&metric=coverage)| |Plugin|[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=org.sonarsource.dotnet%3Asonar-dotnet&metric=alert_status)](https://sonarcloud.io/dashboard?id=org.sonarsource.dotnet%3Asonar-dotnet)|[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=org.sonarsource.dotnet%3Asonar-dotnet&metric=coverage)](https://sonarcloud.io/component_measures?id=org.sonarsource.dotnet%3Asonar-dotnet&metric=coverage)| [Static analysis](https://en.wikipedia.org/wiki/Static_program_analysis) of C# and VB.NET languages in [SonarQube server](https://www.sonarsource.com/products/sonarqube), [SonarQube cloud](https://www.sonarsource.com/products/sonarcloud) and [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint) code quality and security products. These Roslyn analyzers allow you to deliver code with integrated code quality and security that is safe, reliable and maintainable by helping you find and correct bugs, vulnerabilities and code smells in your codebase. ## Features * 470+ C# rules and 210+ VB.​NET rules * Metrics (cognitive complexity, duplications, number of lines, etc.) * Import of [test coverage reports](https://community.sonarsource.com/t/9871) from Visual Studio Code Coverage, dotCover, OpenCover, Coverlet, Altcover. * Import of third-party Roslyn Analyzers results * Support for [custom rules](https://github.com/SonarSource/sonarqube-roslyn-sdk) ## Useful public resources * [Project homepage](https://redirect.sonarsource.com/plugins/csharp.html) * [Issue tracking](./docs/issues.md) ### Nuget.org packages * [SonarAnalyzer.CSharp](https://www.nuget.org/packages/SonarAnalyzer.CSharp/) * [SonarAnalyzer.VisualBasic](https://www.nuget.org/packages/SonarAnalyzer.VisualBasic/) ### Integration with SonarQube * [Analyze projects with SonarScanner for .NET](https://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html) * [Importing code coverage](https://community.sonarsource.com/t/9871) * [SonarQube and the code coverage](https://community.sonarsource.com/t/4725) ## Do you have a question or feedback? * Contact us on [our Community Forum](https://community.sonarsource.com/) to provide feedback, ask for help, and request new rules or features. * [Create a GitHub Issue](https://github.com/SonarSource/sonar-dotnet/issues/new/choose) if you've found a bug, False-Positive or False-Negative. ## Get started * [Building, testing and debugging the .NET analyzer](./docs/contributing-analyzer.md) * [Building, testing and debugging the Java plugin](./docs/contributing-plugin.md) * [How to re-generate NuGet lock files](./docs/regenerate-lock-files.md) * [Using the rspec.ps1 script](./scripts/rspec/README.md) ## How to contribute There are many ways you can contribute to the `sonar-dotnet` project. When contributing, please respect our [Code of Conduct](./docs/code-of-conduct.md). ### Join the discussions One of the easiest ways to contribute is to share your feedback with us (see [give feedback](#do-you-have-a-question-or-feedback)) and also answer questions from [our community forum](https://community.sonarsource.com/). You can also monitor the activity on this repository (opened issues, opened PRs) to get more acquainted with what we do. ### Pull Request (PR) If you want to fix [an issue](https://github.com/SonarSource/sonar-dotnet/issues), please read the [Get started](#get-started) pages first and make sure that you follow [our coding style](./docs/coding-style.md). We suggest avoiding the implementation of new rules, as a specification process is required first. Before submitting the PR, make sure [all tests](./docs/contributing-analyzer.md#running-unit-tests) are passing (all checks must be green). If you did not sign the Contributor License Agreement in the past, please let us know in the PR your user handle from our [Community Forum](https://community.sonarsource.com/). We will arrange the signing via private message. Note: Our CI does not get automatically triggered on the PRs from external contributors. A member of our team will review the code and trigger the CI on demand by adding a comment on the PR (see [Azure Pipelines Comment triggers docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#comment-triggers)): - `/azp run Sonar.Net` - It will run the full pipeline, including plugin tests and promotion ## Custom Rules To request new rules, Contact us on [our Community Forum](https://community.sonarsource.com/c/suggestions/). If you have an idea for a rule but you are not sure that everyone needs it, you can implement your own Roslyn analyzer. - You can start with [this tutorial from Microsoft](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix) to write an analyzer. - All Roslyn-based issues are picked up by the [SonarScanner for .NET](https://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html) and pushed to SonarQube as external issues. - Also check out [SonarQube Roslyn SDK](https://github.com/SonarSource-VisualStudio/sonarqube-roslyn-sdk) to embed your Roslyn analyzer in a SonarQube plugin, if you want to manage your rules from SonarQube. ## Configuring Rules ### SonarQube for IDE The easiest way is to configure a Quality Profile in SonarQube. Use SonarQube for IDE Connected Mode to connect to SonarQube Server or Cloud. ### Standalone NuGet The rules from standalone NuGet packages can be enabled or disabled in the same way as the other analyzers based on Roslyn, by using the `.globalconfig` or `.editorconfig` files. See: https://learn.microsoft.com/en-us/visualstudio/code-quality/use-roslyn-analyzers?view=vs-2022#set-rule-severity-in-an-editorconfig-file If the rules are parameterized, the parameter values can be changed using `SonarLint.xml` additional files. The first step is to create a new file, named `SonarLint.xml`, that has the following structure: ```xml sonar.cs.analyzeGeneratedCode false S107 max 2 ``` Then, update the projects to include this additional file: ```xml ``` ## Security Issues If you believe you have discovered a security vulnerability in Sonar's products, please check [this document](./SECURITY.md). ## License Copyright SonarSource Sàrl. Licensed under the [SONAR Source-Available License v1.0](https://www.sonarsource.com/license/ssal/) ================================================ FILE: SECURITY.md ================================================ # Reporting Security Issues A mature software vulnerability treatment process is a cornerstone of a robust information security management system. Contributions from the community play an important role in the evolution and security of our products, and in safeguarding the security and privacy of our users. If you believe you have discovered a security vulnerability in Sonar's products, we encourage you to report it immediately. To responsibly report a security issue, please email us at [security@sonarsource.com](mailto:security@sonarsource.com). Sonar’s security team will acknowledge your report, guide you through the next steps, or request additional information if necessary. Customers with a support contract can also report the vulnerability directly through the support channel. For security vulnerabilities found in third-party libraries, please also contact the library's owner or maintainer directly. ## Responsible Disclosure Policy For more information about disclosing a security vulnerability to Sonar, please refer to our community post: [Responsible Vulnerability Disclosure](https://community.sonarsource.com/t/9317). ================================================ FILE: analyzers/.runsettings ================================================ 0 ================================================ FILE: analyzers/CI.NuGet.Config ================================================ Youssef1313;protobuf-packages;Microsoft;sharwell;meirb;dotnetfoundation;castleproject;jonorossi;onovotny;fluentassertions;SteveGilham;jamesnk;commandlineparser;grpc-packages;Fody;NSubstitute;jetbrains;simoncropp ================================================ FILE: analyzers/CodeAnalysis.targets ================================================ all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive stylecop.json ================================================ FILE: analyzers/Common.targets ================================================ 14 false true $(IntermediateOutputPath)generated true false true SonarSource Sàrl $(ShortVersion) $(FullVersion) Version:$(FullVersion) Branch:$(Branch) Sha1:$(Sha1) Copyright © SonarSource Sàrl SonarAnalyzer SonarLint, SonarQube, SonarSource en false true true ================================================ FILE: analyzers/README.md ================================================ # SonarAnalyzer for C# and Visual Basic .NET This folder contains the code specific to the Roslyn based analyzer for C# and VB.NET. To get more information please read the repository main [README](../README.md). ================================================ FILE: analyzers/SonarAnalyzer.sln ================================================ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.0.11205.157 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B7233F78-E142-4882-B084-7C83BE472109}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.Core", "src\SonarAnalyzer.Core\SonarAnalyzer.Core.csproj", "{8A8A663E-1318-4361-BC97-2E63666FEADB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.Test", "tests\SonarAnalyzer.Test\SonarAnalyzer.Test.csproj", "{E11606CA-A186-4FEE-BA30-B1688747CD1A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RuleDescriptorGenerator", "src\RuleDescriptorGenerator\RuleDescriptorGenerator.csproj", "{07E31F39-7419-4B4E-998E-C2BF1A6BB91C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.CSharp", "src\SonarAnalyzer.CSharp\SonarAnalyzer.CSharp.csproj", "{CA8EEC07-8775-42E3-91EB-E51F4DB72A48}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.VisualBasic", "src\SonarAnalyzer.VisualBasic\SonarAnalyzer.VisualBasic.csproj", "{7CFA8FA5-8842-4E89-BB90-39D5C0F20BA8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.CFG", "src\SonarAnalyzer.CFG\SonarAnalyzer.CFG.csproj", "{F766F556-CB91-408A-9149-EB963DE1B817}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SonarAnalyzer.Shared", "src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.shproj", "{892CF3FA-3BE2-42E8-A7E6-76AE72864DEC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.ShimLayer.CodeGeneration", "src\SonarAnalyzer.ShimLayer.CodeGeneration\SonarAnalyzer.ShimLayer.CodeGeneration.csproj", "{684F0AD7-BBC9-40C8-9E54-D9C2B57560D9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.SourceGenerators", "src\SonarAnalyzer.SourceGenerators\SonarAnalyzer.SourceGenerators.csproj", "{76D6EBB8-D2B0-41C1-8880-027EC78CB7FF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.TestFramework.Test", "tests\SonarAnalyzer.TestFramework.Test\SonarAnalyzer.TestFramework.Test.csproj", "{ADBE6CFE-980F-4D4F-8E25-E391581D291E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.TestFramework", "tests\SonarAnalyzer.TestFramework\SonarAnalyzer.TestFramework.csproj", "{4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.CSharp.Styling", "src\SonarAnalyzer.CSharp.Styling\SonarAnalyzer.CSharp.Styling.csproj", "{7F1A25AD-2EEF-4CF9-92D9-FE5B203EECE3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SonarAnalyzer.CSharp.Styling.Test", "tests\SonarAnalyzer.CSharp.Styling.Test\SonarAnalyzer.CSharp.Styling.Test.csproj", "{6F4F7666-8A0D-47FE-A312-56010C4CE082}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.ShimLayer.Lightup", "src\SonarAnalyzer.ShimLayer.Lightup\SonarAnalyzer.ShimLayer.Lightup.csproj", "{93AF346B-C43A-4A42-AD5B-72681367180E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.CSharp.Core", "src\SonarAnalyzer.CSharp.Core\SonarAnalyzer.CSharp.Core.csproj", "{6841D9C0-1B5D-4853-87A5-9D846A165948}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.VisualBasic.Core", "src\SonarAnalyzer.VisualBasic.Core\SonarAnalyzer.VisualBasic.Core.csproj", "{6273FCC8-CD1C-4C50-B1C8-CCA1A26BFB3C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.Core.Test", "tests\SonarAnalyzer.Core.Test\SonarAnalyzer.Core.Test.csproj", "{F97BFB85-443A-45D1-98AB-119BE9A1D121}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.CSharp.Core.Test", "tests\SonarAnalyzer.CSharp.Core.Test\SonarAnalyzer.CSharp.Core.Test.csproj", "{04CA2E3F-D46B-4178-B198-EB170C1685AC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.VisualBasic.Core.Test", "tests\SonarAnalyzer.VisualBasic.Core.Test\SonarAnalyzer.VisualBasic.Core.Test.csproj", "{5624BF83-AA2B-4D55-97FB-BB8B0158C92A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.ShimLayer.Generator", "src\SonarAnalyzer.ShimLayer.Generator\SonarAnalyzer.ShimLayer.Generator.csproj", "{3DB9C3E6-E845-F032-3E92-9F3D90FBF1A6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.ShimLayer", "src\SonarAnalyzer.ShimLayer\SonarAnalyzer.ShimLayer.csproj", "{F38DD14C-BD40-F98B-93CC-635BFD353558}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonarAnalyzer.ShimLayer.Generator.Test", "tests\SonarAnalyzer.ShimLayer.Generator.Test\SonarAnalyzer.ShimLayer.Generator.Test.csproj", "{ECAEE259-90AD-50B6-BA9C-110A0CC40D30}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8A8A663E-1318-4361-BC97-2E63666FEADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A8A663E-1318-4361-BC97-2E63666FEADB}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A8A663E-1318-4361-BC97-2E63666FEADB}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A8A663E-1318-4361-BC97-2E63666FEADB}.Release|Any CPU.Build.0 = Release|Any CPU {E11606CA-A186-4FEE-BA30-B1688747CD1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E11606CA-A186-4FEE-BA30-B1688747CD1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E11606CA-A186-4FEE-BA30-B1688747CD1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {E11606CA-A186-4FEE-BA30-B1688747CD1A}.Release|Any CPU.Build.0 = Release|Any CPU {07E31F39-7419-4B4E-998E-C2BF1A6BB91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07E31F39-7419-4B4E-998E-C2BF1A6BB91C}.Debug|Any CPU.Build.0 = Debug|Any CPU {07E31F39-7419-4B4E-998E-C2BF1A6BB91C}.Release|Any CPU.ActiveCfg = Release|Any CPU {07E31F39-7419-4B4E-998E-C2BF1A6BB91C}.Release|Any CPU.Build.0 = Release|Any CPU {CA8EEC07-8775-42E3-91EB-E51F4DB72A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CA8EEC07-8775-42E3-91EB-E51F4DB72A48}.Debug|Any CPU.Build.0 = Debug|Any CPU {CA8EEC07-8775-42E3-91EB-E51F4DB72A48}.Release|Any CPU.ActiveCfg = Release|Any CPU {CA8EEC07-8775-42E3-91EB-E51F4DB72A48}.Release|Any CPU.Build.0 = Release|Any CPU {7CFA8FA5-8842-4E89-BB90-39D5C0F20BA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7CFA8FA5-8842-4E89-BB90-39D5C0F20BA8}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CFA8FA5-8842-4E89-BB90-39D5C0F20BA8}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CFA8FA5-8842-4E89-BB90-39D5C0F20BA8}.Release|Any CPU.Build.0 = Release|Any CPU {F766F556-CB91-408A-9149-EB963DE1B817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F766F556-CB91-408A-9149-EB963DE1B817}.Debug|Any CPU.Build.0 = Debug|Any CPU {F766F556-CB91-408A-9149-EB963DE1B817}.Release|Any CPU.ActiveCfg = Release|Any CPU {F766F556-CB91-408A-9149-EB963DE1B817}.Release|Any CPU.Build.0 = Release|Any CPU {684F0AD7-BBC9-40C8-9E54-D9C2B57560D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {684F0AD7-BBC9-40C8-9E54-D9C2B57560D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {684F0AD7-BBC9-40C8-9E54-D9C2B57560D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {684F0AD7-BBC9-40C8-9E54-D9C2B57560D9}.Release|Any CPU.Build.0 = Release|Any CPU {76D6EBB8-D2B0-41C1-8880-027EC78CB7FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {76D6EBB8-D2B0-41C1-8880-027EC78CB7FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {76D6EBB8-D2B0-41C1-8880-027EC78CB7FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {76D6EBB8-D2B0-41C1-8880-027EC78CB7FF}.Release|Any CPU.Build.0 = Release|Any CPU {ADBE6CFE-980F-4D4F-8E25-E391581D291E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ADBE6CFE-980F-4D4F-8E25-E391581D291E}.Debug|Any CPU.Build.0 = Debug|Any CPU {ADBE6CFE-980F-4D4F-8E25-E391581D291E}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADBE6CFE-980F-4D4F-8E25-E391581D291E}.Release|Any CPU.Build.0 = Release|Any CPU {4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8}.Debug|Any CPU.Build.0 = Debug|Any CPU {4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8}.Release|Any CPU.Build.0 = Release|Any CPU {7F1A25AD-2EEF-4CF9-92D9-FE5B203EECE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F1A25AD-2EEF-4CF9-92D9-FE5B203EECE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F1A25AD-2EEF-4CF9-92D9-FE5B203EECE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F1A25AD-2EEF-4CF9-92D9-FE5B203EECE3}.Release|Any CPU.Build.0 = Release|Any CPU {6F4F7666-8A0D-47FE-A312-56010C4CE082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F4F7666-8A0D-47FE-A312-56010C4CE082}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F4F7666-8A0D-47FE-A312-56010C4CE082}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F4F7666-8A0D-47FE-A312-56010C4CE082}.Release|Any CPU.Build.0 = Release|Any CPU {93AF346B-C43A-4A42-AD5B-72681367180E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93AF346B-C43A-4A42-AD5B-72681367180E}.Debug|Any CPU.Build.0 = Debug|Any CPU {93AF346B-C43A-4A42-AD5B-72681367180E}.Release|Any CPU.ActiveCfg = Release|Any CPU {93AF346B-C43A-4A42-AD5B-72681367180E}.Release|Any CPU.Build.0 = Release|Any CPU {6841D9C0-1B5D-4853-87A5-9D846A165948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6841D9C0-1B5D-4853-87A5-9D846A165948}.Debug|Any CPU.Build.0 = Debug|Any CPU {6841D9C0-1B5D-4853-87A5-9D846A165948}.Release|Any CPU.ActiveCfg = Release|Any CPU {6841D9C0-1B5D-4853-87A5-9D846A165948}.Release|Any CPU.Build.0 = Release|Any CPU {6273FCC8-CD1C-4C50-B1C8-CCA1A26BFB3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6273FCC8-CD1C-4C50-B1C8-CCA1A26BFB3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6273FCC8-CD1C-4C50-B1C8-CCA1A26BFB3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6273FCC8-CD1C-4C50-B1C8-CCA1A26BFB3C}.Release|Any CPU.Build.0 = Release|Any CPU {F97BFB85-443A-45D1-98AB-119BE9A1D121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F97BFB85-443A-45D1-98AB-119BE9A1D121}.Debug|Any CPU.Build.0 = Debug|Any CPU {F97BFB85-443A-45D1-98AB-119BE9A1D121}.Release|Any CPU.ActiveCfg = Release|Any CPU {F97BFB85-443A-45D1-98AB-119BE9A1D121}.Release|Any CPU.Build.0 = Release|Any CPU {04CA2E3F-D46B-4178-B198-EB170C1685AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {04CA2E3F-D46B-4178-B198-EB170C1685AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {04CA2E3F-D46B-4178-B198-EB170C1685AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {04CA2E3F-D46B-4178-B198-EB170C1685AC}.Release|Any CPU.Build.0 = Release|Any CPU {5624BF83-AA2B-4D55-97FB-BB8B0158C92A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5624BF83-AA2B-4D55-97FB-BB8B0158C92A}.Debug|Any CPU.Build.0 = Debug|Any CPU {5624BF83-AA2B-4D55-97FB-BB8B0158C92A}.Release|Any CPU.ActiveCfg = Release|Any CPU {5624BF83-AA2B-4D55-97FB-BB8B0158C92A}.Release|Any CPU.Build.0 = Release|Any CPU {3DB9C3E6-E845-F032-3E92-9F3D90FBF1A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3DB9C3E6-E845-F032-3E92-9F3D90FBF1A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3DB9C3E6-E845-F032-3E92-9F3D90FBF1A6}.Release|Any CPU.ActiveCfg = Release|Any CPU {3DB9C3E6-E845-F032-3E92-9F3D90FBF1A6}.Release|Any CPU.Build.0 = Release|Any CPU {F38DD14C-BD40-F98B-93CC-635BFD353558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F38DD14C-BD40-F98B-93CC-635BFD353558}.Debug|Any CPU.Build.0 = Debug|Any CPU {F38DD14C-BD40-F98B-93CC-635BFD353558}.Release|Any CPU.ActiveCfg = Release|Any CPU {F38DD14C-BD40-F98B-93CC-635BFD353558}.Release|Any CPU.Build.0 = Release|Any CPU {ECAEE259-90AD-50B6-BA9C-110A0CC40D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ECAEE259-90AD-50B6-BA9C-110A0CC40D30}.Debug|Any CPU.Build.0 = Debug|Any CPU {ECAEE259-90AD-50B6-BA9C-110A0CC40D30}.Release|Any CPU.ActiveCfg = Release|Any CPU {ECAEE259-90AD-50B6-BA9C-110A0CC40D30}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {E11606CA-A186-4FEE-BA30-B1688747CD1A} = {B7233F78-E142-4882-B084-7C83BE472109} {ADBE6CFE-980F-4D4F-8E25-E391581D291E} = {B7233F78-E142-4882-B084-7C83BE472109} {4C1DB1C4-C1FC-4AA1-B855-0D1948E68FB8} = {B7233F78-E142-4882-B084-7C83BE472109} {6F4F7666-8A0D-47FE-A312-56010C4CE082} = {B7233F78-E142-4882-B084-7C83BE472109} {F97BFB85-443A-45D1-98AB-119BE9A1D121} = {B7233F78-E142-4882-B084-7C83BE472109} {04CA2E3F-D46B-4178-B198-EB170C1685AC} = {B7233F78-E142-4882-B084-7C83BE472109} {5624BF83-AA2B-4D55-97FB-BB8B0158C92A} = {B7233F78-E142-4882-B084-7C83BE472109} {ECAEE259-90AD-50B6-BA9C-110A0CC40D30} = {B7233F78-E142-4882-B084-7C83BE472109} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4259CA71-C565-42DD-8D58-F59819A11065} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.projitems*{6273fcc8-cd1c-4c50-b1c8-cca1a26bfb3c}*SharedItemsImports = 5 src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.projitems*{6841d9c0-1b5d-4853-87a5-9d846a165948}*SharedItemsImports = 5 src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.projitems*{7cfa8fa5-8842-4e89-bb90-39d5c0f20ba8}*SharedItemsImports = 5 src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.projitems*{892cf3fa-3be2-42e8-a7e6-76ae72864dec}*SharedItemsImports = 13 src\SonarAnalyzer.Shared\SonarAnalyzer.Shared.projitems*{ca8eec07-8775-42e3-91eb-e51f4db72a48}*SharedItemsImports = 5 EndGlobalSection EndGlobal ================================================ FILE: analyzers/Version.targets ================================================  10.26 0 .0 $(ShortVersion)$(ShortVersionSuffix).$(BuildNumber) ================================================ FILE: analyzers/packaging/Licenses/THIRD_PARTY_LICENSES/Google.Protobuf-LICENSE.txt ================================================ Copyright 2008 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Code generated by the Protocol Buffer compiler is owned by the owner of the input file used when generating it. This code is not standalone and requires a support library to be linked with it. This support library is itself covered by the above license. ================================================ FILE: analyzers/packaging/Licenses/THIRD_PARTY_LICENSES/Roslyn-LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) .NET Foundation and Contributors All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: analyzers/packaging/Licenses/THIRD_PARTY_LICENSES/StyleCop.Analyzers-LICENSE.txt ================================================ MIT License Copyright (c) Tunnel Vision Laboratories, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: analyzers/packaging/SonarAnalyzer.CFG.nuspec ================================================  SonarAnalyzer.CFG $Version$ CFG library for SonarAnalyzer SonarSource SonarSource licenses\LICENSE.txt false SonarSource CFG library https://github.com/SonarSource/sonar-dotnet/releases/tag/$Version$ en-US Copyright © SonarSource Sàrl ================================================ FILE: analyzers/packaging/SonarAnalyzer.CSharp.Styling.nuspec ================================================  SonarAnalyzer.CSharp.Styling $Version$ Internal Sonar styling analyzer for C# SonarSource SonarSource licenses\LICENSE.txt https://redirect.sonarsource.com/doc/sonar-visualstudio.html images\sonarsource_64.png false Internal Sonar styling analyzer for C# This package contains a set of coding style rules to follow in Sonar code base. Coding style changes as well as breaking changes will appear between minor versions without prior notice. We do not provide support for this package. https://github.com/SonarSource/sonar-dotnet/releases/tag/$Version$ en-US Copyright © SonarSource Sàrl true ================================================ FILE: analyzers/packaging/SonarAnalyzer.CSharp.nuspec ================================================  SonarAnalyzer.CSharp $Version$ SonarAnalyzer for C# SonarSource SonarSource licenses\LICENSE.txt https://redirect.sonarsource.com/doc/sonar-visualstudio.html images\sonarsource_64.png false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarQube for IDE (Visual Studio, Rider), which is a free extension that can be used standalone or with SonarQube (Server, Cloud). Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarQube for IDE (Visual Studio, Rider, see https://www.sonarsource.com/products/sonarlint), which is a free extension that can be used standalone or with SonarQube (Server, Cloud, see: https://www.sonarsource.com/products/sonarqube/ and https://www.sonarsource.com/products/sonarcloud/). https://github.com/SonarSource/sonar-dotnet/releases/tag/$Version$ en-US Copyright © SonarSource Sàrl Roslyn Analyzer Analyzers Refactoring CodeAnalysis CleanCode Clean Code Sonar SonarAnalyzer Dotnet CSharp CodeQuality CodeReview StaticCodeAnalysis SonarQube SonarCloud SonarLint SonarQubeServer SonarQubeCloud SonarQubeIDE true ================================================ FILE: analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec ================================================  SonarAnalyzer.VisualBasic $Version$ SonarAnalyzer for Visual Basic SonarSource SonarSource licenses\LICENSE.txt https://redirect.sonarsource.com/doc/sonar-visualstudio.html images\sonarsource_64.png false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarQube for IDE (Visual Studio, Rider), which is a free extension that can be used standalone or with SonarQube (Server, Cloud). Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarQube for IDE (Visual Studio, Rider, see https://www.sonarsource.com/products/sonarlint), which is a free extension that can be used standalone or with SonarQube (Server, Cloud, see: https://www.sonarsource.com/products/sonarqube/ and https://www.sonarsource.com/products/sonarcloud/). https://github.com/SonarSource/sonar-dotnet/releases/tag/$Version$ en-US Copyright © SonarSource Sàrl Roslyn Analyzer Analyzers Refactoring CodeAnalysis CleanCode Clean Code Sonar SonarAnalyzer Dotnet VisualBasic CodeQuality CodeReview StaticCodeAnalysis SonarQube SonarCloud SonarLint true ================================================ FILE: analyzers/packaging/tools-cs/install.ps1 ================================================ param($installPath, $toolsPath, $package, $project) $invalidVsVersion = $false if ([Version]$project.DTE.Version -lt [Version]"14.0") { $invalidVsVersion = $true } if ($project.DTE.Version -eq '14.0') { $currentAppDomainBaseDir = [System.AppDomain]::CurrentDomain.BaseDirectory $path = Join-Path $currentAppDomainBaseDir "msenv.dll" if (Test-Path $path) { $versionInfo = (Get-Item $path).VersionInfo $fullVersion = New-Object System.Version -ArgumentList @( $versionInfo.FileMajorPart $versionInfo.FileMinorPart $versionInfo.FileBuildPart $versionInfo.FilePrivatePart ) $minVersion = [version]"14.0.25420.00" if ($fullVersion -lt $minVersion) { $invalidVsVersion = $true } } else { $invalidVsVersion = $true } } if ($invalidVsVersion) { throw 'This package can only be installed on Visual Studio 2015 Update 3 or later.' } if ($project.Object.AnalyzerReferences -eq $null) { throw 'This package cannot be installed without an analyzer reference.' } if ($project.Type -ne "C#") { throw 'This package can only be installed on C# projects.' } $analyzersPath = Split-Path -Path $toolsPath -Parent $analyzersPath = Join-Path $analyzersPath "analyzers" $analyzerFilePath = Join-Path $analyzersPath "SonarAnalyzer.CSharp.dll" $project.Object.AnalyzerReferences.Add($analyzerFilePath) ================================================ FILE: analyzers/packaging/tools-cs/uninstall.ps1 ================================================ param($installPath, $toolsPath, $package, $project) if ($project.Type -ne "C#") { return } if ([Version]$project.DTE.Version -lt [Version]"14.0") { return } if ($project.Object.AnalyzerReferences -eq $null) { return } $analyzersPath = Split-Path -Path $toolsPath -Parent $analyzersPath = Join-Path $analyzersPath "analyzers" $analyzerFilePath = Join-Path $analyzersPath "SonarAnalyzer.CSharp.dll" try { $project.Object.AnalyzerReferences.Remove($analyzerFilePath) } catch { } ================================================ FILE: analyzers/packaging/tools-vbnet/install.ps1 ================================================ param($installPath, $toolsPath, $package, $project) $invalidVsVersion = $false if ([Version]$project.DTE.Version -lt [Version]"14.0") { $invalidVsVersion = $true } if ($project.DTE.Version -eq '14.0') { $currentAppDomainBaseDir = [System.AppDomain]::CurrentDomain.BaseDirectory $path = Join-Path $currentAppDomainBaseDir "msenv.dll" if (Test-Path $path) { $versionInfo = (Get-Item $path).VersionInfo $fullVersion = New-Object System.Version -ArgumentList @( $versionInfo.FileMajorPart $versionInfo.FileMinorPart $versionInfo.FileBuildPart $versionInfo.FilePrivatePart ) $minVersion = [version]"14.0.25420.00" if ($fullVersion -lt $minVersion) { $invalidVsVersion = $true } } else { $invalidVsVersion = $true } } if ($invalidVsVersion) { throw 'This package can only be installed on Visual Studio 2015 Update 3 or later.' } if ($project.Object.AnalyzerReferences -eq $null) { throw 'This package cannot be installed without an analyzer reference.' } if ($project.Type -ne "VB.NET") { throw 'This package can only be installed on VB.NET projects.' } $analyzersPath = Split-Path -Path $toolsPath -Parent $analyzersPath = Join-Path $analyzersPath "analyzers" $analyzerFilePath = Join-Path $analyzersPath "SonarAnalyzer.VisualBasic.dll" $project.Object.AnalyzerReferences.Add($analyzerFilePath) ================================================ FILE: analyzers/packaging/tools-vbnet/uninstall.ps1 ================================================ param($installPath, $toolsPath, $package, $project) if ($project.Type -ne "VB.NET") { return } if ([Version]$project.DTE.Version -lt [Version]"14.0") { return } if ($project.Object.AnalyzerReferences -eq $null) { return } $analyzersPath = Split-Path -Path $toolsPath -Parent $analyzersPath = Join-Path $analyzersPath "analyzers" $analyzerFilePath = Join-Path $analyzersPath "SonarAnalyzer.VisualBasic.dll" try { $project.Object.AnalyzerReferences.Remove($analyzerFilePath) } catch { } ================================================ FILE: analyzers/rspec/cs/S100.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently.

This rule raises an issue when a method or a property name is not PascalCased.

For example, the method

public int doSomething() {...} // Noncompliant

should be renamed to

public int DoSomething() {...}

Exceptions

void My_method_(){...} // Noncompliant, leading and trailing underscores are reported

void My_method(){...} // Compliant by exception

Resources

Documentation

Microsoft Capitalization Conventions

================================================ FILE: analyzers/rspec/cs/S100.json ================================================ { "title": "Methods and properties should be named in PascalCase", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-100", "sqKey": "S100", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1006.html ================================================

Why is this an issue?

Default arguments are determined by the static type of the object.

class Base
{
    public virtual void Run(int distance = 42) { /* ... */ }
}

class Derived : Base
{
    public override void Run(int distance = 5) { /* ... */ }
}

Base x = new Base();
x.Run(); // Here the default value of distance is 42
Derived d = new Derived();
d.Run(); // Here the default value of distance is 5
Base b = new Derived();
b.Run(); // Here the default value of distance is 42, not 5

If a default argument is different for a parameter in an overriding method, the value used in the call will be different when calls are made via the base or derived object, which may be contrary to developer expectations.

Default parameter values in explicit interface implementations will be never used, because the static type of the object will always be the implemented interface. Thus, specifying default values in this case is confusing.

interface IRunner
{
    void Run(int distance = 42) { /* ... */ }
}

class Runner : IRunner
{
    void IRunner.Run(int distance = 5) { /* ... */ }
}

IRunner x = new Runner();
x.Run(); // Here the default value of distance is 42
Runner d = new Runner();
d.Run(); // This will not compile as the Run method is only visible through the specified interface

Noncompliant code example

using System;

public class Base
{
    public virtual void Write(int i = 42)
    {
        Console.WriteLine(i);
    }
}

public class Derived : Base
{
    public override void Write(int i = 5) // Noncompliant
    {
        Console.WriteLine(i);
    }
}

public class Program
{
    public static void Main()
    {
        var derived = new Derived();
        derived.Write(); // writes 5
        Print(derived); // writes 42; was that expected?
    }

    private static void Print(Base item)
    {
        item.Write();
    }
}

Compliant solution

using System;

public class Base
{
    public virtual void Write(int i = 42)
    {
        Console.WriteLine(i);
    }
}

public class Derived : Base
{
    public override void Write(int i = 42)
    {
        Console.WriteLine(i);
    }
}

public class Program
{
    public static void Main()
    {
        var derived = new Derived();
        derived.Write(); // writes 42
        Print(derived);  // writes 42
    }

    private static void Print(Base item)
    {
        item.Write();
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1006.json ================================================ { "title": "Method overrides should not change parameter defaults", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1006", "sqKey": "S1006", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S101.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently.

This rule raises an issue when a type name is not PascalCased.

For example, the classes

class my_class {...}
class SOMEName42 {...}

should be renamed to

class MyClass {...}
class SomeName42 {...}

Exceptions

class Some_Name___42 {...} // Compliant in tests
class Some_name___42 {...} // Noncompliant
class Some_Name_XC {...} // Noncompliant because of XC, should be Some_Name_Xc

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S101.json ================================================ { "title": "Types should be named in PascalCase", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-101", "sqKey": "S101", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S103.html ================================================

Why is this an issue?

Scrolling horizontally to see a full line of code lowers the code readability.

================================================ FILE: analyzers/rspec/cs/S103.json ================================================ { "title": "Lines should not be too long", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-103", "sqKey": "S103", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S104.html ================================================

Why is this an issue?

When a source file grows too much, it can accumulate numerous responsibilities and become challenging to understand and maintain.

Above a specific threshold, refactor the file into smaller files whose code focuses on well-defined tasks. Those smaller files will be easier to understand and test.

================================================ FILE: analyzers/rspec/cs/S104.json ================================================ { "title": "Files should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-104", "sqKey": "S104", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1048.html ================================================

Why is this an issue?

The finalizers are used to perform any necessary final clean-up when the garbage collector is collecting a class instance. The programmer has no control over when the finalizer is called; the garbage collector decides when to call it.

When creating a finalizer, it should never throw an exception, as there is a high risk of having the application terminated leaving unmanaged resources without a graceful cleanup.

The rule raises an issue on throw statements used in a finalizer.

How to fix it

Code examples

Noncompliant code example

class MyClass
{
    ~MyClass()
    {
        throw new NotImplementedException(); // Noncompliant: finalizer throws an exception
    }
}

Compliant solution

class MyClass
{
    ~MyClass()
    {
        // Compliant: finalizer does not throw an exception
    }
}

Going the extra mile

In general object finalization can be a complex and error-prone operation and should not be implemented except within the dispose pattern.

When cleaning up unmanaged resources, it is recommended to implement the dispose pattern or, to cover uncalled Dispose method by the consumer, implement SafeHandle.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1048.json ================================================ { "title": "Finalizers should not throw exceptions", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1048", "sqKey": "S1048", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S105.html ================================================

Why is this an issue?

The tab width can differ from one development environment to another. Using tabs may require other developers to configure their environment (text editor, preferences, etc.) to read source code.

That is why using spaces is preferable.

================================================ FILE: analyzers/rspec/cs/S105.json ================================================ { "title": "Tabulation characters should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-105", "sqKey": "S105", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S106.html ================================================

Why is this an issue?

In software development, logs serve as a record of events within an application, providing crucial insights for debugging. When logging, it is essential to ensure that the logs are:

Those requirements are not met if a program directly writes to the standard outputs (e.g., Console). That is why defining and using a dedicated logger is highly recommended.

Exceptions

The rule doesn’t raise an issue for:

Code examples

The following noncompliant code:

public class MyClass
{
    private void DoSomething()
    {
        // ...
        Console.WriteLine("My Message"); // Noncompliant
        // ...
    }
}

Could be replaced by:

public class MyClass
{
    private readonly ILogger _logger;

    // ...

    private void DoSomething()
    {
        // ...
        _logger.LogInformation("My Message");
        // ...
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S106.json ================================================ { "title": "Standard outputs should not be used directly to log anything", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-106", "sqKey": "S106", "scope": "Main", "securityStandards": { "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A9" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1066.html ================================================

Why is this an issue?

Nested code - blocks of code inside blocks of code - is eventually necessary, but increases complexity. This is why keeping the code as flat as possible, by avoiding unnecessary nesting, is considered a good practice.

Merging if statements when possible will decrease the nesting of the code and improve its readability.

Code like

if (condition1)
{
    if (condition2)           // Noncompliant
    {
        // ...
    }
}

Will be more readable as

if (condition1 && condition2) // Compliant
{
    // ...
}

How to fix it

If merging the conditions seems to result in a more complex code, extracting the condition or part of it in a named function or variable is a better approach to fix readability.

Code examples

Noncompliant code example

if (file != null)
{
  if (file.isFile() || file.isDirectory())    // Noncompliant
  {
    /* ... */
  }
}

Compliant solution

bool isFileOrDirectory(File file)
{
  return file.isFile() || file.isDirectory();
}

/* ... */

if (file != null && isFileOrDirectory(file))  // Compliant
{
  /* ... */
}
================================================ FILE: analyzers/rspec/cs/S1066.json ================================================ { "title": "Mergeable \"if\" statements should be combined", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1066", "sqKey": "S1066", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1067.html ================================================

Why is this an issue?

The complexity of an expression is defined by the number of &&, || and condition ? ifTrue : ifFalse operators it contains.

A single expression’s complexity should not become too high to keep the code readable.

Noncompliant code example

With the default threshold value of 3

if (((condition1 && condition2) || (condition3 && condition4)) && condition5) { ... }

Compliant solution

if ((MyFirstCondition() || MySecondCondition()) && MyLastCondition()) { ... }
================================================ FILE: analyzers/rspec/cs/S1067.json ================================================ { "title": "Expressions should not be too complex", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "3min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1067", "sqKey": "S1067", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S107.html ================================================

Why is this an issue?

Methods with a long parameter list are difficult to use because maintainers must figure out the role of each parameter and keep track of their position.

void SetCoordinates(int x1, int y1, int z1, int x2, int y2, int z2) // Noncompliant
{
   // ...
}

The solution can be to:

// Each function does a part of what the original setCoordinates function was doing, so confusion risks are lower
void SetOrigin(int x, int y, int z)
{
   // ...
}

void SetSize(int width, int height, int depth)
{
   //
}
// In geometry, Point is a logical structure to group data
readonly record struct Point(int X, int Y, int Z);

void SetCoordinates(Point p1, Point p2)
{
    // ...
}

This rule raises an issue when a method has more parameters than the provided threshold.

Exceptions

The rule does not count the parameters intended for a base class constructor.

With a maximum number of 4 parameters:

public class BaseClass
{
    public BaseClass(int param1)
    {
        // ...
    }
}

public class DerivedClass : BaseClass
{
    public DerivedClass(int param1, int param2, int param3, string param4, long param5) : base(param1) // Compliant by exception
    {
        // ...
    }
}
================================================ FILE: analyzers/rspec/cs/S107.json ================================================ { "title": "Methods should not have too many parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-107", "sqKey": "S107", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1075.html ================================================

Why is this an issue?

Hard-coding a URI makes it difficult to test a program for a variety of reasons:

In addition, hard-coded URIs can contain sensitive information, like IP addresses, and they should not be stored in the code.

For all those reasons, a URI should never be hard coded. Instead, it should be replaced by a customizable parameter.

Further, even if the elements of a URI are obtained dynamically, portability can still be limited if the path delimiters are hard-coded.

This rule raises an issue when URIs or path delimiters are hard-coded.

Exceptions

This rule does not raise an issue when an ASP.NET virtual path is passed as an argument to one of the following:

How to fix it

Code examples

Noncompliant code example

public class Foo {
  public List<User> ListUsers() {
    string userListPath = "/home/mylogin/Dev/users.txt"; // Noncompliant
    return ParseUsers(userListPath);
  }
}

Compliant solution

public class Foo {
  // Configuration is a class that returns customizable properties: it can be mocked to be injected during tests.
  private Configuration config;
  public Foo(Configuration myConfig) {
    this.config = myConfig;
  }
  public List<User> ListUsers() {
    // Find here the way to get the correct folder, in this case using the Configuration object
    string listingFolder = config.GetProperty("myApplication.listingFolder");
    // and use this parameter instead of the hard coded path
    string userListPath = Path.Combine(listingFolder, "users.txt"); // Compliant
    return ParseUsers(userListPath);
  }
}
================================================ FILE: analyzers/rspec/cs/S1075.json ================================================ { "title": "URIs should not be hardcoded", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1075", "sqKey": "S1075", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S108.html ================================================

Why is this an issue?

An empty code block is confusing. It will require some effort from maintainers to determine if it is intentional or indicates the implementation is incomplete.

for (int i = 0; i < 42; i++){}  // Noncompliant: is the block empty on purpose, or is code missing?

Removing or filling the empty code blocks takes away ambiguity and generally results in a more straightforward and less surprising code.

Exceptions

The rule ignores code blocks that contain comments.

================================================ FILE: analyzers/rspec/cs/S108.json ================================================ { "title": "Nested blocks of code should not be left empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-108", "sqKey": "S108", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S109.html ================================================

A magic number is a hard-coded numerical value that may lack context or meaning. They should not be used because they can make the code less readable and maintainable.

Why is this an issue?

Magic numbers make the code more complex to understand as it requires the reader to have knowledge about the global context to understand the number itself. Their usage may seem obvious when writing the code, but it may not be the case for another developer or later once the context faded away. -1, 0, and 1 are not considered magic numbers.

Exceptions

This rule doesn’t raise an issue when the magic number is used as part of:

How to fix it

Replacing them with a constant allows us to provide a meaningful name associated with the value. Instead of adding complexity to the code, it brings clarity and helps to understand the context and the global meaning.

Code examples

Noncompliant code example

public void DoSomething()
{
    for (int i = 0; i < 4; i++)  // Noncompliant, 4 is a magic number
    {
        ...
    }
}

Compliant solution

private const int NUMBER_OF_CYCLES = 4;

public void DoSomething()
{
    for (int i = 0; i < NUMBER_OF_CYCLES; i++)  // Compliant
    {
        ...
    }
}
================================================ FILE: analyzers/rspec/cs/S109.json ================================================ { "title": "Magic numbers should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-109", "sqKey": "S109", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S110.html ================================================

Why is this an issue?

Inheritance is one of the most valuable concepts in object-oriented programming. It’s a way to categorize and reuse code by creating collections of attributes and behaviors called classes, which can be based on previously created classes.

But abusing this concept by creating a deep inheritance tree can lead to complex and unmaintainable source code. Often, an inheritance tree becoming too deep is the symptom of systematic use of "inheritance" when other approaches like "composition" would be better suited.

This rule raises an issue when the inheritance tree, starting from Object, has a greater depth than is allowed.

Resources

Documentation

Composition over inheritance: difference between composition and inheritance in object-oriented programming

================================================ FILE: analyzers/rspec/cs/S110.json ================================================ { "title": "Inheritance tree of classes should not be too deep", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Linear with offset", "linearDesc": "Number of parents above the defined threshold", "linearOffset": "4h", "linearFactor": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-110", "sqKey": "S110", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1104.html ================================================

Why is this an issue?

Public fields in public classes do not respect the encapsulation principle and have three main disadvantages:

To prevent unauthorized modifications, private attributes and accessor methods (set and get) should be used.

Note that due to optimizations on simple properties, public fields provide only very little performance gain.

What is the potential impact?

Public fields can be modified by any part of the code and this can lead to unexpected changes and hard-to-trace bugs.

Public fields don’t hide the implementation details. As a consequence, it is no longer possible to change how the data is stored internally without impacting the client code of the class.

The code is harder to maintain.

Exceptions

Fields marked as readonly or const are ignored by this rule.

Fields inside classes or structs annotated with [StructLayout] are ignored by this rule.

Fields inside classes or structs annotated with [Serializable] are ignored by this rule unless they are annotated with [NonSerialized].

How to fix it

Depending on your needs:

Code examples

Noncompliant code example

public class Foo
{
    public int InstanceData = 32; // Noncompliant
    public int AnotherInstanceData = 32; // Noncompliant

}

Compliant solution

public class Foo
{
    // using auto-implemented properties
    public int InstanceData { get; set; } = 32;

    // using field encapsulation
    private int _anotherInstanceData = 32;

    public int AnotherInstanceData
    {
        get { return _anotherInstanceData; }
        set
        {
            // perform validation
            _anotherInstanceData = value;
        }
    }

}

Pitfalls

Please be aware that changing a field by a property in a software that uses serialization could lead to binary incompatibility.

Resources

================================================ FILE: analyzers/rspec/cs/S1104.json ================================================ { "title": "Fields should not have public accessibility", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1104", "sqKey": "S1104", "scope": "Main", "securityStandards": { "CWE": [ 493 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1109.html ================================================

Why is this an issue?

Shared coding conventions make it possible for a team to efficiently collaborate. This rule makes it mandatory to place a close curly brace at the beginning of a line.

Noncompliant code example

if(condition)
{
  doSomething();}

Compliant solution

if(condition)
{
  doSomething();
}

Exceptions

When blocks are inlined (open and close curly braces on the same line), no issue is triggered.

if(condition) {doSomething();}
================================================ FILE: analyzers/rspec/cs/S1109.json ================================================ { "title": "A close curly brace should be located at the beginning of a line", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1109", "sqKey": "S1109", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1110.html ================================================

Why is this an issue?

Parentheses can disambiguate the order of operations in complex expressions and make the code easier to understand.

a = (b * c) + (d * e); // Compliant: the intent is clear.

Redundant parentheses are parenthesis that do not change the behavior of the code, and do not clarify the intent. They can mislead and complexify the code. They should be removed.

Noncompliant code example

int x = ((y / 2 + 1)); // Noncompliant

if (a && ((x + y > 0))) { // Noncompliant
  return ((x + 1)); // Noncompliant
}

Compliant solution

int x = (y / 2 + 1);

if (a && (x + y > 0)) {
  return (x + 1);
}
================================================ FILE: analyzers/rspec/cs/S1110.json ================================================ { "title": "Redundant pairs of parentheses should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1110", "sqKey": "S1110", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1116.html ================================================

Why is this an issue?

Empty statements represented by a semicolon ; are statements that do not perform any operation. They are often the result of a typo or a misunderstanding of the language syntax. It is a good practice to remove empty statements since they don’t add value and lead to confusion and errors.

Exceptions

This rule does not raise when an empty statement is the only statement in a loop.

for (int i = 0; i < 3; Console.WriteLine(i), i++);

Code examples

Noncompliant code example

void DoSomething()
{
    ; // Noncompliant - was used as a kind of TODO marker
}

void DoSomethingElse()
{
    Console.WriteLine("Hello, world!");;  // Noncompliant - double ;
}

Compliant solution

void DoSomething()
{
}

void DoSomethingElse()
{
    Console.WriteLine("Hello, world!");
}
================================================ FILE: analyzers/rspec/cs/S1116.json ================================================ { "title": "Empty statements should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1116", "sqKey": "S1116", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1117.html ================================================

Why is this an issue?

Shadowing occurs when a local variable has the same name as a variable, field, or property in an outer scope.

This can lead to three main problems:

To avoid these problems, rename the shadowing, shadowed, or both variables/fields/properties to accurately represent their purpose with unique and meaningful names. It improves clarity and allows reasoning locally about the code without considering other software parts.

This rule focuses on variables shadowing fields or properties.

Noncompliant code example

class Foo
{
  public int myField;
  public int MyProperty { get; set; }

  public void DoSomething()
  {
    int myField = 0;    // Noncompliant
    int MyProperty = 0; // Noncompliant
  }
}

Resources

Documentation

Related rules

================================================ FILE: analyzers/rspec/cs/S1117.json ================================================ { "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1117", "sqKey": "S1117", "scope": "All", "quickfix": "infeasible", "title": "Local variables should not shadow class fields or properties" } ================================================ FILE: analyzers/rspec/cs/S1118.html ================================================

Why is this an issue?

Whenever there are portions of code that are duplicated and do not depend on the state of their container class, they can be centralized inside a "utility class". A utility class is a class that only has static members, hence it should not be instantiated.

How to fix it

To prevent the class from being instantiated, you should define a non-public constructor. This will prevent the compiler from implicitly generating a public parameterless constructor.

Alternatively, adding the static keyword as class modifier will also prevent it from being instantiated.

Code examples

Noncompliant code example

public class StringUtils // Noncompliant: implicit public constructor
{
  public static string Concatenate(string s1, string s2)
  {
    return s1 + s2;
  }
}

or

public class StringUtils // Noncompliant: explicit public constructor
{
  public StringUtils()
  {
  }

  public static string Concatenate(string s1, string s2)
  {
    return s1 + s2;
  }
}

Compliant solution

public static class StringUtils // Compliant: the class is static
{
  public static string Concatenate(string s1, string s2)
  {
    return s1 + s2;
  }
}

or

public class StringUtils // Compliant: the constructor is not public
{
  private StringUtils()
  {
  }

  public static string Concatenate(string s1, string s2)
  {
    return s1 + s2;
  }
}
================================================ FILE: analyzers/rspec/cs/S1118.json ================================================ { "title": "Utility classes should not have public constructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1118", "sqKey": "S1118", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S112.html ================================================

This rule raises an issue when a general or reserved exception is thrown.

Why is this an issue?

Throwing general exceptions such as Exception, SystemException and ApplicationException will have a negative impact on any code trying to catch these exceptions.

From a consumer perspective, it is generally a best practice to only catch exceptions you intend to handle. Other exceptions should ideally be let to propagate up the stack trace so that they can be dealt with appropriately. When a general exception is thrown, it forces consumers to catch exceptions they do not intend to handle, which they then have to re-throw.

Besides, when working with a general type of exception, the only way to distinguish between multiple exceptions is to check their message, which is error-prone and difficult to maintain. Legitimate exceptions may be unintentionally silenced and errors may be hidden.

For instance, if an exception such as StackOverflowException is caught and not re-thrown, it may prevent the program from terminating gracefully.

When throwing an exception, it is therefore recommended to throw the most specific exception possible so that it can be handled intentionally by consumers.

Additionally, some reserved exceptions should not be thrown manually. Exceptions such as IndexOutOfRangeException, NullReferenceException, OutOfMemoryException or ExecutionEngineException will be thrown automatically by the runtime when the corresponding error occurs. Many of them indicate serious errors, which the application may not be able to recover from. It is therefore recommended to avoid throwing them as well as using them as base classes.

How to fix it

To fix this issue, make sure to throw specific exceptions that are relevant to the context in which they arise. It is recommended to either:

Code examples

Noncompliant code example

public void DoSomething(object obj)
{
  if (obj == null)
  {
    throw new NullReferenceException("obj");  // Noncompliant: This reserved exception should not be thrown manually
  }
  // ...
}

Compliant solution

public void DoSomething(object obj)
{
  if (obj == null)
  {
    throw new ArgumentNullException("obj");  // Compliant: this is a specific and non-reserved exception type
  }
  // ...
}

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S112.json ================================================ { "title": "General or reserved exceptions should never be thrown", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-112", "sqKey": "S112", "scope": "Main", "securityStandards": { "CWE": [ 397 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1121.html ================================================

Why is this an issue?

A common code smell that can hinder the clarity of source code is making assignments within sub-expressions. This practice involves assigning a value to a variable inside a larger expression, such as within a loop or a conditional statement.

This practice essentially gives a side-effect to a larger expression, thus making it less readable. This often leads to confusion and potential errors.

Exceptions

Assignments inside lambda and delegate expressions are allowed.

var result = Foo(() =>
{
   int x = 100; // dead store, but ignored
   x = 200;
   return x;
}

The rule also ignores the following patterns:

var a = b = c = 10;
while ((val = GetNewValue()) > 0)
{
...
}
private MyClass instance;
public MyClass Instance => instance ?? (instance = new MyClass());

How to fix it

Making assignments within sub-expressions can hinder the clarity of source code.

This practice essentially gives a side-effect to a larger expression, thus making it less readable. This often leads to confusion and potential errors.

Extracting assignments into separate statements is encouraged to keep the code clear and straightforward.

Code examples

Noncompliant code example

if (string.IsNullOrEmpty(result = str.Substring(index, length))) // Noncompliant
{
  // do something with "result"
}

Compliant solution

var result = str.Substring(index, length);
if (string.IsNullOrEmpty(result))
{
  // do something with "result"
}

Resources

================================================ FILE: analyzers/rspec/cs/S1121.json ================================================ { "title": "Assignments should not be made from within sub-expressions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1121", "sqKey": "S1121", "scope": "All", "securityStandards": { "CWE": [ 481 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1123.html ================================================

Why is this an issue?

The Obsolete attribute can be applied with or without a message argument. Marking something Obsolete without including advice on why it’s obsolete or what to use instead will lead maintainers to waste time trying to figure those things out.

Noncompliant code example

public class Car
{

  [Obsolete]  // Noncompliant
  public void CrankEngine(int turnsOfCrank)
  { ... }
}

Compliant solution

public class Car
{

  [Obsolete("Replaced by the automatic starter")]
  public void CrankEngine(int turnsOfCrank)
  { ... }
}
================================================ FILE: analyzers/rspec/cs/S1123.json ================================================ { "title": "\"Obsolete\" attributes should include explanations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "obsolete", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1123", "sqKey": "S1123", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1125.html ================================================

Why is this an issue?

A boolean literal can be represented in two different ways: true or false. They can be combined with logical operators (!, &&, ||, ==, !=) to produce logical expressions that represent truth values. However, comparing a boolean literal to a variable or expression that evaluates to a boolean value is unnecessary and can make the code harder to read and understand. The more complex a boolean expression is, the harder it will be for developers to understand its meaning and expected behavior, and it will favour the introduction of new bugs.

How to fix it

Remove redundant boolean literals from expressions to improve readability and make the code more maintainable.

Code examples

Noncompliant code example

if (booleanMethod() == true) { /* ... */ }
if (booleanMethod() == false) { /* ... */ }
if (booleanMethod() || false) { /* ... */ }
doSomething(!false);
doSomething(booleanMethod() == true);

booleanVariable = booleanMethod() ? true : false;
booleanVariable = booleanMethod() ? true : exp;
booleanVariable = booleanMethod() ? false : exp;
booleanVariable = booleanMethod() ? exp : true;
booleanVariable = booleanMethod() ? exp : false;

for (var x = 0; true; x++)
{
 ...
}

Compliant solution

if (booleanMethod()) { /* ... */ }
if (!booleanMethod()) { /* ... */ }
if (booleanMethod()) { /* ... */ }
doSomething(true);
doSomething(booleanMethod());

booleanVariable = booleanMethod();
booleanVariable = booleanMethod() || exp;
booleanVariable = !booleanMethod() && exp;
booleanVariable = !booleanMethod() || exp;
booleanVariable = booleanMethod() && exp;

for (var x = 0; ; x++)
{
 ...
}
================================================ FILE: analyzers/rspec/cs/S1125.json ================================================ { "title": "Boolean literals should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1125", "sqKey": "S1125", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1128.html ================================================

Why is this an issue?

Unnecessary using directives refer to importing namespaces, types or creating aliases that are not used or referenced anywhere in the code.

Although they don’t affect the runtime behavior of the application after compilation, removing them will:

Starting with C# 10, it’s possible to define global usings for an entire project. They reduce the need for repetitive namespace inclusions, but can also mask which namespaces are truly necessary for the code at hand. Over-relying on them can lead to less transparent code dependencies, especially for newcomers to the project.

Exceptions

The rule will not raise a warning for global using directives, even if none of the types of that namespace are used in the project:

global using System.Net.Sockets; // Compliant by exception

Unnecessary using directives are ignored in ASP.NET Core projects in the following files:

How to fix it

While it’s not difficult to remove these unneeded lines manually, modern code editors support the removal of every unnecessary using directive with a single click from every file of the project.

Code examples

Noncompliant code example

using System.IO;
using System.Linq;
using System.Collections.Generic;   // Noncompliant - no types are used from this namespace
using MyApp.Helpers;                // Noncompliant - FileHelper is in the same namespace
using MyCustomNamespace;            // Noncompliant - no types are used from this namespace

namespace MyApp.Helpers
{
    public class FileHelper
    {
        public static string ReadFirstLine(string filePath) =>
            File.ReadAllLines(filePath).First();
    }
}

Compliant solution

using System.IO;
using System.Linq;

namespace MyApp.Helpers
{
    public class FileHelper
    {
        public static string ReadFirstLine(string filePath) =>
            File.ReadAllLines(filePath).First();
    }
}

Resources

Documentation

Related rules

================================================ FILE: analyzers/rspec/cs/S1128.json ================================================ { "title": "Unnecessary \"using\" should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1128", "sqKey": "S1128", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S113.html ================================================

Why is this an issue?

Some tools work better when files end with an empty line.

This rule simply generates an issue if it is missing.

For example, a Git diff looks like this if the empty line is missing at the end of the file:

+class Test
+{
+}
\ No newline at end of file
================================================ FILE: analyzers/rspec/cs/S113.json ================================================ { "title": "Files should end with a newline", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-113", "sqKey": "S113", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1133.html ================================================

Why is this an issue?

This rule is meant to be used as a way to track code which is marked as being deprecated. Deprecated code should eventually be removed.

Noncompliant code example

[Obsolete] // Noncompliant
void Method()
{
    // ..
}
================================================ FILE: analyzers/rspec/cs/S1133.json ================================================ { "title": "Deprecated code should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "INFO" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "obsolete" ], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1133", "sqKey": "S1133", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1134.html ================================================

Why is this an issue?

FIXME tags are commonly used to mark places where a bug is suspected, but which the developer wants to deal with later.

Sometimes the developer will not have the time or will simply forget to get back to that tag.

This rule is meant to track those tags and to ensure that they do not go unnoticed.

private int Divide(int numerator, int denominator)
{
    return numerator / denominator; // FIXME denominator value might be 0
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1134.json ================================================ { "title": "Track uses of \"FIXME\" tags", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1134", "sqKey": "S1134", "scope": "All", "securityStandards": { "CWE": [ 546 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1135.html ================================================

Why is this an issue?

Developers often use TODO tags to mark areas in the code where additional work or improvements are needed but are not implemented immediately. However, these TODO tags sometimes get overlooked or forgotten, leading to incomplete or unfinished code. This rule aims to identify and address unattended TODO tags to ensure a clean and maintainable codebase. This description explores why this is a problem and how it can be fixed to improve the overall code quality.

What is the potential impact?

Unattended TODO tags in code can have significant implications for the development process and the overall codebase.

Incomplete Functionality: When developers leave TODO tags without implementing the corresponding code, it results in incomplete functionality within the software. This can lead to unexpected behavior or missing features, adversely affecting the end-user experience.

Missed Bug Fixes: If developers do not promptly address TODO tags, they might overlook critical bug fixes and security updates. Delayed bug fixes can result in more severe issues and increase the effort required to resolve them later.

Impact on Collaboration: In team-based development environments, unattended TODO tags can hinder collaboration. Other team members might not be aware of the intended changes, leading to conflicts or redundant efforts in the codebase.

Codebase Bloat: The accumulation of unattended TODO tags over time can clutter the codebase and make it difficult to distinguish between work in progress and completed code. This bloat can make it challenging to maintain an organized and efficient codebase.

Addressing this code smell is essential to ensure a maintainable, readable, reliable codebase and promote effective collaboration among developers.

Noncompliant code example

private void DoSomething()
{
  // TODO
}

Resources

================================================ FILE: analyzers/rspec/cs/S1135.json ================================================ { "title": "Track uses of \"TODO\" tags", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "INFO" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" }, "tags": [ "cwe" ], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1135", "sqKey": "S1135", "scope": "All", "securityStandards": { "CWE": [ 546 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1144.html ================================================

This rule raises an issue when a private/internal type or member is never referenced in the code.

Why is this an issue?

A type or member that is never called is dead code, and should be removed. Cleaning out dead code decreases the size of the maintained codebase, making it easier to understand the program and preventing bugs from being introduced.

This rule detects type or members that are never referenced from inside a translation unit, and cannot be referenced from the outside.

Exceptions

This rule doesn’t raise issues on:

Code examples

Noncompliant code example

public class Foo
{
    private void UnusedPrivateMethod(){...} // Noncompliant, this private method is unused and can be removed.

    private class UnusedClass {...} // Noncompliant, unused private class that can be removed.
}

Compliant solution

public class Foo
{
    public Foo()
    {
        UsedPrivateMethod();
    }

    private void UsedPrivateMethod()
    {
        var c = new UsedClass();
    }

    private class UsedClass {...}
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1144.json ================================================ { "title": "Unused private types or members should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1144", "sqKey": "S1144", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1147.html ================================================

Why is this an issue?

Calling Environment.Exit(exitCode) or Application.Exit() terminates the process and returns an exit code to the operating system..

Each of these methods should be used with extreme care, and only when the intent is to stop the whole application.

Noncompliant code example

Environment.Exit(0);
Application.Exit();

Exceptions

These methods are ignored inside Main.

================================================ FILE: analyzers/rspec/cs/S1147.json ================================================ { "title": "Exit methods should not be called", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1147", "sqKey": "S1147", "scope": "Main", "securityStandards": { "CWE": [ 382 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1151.html ================================================

Why is this an issue?

The switch statement should be used only to clearly define some new branches in the control flow. As soon as a case clause contains too many statements this highly decreases the readability of the overall control flow statement. In such case, the content of the case clause should be extracted into a dedicated method.

Noncompliant code example

With the default threshold of 8:

switch (myVariable)
{
    case 0: // Noncompliant: 9 statements in the case
        methodCall1("");
        methodCall2("");
        methodCall3("");
        methodCall4("");
        methodCall5("");
        methodCall6("");
        methodCall7("");
        methodCall8("");
        methodCall9("");
        break;
    case 1:
        ...
}

Compliant solution

switch (myVariable)
{
    case 0:
        DoSomething()
        break;
    case 1:
        ...
}
...
private void DoSomething()
{
    methodCall1("");
    methodCall2("");
    methodCall3("");
    methodCall4("");
    methodCall5("");
    methodCall6("");
    methodCall7("");
    methodCall8("");
    methodCall9("");
}
================================================ FILE: analyzers/rspec/cs/S1151.json ================================================ { "title": "\"switch case\" clauses should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1151", "sqKey": "S1151", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1155.html ================================================

Why is this an issue?

When you call Any(), it clearly communicates the code’s intention, which is to check if the collection is empty. Using Count() == 0 for this purpose is less direct and makes the code slightly more complex. However, there are some cases where special attention should be paid:

How to fix it

Prefer using Any() to test for emptiness over Count().

Code examples

Noncompliant code example

private static bool HasContent(IEnumerable<string> strings)
{
  return strings.Count() > 0;  // Noncompliant
}

private static bool HasContent2(IEnumerable<string> strings)
{
  return strings.Count() >= 1;  // Noncompliant
}

private static bool IsEmpty(IEnumerable<string> strings)
{
  return strings.Count() == 0;  // Noncompliant
}

Compliant solution

private static bool HasContent(IEnumerable<string> strings)
{
  return strings.Any();
}

private static bool HasContent2(IEnumerable<string> strings)
{
  return strings.Any();
}

private static bool IsEmpty(IEnumerable<string> strings)
{
  return !strings.Any();
}

Resources

Benchmarks

Method Runtime Mean Standard Deviation

Count

.NET 9.0

2,841.003 ns

266.0238 ns

Any

.NET 9.0

1.749 ns

0.1242 ns

Count

.NET Framework 4.8.1

71,125.275 ns

731.0382 ns

Any

.NET Framework 4.8.1

31.774 ns

0.3196 ns

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private IEnumerable<int> collection;

public const int N = 10_000;

[GlobalSetup]
public void GlobalSetup()
{
    collection = Enumerable.Range(0, N).Select(x => N - x);
}

[Benchmark(Baseline = true)]
public bool Count() =>
    collection.Count() > 0;

[Benchmark]
public bool Any() =>
    collection.Any();

Hardware Configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S1155.json ================================================ { "title": "\"Any()\" should be used to test for emptiness", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1155", "sqKey": "S1155", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1163.html ================================================

Why is this an issue?

If an exception is already being thrown within the try block or caught in a catch block, throwing another exception in the finally block will override the original exception. This means that the original exception’s message and stack trace will be lost, potentially making it challenging to diagnose and troubleshoot the root cause of the problem.

try
{
  // Some work which end up throwing an exception
  throw new ArgumentException();
}
finally
{
  // Cleanup
  throw new InvalidOperationException(); // Noncompliant: will mask the ArgumentException
}
try
{
  // Some work which end up throwing an exception
  throw new ArgumentException();
}
finally
{
  // Cleanup without throwing
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1163.json ================================================ { "title": "Exceptions should not be thrown in finally blocks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "error-handling", "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1163", "sqKey": "S1163", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1168.html ================================================

Why is this an issue?

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()
{
    return null; // Noncompliant
}

public IEnumerable<Result> GetResults(bool condition)
{
    var results = GenerateResults();
    return condition
        ? results
        : null; // Noncompliant
}

public IEnumerable<Result> GetResults() => null; // Noncompliant

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

public IEnumerable<Result> Results => default; // Noncompliant

Compliant solution

public Result[] GetResults()
{
    return new Result[0];
}

public IEnumerable<Result> GetResults(bool condition)
{
    var results = GenerateResults();
    return condition
        ? results
        : Enumerable.Empty<Result>();
}

public IEnumerable<Result> GetResults() => Enumerable.Empty<Result>();

public IEnumerable<Result> Results
{
    get
    {
        return Enumerable.Empty<Result>();
    }
}

public IEnumerable<Result> Results => Enumerable.Empty<Result>();

Exceptions

Although string is a collection, the rule won’t report on it.

================================================ FILE: analyzers/rspec/cs/S1168.json ================================================ { "title": "Empty arrays and collections should be returned instead of null", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1168", "sqKey": "S1168", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1172.html ================================================

Why is this an issue?

A typical code smell known as unused function parameters refers to parameters declared in a function but not used anywhere within the function’s body. While this might seem harmless at first glance, it can lead to confusion and potential errors in your code. Disregarding the values passed to such parameters, the function’s behavior will be the same, but the programmer’s intention won’t be clearly expressed anymore. Therefore, removing function parameters that are not being utilized is considered best practice.

This rule raises an issue when a private method or constructor of a class/struct takes a parameter without using it.

Exceptions

This rule doesn’t raise any issue in the following contexts:

How to fix it

Having unused function parameters in your code can lead to confusion and misunderstanding of a developer’s intention. They reduce code readability and introduce the potential for errors. To avoid these problems, developers should remove unused parameters from function declarations.

Code examples

Noncompliant code example

private void DoSomething(int a, int b) // Noncompliant, "b" is unused
{
    Compute(a);
}

private void DoSomething2(int a) // Noncompliant, the value of "a" is unused
{
    a = 10;
    Compute(a);
}

Compliant solution

private void DoSomething(int a)
{
    Compute(a);
}

private void DoSomething2()
{
    var a = 10;
    Compute(a);
}
================================================ FILE: analyzers/rspec/cs/S1172.json ================================================ { "title": "Unused method parameters should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1172", "sqKey": "S1172", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1185.html ================================================

Why is this an issue?

Overriding a method just to call the same method from the base class without performing any other actions is useless and misleading. The only time this is justified is in sealed overriding methods, where the effect is to lock in the parent class behavior. This rule ignores overrides of Equals and GetHashCode.

NOTE: In some cases it might be dangerous to add or remove empty overrides, as they might be breaking changes.

Noncompliant code example

public override void Method() // Noncompliant
{
  base.Method();
}

Compliant solution

public override void Method()
{
  //do something else
}

Exceptions

If there is an attribute in any level of the overriding chain, then the overridden member is ignored.

public class Base
{
  [Required]
  public virtual string Name { get; set; }
}

public class Derived : Base
{
  public override string Name
  {
    get
    {
      return base.Name;
    }
    set
    {
      base.Name = value;
    }
  }
}

If there is a documentation comment on the overriding method, it will be ignored:

public class Foo : Bar
{
    /// <summary>
    /// Keep this method for backwards compatibility.
    /// </summary>
    public override void DoSomething()
    {
        base.DoSomething();
    }
}
================================================ FILE: analyzers/rspec/cs/S1185.json ================================================ { "title": "Overriding members should do more than simply call the same member in the base class", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "redundant", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1185", "sqKey": "S1185", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1186.html ================================================

Why is this an issue?

An empty method is generally considered bad practice and can lead to confusion, readability, and maintenance issues. Empty methods bring no functionality and are misleading to others as they might think the method implementation fulfills a specific and identified requirement.

There are several reasons for a method not to have a body:

Exceptions

The following empty methods are considered compliant:

How to fix it

Code examples

Noncompliant code example

public void ShouldNotBeEmpty() {  // Noncompliant - method is empty
}

public void NotImplementedYet() {  // Noncompliant - method is empty
}

public void WillNeverBeImplemented() {  // Noncompliant - method is empty
}

public void EmptyOnPurpose() {  // Noncompliant - method is empty
}

Compliant solution

public void ShouldNotBeEmpty() {
    DoSomething();
}

public void NotImplementedYet() {
    throw new NotImplementedException();
}

public void WillNeverBeImplemented() {
    throw new NotSupportedException();
}

public void EmptyOnPurpose() {
  // comment explaining why the method is empty
}

Compliant solution

================================================ FILE: analyzers/rspec/cs/S1186.json ================================================ { "title": "Methods should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1186", "sqKey": "S1186", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1192.html ================================================

Why is this an issue?

Duplicated string literals make the process of refactoring complex and error-prone, as any change would need to be propagated on all occurrences.

Exceptions

The following are ignored:

How to fix it

Use constants to replace the duplicated string literals. Constants can be referenced from many places, but only need to be updated in a single place.

Code examples

Noncompliant code example

public class Foo
{
    private string name = "foobar"; // Noncompliant

    public string DefaultName { get; } = "foobar"; // Noncompliant

    public Foo(string value = "foobar") // Noncompliant
    {
        var something = value ?? "foobar"; // Noncompliant
    }
}

Compliant solution

public class Foo
{
    private const string Foobar = "foobar";

    private string name = Foobar;

    public string DefaultName { get; } = Foobar;

    public Foo(string value = Foobar)
    {
        var something = value ?? Foobar;
    }
}
================================================ FILE: analyzers/rspec/cs/S1192.json ================================================ { "title": "String literals should not be duplicated", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Linear with offset", "linearDesc": "per duplicate instance", "linearOffset": "2min", "linearFactor": "2min" }, "tags": [ "design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1192", "sqKey": "S1192", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1199.html ================================================

Why is this an issue?

Nested code blocks create new scopes where variables declared within are inaccessible from the outside, and their lifespan ends with the block.

Although this may appear beneficial, their usage within a function often suggests that the function is overloaded. Thus, it may violate the Single Responsibility Principle, and the function needs to be broken down into smaller functions.

The presence of nested blocks that don’t affect the control flow might suggest possible mistakes in the code.

Exceptions

The usage of a code block after a case is allowed.

How to fix it

The nested code blocks should be extracted into separate methods.

Code examples

Noncompliant code example

public void Evaluate()
{
    /* ... */
    {     // Noncompliant - nested code block '{' ... '}'
          int a = stack.pop();
          int b = stack.pop();
          int result = a + b;
          stack.push(result);
    }
    /* ... */
}

Compliant solution

public void Evaluate()
{
    /* ... */
    StackAdd();
    /* ... */
}

private void StackAdd()
{
      int a = stack.pop();
      int b = stack.pop();
      int result = a + b;
      stack.push(result);
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1199.json ================================================ { "title": "Nested code blocks should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1199", "sqKey": "S1199", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1200.html ================================================

Why is this an issue?

According to the Single Responsibility Principle, introduced by Robert C. Martin in his book "Principles of Object Oriented Design", a class should have only one responsibility:

If a class has more than one responsibility, then the responsibilities become coupled.

Changes to one responsibility may impair or inhibit the class' ability to meet the others.

This kind of coupling leads to fragile designs that break in unexpected ways when changed.

Classes which rely on many other classes tend to aggregate too many responsibilities and should be split into several smaller ones.

Nested classes dependencies are not counted as dependencies of the outer class.

Noncompliant code example

With a threshold of 5:

public class Foo    // Noncompliant - Foo depends on too many classes: T1, T2, T3, T4, T5, T6 and T7
{
  private T1 a1;    // Foo is coupled to T1
  private T2 a2;    // Foo is coupled to T2
  private T3 a3;    // Foo is coupled to T3

  public T4 Compute(T5 a, T6 b)    // Foo is coupled to T4, T5 and T6
  {
    T7 result = a.Process(b);    // Foo is coupled to T7
    return result;
  }

  public static class Bar    // Compliant - Bar depends on 2 classes: T8 and T9
  {
    public T8 a8;
    public T9 a9;
  }
}
================================================ FILE: analyzers/rspec/cs/S1200.json ================================================ { "title": "Classes should not be coupled to too many other classes", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2h" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1200", "sqKey": "S1200", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1206.html ================================================

Why is this an issue?

Suppose you override Object.Equals in a type, you must also override Object.GetHashCode. If two objects are equal according to the Equals method, then calling GetHashCode on each of them must yield the same integer. If this is not the case, many collections, such as a Hashtable or a Dictionary won’t handle class instances correctly.

In order to not have unpredictable behavior, Equals and GetHashCode should be either both inherited, or both overridden.

How to fix it

When you override Equals then you have to also override GetHashCode. You have to override both of them, or simply inherit them.

Code examples

Noncompliant code example

class MyClass   // Noncompliant: should also override GetHashCode
{
    public override bool Equals(object obj)
    {
        // ...
    }
}

Compliant solution

class MyClass
{
    public override bool Equals(object obj)
    {
        // ...
    }

    public override int GetHashCode()
    {
        // ...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1206.json ================================================ { "title": "\"Equals(Object)\" and \"GetHashCode()\" should be overridden in pairs", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15 min" }, "tags": [ "cwe" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1206", "sqKey": "S1206", "scope": "Main", "securityStandards": { "CWE": [ 581 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S121.html ================================================

Control structures are code statements that impact the program’s control flow (e.g., if statements, for loops, etc.)

Why is this an issue?

While not technically incorrect, the omission of curly braces can be misleading and may lead to the introduction of errors during maintenance.

In the following example, the two calls seem to be attached to the if statement, but only the first one is, and CheckSomething will always be executed:

if (condition) // Noncompliant
  ExecuteSomething();
  CheckSomething();

Adding curly braces improves the code readability and its robustness:

if (condition)
{
  ExecuteSomething();
  CheckSomething();
}

The rule raises an issue when a control structure has no curly braces.

================================================ FILE: analyzers/rspec/cs/S121.json ================================================ { "title": "Control structures should use curly braces", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-121", "sqKey": "S121", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1210.html ================================================

Why is this an issue?

When you implement IComparable or IComparable<T> on a class you should also override Equals(object) and overload the comparison operators (==, !=, <, <=, >, >=). That’s because the CLR cannot automatically call your CompareTo implementation from Equals(object) or from the base comparison operator implementations. Additionally, it is best practice to override GetHashCode along with Equals.

This rule raises an issue when a class implements IComparable without also overriding Equals(object) and the comparison operators.

Noncompliant code example

public class Foo: IComparable  // Noncompliant
{
  public int CompareTo(object obj) { /* ... */ }
}

Compliant solution

public class Foo: IComparable
{
  public int CompareTo(object obj) { /* ... */ }
  public override bool Equals(object obj)
  {
    var other = obj as Foo;
    if (object.ReferenceEquals(other, null))
    {
      return false;
    }
    return this.CompareTo(other) == 0;
  }
  public int GetHashCode() { /* ... */ }
  public static bool operator == (Foo left, Foo right)
  {
    if (object.ReferenceEquals(left, null))
    {
      return object.ReferenceEquals(right, null);
    }
    return left.Equals(right);
  }
  public static bool operator > (Foo left, Foo right)
  {
    return Compare(left, right) > 0;
  }
  public static bool operator < (Foo left, Foo right)
  {
    return Compare(left, right) < 0;
  }
  public static bool operator != (Foo left, Foo right)
  {
    return !(left == right);
  }
}
================================================ FILE: analyzers/rspec/cs/S1210.json ================================================ { "title": "\"Equals\" and the comparison operators should be overridden when implementing \"IComparable\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1210", "sqKey": "S1210", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1215.html ================================================

Why is this an issue?

GC.Collect is a method that forces or suggests to the garbage collector to run a collection of objects in the managed heap that are no longer being used and free their memory.

Calling GC.Collect is rarely necessary and can significantly affect application performance. That’s because it is a tracing garbage collector and needs to examine every object in memory for cleanup and analyze all reachable objects from every application’s root (static fields, local variables on thread stacks, etc.).

To perform tracing and memory releasing correctly, the garbage collection may need to block all threads currently in execution. That is why, as a general rule, the performance implications of calling GC.Collect far outweigh the benefits.

This rule raises an issue when any overload of Collect is invoked.

static void Main(string[] args)
{
  // ...
  GC.Collect();                              // Noncompliant
  GC.Collect(2, GCCollectionMode.Optimized); // Noncompliant
}

There may be exceptions to this rule: for example, you’ve just triggered some event that is unique in the run of your program that caused a lot of long-lived objects to die, and you want to release their memory.

This rule also raises on GC.GetTotalMemory when forceFullCollection is true as it directly invokes GC.Collect.

Resources

Documentation

Benchmarks

Each .NET runtime features distinct implementations, modes, and configurations for its garbage collector. The benchmark below illustrates how invoking GC.Collect() can have opposite effects across different runtimes.

Runtime Collect Mean Standard Deviation Allocated

.NET 9.0

False

659.2 ms

15.69 ms

205.95 MB

.NET 9.0

True

888.8 ms

15.34 ms

205.95 MB

.NET Framework 4.8.1

False

545.7 ms

19.49 ms

228.8 MB

.NET Framework 4.8.1

True

484.8 ms

11.79 ms

228.8 MB

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

class Tree
{
    public List<Tree> Children = new();
}

private void AppendToTree(Tree tree, int childsPerTree, int depth)
{
    if (depth == 0)
    {
        return;
    }
    for (int i = 0; i < childsPerTree; i++)
    {
        var child = new Tree();
        tree.Children.Add(child);
        AppendToTree(child, childsPerTree, depth - 1);
    }
}

[Benchmark]
[Arguments(true)]
[Arguments(false)]
public void Benchmark(bool collect)
{
    var tree = new Tree();
    AppendToTree(tree, 8, 7);        // Create 8^7 Tree objects (2.097.152 objects) linked via List<Tree> Children
    GC.Collect();
    GC.Collect();                    // Move the objects to generation 2
    AppendToTree(new Tree(), 8, 6);  // Add some more memory preasure (8^6 262.144 objects) which can be collected right after this call
    tree = null;                     // Remove all references to the tree and its content. This freees up 8^7 Tree objects (2.097.152 objects)
    if (collect)
    {
        GC.Collect();                // Force GC to run and block until it finishes
    }
    AppendToTree(new Tree(), 3, 10); // Do some more allocations (3^10 = 59.049)
    AppendToTree(new Tree(), 4, 7);  // 4^10 = 1.048.576
    AppendToTree(new Tree(), 5, 7);  // 5^7 = 78.125
    GC.Collect();                    // Collect all the memory allocated in this method
}

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
Intel Core Ultra 7 165H, 1 CPU, 22 logical and 16 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S1215.json ================================================ { "title": "\"GC.Collect\" should not be called", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "performance", "unpredictable", "bad-practice" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1215", "sqKey": "S1215", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S122.html ================================================

Why is this an issue?

Putting multiple statements on a single line lowers the code readability and makes debugging the code more complex.

if (someCondition) { DoSomething(); } // Noncompliant

DoSomething(); DoSomethingElse(); // Noncompliant

Write one statement per line to improve readability.

if (someCondition)
{
  DoSomething();
}

DoSomething();
DoSomethingElse();

Exceptions

The rule ignores:

Func<object, bool> item1 = o => { return true; }; // Compliant by exception
Func<object, bool> item1 = o => { var r = false; return r; }; // Noncompliant

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S122.json ================================================ { "title": "Statements should be on separate lines", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-122", "sqKey": "S122", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1226.html ================================================

Why is this an issue?

While it is technically correct to assign to parameters from within method bodies, doing so before the parameter value is read is likely a bug. Instead, initial values of parameters, caught exceptions, and foreach parameters should be, if not treated as final, then at least read before reassignment.

Noncompliant code example

public void DoTheThing(string str, int i, List<string> strings)
{
  str = i.ToString(i);  // Noncompliant

  foreach (var s in strings)
  {
    s = "hello world";  // Noncompliant
  }
}
================================================ FILE: analyzers/rspec/cs/S1226.json ================================================ { "title": "Method parameters, caught exceptions and foreach variables\u0027 initial values should not be ignored", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1226", "sqKey": "S1226", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1227.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

break; is an unstructured control flow statement which makes code harder to read.

Ideally, every loop should have a single termination condition.

Noncompliant code example

int i = 0;
while (true)
{
  if (i == 10)
  {
    break;      // Non-Compliant
  }

  Console.WriteLine(i);
  i++;
}

Compliant solution

int i = 0;
while (i != 10) // Compliant
{
  Console.WriteLine(i);
  i++;
}
================================================ FILE: analyzers/rspec/cs/S1227.json ================================================ { "title": "break statements should not be used except for switch cases", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1227", "sqKey": "S1227", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1244.html ================================================

Why is this an issue?

Floating point numbers in C# (and in most other programming languages) are not precise. They are a binary approximation of the actual value. This means that even if two floating point numbers appear to be equal, they might not be due to the tiny differences in their binary representation.

Even simple floating point assignments are not simple:

float f = 0.100000001f; // 0.1
double d = 0.10000000000000001; // 0.1

(Note: Results may vary based on the compiler and its settings)

This issue is further compounded by the non-associative nature of floating point arithmetic. The order in which operations are performed can affect the outcome due to the rounding that occurs at each step. Consequently, the outcome of a series of mathematical operations can vary based on the order of operations.

As a result, using the equality (==) or inequality (!=) operators with float or double values is typically a mistake, as it can lead to unexpected behavior.

How to fix it

Consider using a small tolerance value to check if the numbers are "close enough" to be considered equal. This tolerance value, often called epsilon, should be chosen based on the specifics of your program.

Code examples

Noncompliant code example

float myNumber = 3.146f;

if (myNumber == 3.146f) // Noncompliant: due to floating point imprecision, this will likely be false
{
  // ...
}

if (myNumber < 4 || myNumber > 4) // Noncompliant: indirect inequality test
{
  // ...
}

Compliant solution

float myNumber = 3.146f;
float epsilon = 0.0001f; // or some other small value

if (Math.Abs(myNumber - 3.146f) < epsilon)
{
  // ...
}

if (myNumber <= 4 - epsilon || myNumber >= 4 + epsilon)
{
  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1244.json ================================================ { "title": "Floating point numbers should not be tested for equality", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1244", "sqKey": "S1244", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S125.html ================================================

Why is this an issue?

Commented-out code distracts the focus from the actual executed code. It creates a noise that increases maintenance code. And because it is never executed, it quickly becomes out of date and invalid.

Commented-out code should be deleted and can be retrieved from source control history if required.

How to fix it

Delete the commented out code.

Code examples

Noncompliant code example

void Method(string s)
{
    // if (s.StartsWith('A'))
    // {
    //     s = s.Substring(1);
    // }

    // Do something...
}

Compliant solution

void Method(string s)
{
    // Do something...
}
================================================ FILE: analyzers/rspec/cs/S125.json ================================================ { "title": "Sections of code should not be commented out", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-125", "sqKey": "S125", "scope": "All", "quickfix": "partial" } ================================================ FILE: analyzers/rspec/cs/S126.html ================================================

Why is this an issue?

This rule applies whenever an if statement is followed by one or more else if statements; the final else if should be followed by an else statement.

The requirement for a final else statement is defensive programming.

The else statement should either take appropriate action or contain a suitable comment as to why no action is taken. This is consistent with the requirement to have a final default clause in a switch statement.

Noncompliant code example

if (x == 0)
{
    DoSomething();
}
else if (x == 1)
{
    DoSomethingElse();
}

Compliant solution

if (x == 0)
{
    DoSomething();
}
else if (x == 1)
{
    DoSomethingElse();
}
else
{
    throw new InvalidOperationException();
}

Exceptions

None

================================================ FILE: analyzers/rspec/cs/S126.json ================================================ { "title": "\"if ... else if\" constructs should end with \"else\" clauses", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-126", "sqKey": "S126", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1264.html ================================================

Why is this an issue?

Using a for loop without its typical structure (initialization, condition, increment) can be confusing. In those cases, it is better to use a while loop as it is more readable.

The initializer section should contain a variable declaration to be considered as a valid initialization.

How to fix it

Replace the for loop with a while loop.

Code example

Noncompliant code example

for (;condition;) // Noncompliant; both the initializer and increment sections are missing
{
    // Do something
}

Compliant solution

while (condition)
{
    // Do something
}

Noncompliant code example

int i;

for (i = 0; i < 10;) // Noncompliant; the initializer section should contain a variable declaration
{
    // Do something
    i++;
}

Compliant solution

int i = 0;

while (i < 10)
{
    // Do something
    i++;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1264.json ================================================ { "title": "A \"while\" loop should be used instead of a \"for\" loop", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1264", "sqKey": "S1264", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S127.html ================================================

Why is this an issue?

A for loop stop condition should test the loop counter against an invariant value, one that is true at both the beginning and ending of every loop iteration. Ideally, this means that the stop condition is set to a local variable just before the loop begins.

This rule tracks when incremented counters used in the stop condition are updated in the body of the for loop.

What is the potential impact?

Non-invariant stop conditions can lead to unexpected loop behavior, making the code harder to debug and maintain. If the stop condition changes unexpectedly during iteration, it may cause:

How to fix it

It is generally recommended to only update the loop counter in the loop declaration. If skipping elements or iterating at a different pace based on a condition is needed, consider using a while loop or a different structure that better fits the needs.

Code examples

Noncompliant code example

for (int i = 1; i <= 5; i++)
{
    Console.WriteLine(i);
    if (condition)
    {
        i = 20;
    }
}

Compliant solution

int i = 1;
while (i <= 5)
{
    Console.WriteLine(i);
    if (condition)
    {
        i = 20;
    }
    else
    {
        i++;
    }
}

How does this work?

A while loop signals that the iteration logic may be more complex, so readers will naturally look for control flow changes within the loop body. This makes the code’s intent clearer and easier to reason about.

================================================ FILE: analyzers/rspec/cs/S127.json ================================================ { "title": "\"for\" loop stop conditions should be invariant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-127", "sqKey": "S127", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1301.html ================================================

Why is this an issue?

switch statements and expressions are useful when there are many different cases depending on the value of the same expression.

When a switch statement or expression is simple enough, the code will be more readable with a single if, if-else or ternary conditional operator.

Noncompliant code example

switch (variable)
{
  case 0:
    doSomething();
    break;
  default:
    doSomethingElse();
    break;
}

var foo = variable switch
{
  0 => doSomething(),
  _ => doSomethingElse(),
}

Compliant solution

if (variable == 0)
{
  doSomething();
}
else
{
  doSomethingElse();
}

var foo = variable == 0
  ? doSomething()
  : doSomethingElse();
================================================ FILE: analyzers/rspec/cs/S1301.json ================================================ { "title": "\"switch\" statements should have at least 3 \"case\" clauses", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1301", "sqKey": "S1301", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1309.html ================================================

Why is this an issue?

This rule allows you to track the usage of the SuppressMessage attributes and #pragma warning disable mechanism.

Noncompliant code example

[SuppressMessage("", "S100")]
...

#pragma warning disable S100
...
#pragma warning restore S100
================================================ FILE: analyzers/rspec/cs/S1309.json ================================================ { "title": "Track uses of in-source issue suppressions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "INFO" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1309", "sqKey": "S1309", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S131.html ================================================

Why is this an issue?

The requirement for a final default clause is defensive programming. The clause should either take appropriate action, or contain a suitable comment as to why no action is taken. Even when the switch covers all current values of an enum, a default case should still be used because there is no guarantee that the enum won’t be extended.

Noncompliant code example

int foo = 42;
switch (foo) // Noncompliant
{
  case 0:
    Console.WriteLine("foo = 0");
    break;
  case 42:
    Console.WriteLine("foo = 42");
    break;
}

Compliant solution

int foo = 42;
switch (foo) // Compliant
{
  case 0:
    Console.WriteLine("foo = 0");
    break;
  case 42:
    Console.WriteLine("foo = 42");
    break;
  default:
    throw new InvalidOperationException("Unexpected value foo = " + foo);
}

Resources

================================================ FILE: analyzers/rspec/cs/S131.json ================================================ { "title": "\"switch\/Select\" statements should contain a \"default\/Case Else\" clauses", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-131", "sqKey": "S131", "scope": "All", "securityStandards": { "CWE": [ 478 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1312.html ================================================

Why is this an issue?

Regardless of the logging framework in use (Microsoft.Extension.Logging, Serilog, Log4net, NLog, …​​), logger fields should be:

This rule should be activated when Service Locator Design pattern is followed in place of Dependency Injection for logging.

The rule supports the most popular logging frameworks:

How to fix it

Make the logging field {private static readonly}.

Noncompliant code example

public Logger logger;

Compliant solution

private static readonly Logger logger;

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S1312.json ================================================ { "title": "Logger fields should be \"private static readonly\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1312", "sqKey": "S1312", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S1313.html ================================================

Hardcoding IP addresses is security-sensitive. It has led in the past to the following vulnerabilities:

Today’s services have an ever-changing architecture due to their scaling and redundancy needs. It is a mistake to think that a service will always have the same IP address. When it does change, the hardcoded IP will have to be modified too. This will have an impact on the product development, delivery, and deployment:

Last but not least it has an effect on application security. Attackers might be able to decompile the code and thereby discover a potentially sensitive address. They can perform a Denial of Service attack on the service, try to get access to the system, or try to spoof the IP address to bypass security checks. Such attacks can always be possible, but in the case of a hardcoded IP address solving the issue will take more time, which will increase an attack’s impact.

Ask Yourself Whether

The disclosed IP address is sensitive, e.g.:

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

Recommended Secure Coding Practices

Don’t hard-code the IP address in the source code, instead make it configurable with environment variables, configuration files, or a similar approach. Alternatively, if confidentially is not required a domain name can be used since it allows to change the destination quickly without having to rebuild the software.

Sensitive Code Example

var ip = "192.168.12.42";
var address = IPAddress.Parse(ip);

Compliant Solution

var ip = ConfigurationManager.AppSettings["myapplication.ip"];
var address = IPAddress.Parse(ip);

Exceptions

No issue is reported for the following cases because they are not considered sensitive:

See

================================================ FILE: analyzers/rspec/cs/S1313.json ================================================ { "title": "Using hardcoded IP addresses is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1313", "sqKey": "S1313", "scope": "Main", "securityStandards": { "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A1" ], "CWE": [ 547 ] } } ================================================ FILE: analyzers/rspec/cs/S134.html ================================================

Why is this an issue?

Nested control flow statements if, switch, for, foreach, while, do, and try are often key ingredients in creating what’s known as "Spaghetti code". This code smell can make your program difficult to understand and maintain.

When numerous control structures are placed inside one another, the code becomes a tangled, complex web. This significantly reduces the code’s readability and maintainability, and it also complicates the testing process.

How to fix it

Code examples

The following example demonstrates the behavior of the rule with the default threshold of 3 levels of nesting and one of the potential ways to fix the code smell by introducing guard clauses:

Noncompliant code example

if (condition1)                  // Compliant - depth = 1
{
  /* ... */
  if (condition2)                // Compliant - depth = 2
  {
    /* ... */
    for (int i = 0; i < 10; i++)  // Compliant - depth = 3
    {
      /* ... */
      if (condition4)            // Noncompliant - depth = 4, which exceeds the limit
      {
        if (condition5)          // Depth = 5, exceeding the limit, but issues are only reported on depth = 4
        {
          /* ... */
        }
        return;
      }
    }
  }
}

Compliant solution

if (!condition1)
{
  return;
}
/* ... */
if (!condition2)
{
  return;
}
for (int i = 0; i < 10; i++)
{
  /* ... */
  if (condition4)
  {
    if (condition5)
    {
      /* ... */
    }
    return;
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S134.json ================================================ { "title": "Control flow statements \"if\", \"switch\", \"for\", \"foreach\", \"while\", \"do\" and \"try\" should not be nested too deeply", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-134", "sqKey": "S134", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S138.html ================================================

Why is this an issue?

A function that grows too large tends to aggregate too many responsibilities.

Such functions inevitably become harder to understand and therefore harder to maintain.

Above a specific threshold, it is strongly advised to refactor into smaller functions which focus on well-defined tasks.

Those smaller functions will not only be easier to understand, but also probably easier to test.

================================================ FILE: analyzers/rspec/cs/S138.json ================================================ { "title": "Functions should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-138", "sqKey": "S138", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1449.html ================================================

Why is this an issue?

string.ToLower(), ToUpper, IndexOf, LastIndexOf, and Compare are all culture-dependent, as are some (floating point number and DateTime-related) calls to ToString. Fortunately, all have variants which accept an argument specifying the culture or formatter to use. Leave that argument off and the call will use the system default culture, possibly creating problems with international characters.

string.CompareTo() is also culture specific, but has no overload that takes a culture information, so instead it’s better to use CompareOrdinal, or Compare with culture.

Calls without a culture may work fine in the system’s "home" environment, but break in ways that are extremely difficult to diagnose for customers who use different encodings. Such bugs can be nearly, if not completely, impossible to reproduce when it’s time to fix them.

Noncompliant code example

var lowered = someString.ToLower(); //Noncompliant

Compliant solution

var lowered = someString.ToLower(CultureInfo.InvariantCulture);

or

var lowered = someString.ToLowerInvariant();
================================================ FILE: analyzers/rspec/cs/S1449.json ================================================ { "title": "Culture should be specified for \"string\" operations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unpredictable" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1449", "sqKey": "S1449", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1450.html ================================================

Why is this an issue?

When the value of a private field is always assigned to in a class' methods before being read, then it is not being used to store class information. Therefore, it should become a local variable in the relevant methods to prevent any misunderstanding.

Noncompliant code example

public class Foo
{
  private int singularField;

  public void DoSomething(int x)
  {
    singularField = x + 5;

    if (singularField == 0) { /* ... */ }
  }
}

Compliant solution

public class Foo
{
  public void DoSomething(int x)
  {
    int localVariable = x + 5;

    if (localVariable == 0) { /* ... */ }
  }
}
================================================ FILE: analyzers/rspec/cs/S1450.json ================================================ { "title": "Private fields only used as local variables in methods should become local variables", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1450", "sqKey": "S1450", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1451.html ================================================

Why is this an issue?

Each source file should start with a header stating file ownership and the license which must be used to distribute the application.

This rule must be fed with the header text that is expected at the beginning of every file.

The headerFormat must end with an empty line if you want to have an empty line between the file header and the first line for your source file (using, namespace…​).

For example, if you want the source file to look like this

// Copyright (c) SonarSource. All Rights Reserved. Licensed under the LGPL License.  See License.txt in the project root for license information.

namespace Foo
{
}

then the headerFormat parameter should end with an empty line like this

// Copyright (c) SonarSource. All Rights Reserved. Licensed under the LGPL License.  See License.txt in the project root for license information.

Compliant solution

/*
 * SonarQube, open source software quality management tool.
 * Copyright (C) 2008-2013 SonarSource
 * mailto:contact AT sonarsource DOT com
 *
 * SonarQube 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.
 *
 * SonarQube 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.
 */
================================================ FILE: analyzers/rspec/cs/S1451.json ================================================ { "title": "Track lack of copyright and license headers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LAWFUL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1451", "sqKey": "S1451", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1479.html ================================================

Why is this an issue?

When switch statements have large sets of multi-line case clauses, the code becomes hard to read and maintain.

For example, the Cognitive Complexity is going to be particularly high.

In such scenarios, it’s better to refactor the switch to only have single-line case clauses.

When all the case clauses of a switch statement are single-line, the readability of the code is not affected. Moreover, switch statements with single-line case clauses can easily be converted into switch expressions, which are more concise for assignment and avoid the need for break statements.

Exceptions

This rule ignores:

How to fix it

Extract the logic of multi-line case clauses into separate methods.

Code examples

The examples below use the "Maximum number of case" property set to 4.

Note that from C# 8, you can use switch expression.

Noncompliant code example

public int MapChar(char ch, int value)
{
    switch(ch) // Noncompliant
    {
        case 'a':
            return 1;
        case 'b':
            return 2;
        case 'c':
            return 3;
        // ...
        case '-':
            if (value > 10)
            {
                return 42;
            }
            else if (value < 5 && value > 1)
            {
                return 21;
            }
            return 99;
        default:
            return 1000;
    }
}

Compliant solution

public int MapChar(char ch, int value)
{
    switch(ch) // Compliant: All 5 cases are single line statements
    {
        case 'a':
            return 1;
        case 'b':
            return 2;
        case 'c':
            return 3;
        // ...
        case '-':
            return HandleDash(value);
        default:
            return 1000;
    }
}

private int HandleDash(int value)
{
    if (value > 10)
    {
        return 42;
    }
    else if (value < 5 && value > 1)
    {
        return 21;
    }
    return 99;
}

For this example, a switch expression is more concise and clear:

public int MapChar(char ch, int value) =>
    ch switch // Compliant
    {
        'a' => 1,
        'b' => 2,
        'c' => 3,
        // ...
        '-' => HandleDash(value),
        _ => 1000,
    };

private int HandleDash(int value)
{
    if (value > 10)
    {
        return 42;
    }
    else if (value < 5 && value > 1)
    {
        return 21;
    }
    return 99;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1479.json ================================================ { "title": "\"switch\" statements with many \"case\" clauses should have only one statement", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1479", "sqKey": "S1479", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1481.html ================================================

Why is this an issue?

An unused local variable is a variable that has been declared but is not used anywhere in the block of code where it is defined. It is dead code, contributing to unnecessary complexity and leading to confusion when reading the code. Therefore, it should be removed from your code to maintain clarity and efficiency.

What is the potential impact?

Having unused local variables in your code can lead to several issues:

In summary, unused local variables can make your code less readable, more confusing, and harder to maintain, and they can potentially lead to bugs or inefficient memory use. Therefore, it is best to remove them.

Exceptions

Unused locally created resources in a using statement are not reported.

using(var t = new TestTimer()) // t never used, but compliant.
{
  //...
}

How to fix it

The fix for this issue is straightforward. Once you ensure the unused variable is not part of an incomplete implementation leading to bugs, you just need to remove it.

Code examples

Noncompliant code example

public int NumberOfMinutes(int hours)
{
  int seconds = 0;   // Noncompliant - seconds is unused
  return hours * 60;
}

Compliant solution

public int NumberOfMinutes(int hours)
{
  return hours * 60;
}
================================================ FILE: analyzers/rspec/cs/S1481.json ================================================ { "title": "Unused local variables should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1481", "sqKey": "S1481", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1541.html ================================================

Why is this an issue?

The cyclomatic complexity of methods and properties should not exceed a defined threshold. Complex code can perform poorly and will in any case be difficult to understand and therefore to maintain.

================================================ FILE: analyzers/rspec/cs/S1541.json ================================================ { "title": "Methods and properties should not be too complex", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1541", "sqKey": "S1541", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1607.html ================================================

Why is this an issue?

When a test fails due, for example, to infrastructure issues, you might want to ignore it temporarily. But without some kind of notation about why the test is being ignored, it may never be reactivated. Such tests are difficult to address without comprehensive knowledge of the project, and end up polluting their projects.

This rule raises an issue for each ignored test that does not have a WorkItem attribute nor a comment about why it is being skipped on the right side of the Ignore attribute.

Noncompliant code example

[TestMethod]
[Ignore]
public void Test_DoTheThing()
{
  // ...
}

Compliant solution

[TestMethod]
[Ignore]  // renable when TCKT-1234 is fixed
public void Test_DoTheThing()
{
  // ...
}

or

[TestMethod]
[Ignore]
[WorkItem(1234)]
public void Test_DoTheThing()
{
  // ...
}

Exceptions

The rule doesn’t raise an issue if:

================================================ FILE: analyzers/rspec/cs/S1607.json ================================================ { "title": "Tests should not be ignored", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "tests", "bad-practice", "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1607", "sqKey": "S1607", "scope": "Tests", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1643.html ================================================

Why is this an issue?

Concatenating multiple string literals or strings using the + operator creates a new string object for each concatenation. This can lead to a large number of intermediate string objects and can be inefficient. The StringBuilder class is more efficient than string concatenation, especially when the operator is repeated over and over as in loops.

How to fix it

Replace string concatenation with StringBuilder.

Code examples

Noncompliant code example

string str = "";
for (int i = 0; i < arrayOfStrings.Length ; ++i)
{
  str = str + arrayOfStrings[i];
}

Compliant solution

StringBuilder bld = new StringBuilder();
for (int i = 0; i < arrayOfStrings.Length; ++i)
{
  bld.Append(arrayOfStrings[i]);
}
string str = bld.ToString();

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

StringConcatenation

.NET 9.0

50,530.75 us

2,699.189 us

586280.70 KB

StringBuilder

.NET 9.0

82.31 us

3.387 us

243.79 KB

StringConcatenation

.NET Framework 4.8.1

37,453.72 us

1,543.051 us

586450.38 KB

StringBuilder

.NET Framework 4.8.1

178.32 us

6.148 us

244.15 KB

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

[Params(10_000)]
public int Iterations;

[Benchmark]
public void StringConcatenation()
{
    string str = "";
    for (int i = 0; i < Iterations; i++)
    {
        str = str + "append";
    }
}

[Benchmark]
public void StringBuilder()
{
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < Iterations; i++)
    {
        builder.Append("append");
    }
    _ = builder.ToString();
}

Hardware Configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S1643.json ================================================ { "title": "Strings should not be concatenated using \u0027+\u0027 in a loop", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1643", "sqKey": "S1643", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1656.html ================================================

Why is this an issue?

Re-assigning a variable to itself is a defect as it has no actual effect and indicates meaning to do something else. It usually means that:

Code examples

Noncompliant code example

public class Choice {
    private bool selected;

    public void MakeChoice(bool selected)
    {
        selected = selected; // Noncompliant
    }
}

Compliant solution

public class Choice {
    private bool selected;

    public void MakeChoice(bool selected)
    {
        this.selected = selected; // Compliant
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1656.json ================================================ { "title": "Variables should not be self-assigned", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "3min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1656", "sqKey": "S1656", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S1659.html ================================================

Why is this an issue?

Declaring multiple variable on one line is difficult to read.

Noncompliant code example

class MyClass
{
  private int a, b; // Noncompliant

  public void Method()
  {
    int c, d; // Noncompliant
  }
}

Compliant solution

class MyClass
{
  private int a;
  private int b;

  public void Method()
  {
    int c;
    int d;
  }
}
================================================ FILE: analyzers/rspec/cs/S1659.json ================================================ { "title": "Multiple variables should not be declared on the same line", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1659", "sqKey": "S1659", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1694.html ================================================

A class with only abstract methods and no inheritable behavior should be converted to an interface.

Why is this an issue?

The purpose of an abstract class is to provide some overridable behaviors while also defining methods that are required to be implemented by sub-classes.

A class that contains only abstract methods, often called pure abstract class, is effectively an interface, but with the disadvantage of not being able to be implemented by multiple classes.

Using interfaces over pure abstract classes presents multiple advantages:

Exceptions

abstract classes that contain non-abstract methods, in addition to abstract ones, cannot easily be converted to interfaces, and are not the subject of this rule:

public abstract class Lamp // Compliant: Glow is abstract, but FlipSwitch is not
{
  private bool switchLamp = false;

  public abstract void Glow();

  public void FlipSwitch()
  {
    switchLamp = !switchLamp;
    if (switchLamp)
    {
      Glow();
    }
  }
}

Notice that, since C# 8.0, you can also define default implementations for interface methods, which is yet another reason to prefer interfaces over abstract classes when you don’t need to provide any inheritable behavior.

However, interfaces cannot have fields (such as switchLamp in the example above), and that remains true even in C# 8.0 and upwards. This can be a valid reason to still prefer an abstract class over an interface.

How to fix it

Convert the abstract class to an interface with the same methods.

Code examples

Noncompliant code example

public abstract class Animal // Noncompliant: should be an interface
{
  public abstract void Move();
  public abstract void Feed();
}

Compliant solution

public interface Animal
{
  void Move();
  void Feed();
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1694.json ================================================ { "title": "An abstract class should have both abstract and concrete methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1694", "sqKey": "S1694", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1696.html ================================================

Why is this an issue?

Catching NullReferenceException is generally considered a bad practice because it can hide bugs in your code. Instead of catching this exception, you should aim to prevent it. This makes your code more robust and easier to understand. In addition, constantly catching and handling NullReferenceException can lead to performance issues. Exceptions are expensive in terms of system resources, so they should be used cautiously and only for exceptional conditions, not for regular control flow.

How to fix it

Instead of catching NullReferenceException, it’s better to prevent it from happening in the first place. You can do this by using null checks or null conditional operators (?.) before accessing members of an object.

Code examples

Noncompliant code example

public int GetLengthPlusTwo(string str)
{
    try
    {
        return str.Length + 2;
    }
    catch (NullReferenceException e)
    {
        return 2;
    }
}

Compliant solution

public int GetLengthPlusTwo(string str)
{
    if (str is null)
    {
        return 2;
    }
    return str.Length + 2;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1696.json ================================================ { "title": "NullReferenceException should not be caught", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1696", "sqKey": "S1696", "scope": "Main", "securityStandards": { "CWE": [ 395 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1698.html ================================================

Why is this an issue?

Using the equality == and inequality != operators to compare two objects generally works. The operators can be overloaded, and therefore the comparison can resolve to the appropriate method. However, when the operators are used on interface instances, then == resolves to reference equality, which may result in unexpected behavior if implementing classes override Equals. Similarly, when a class overrides Equals, but instances are compared with non-overloaded ==, there is a high chance that value comparison was meant instead of the reference one.

Noncompliant code example

public interface IMyInterface
{
}

public class MyClass : IMyInterface
{
    public override bool Equals(object obj)
    {
        //...
    }
}

public class Program
{
    public static void Method(IMyInterface instance1, IMyInterface instance2)
    {
        if (instance1 == instance2) // Noncompliant, will do reference equality check, but was that intended? MyClass overrides Equals.
        {
            Console.WriteLine("Equal");
        }
    }
}

Compliant solution

public interface IMyInterface
{
}

public class MyClass : IMyInterface
{
    public override bool Equals(object obj)
    {
        //...
    }
}

public class Program
{
    public static void Method(IMyInterface instance1, IMyInterface instance2)
    {
        if (object.Equals(instance1, instance2)) // object.Equals checks for null and then calls the instance based Equals, so MyClass.Equals
        {
            Console.WriteLine("Equal");
        }
    }
}

Exceptions

The rule does not report on comparisons of System.Type instances and on comparisons inside Equals overrides.

It also does not raise an issue when one of the operands is null nor when one of the operand is cast to object (because in this case we want to ensure reference equality even if some == overload is present).

Resources

================================================ FILE: analyzers/rspec/cs/S1698.json ================================================ { "title": "\"\u003d\u003d\" should not be used when \"Equals\" is overridden", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1698", "sqKey": "S1698", "scope": "Main", "securityStandards": { "CWE": [ 595, 597 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1699.html ================================================

Why is this an issue?

Calling an overridable method from a constructor could result in failures or strange behaviors when instantiating a subclass which overrides the method.

When constructing an object of a derived class, the constructor of the parent class is invoked first, and only then the constructor of the derived class is called. This sequential construction process applies to multiple levels of inheritance as well, starting from the base class and progressing to the most derived class.

If an overridable method is called within the constructor of the parent class, it may inadvertently invoke an overridden implementation in the derived class. This can lead to unexpected failures or strange behaviors because the object’s construction is still in progress and may not have reached a fully initialized state. Consequently, the overridden method may rely on uninitialized members or have assumptions about the object’s state that are not yet valid.

For example:

public class Parent
{
  public Parent()
  {
    DoSomething();  // Noncompliant
  }

  public virtual void DoSomething() // can be overridden
  {
    // ...
  }
}

public class Child : Parent
{
  private string foo;

  public Child(string foo) // leads to call DoSomething() in Parent constructor which triggers a NullReferenceException as foo has not yet been initialized
  {
    this.foo = foo;
  }

  public override void DoSomething()
  {
    Console.WriteLine(this.foo.Length);
  }
}

How to fix it

Depending on the context, you can either:

Code examples

Noncompliant code example

class Parent
{
  public virtual void DoSomething() { }
}

class Child : Parent
{
  public Child()
  {
    DoSomething();  // Noncompliant
  }
}
class Parent
{
  public virtual void DoSomething() { }
}

class Child : Parent
{
  public Child()
  {
    DoSomething();  // Noncompliant
  }
}
class Parent
{
  public virtual void DoSomething() { }
}

class Child : Parent
{
  public Child()
  {
    DoSomething();  // Noncompliant
  }
}

Compliant solution

class Parent
{
  public virtual void DoSomething() { }
}

class Child : Parent
{
  public Child()
  {
    // Call removed
  }
}
class Parent
{
  public virtual void DoSomething() { }
}

class Child : Parent
{
  public Child()
  {
    DoSomething();
  }

  // Method sealed to prevent overriding
  public sealed override void DoSomething()
  {
    base.DoSomething();
  }
}
class Parent
{
  public virtual void DoSomething() { }
}

// Class sealed to prevent inheritance
sealed class Child : Parent
{
  public Child()
  {
    DoSomething();
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1699.json ================================================ { "title": "Constructors should only call non-overridable methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1699", "sqKey": "S1699", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1751.html ================================================

Why is this an issue?

A loop statement with at most one iteration is equivalent to an if statement; the following block is executed only once.

If the initial intention was to conditionally execute the block only once, an if statement should be used instead. If that was not the initial intention, the block of the loop should be fixed so the block is executed multiple times.

A loop statement with at most one iteration can happen when a statement unconditionally transfers control, such as a jump statement or a throw statement, is misplaced inside the loop block.

This rule raises when the following statements are misplaced:

How to fix it

Code examples

Noncompliant code example

public object Method(IEnumerable<object> items)
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
        break; // Noncompliant: loop only executes once
    }

    foreach (object item in items)
    {
        return item; // Noncompliant: loop only executes once
    }
    return null;
}

Compliant solution

public object Method(IEnumerable<object> items)
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
    }

    var item = items.FirstOrDefault();
    if (item != null)
    {
        return item;
    }
    return null;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1751.json ================================================ { "title": "Loops with at most one iteration should be refactored", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1751", "sqKey": "S1751", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1764.html ================================================

Why is this an issue?

Using the same value on both sides of certain operators is a code defect. In the case of logical operators, it is either a copy/paste error and, therefore, a bug, or it is simply duplicated code and should be simplified. For bitwise operators and most binary mathematical operators, having the same value on both sides of an operator yields predictable results and should be simplified as well to avoid further code defects.

This rule raises for the following operators.

Exceptions

This rule ignores the following operators:

Code examples

Noncompliant code example

if ( a == a ) // always true
{
  doZ();
}
if ( a != a ) // always false
{
  doY();
}
if ( a == b && a == b ) // if the first one is true, the second one is too
{
  doX();
}
if ( a == b || a == b ) // if the first one is true, the second one is too
{
  doW();
}

int j = 5 / 5; // always 1
int k = 5 - 5; // always 0

c.Equals(c);    // always true
Object.Equals(c, c); // always true

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1764.json ================================================ { "title": "Identical expressions should not be used on both sides of operators", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1764", "sqKey": "S1764", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S1821.html ================================================

Why is this an issue?

Nested switch structures are difficult to understand because you can easily confuse the cases of an inner switch as belonging to an outer statement. Therefore nested switch statements should be avoided.

Specifically, you should structure your code to avoid the need for nested switch statements, but if you cannot, then consider moving the inner switch to another function.

================================================ FILE: analyzers/rspec/cs/S1821.json ================================================ { "title": "\"switch\" statements should not be nested", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1821", "sqKey": "S1821", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S1848.html ================================================

Why is this an issue?

Creating objects that are not used is a vulnerability that can lead to unexpected behavior.

If this was done intentionally due to side effects in the object’s constructor, the code should be moved to a dedicated method.

How to fix it

Code examples

Noncompliant code example

public void Method(MyObject myObject)
{
    if (myObject is null)
    {
        new MyObject(); // Noncompliant
    }

    if (myObject.IsCorrupted)
    {
        new ArgumentException($"{nameof(myObject)} is corrupted"); // Noncompliant
    }

    // ...
}

Compliant solution

public void Method(MyObject myObject)
{
    if (myObject is null)
    {
        myObject = new MyObject(); // Compliant
    }

    if (myObject.IsCorrupted)
    {
        throw new ArgumentException($"{nameof(myObject)} is corrupted"); // Compliant
    }

    // ...
}
================================================ FILE: analyzers/rspec/cs/S1848.json ================================================ { "title": "Objects should not be created to be dropped immediately without being used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1848", "sqKey": "S1848", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1854.html ================================================

Why is this an issue?

Dead stores refer to assignments made to local variables that are subsequently never used or immediately overwritten. Such assignments are unnecessary and don’t contribute to the functionality or clarity of the code. They may even negatively impact performance. Removing them enhances code cleanliness and readability. Even if the unnecessary operations do not do any harm in terms of the program’s correctness, they are - at best - a waste of computing resources.

Exceptions

No issue is reported when

How to fix it

Remove the unnecessary assignment, then test the code to make sure that the right-hand side of a given assignment had no side effects (e.g. a method that writes certain data to a file and returns the number of written bytes).

You can also use discards (rather than a variable) to express that result of a method call is ignored on purpose.

Code examples

Noncompliant code example

int Foo(int y)
{
  int x = 100; // Noncompliant: dead store
  x = 150;     // Noncompliant: dead store
  x = 200;
  return x + y;
}

Compliant solution

int Foo(int y)
{
  int x = 200; // Compliant: no unnecessary assignment
  return x + y;
}

Resources

Standards

Related rules

================================================ FILE: analyzers/rspec/cs/S1854.json ================================================ { "title": "Unused assignments should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "cwe", "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1854", "sqKey": "S1854", "scope": "All", "securityStandards": { "CWE": [ 563 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S1858.html ================================================

Why is this an issue?

Invoking a method designed to return a string representation of an object which is already a string is a waste of keystrokes. Similarly, explicitly invoking ToString() when the compiler would do it implicitly is also needless code-bloat.

This rule raises an issue when ToString() is invoked:

Noncompliant code example

var s = "foo";
var t = "fee fie foe " + s.ToString();  // Noncompliant
var someObject = new object();
var u = "" + someObject.ToString(); // Noncompliant
var v = string.Format("{0}", someObject.ToString()); // Noncompliant

Compliant solution

var s = "foo";
var t = "fee fie foe " + s;
var someObject = new object();
var u = "" + someObject;
var v = string.Format("{0}", someObject);

Exceptions

The rule does not report on value types, where leaving off the ToString() call would result in automatic boxing.

var v = string.Format("{0}", 1.ToString());
================================================ FILE: analyzers/rspec/cs/S1858.json ================================================ { "title": "\"ToString()\" calls should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1858", "sqKey": "S1858", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1862.html ================================================

Why is this an issue?

A chain of if/else if statements is evaluated from top to bottom. At most, only one branch will be executed: the first statement with a condition that evaluates to true. Therefore, duplicating a condition leads to unreachable code inside the duplicated condition block. Usually, this is due to a copy/paste error.

The result of such duplication can lead to unreachable code or even to unexpected behavior.

How to fix it

Code examples

Noncompliant code example

if (param == 1)
{
  OpenWindow();
}
else if (param == 2)
{
  CloseWindow();
}
else if (param == 1) // Noncompliant: condition has already been checked
{
  MoveWindowToTheBackground(); // unreachable code
}

Compliant solution

if (param == 1)
{
  OpenWindow();
}
else if (param == 2)
{
  CloseWindow();
}
else if (param == 3)
{
  MoveWindowToTheBackground();
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1862.json ================================================ { "title": "Related \"if\/else if\" statements should not have the same condition", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "unused", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1862", "sqKey": "S1862", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1871.html ================================================

Why is this an issue?

When the same code is duplicated in two or more separate branches of a conditional, it can make the code harder to understand, maintain, and can potentially introduce bugs if one instance of the code is changed but others are not.

Having two cases in a switch statement or two branches in an if chain with the same implementation is at best duplicate code, and at worst a coding error.

if (a >= 0 && a < 10)
{
  DoFirst();
  DoTheThing();
}
else if (a >= 10 && a < 20)
{
  DoTheOtherThing();
}
else if (a >= 20 && a < 50) // Noncompliant; duplicates first condition
{
  DoFirst();
  DoTheThing();
}
switch (i)
{
  case 1:
    DoFirst();
    DoSomething();
    break;
  case 2:
    DoSomethingDifferent();
    break;
  case 3:  // Noncompliant; duplicates case 1's implementation
    DoFirst();
    DoSomething();
    break;
  default:
    DoTheRest();
}

If the same logic is truly needed for both instances, then:

if ((a >= 0 && a < 10) || (a >= 20 && a < 50))
{
  DoFirst();
  DoTheThing();
}
else if (a >= 10 && a < 20)
{
  DoTheOtherThing();
}
switch (i)
{
  case 1:
  case 3:
    DoFirst();
    DoSomething();
    break;
  case 2:
    DoSomethingDifferent();
    break;
  default:
    DoTheRest();
}

Exceptions

The rule does not raise an issue for blocks in an if chain that contain a single line of code. The same applies to blocks in a switch statement that contain a single line of code with or without a following break.

if (a >= 0 && a < 10)
{
  DoTheThing();
}
else if (a >= 10 && a < 20)
{
  DoTheOtherThing();
}
else if (a >= 20 && a < 50)    //no issue, usually this is done on purpose to increase the readability
{
  DoTheThing();
}

However, this exception does not apply to if chains without an else statement or to a switch statement without a default clause when all branches have the same single line of code.

if (a == 1)
{
  DoSomething();  // Noncompliant, this might have been done on purpose but probably not
}
else if (a == 2)
{
  DoSomething();
}

Resources

Related rules

================================================ FILE: analyzers/rspec/cs/S1871.json ================================================ { "title": "Two branches in a conditional structure should not have exactly the same implementation", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "design", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1871", "sqKey": "S1871", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S1905.html ================================================

Why is this an issue?

Casting expressions are utilized to convert one data type to another, such as transforming an integer into a string. This is especially crucial in strongly typed languages like C, C++, C#, Java, Python, and others.

However, there are instances where casting expressions are not needed. These include situations like:

These scenarios are considered unnecessary casting expressions. They can complicate the code and make it more difficult to understand, without offering any advantages.

As a result, it’s generally advised to avoid unnecessary casting expressions. Instead, rely on the language’s type system to ensure type safety and code clarity.

Exceptions

Issues are not raised against the default literal.

How to fix it

To fix your code remove the unnecessary casting expression.

Code examples

Noncompliant code example

public int Example(int i)
{
    return (int) (i + 42); // Noncompliant
}

public IEnumerable<int> ExampleCollection(IEnumerable<int> coll)
{
    return coll.Reverse().OfType<int>(); // Noncompliant
}

Compliant solution

public int Example(int i)
{
    return i + 42;
}

public IEnumerable<int> ExampleCollection(IEnumerable<int> coll)
{
    return coll.Reverse();
}
bool b = (bool)default; // Doesn't raise an issue

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1905.json ================================================ { "title": "Redundant casts should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "redundant", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1905", "sqKey": "S1905", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1939.html ================================================

Why is this an issue?

An inheritance list entry is redundant if:

Such redundant declarations should be removed because they needlessly clutter the code and can be confusing.

Noncompliant code example

public class MyClass : Object  // Noncompliant

enum MyEnum : int  // Noncompliant

Compliant solution

public class MyClass

enum MyEnum
================================================ FILE: analyzers/rspec/cs/S1939.json ================================================ { "title": "Inheritance list should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1939", "sqKey": "S1939", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1940.html ================================================

Why is this an issue?

It is needlessly complex to invert the result of a boolean comparison. The opposite comparison should be made instead.

Noncompliant code example

if ( !(a == 2)) { ...}  // Noncompliant
bool b = !(i < 10);  // Noncompliant

Compliant solution

if (a != 2) { ...}
bool b = (i >= 10);
================================================ FILE: analyzers/rspec/cs/S1940.json ================================================ { "title": "Boolean checks should not be inverted", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1940", "sqKey": "S1940", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S1944.html ================================================

Why is this an issue?

A cast is an explicit conversion, which is a way to tell the compiler the intent to convert from one type to another.

void Method(object value)
{
    int i;
    i = (int)value;   // Casting (explicit conversion) from float to int
}

In most cases, the compiler will be able to catch invalid casts between incompatible value types or reference types.

However, the compiler will not be able to detect invalid casts to interfaces.

What is the potential impact?

Invalid casts will lead to unexpected behaviors or runtime errors such as InvalidCastException.

Exceptions

No issue is reported if the interface has no implementing class in the assembly.

How to fix it

To prevent an InvalidCastException from raising during an explicit conversion, it is recommended to use the as operator. When the conversion is not possible, the as operator returns null and will never raise an exception.

Code examples

Noncompliant code example

public interface IMyInterface
{ /* ... */ }

public class Implementer : IMyInterface
{ /* ... */ }

public class AnotherClass
{ /* ... */ }

public static class Program
{
  public static void Main()
  {
    var another = new AnotherClass();
    var x = (IMyInterface)another;     // Noncompliant: InvalidCastException is being thrown
  }
}

Compliant solution

public interface IMyInterface
{ /* ... */ }

public class Implementer : IMyInterface
{ /* ... */ }

public class AnotherClass
{ /* ... */ }

public static class Program
{
  public static void Main()
  {
    var another = new AnotherClass();
    var x = another as IMyInterface;    // Compliant: but will always be null
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1944.json ================================================ { "title": "Invalid casts should be avoided", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1944", "sqKey": "S1944", "scope": "All", "securityStandards": { "CWE": [ 588, 704 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S1994.html ================================================

Why is this an issue?

The for loop is designed to iterate over a range using a counter variable, with the counter being updated in the loop’s increment section. Misusing this structure can lead to issues such as infinite loops if the counter is not updated correctly. If this is intentional, use a while or do while loop instead of a for loop.

Using a for loop for purposes other than its intended use can lead to confusion and potential bugs. If the for loop structure does not fit your needs, consider using an alternative iteration statement.

How to fix it

Move the counter variable update to the loop’s increment section. If this is impossible, consider using another iteration statement instead.

Code examples

Noncompliant code example

int sum = 0;
for (int i = 0; i < 10; sum++) // Noncompliant: `i` is not updated in the increment section
{
  // ...
  i++;
}
for (int i = 0;; i++) // Noncompliant: the loop condition is empty although incrementing `i`
{
  // ...
}

Compliant solution

int sum = 0;
for (int i = 0; i < 10; i++)
{
  // ...
  sum++;
}
int i = 0;
while (true)
{
  // ...
  i++;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S1994.json ================================================ { "title": "\"for\" loop increment clauses should modify the loops\u0027 counters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "confusing" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1994", "sqKey": "S1994", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2053.html ================================================

This vulnerability increases the likelihood that attackers are able to compute the cleartext of password hashes.

Why is this an issue?

During the process of password hashing, an additional component, known as a "salt," is often integrated to bolster the overall security. This salt, acting as a defensive measure, primarily wards off certain types of attacks that leverage pre-computed tables to crack passwords.

However, potential risks emerge when the salt is deemed insecure. This can occur when the salt is consistently the same across all users or when it is too short or predictable. In scenarios where users share the same password and salt, their password hashes will inevitably mirror each other. Similarly, a short salt heightens the probability of multiple users unintentionally having identical salts, which can potentially lead to identical password hashes. These identical hashes streamline the process for potential attackers to recover clear-text passwords. Thus, the emphasis on implementing secure, unique, and sufficiently lengthy salts in password-hashing functions is vital.

What is the potential impact?

Despite best efforts, even well-guarded systems might have vulnerabilities that could allow an attacker to gain access to the hashed passwords. This could be due to software vulnerabilities, insider threats, or even successful phishing attempts that give attackers the access they need.

Once the attacker has these hashes, they will likely attempt to crack them using a couple of methods. One is brute force, which entails trying every possible combination until the correct password is found. While this can be time-consuming, having the same salt for all users or a short salt can make the task significantly easier and faster.

If multiple users have the same password and the same salt, their password hashes would be identical. This means that if an attacker successfully cracks one hash, they have effectively cracked all identical ones, granting them access to multiple accounts at once.

A short salt, while less critical than a shared one, still increases the odds of different users having the same salt. This might create clusters of password hashes with identical salt that can then be attacked as explained before.

With short salts, the probability of a collision between two users' passwords and salts couple might be low depending on the salt size. The shorter the salt, the higher the collision probability. In any case, using longer, cryptographically secure salt should be preferred.

Exceptions

To securely store password hashes, it is a recommended to rely on key derivation functions that are computationally intensive. Examples of such functions are:

When they are used for password storage, using a secure, random salt is required.

However, those functions can also be used for other purposes such as master key derivation or password-based pre-shared key generation. In those cases, the implemented cryptographic protocol might require using a fixed salt to derive keys in a deterministic way. In such cases, using a fixed salt is safe and accepted.

How to fix it in .NET

Code examples

The following code contains examples of hard-coded salts.

Noncompliant code example

using System.Security.Cryptography;

public static void hash(string password)
{
    var salt = Encoding.UTF8.GetBytes("salty");
    var hashed = new Rfc2898DeriveBytes(password, salt); // Noncompliant
}

Compliant solution

using System.Security.Cryptography;

public static void hash(string password)
{
    var saltSize = 16;
    var iterations = 100_000;
    var hashed = new Rfc2898DeriveBytes(password, saltSize, iterations, HashAlgorithmName.SHA512);
}

How does this work?

This code ensures that each user’s password has a unique salt value associated with it. It generates a salt randomly and with a length that provides the required security level. It uses a salt length of at least 16 bytes (128 bits), as recommended by industry standards.

In the case of the code sample, the class automatically takes care of generating a secure salt if none is specified.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S2053.json ================================================ { "title": "Password hashing functions should use an unpredictable salt", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2053", "sqKey": "S2053", "scope": "Main", "securityStandards": { "CWE": [ 759, 760 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222542" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2068.html ================================================

Why is this an issue?

Hard-coding credentials in source code or binaries makes it easy for attackers to extract sensitive information, especially in distributed or open-source applications. This practice exposes your application to significant security risks.

This rule flags instances of hard-coded credentials used in database and LDAP connections. It looks for hard-coded credentials in connection strings, and for variable names that match any of the patterns from the provided list.

In the past, it has led to the following vulnerabilities:

Exceptions

How to fix it

Credentials should be stored in a configuration file that is not committed to the code repository, in a database, or managed by your cloud provider’s secrets management service. If a password is exposed in the source code, it must be changed immediately.

Code Examples

Noncompliant code example

string username = "admin";
string password = "Admin123"; // Noncompliant
string usernamePassword  = "user=admin&password=Admin123"; // Noncompliant
string url = "scheme://user:Admin123@domain.com"; // Noncompliant

Compliant solution

string username = "admin";
string password = GetEncryptedPassword();
string usernamePassword = string.Format("user={0}&password={1}", GetEncryptedUsername(), GetEncryptedPassword());
string url = $"scheme://{username}:{password}@domain.com";

string url2 = "http://guest:guest@domain.com"; // Compliant
const string Password_Property = "custom.password"; // Compliant

Resources

================================================ FILE: analyzers/rspec/cs/S2068.json ================================================ { "title": "Credentials should not be hard-coded", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "quickfix": "infeasible", "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2068", "sqKey": "S2068", "scope": "Main", "securityStandards": { "CWE": [ 798, 259 ], "OWASP": [ "A2" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.4", "3.5.2", "6.4.1" ] } } ================================================ FILE: analyzers/rspec/cs/S2077.html ================================================

Formatted SQL queries can be difficult to maintain, debug and can increase the risk of SQL injection when concatenating untrusted values into the query. However, this rule doesn’t detect SQL injections (unlike rule {rule:csharpsquid:S3649}), the goal is only to highlight complex/formatted queries.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

public void Foo(DbContext context, string query, string param)
{
    string sensitiveQuery = string.Concat(query, param);
    context.Database.ExecuteSqlCommand(sensitiveQuery); // Sensitive
    context.Query<User>().FromSql(sensitiveQuery); // Sensitive

    context.Database.ExecuteSqlCommand($"SELECT * FROM mytable WHERE mycol={value}", param); // Sensitive, the FormattableString is evaluated and converted to RawSqlString
    string query = $"SELECT * FROM mytable WHERE mycol={param}";
    context.Database.ExecuteSqlCommand(query); // Sensitive, the FormattableString has already been evaluated, it won't be converted to a parametrized query.
}

public void Bar(SqlConnection connection, string param)
{
    SqlCommand command;
    string sensitiveQuery = string.Format("INSERT INTO Users (name) VALUES (\"{0}\")", param);
    command = new SqlCommand(sensitiveQuery); // Sensitive

    command.CommandText = sensitiveQuery; // Sensitive

    SqlDataAdapter adapter;
    adapter = new SqlDataAdapter(sensitiveQuery, connection); // Sensitive
}

Compliant Solution

public void Foo(DbContext context, string query, string param)
{
    context.Database.ExecuteSqlCommand("SELECT * FROM mytable WHERE mycol=@p0", param); // Compliant, it's a parametrized safe query
}

See

================================================ FILE: analyzers/rspec/cs/S2077.json ================================================ { "title": "Formatting SQL queries is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "MAINTAINABILITY": "LOW", "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "bad-practice", "sql" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2077", "sqKey": "S2077", "scope": "Main", "securityStandards": { "CWE": [ 20, 89 ], "OWASP": [ "A1" ], "OWASP Top 10 2021": [ "A3" ], "PCI DSS 3.2": [ "6.5.1" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.1.3", "5.1.4", "5.3.4", "5.3.5" ] } } ================================================ FILE: analyzers/rspec/cs/S2092.html ================================================

When a cookie is protected with the secure attribute set to true it will not be send by the browser over an unencrypted HTTP request and thus cannot be observed by an unauthorized person during a man-in-the-middle attack.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

When the HttpCookie.Secure property is set to false then the cookie will be send during an unencrypted HTTP request:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
myCookie.Secure = false; //  Sensitive: a security-sensitive cookie is created with the secure flag set to false

The default value of Secure flag is false, unless overwritten by an application’s configuration file:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
//  Sensitive: a security-sensitive cookie is created with the secure flag not defined (by default set to false)

Compliant Solution

Set the HttpCookie.Secure property to true:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
myCookie.Secure = true; // Compliant

Or change the default flag values for the whole application by editing the Web.config configuration file:

<httpCookies httpOnlyCookies="true" requireSSL="true" />

See

================================================ FILE: analyzers/rspec/cs/S2092.json ================================================ { "title": "Creating cookies without the \"secure\" flag is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2092", "sqKey": "S2092", "scope": "Main", "securityStandards": { "CWE": [ 614, 311, 315 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A4", "A5" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "3.4.1", "6.1.1", "6.1.2", "6.1.3" ], "STIG ASD_V5R3": [ "V-222576" ] } } ================================================ FILE: analyzers/rspec/cs/S2094.html ================================================

Why is this an issue?

There is no good excuse for an empty class. If it’s being used simply as a common extension point, it should be replaced with an interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be eliminated.

Noncompliant code example

public class Empty // Noncompliant
{
}

Compliant solution

public interface IEmpty
{
}

Exceptions

using Microsoft.AspNetCore.Mvc.RazorPages;

public class EmptyPageModel: PageModel // Compliant - an empty PageModel can be fully functional, the C# code can be in the cshtml file
{
}
================================================ FILE: analyzers/rspec/cs/S2094.json ================================================ { "title": "Classes should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2094", "sqKey": "S2094", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2114.html ================================================

Why is this an issue?

Passing a collection as an argument to the collection’s own method is a code defect. Doing so might either have unexpected side effects or always have the same result.

Another case is using set-like operations. For example, using Union between a list and itself will always return the same list. Conversely, using Except between a list and itself will always return an empty list.

Exceptions to this rule are the methods AddRange and Concat, since the developer can use them to multiply the list elements or the list itself respectively.

var list = new List<int>();

list.Union(list);             // Noncompliant: always returns list
list.Intersect(list);         // Noncompliant: always returns list
list.Except(list);            // Noncompliant: always returns empty
list.SequenceEqual(list);     // Noncompliant: always returns true

var set = new HashSet<int>();
set.UnionWith(set);           // Noncompliant: no changes
set.IntersectWith(set);       // Noncompliant: no changes
set.ExceptWith(set);          // Noncompliant: always returns empty
set.SymmetricExceptWith(set); // Noncompliant: always returns empty
set.IsProperSubsetOf(set);    // Noncompliant: always returns false
set.IsProperSupersetOf(set);  // Noncompliant: always returns false
set.IsSubsetOf(set);          // Noncompliant: always returns true
set.IsSupersetOf(set);        // Noncompliant: always returns true
set.Overlaps(set);            // Noncompliant: always returns true
set.SetEquals(set);           // Noncompliant: always returns true

list.AddRange(list);          // Compliant
list.Concat(list);            // Compliant

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2114.json ================================================ { "title": "Collections should not be passed as arguments to their own methods", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2114", "sqKey": "S2114", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2115.html ================================================

When accessing a database, an empty password should be avoided as it introduces a weakness.

Why is this an issue?

When a database does not require a password for authentication, it allows anyone to access and manipulate the data stored within it. Exploiting this vulnerability typically involves identifying the target database and establishing a connection to it without the need for any authentication credentials.

What is the potential impact?

Once connected, an attacker can perform various malicious actions, such as viewing, modifying, or deleting sensitive information, potentially leading to data breaches or unauthorized access to critical systems. It is crucial to address this vulnerability promptly to ensure the security and integrity of the database and the data it contains.

Unauthorized Access to Sensitive Data

When a database lacks a password for authentication, it opens the door for unauthorized individuals to gain access to sensitive data. This can include personally identifiable information (PII), financial records, intellectual property, or any other confidential information stored in the database. Without proper access controls in place, malicious actors can exploit this vulnerability to retrieve sensitive data, potentially leading to identity theft, financial loss, or reputational damage.

Compromise of System Integrity

Without a password requirement, unauthorized individuals can gain unrestricted access to a database, potentially compromising the integrity of the entire system. Attackers can inject malicious code, alter configurations, or manipulate data within the database, leading to system malfunctions, unauthorized system access, or even complete system compromise. This can disrupt business operations, cause financial losses, and expose the organization to further security risks.

Unwanted Modifications or Deletions

The absence of a password for database access allows anyone to make modifications or deletions to the data stored within it. This poses a significant risk, as unauthorized changes can lead to data corruption, loss of critical information, or the introduction of malicious content. For example, an attacker could modify financial records, tamper with customer orders, or delete important files, causing severe disruptions to business processes and potentially leading to financial and legal consequences.

Overall, the lack of a password configured to access a database poses a serious security risk, enabling unauthorized access, data breaches, system compromise, and unwanted modifications or deletions. It is essential to address this vulnerability promptly to safeguard sensitive data, maintain system integrity, and protect the organization from potential harm.

How to fix it in Entity Framework Core

Code examples

The following code uses an empty password to connect to a SQL Server database.

The vulnerability can be fixed by using Windows authentication (sometimes referred to as integrated security).

Noncompliant code example

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
  optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password="); // Noncompliant
}

Compliant solution

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
  optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;Integrated Security=True");
}

How does this work?

Windows authentication (integrated security)

When the connection string includes the Integrated Security=true parameter, it enables Windows authentication (sometimes called integrated security) for the database connection. With integrated security, the user’s Windows credentials are used to authenticate and authorize access to the database. It eliminates the need for a separate username and password for the database connection. Integrated security simplifies authentication and leverages the existing Windows authentication infrastructure for secure database access in your C# application.

It’s important to note that when using integrated security, the user running the application must have the necessary permissions to access the database. Ensure that the user account running the application has the appropriate privileges and is granted access to the database.

The syntax employed in connection strings varies by provider:

Syntax

Supported by

Integrated Security=true;

SQL Server, Oracle, Postgres

Integrated Security=SSPI;

SQL Server, OLE DB

Integrated Security=yes;

MySQL

Trusted_Connection=true;

SQL Server

Trusted_Connection=yes;

ODBC

Note: Some providers such as MySQL do not support Windows authentication with .NET Core.

Pitfalls

Hard-coded passwords

It could be tempting to replace the empty password with a hard-coded one. Hard-coding passwords in the code can pose significant security risks. Here are a few reasons why it is not recommended:

  1. Security Vulnerability: Hard-coded passwords can be easily discovered by anyone who has access to the code, such as other developers or attackers. This can lead to unauthorized access to the database and potential data breaches.
  2. Lack of Flexibility: Hard-coded passwords make it difficult to change the password without modifying the code. If the password needs to be updated, it would require recompiling and redeploying the code, which can be time-consuming and error-prone.
  3. Version Control Issues: Storing passwords in code can lead to version control issues. If the code is shared or stored in a version control system, the password will be visible to anyone with access to the repository, which is a security risk.

To mitigate these risks, it is recommended to use secure methods for storing and retrieving passwords, such as using environment variables, configuration files, or secure key management systems. These methods allow for better security, flexibility, and separation of sensitive information from the codebase.

How to fix it in ASP.NET

Code examples

The following configuration file uses an empty password to connect to a database.

The vulnerability can be fixed by using Windows authentication (sometimes referred to as integrated security)

Noncompliant code example

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="myConnection" connectionString="Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=" /> <!-- Noncompliant -->
  </connectionStrings>
</configuration>

Compliant solution

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="myConnection" connectionString="Server=myServerAddress;Database=myDataBase;Integrated Security=True" />
  </connectionStrings>
</configuration>

How does this work?

Windows authentication (integrated security)

When the connection string includes the Integrated Security=true parameter, it enables Windows authentication (sometimes called integrated security) for the database connection. With integrated security, the user’s Windows credentials are used to authenticate and authorize access to the database. It eliminates the need for a separate username and password for the database connection. Integrated security simplifies authentication and leverages the existing Windows authentication infrastructure for secure database access in your C# application.

It’s important to note that when using integrated security, the user running the application must have the necessary permissions to access the database. Ensure that the user account running the application has the appropriate privileges and is granted access to the database.

The syntax employed in connection strings varies by provider:

Syntax

Supported by

Integrated Security=true;

SQL Server, Oracle, Postgres

Integrated Security=SSPI;

SQL Server, OLE DB

Integrated Security=yes;

MySQL

Trusted_Connection=true;

SQL Server

Trusted_Connection=yes;

ODBC

Note: Some providers such as MySQL do not support Windows authentication with .NET Core.

Pitfalls

Hard-coded passwords

It could be tempting to replace the empty password with a hard-coded one. Hard-coding passwords in the code can pose significant security risks. Here are a few reasons why it is not recommended:

  1. Security Vulnerability: Hard-coded passwords can be easily discovered by anyone who has access to the code, such as other developers or attackers. This can lead to unauthorized access to the database and potential data breaches.
  2. Lack of Flexibility: Hard-coded passwords make it difficult to change the password without modifying the code. If the password needs to be updated, it would require recompiling and redeploying the code, which can be time-consuming and error-prone.
  3. Version Control Issues: Storing passwords in code can lead to version control issues. If the code is shared or stored in a version control system, the password will be visible to anyone with access to the repository, which is a security risk.

To mitigate these risks, it is recommended to use secure methods for storing and retrieving passwords, such as using environment variables, configuration files, or secure key management systems. These methods allow for better security, flexibility, and separation of sensitive information from the codebase.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S2115.json ================================================ { "title": "A secure password should be used when connecting to a database", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "45min" }, "tags": [ "cwe" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2115", "sqKey": "S2115", "scope": "Main", "securityStandards": { "CWE": [ 521 ], "OWASP": [ "A2", "A3" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "9.2.2", "9.2.3" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2123.html ================================================

Why is this an issue?

When using the postfix increment operator, it is important to know that the result of the expression x++ is the value before the operation x.

This means that in some cases, the result might not be what you expect:

The same applies to the postfix and prefix decrement operators.

How to fix it

To solve the issue in assignments, eliminate the assignment, since x\++ mutates x anyways.

To solve the issue in return statements, consider using the prefix increment operator, since it works in reverse: the result of the expression ++x is the value after the operation, which is x+1, as one might expect.

The same applies to the postfix and prefix decrement operators.

Code examples

Noncompliant code example

int PickNumber()
{
  int i = 0;
  int j = 0;

  i = i++;      // Noncompliant: i is still 0
  return j--;   // Noncompliant: returns 0
}

Compliant solution

int PickNumber()
{
  int i = 0;
  int j = 0;

  i++;          // Compliant: i is incremented to 1
  return --j;   // Compliant: returns -1
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2123.json ================================================ { "title": "Values should not be uselessly incremented", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2123", "sqKey": "S2123", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2139.html ================================================

Why is this an issue?

When an exception is logged and rethrown, the upstream code may not be aware that the exception has already been logged. As a result, the same exception gets logged multiple times, making it difficult to identify the root cause of the issue. This can be particularly problematic in multi-threaded applications where messages from other threads can be interwoven with the repeated log entries.

Exceptions

This rule will not generate issues if, within the catch block, one of the following conditions are met:

How to fix it

To address this issue, it is recommended to modify the code to log exceptions only when they are handled locally. In all other cases, simply rethrow the exception and allow the higher-level layers of the application to handle the logging and appropriate actions.

Code examples

Noncompliant code example

try {}
catch (Exception ex)
{
  logger.LogError(ex.Message);
  throw;
}

Compliant solution

try {}
catch (Exception ex)
{
  logger.LogError(ex.Message);
  // Handle exception
}

or

try {}
catch (Exception ex)
{
  // ...
  throw;
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2139.json ================================================ { "title": "Exceptions should be either logged or rethrown but not both", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "logging", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2139", "sqKey": "S2139", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2148.html ================================================

Why is this an issue?

Beginning with C# 7, it is possible to add underscores ('_') to numeric literals to enhance readability. The addition of underscores in this manner has no semantic meaning, but makes it easier for maintainers to understand the code.

The number of digits to the left of a decimal point needed to trigger this rule varies by base.

Base Minimum digits

binary

9

decimal

6

hexadecimal

9

It is only the presence of underscores, not their spacing that is scrutinized by this rule.

Note that this rule is automatically disabled when the project’s C# version is lower than 7.

Noncompliant code example

int i = 10000000;  // Noncompliant; is this 10 million or 100 million?
int  j = 0b01101001010011011110010101011110;  // Noncompliant
long l = 0x7fffffffffffffffL;  // Noncompliant

Compliant solution

int i = 10_000_000;
int  j = 0b01101001_01001101_11100101_01011110;
long l = 0x7fff_ffff_ffff_ffffL;
================================================ FILE: analyzers/rspec/cs/S2148.json ================================================ { "title": "Underscores should be used to make large numbers readable", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2148", "sqKey": "S2148", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2156.html ================================================

Why is this an issue?

The difference between private and protected visibility is that child classes can see and use protected members, but they cannot see private ones. Since a sealed class cannot have children, marking its members protected is confusingly pointless.

Noncompliant code example

public sealed class MySealedClass
{
    protected string name = "Fred";  // Noncompliant
    protected void SetName(string name) // Noncompliant
    {
        // ...
    }
}

Compliant solution

public sealed class MySealedClass
{
    private string name = "Fred";
    public void SetName(string name)
    {
        // ...
    }
}
================================================ FILE: analyzers/rspec/cs/S2156.json ================================================ { "title": "\"sealed\" classes should not have \"protected\" members", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2156", "sqKey": "S2156", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2166.html ================================================

Why is this an issue?

Clear, communicative naming is important in code. It helps maintainers and API users understand the intentions for and uses of a unit of code. Using "exception" in the name of a class that does not extend Exception or one of its subclasses is a clear violation of the expectation that a class' name will indicate what it is and/or does.

Noncompliant code example

public class FruitException // Noncompliant - this has nothing to do with Exception
{
  private Fruit expected;
  private string unusualCharacteristics;
  private bool appropriateForCommercialExploitation;
  // ...
}

public class CarException // Noncompliant - does not derive from any Exception-based class
{
  public CarException(string message, Exception inner)
  {
     // ...
  }
}

Compliant solution

public class FruitSport // Compliant - class name does not end with 'Exception'
{
  private Fruit expected;
  private string unusualCharacteristics;
  private bool appropriateForCommercialExploitation;
  // ...
}

public class CarException: Exception // Compliant - correctly extends System.Exception
{
  public CarException(string message, Exception inner): base(message, inner)
  {
     // ...
  }
}
================================================ FILE: analyzers/rspec/cs/S2166.json ================================================ { "title": "Classes named like \"Exception\" should extend \"Exception\" or a subclass", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "error-handling", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2166", "sqKey": "S2166", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2178.html ================================================

Why is this an issue?

Short-circuit evaluation is an evaluation strategy for Boolean operators, that doesn’t evaluates the second argument of the operator if it is not needed to determine the result of the operation.

C# provides logical operators that implement short-circuit evaluation: && and ||, as well as non-short-circuit versions: & and |. Unlike short-circuit operators, non-short-circuit ones evaluate both operands and afterwards perform the logical operation.

For example false && FunctionCall() always results in false, even when FunctionCall invocation would raise an exception. Instead, false & FunctionCall() also evaluates FunctionCall(), and results in an exception if FunctionCall() invocation raises an exception.

Similarly, true || FunctionCall() always results in true, no matter what the return value of FunctionCall() would be.

The use of non-short-circuit logic in a boolean context is likely a mistake - one that could cause serious program errors as conditions are evaluated under the wrong circumstances.

How to fix it

Code examples

Noncompliant code example

if (GetTrue() | GetFalse()) // Noncompliant: both sides evaluated
{
}

Compliant solution

if (GetTrue() || GetFalse()) // Compliant: short-circuit logic used
{
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2178.json ================================================ { "title": "Short-circuit logic should be used in boolean contexts", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2178", "sqKey": "S2178", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2183.html ================================================

Why is this an issue?

The shifting operators are used to do an arithmetic shift to the bits of an integral numeric value, either to the left or the right.

var number = 14;         // ...01110 (14)
var left = number << 1;  // ...11100 (28)
var right = number >> 1; // ...00111 (7)

Therefore, shifting an integral number by 0 is equivalent to doing nothing, since the bits do not move any positions to the left or the right.

On the other hand, shifting an integral number by a value greater than their count of bits minus one (n_bits-1) is equivalent to shifting by the value modulo the bit count of the number (value % n_bits).

In the case of int and uint, which take 32 bits in the memory, the shift count is given by the five low-order bits of the second operand, which can represent numbers from 0 to 31. This means that numbers having the same five low-order bits are treated the same by the shift operators.

var one =         0b0_00001;
var thirtyThree = 0b1_00001; // Same five low-order bits, 33 % 32 = 1

var shifted1 = 42 << one;           // Results in 84
var shifted2 = 42 << thirtyThree;   // Results in 84

Note that integral number with a less than 32-bit quantity (e.g. short, ushort) are implicitly converted to int before the shifting operation and so the rule for int/uint applies.

If the first operand is a long or ulong (64-bit quantity), the shift count is given by the six low-order bits of the second operand. That is, the actual shift count is 0 to 63 bits.

Exceptions

This rule doesn’t raise an issue when the shift by zero is obviously for cosmetic reasons:

bytes[loc+0] = (byte)(value >> 8);
bytes[loc+1] = (byte)(value >> 0);

How to fix it

Code examples

Noncompliant code example

short s = 1;
short shortShift1 = (short)(s << 0); // Noncompliant: the value does not change
short shortShift2 = (short)(s << 33); // Noncompliant: this is equivalent to shifting by 1

int i = 1;
int intShift = i << 33; // Noncompliant: this is equivalent to shifting by 1

long lg = 1;
long longShift1 = lg << 0; // Noncompliant: the value does not change
long longShift2 = lg << 65; // Noncompliant: this is equivalent to shifting by 1

Compliant solution

short s = 1;
short shortShift1 = s;
short shortShift2 = (short)(s << 1);

int i = 1;
var intShift = i << 1;

long lg = 1;
var longShift1 = lg;
var longShift2 = lg << 1;

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2183.json ================================================ { "title": "Integral numbers should not be shifted by zero or more than their number of bits-1", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2183", "sqKey": "S2183", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2184.html ================================================

Why is this an issue?

When division is performed on ints, the result will always be an int. You can assign that result to a double, float or decimal with automatic type conversion, but having started as an int, the result will likely not be what you expect. If the result of int division is assigned to a floating-point variable, precision will have been lost before the assignment. Instead, at least one operand should be cast or promoted to the final type before the operation takes place.

Noncompliant code example

static void Main()
{
  decimal dec = 3/2; // Noncompliant
  Method(3/2); // Noncompliant
}

static void Method(float f) { }

Compliant solution

static void Main()
{
  decimal dec = (decimal)3/2;
  Method(3.0F/2);
}

static void Method(float f) { }

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S2184.json ================================================ { "title": "Results of integer division should not be assigned to floating point variables", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "overflow" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2184", "sqKey": "S2184", "scope": "All", "securityStandards": { "CWE": [ 190 ], "ASVS 4.0": [ "5.4.3" ], "STIG ASD_V5R3": [ "V-222612" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2187.html ================================================

Why is this an issue?

To ensure proper testing, it is important to include test cases in a test class. If a test class does not have any test cases, it can give the wrong impression that the class being tested has been thoroughly tested, when in reality, it has not.

This rule will raise an issue when any of these conditions are met:

It does not apply to xUnit since xUnit does not require a test class attribute.

Exceptions

There are scenarios where not having any test cases within a test class is perfectly acceptable and not seen as a problem.

Abstract classes

To facilitate the creation of common test cases, test logic, or test infrastructure, it is advisable to use a base class.

Additionally, in both NUnit and MSTest, abstract classes that are annotated with their respective attributes (TestFixture in NUnit and TestClass in MSTest) are automatically ignored.

Therefore, there is no need to raise an issue in this particular scenario.

More information here:

Derived classes that inherit test cases from a base class

A base class containing one or more test cases to provide generic test cases is also considered a compliant scenario.

Classes that contain AssemblyInitialize or AssemblyCleanup methods

This particular exception scenario only applies to the MSTest test framework.

The AssemblyInitialize and AssemblyCleanup attributes are used to annotate methods that are executed only once at the beginning and at the end of a test run. These attributes can only be applied once per assembly.

It is logical to have a dedicated class for these methods, and this scenario is also considered compliant.

Furthermore, it is important to note that the test engine will execute a method annotated with either the AssemblyInitialize or AssemblyCleanup attribute only if that method is part of a class annotated with the TestClass attribute.

More information here:

How to fix it in MSTest

To fix this issue in MSTest, it is important that all test classes annotated with the [TestClass] attribute contain at least one test case.

To achieve this, at least one method needs to be annotated with one of the following method attributes:

Code examples

Noncompliant code example

[TestClass]
public class SomeOtherClassTest { } // Noncompliant: no test

Compliant solution

[TestClass]
public class SomeOtherClassTest
{
    [TestMethod]
    public void SomeMethodShouldReturnTrue() { }
}

How to fix it in NUnit

To fix this issue in NUnit, it is important that all test classes annotated with the [TestFixture] attribute contain at least one test case.

To achieve this, at least one method needs to be annotated with one of the following method attributes:

Code examples

Noncompliant code example

[TestFixture]
public class SomeClassTest { } // Noncompliant: no test

Compliant solution

[TestFixture]
public class SomeClassTest
{
    [Test]
    public void SomeMethodShouldReturnTrue() { }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2187.json ================================================ { "title": "Test classes should contain at least one test case", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "tests", "unused", "confusing" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2187", "sqKey": "S2187", "scope": "Tests", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2190.html ================================================

Why is this an issue?

Having an infinite loop or recursion will lead to a program failure or a program never finishing the execution.

public int Sum()
{
    var i = 0;
    var result = 0;
    while (true) // Noncompliant: the program will never stop
    {
        result += i;
        i++;
    }
    return result;
}

This can happen in multiple scenarios.

Loop statements

while and for loops with no break or return statements that have exit conditions which are always false will be indefinitely executed.

"goto" statements

goto statement with nothing that stops it from being executed over and over again will prevent the program from the completion.

Recursion

When a recursive method call chain lacks an exit condition, the call stack will reach its limit and the program will crash due to a StackOverflowException.

int Pow(int num, int exponent)
{
  return num * Pow(num, exponent - 1); // Noncompliant: no condition under which Pow isn't re-called
}

In this example, Pow will keep calling Pow with exponent - 1 forever, until the program crashes with a StackOverflowException.

Recursion provides some benefits.

However, it has disadvantages as well.

How to fix it

The program’s logic should incorporate a mechanism to break out of the control flow loop. Here are some examples.

Code examples

Noncompliant code example

public int Sum()
{
    var i = 0;
    var result = 0;
    while (true) // Noncompliant: the program will never stop
    {
        result += i;
        i++;
    }
    return result;
}

Compliant solution

public int Sum()
{
    var i = 0;
    var result = 0;
    while (result < 1000)
    {
        result += i;
        i++;
    }
    return result;
}

Noncompliant code example

public int Sum()
{
    var result = 0;
    var i = 0;
iteration:
    // Noncompliant: program never ends
    result += i;
    i++;
    goto iteration;
    return result;
}

Compliant solution

public int Sum()
{
    var i = 0;
    var result = 0;
    while (result < 1000)
    {
        result += i;
        i++;
    }
    return result;
}

Noncompliant code example

int Pow(int num, int exponent)
{
  return num * Pow(num, exponent - 1); // Noncompliant: no condition under which Pow isn't re-called
}

Compliant solution

int Pow(int num, int exponent)
{
  if (exponent > 1) // recursion is now conditional and stoppable
  {
    num = num * Pow(num, exponent - 1);
  }
  return num;
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2190.json ================================================ { "title": "Loops and recursions should not be infinite", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2190", "sqKey": "S2190", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2197.html ================================================

Why is this an issue?

When the modulus of a negative number is calculated, the result will either be negative or zero. Thus, comparing the modulus of a variable for equality with a positive number (or a negative one) could result in unexpected results.

Noncompliant code example

public bool IsOdd(int x)
{
  return x % 2 == 1;  // Noncompliant; if x is an odd negative, x % 2 == -1
}

Compliant solution

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

or

public bool IsOdd(uint x)
{
  return x % 2 == 1;
}
================================================ FILE: analyzers/rspec/cs/S2197.json ================================================ { "title": "Modulus results should not be checked for direct equality", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2197", "sqKey": "S2197", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2198.html ================================================

Why is this an issue?

Certain mathematical comparisons will always return the same value, and should not be performed.

Specifically, the following comparisons will return either always true or always false depending on the kind of comparison:

Noncompliant code example

float f = 42.0f;
if (f <= double.MaxValue) { } // Noncompliant: always true
if (f > double.MaxValue) { }  // Noncompliant: always false

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2198.json ================================================ { "title": "Unnecessary mathematical comparisons should not be made", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2198", "sqKey": "S2198", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2201.html ================================================

Why is this an issue?

When you do not use the return value of a method with no side effects, it indicates that something is wrong. Either this method is unnecessary, or the source code does not behave as expected and could lead to code defects. For example, there are methods, such as DateTime.AddYears, that don’t change the value of the input object, but instead, they return a new object whose value is the result of this operation, and as a result that you will have unexpected effects if you do not use the return value.

This rule raises an issue when the results of the following methods are ignored:

Special cases:

data.All(x =>
{
    x.Property = "foo";
    return true;
});

Such code should be rewritten as a loop because Enumerable.All<TSource> method should be used to determine if all elements satisfy a condition and not to change their state.

Exceptions

This rule doesn’t report issues on invocations with out or ref arguments.

How to fix it

Code examples

Noncompliant code example

data.Where(x => x > 5).Select(x => x * x); // Noncompliant
"this string".Equals("other string"); // Noncompliant

data.All(x =>  // Noncompliant
{
    x.Property = "foo";
    return true;
});

Compliant solution

var res = data.Where(x => x > 5).Select(x => x * x);
var isEqual = "this string".Equals("other string");

foreach (var x in data)
{
    x.Property = "foo";
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2201.json ================================================ { "title": "Methods without side effects should not have their return values ignored", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "suspicious", "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2201", "sqKey": "S2201", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2219.html ================================================

Why is this an issue?

To check the type of an object there are several options:

If runtime calculated Types need to be compared:

Depending on whether the type is returned by a GetType() or typeof() call, the IsAssignableFrom() and IsInstanceOfType() might be simplified. Similarly, if the type is sealed, the type comparison with == can be converted to an is call. Simplifying the calls also make null checking unnecessary because both is and IsInstanceOfType performs it already.

Finally, utilizing the most concise language constructs for type checking makes the code more readable, so

Noncompliant code example

class Fruit { }
sealed class Apple : Fruit { }

class Program
{
  static void Main()
  {
    var apple = new Apple();
    var b = apple != null && apple.GetType() == typeof (Apple); // Noncompliant
    b = typeof(Apple).IsInstanceOfType(apple); // Noncompliant
    if (apple != null)
    {
      b = typeof(Apple).IsAssignableFrom(apple.GetType()); // Noncompliant
    }
    var appleType = typeof (Apple);
    if (apple != null)
    {
      b = appleType.IsAssignableFrom(apple.GetType()); // Noncompliant
    }

    Fruit f = apple;
    if (f as Apple != null) // Noncompliant
    {
    }
    if (apple is Apple) // Noncompliant
    {
    }
  }
}

Compliant solution

class Fruit { }
sealed class Apple : Fruit { }

class Program
{
  static void Main()
  {
    var apple = new Apple();
    var b = apple is Apple;
    b = apple is Apple;
    b = apple is Apple;
    var appleType = typeof(Apple);
    b = appleType.IsInstanceOfType(apple);

    Fruit f = apple;
    if (f is Apple)
    {
    }
    if (apple != null)
    {
    }
  }
}

Exceptions

Calling GetType on an object of Nullable<T> type returns the underlying generic type parameter T, thus a comparison with typeof(Nullable<T>) can’t be simplified to use the is operator, which doesn’t make difference between T and T?.

int? i = 42;
bool condition = i.GetType() == typeof(int?); // false;
condition = i is int?; // true

No issue is reported on the following expressions:

================================================ FILE: analyzers/rspec/cs/S2219.json ================================================ { "title": "Runtime type checking should be simplified", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2219", "sqKey": "S2219", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2221.html ================================================

Why is this an issue?

Catching System.Exception seems like an efficient way to handle multiple possible exceptions. Unfortunately, it traps all exception types, including the ones that were not intended to be caught. To prevent any misunderstandings, exception filters should be used. Alternatively, each exception type should be in a separate catch block.

Noncompliant code example

try
{
  // do something that might throw a FileNotFoundException or IOException
}
catch (Exception e) // Noncompliant
{
  // log exception ...
}

Compliant solution

try
{
  // do something
}
catch (Exception e) when (e is FileNotFoundException or IOException)
{
  // do something
}

Exceptions

The final option is to catch System.Exception and throw it in the last statement in the catch block. This is the least-preferred option, as it is an old-style code, which also suffers from performance penalties compared to exception filters.

try
{
  // do something
}
catch (Exception e)
{
  if (e is FileNotFoundException or IOException)
  {
    // do something
  }
  else
  {
    throw;
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S2221.json ================================================ { "title": "\"Exception\" should not be caught", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "error-handling" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2221", "sqKey": "S2221", "scope": "Main", "securityStandards": { "CWE": [ 396 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2222.html ================================================

Why is this an issue?

To prevent potential deadlocks in an application, it is crucial to release any locks that are acquired within a method along all possible execution paths.

Failing to release locks properly can lead to potential deadlocks, where the lock might not be released, causing issues in the application.

This rule specifically focuses on tracking the following types from the System.Threading namespace:

An issue is reported when a lock is acquired within a method but not released on all paths.

Exceptions

If the lock is never released within the method, no issue is raised, assuming that the callers will handle the release.

How to fix it

To make sure that a lock is always released correctly, you can follow one of these two methods:

Code examples

Noncompliant code example

class MyClass
{
  private object obj = new object();

  public void DoSomethingWithMonitor()
  {
    Monitor.Enter(obj); // Noncompliant: not all paths release the lock
    if (IsInitialized())
    {
      // ...
      Monitor.Exit(obj);
    }
  }
}
class MyClass
{
  private ReaderWriterLockSlim lockObj = new ReaderWriterLockSlim();

  public void DoSomethingWithReaderWriteLockSlim()
  {
    lockObj.EnterReadLock(); // Noncompliant: not all paths release the lock
    if (IsInitialized())
    {
      // ...
      lockObj.ExitReadLock();
    }
  }
}

Compliant solution

class MyClass
{
  private object obj = new object();

  public void DoSomethingWithMonitor()
  {
    lock(obj) // Compliant: the lock will be released at the end of the lock block
    {
      if (IsInitialized())
      {
        // ...
      }
    }
  }
}
class MyClass
{
  private ReaderWriterLockSlim lockObj = new ReaderWriterLockSlim();

  public void DoSomethingWithReaderWriteLockSlim()
  {
    lockObj.EnterReadLock(); // Compliant: the lock will be released in the finally block
    try
    {
      if (IsInitialized())
      {
        // ...
      }
    }
    finally
    {
      lockObj.ExitReadLock();
    }
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S2222.json ================================================ { "title": "Locks should be released on all paths", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "multi-threading", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2222", "sqKey": "S2222", "scope": "Main", "securityStandards": { "CWE": [ 459 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2223.html ================================================

Why is this an issue?

Unlike instance fields, which can only be accessed by code having a hold on the instance, static fields can be accessed by any code having visibility of the field and its type.

public class Math
{
    public static double Pi = 3.14;  // Noncompliant
}

// Somewhere else, where Math and Math.Pi are visible
var pi = Math.Pi; // Reading the value
Math.Pi = 3.1416; // Mutating the value

Another typical scenario of the use of a non-private mutable static field is the following:

public class Shape
{
    public static Shape Empty = new EmptyShape();  // Noncompliant

    private class EmptyShape : Shape
    {
    }
}

Non-private static fields that are neither const nor readonly, like the ones in the examples above, can lead to errors and unpredictable behavior.

This can happen because:

Publicly visible static fields should only be used to store shared data that does not change. To enforce this intent, these fields should be marked readonly or converted to const.

public class Math
{
    public const double Pi = 3.14;
}
public class Shape
{
    public static readonly Shape Empty = new EmptyShape();

    private class EmptyShape : Shape
    {
    }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2223.json ================================================ { "title": "Non-constant static fields should not be visible", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2223", "sqKey": "S2223", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2225.html ================================================

Why is this an issue?

Calling ToString() on an object should always return a string. Thus, overriding the ToString method should never return null, as it breaks the method’s implicit contract, and as a result the consumer’s expectations.

public override string ToString ()
{
  if (this.collection.Count == 0)
  {
    return null; // Noncompliant
  }
  else
  {
    // ...
  }
}

A better alternative is to use the String.Empty built-in field.

public override string ToString ()
{
  if (this.collection.Count == 0)
  {
    return string.Empty;
  }
  else
  {
    // ...
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2225.json ================================================ { "title": "\"ToString()\" method should not return null", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2225", "sqKey": "S2225", "scope": "Main", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2234.html ================================================

Why is this an issue?

Calling a method with argument variables whose names match the method parameter names but in a different order can cause confusion. It could indicate a mistake in the arguments' order, leading to unexpected results.

public double Divide(int divisor, int dividend)
{
    return divisor / dividend;
}

public void DoTheThing()
{
    int divisor = 15;
    int dividend = 5;

    double result = Divide(dividend, divisor);  // Noncompliant: arguments' order doesn't match their respective parameter names
    // ...
}

However, matching the method parameters' order contributes to clearer and more readable code:

public double Divide(int divisor, int dividend)
{
    return divisor / dividend;
}

public void DoTheThing()
{
    int divisor = 15;
    int dividend = 5;

    double result = Divide(divisor, dividend); // Compliant
    // ...
}
================================================ FILE: analyzers/rspec/cs/S2234.json ================================================ { "title": "Arguments should be passed in the same order as the method parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2234", "sqKey": "S2234", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2245.html ================================================

PRNGs are algorithms that produce sequences of numbers that only approximate true randomness. While they are suitable for applications like simulations or modeling, they are not appropriate for security-sensitive contexts because their outputs can be predictable if the internal state is known.

In contrast, cryptographically secure pseudorandom number generators (CSPRNGs) are designed to be secure against prediction attacks. CSPRNGs use cryptographic algorithms to ensure that the generated sequences are not only random but also unpredictable, even if part of the sequence or the internal state becomes known. This unpredictability is crucial for security-related tasks such as generating encryption keys, tokens, or any other values that must remain confidential and resistant to guessing attacks.

For example, the use of non-cryptographic PRNGs has led to vulnerabilities such as:

When software generates predictable values in a context requiring unpredictability, it may be possible for an attacker to guess the next value that will be generated, and use this guess to impersonate another user or access sensitive information. Therefore, it is critical to use CSPRNGs in any security-sensitive application to ensure the robustness and security of the system.

As the System.Random class relies on a non-cryptographic pseudorandom number generator, it should not be used for security-critical applications or for protecting sensitive data. In such context, the System.Cryptography.RandomNumberGenerator class which relies on a CSPRNG should be used in place.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

var random = new Random(); // Sensitive use of Random
byte[] data = new byte[16];
random.NextBytes(data);
return BitConverter.ToString(data); // Check if this value is used for hashing or encryption

Compliant Solution

using System.Security.Cryptography;
...
var randomGenerator = RandomNumberGenerator.Create();
byte[] data = new byte[16];
randomGenerator.GetBytes(data);
return BitConverter.ToString(data);

See

================================================ FILE: analyzers/rspec/cs/S2245.json ================================================ { "title": "Using pseudorandom number generators (PRNGs) is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2245", "sqKey": "S2245", "scope": "Main", "securityStandards": { "CWE": [ 326, 330, 338, 1241 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "ASVS 4.0": [ "6.2.4" ] } } ================================================ FILE: analyzers/rspec/cs/S2251.html ================================================

Why is this an issue?

A for loop with a counter that moves in the wrong direction, away from the stop condition, is not an infinite loop. Because of wraparound, the loop will eventually reach its stop condition, but in doing so, it will probably run more times than anticipated, potentially causing unexpected behavior.

How to fix it

If your stop condition indicates a maximum value, the iterator should increase towards it. Conversely, if your stop condition indicates a minimum value, the iterator should decrease towards it.

Code examples

Noncompliant code example

for (int i = 0; i < maximum; i--)  // Noncompliant: runs until it underflows to int.MaxValue
{
    // ...
}

for (int i = maximum; i >= maximum; i++)  // Noncompliant: runs until it overflows to int.MinValue
{
    // ...
}

Compliant solution

for (int i = 0; i < maximum; i++) // Compliant: Increment towards the maximum value
{
}

for (int i = maximum; i >= 0; i--) // Compliant: Decrement towards the minimum value
{
    // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2251.json ================================================ { "title": "A \"for\" loop update clause should move the counter in the right direction", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2251", "sqKey": "S2251", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2252.html ================================================

Why is this an issue?

A for loop is a fundamental programming construct used to execute a block of code repeatedly. However, if the loop’s condition is false before the first iteration, the loop will never execute.

for (int i = 0; i < 0; i++)  // Noncompliant: the condition is always false, the loop will never execute
{
    // ...
}

Rewrite the loop to ensure the condition evaluates to true at least once.

for (int i = 0; i < 10; i++)  // Compliant: the condition is true at least once, the loop will execute
{
    // ...
}

This bug has the potential to cause unexpected outcomes as the loop might contain critical code that needs to be executed.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2252.json ================================================ { "title": "For-loop conditions should be true at least once", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2252", "sqKey": "S2252", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2257.html ================================================

The use of a non-standard algorithm is dangerous because a determined attacker may be able to break the algorithm and compromise whatever data has been protected. Standard algorithms like AES, RSA, SHA, …​ should be used instead.

This rule tracks custom implementation of these types from System.Security.Cryptography namespace:

Recommended Secure Coding Practices

Sensitive Code Example

public class CustomHash : HashAlgorithm // Noncompliant
{
    private byte[] result;

    public override void Initialize() => result = null;
    protected override byte[] HashFinal() => result;

    protected override void HashCore(byte[] array, int ibStart, int cbSize) =>
        result ??= array.Take(8).ToArray();
}

Compliant Solution

SHA256 mySHA256 = SHA256.Create()

See

================================================ FILE: analyzers/rspec/cs/S2257.json ================================================ { "title": "Using non-standard cryptographic algorithms is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1d" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2257", "sqKey": "S2257", "scope": "Main", "securityStandards": { "CWE": [ 327 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] } } ================================================ FILE: analyzers/rspec/cs/S2259.html ================================================

Why is this an issue?

Accessing a null value will always throw a NullReferenceException most likely causing an abrupt program termination.

Such termination might expose sensitive information that a malicious third party could exploit to, for instance, bypass security measures.

Exceptions

In the following cases, the rule does not raise:

Extensions Methods

Calls to extension methods can still operate on null values.

using System;
using System.Text.RegularExpressions;

public static class Program
{
    public static string RemoveVowels(this string value)
    {
        if (value == null)
        {
            return null;
        }
        return Regex.Replace(value, "[aeoui]*","", RegexOptions.IgnoreCase);
    }

    public static void Main()
    {
        Console.WriteLine(((string?)null).RemoveVowels());  // Compliant: 'RemoveVowels' is an extension method
    }
}

Unreachable code

Unreachable code is not executed, thus null values will never be accessed.

public void Method()
{
    object o = null;
    if (false)
    {
        o.ToString();    // Compliant: code is unreachable
    }
}

Validated value by analysis attributes

Nullable analysis attributes enable the developer to annotate methods with information about the null-state of its arguments. Thus, potential null values validated by one of the following attributes will not raise:

It is important to note those attributes are only available starting .NET Core 3. As a workaround, it is possible to define those attributes manually in a custom class:

using System;

public sealed class NotNullAttribute : Attribute { } // The alternative name 'ValidatedNotNullAttribute' is also supported

public static class Guard
{
    public static void NotNull<T>([NotNull] T value, string name) where T : class
    {
        if (value == null)
        {
            throw new ArgumentNullException(name);
        }
    }
}

public static class Utils
{
    public static string Normalize(string value)
    {
        Guard.NotNull(value, nameof(value)); // Will throw if value is null
        return value.ToUpper(); // Compliant: value is known to be not null here.
    }
}

Validated value by Debug.Assert

A value validated with Debug.Assert to not be null is safe to access.

using System.Diagnostics;

public void Method(object myObject)
{
    Debug.Assert(myObject != null);
    myObject.ToString(); // Compliant: 'myObject' is known to be not null here.
}

Validated value by IDE-specific attributes

Like with null-analysis-attribute, potential null values validated by one of the following IDE-specific attributes will not raise

Visual Studio
JetBrains Rider

Null forgiving operator

Expression marked with the null forgiving operator

public void Method()
{
    object o = null;
    o!.ToString();    // Compliant: the null forgiving operator suppresses the nullable warning
}

How to fix it

To fix the issue, the access of the null value needs to be prevented by either:

Code examples

Noncompliant code example

The variable myObject is equal to null, meaning it has no value:

public void Method()
{
    object myObject = null;
    Console.WriteLine(o.ToString()); // Noncompliant: 'myObject' is always null
}

The parameter input might be null as suggested by the if condition:

public void Method(object input)
{
    if (input is null)
    {
        // ...
    }
    Console.WriteLine(input.ToString()); // Noncompliant: the if condition suggests 'input' might be null
}

Compliant solution

Ensuring the variable myObject has a value resolves the issue:

public void Method()
{
    var myObject = new object();
    Console.WriteLine(myObject.ToString()); // Compliant: 'myObject' is not null
}

Preventing the non-compliant code to be executed by returning early:

public void Method(object input)
{
    if (input is null)
    {
        return;
    }
    Console.WriteLine(input.ToString()); // Compliant: if 'input' is null, this is unreachable
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2259.json ================================================ { "title": "Null pointers should not be dereferenced", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2259", "sqKey": "S2259", "scope": "Main", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2275.html ================================================

Why is this an issue?

Composite format strings in C# are evaluated at runtime, which means they are not verified by the compiler. Introducing an ill-formed format item, or indexing mismatch can lead to unexpected behaviors or runtime errors. The purpose of this rule is to perform static validation on composite format strings used in various string formatting functions to ensure their correct usage. This rule validates the proper behavior of composite formats when invoking the following methods:

Noncompliant code example

s = string.Format("[0}", arg0); // Noncompliant: square bracket '[' instead of curly bracket '{'
s = string.Format("{{0}", arg0); // Noncompliant: double starting curly brackets '{{'
s = string.Format("{0}}", arg0); // Noncompliant: double ending curly brackets '}}'
s = string.Format("{-1}", arg0); // Noncompliant: invalid index for the format item, must be >= 0
s = string.Format("{0} {1}", arg0); // Noncompliant: two format items in the string but only one argument provided

Compliant solution

s = string.Format("{0}", 42); // Compliant
s = string.Format("{0,10}", 42); // Compliant
s = string.Format("{0,-10}", 42); // Compliant
s = string.Format("{0:0000}", 42); // Compliant
s = string.Format("{2}-{0}-{1}", 1, 2, 3); // Compliant
s = string.Format("no format"); // Compliant

Exceptions

The rule does not perform any checks on the format specifier, if present (defined after the :). Moreover, no issues are raised in the following cases:

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2275.json ================================================ { "title": "Composite format strings should not lead to unexpected behavior at runtime", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2275", "sqKey": "S2275", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2290.html ================================================

Why is this an issue?

Field-like events are events that do not have explicit add and remove accessors.

public event EventHandler MyEvent; // No add and remove accessors

The compiler generates a private delegate field to back the event, as well as generating the implicit add and remove accessors.

When a virtual field-like event is overridden by another field-like event, the behavior of the C# compiler is to generate a new private delegate field in the derived class, separate from the parent’s field. This results in multiple and separate events being created, which is rarely what’s actually intended.

Noncompliant code example

abstract class Car
{
  public virtual event EventHandler OnRefuel; // Noncompliant

  public void Refuel()
  {
    // This OnRefuel will always be null
     if (OnRefuel != null)
     {
       OnRefuel(this, EventArgs.Empty);
     }
  }
}

class R2 : Car
{
  public override event EventHandler OnRefuel;
}

class Program
{
  static void Main(string[] args)
  {
    var r2 = new R2();
    r2.OnRefuel += (o, a) =>
    {
        Console.WriteLine("This event will be called");
    };
    r2.Refuel();
  }
}

Compliant solution

To prevent this, remove the virtual designation from the parent class event.

abstract class Car
{
  public event EventHandler OnRefuel; // Compliant

  public void Refuel()
  {
    if (OnRefuel != null)
    {
      OnRefuel(this, EventArgs.Empty);
    }
  }
}

class R2 : Car
{

}

class Program
{
  static void Main(string[] args)
  {
    var r2 = new R2();
    r2.OnRefuel += (o, a) =>
    {
        Console.WriteLine("This event will be called");
    };
    r2.Refuel();
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2290.json ================================================ { "title": "Field-like events should not be virtual", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2290", "sqKey": "S2290", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2291.html ================================================

Why is this an issue?

Enumerable.Sum() always executes addition in a checked context, so an OverflowException will be thrown if the value exceeds MaxValue, even if an unchecked context was specified. Therefore, using this method inside an unchecked context will only make the code more confusing, since the behavior will still be checked.

This rule raises an issue when an unchecked context is specified for a Sum on integer types.

Exceptions

When the Sum call is inside a try-catch block, no issues are reported, since the exception is properly handled.

void Add(List<int> list)
{
  unchecked
  {
    try
    {
      int total = list.Sum();
    }
    catch (System.OverflowException e)
    {
      // Exception handling
    }
  }
}

How to fix it

Remove the unchecked operator/statement, and optionally add some exception handling for the OverflowException.

Code examples

Noncompliant code example

void Add(List<int> list)
{
  int total1 = unchecked(list.Sum());  // Noncompliant

  unchecked
  {
    int total2 = list.Sum();  // Noncompliant
  }
}

Compliant solution

void Add(List<int> list)
{
  int total1 = list.Sum();

  try
  {
    int total2 = list.Sum();
  }
  catch (System.OverflowException e)
  {
    // Exception handling
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2291.json ================================================ { "title": "Overflow checking should not be disabled for \"Enumerable.Sum\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "error-handling" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2291", "sqKey": "S2291", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2292.html ================================================

Why is this an issue?

Trivial properties, which include no logic but setting and getting a backing field should be converted to auto-implemented properties, yielding cleaner and more readable code.

Noncompliant code example

public class Car
{
  private string _make;
  public string Make // Noncompliant
  {
    get { return _make; }
    set { _make = value; }
  }
}

Compliant solution

public class Car
{
  public string Make { get; set; }
}
================================================ FILE: analyzers/rspec/cs/S2292.json ================================================ { "title": "Trivial properties should be auto-implemented", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2292", "sqKey": "S2292", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2302.html ================================================

Why is this an issue?

Because parameter names could be changed during refactoring, they should not be spelled out literally in strings. Instead, use nameof(), and the string that’s output will always be correct.

This rule raises an issue when a string in the throw statement contains the name of one of the method parameters.

Noncompliant code example

void DoSomething(int someParameter, string anotherParam)
{
    if (someParameter < 0)
    {
        throw new ArgumentException("Bad argument", "someParameter");  // Noncompliant
    }
    if (anotherParam == null)
    {
        throw new Exception("anotherParam should not be null"); // Noncompliant
    }
}

Compliant solution

void DoSomething(int someParameter)
{
    if (someParameter < 0)
    {
        throw new ArgumentException("Bad argument", nameof(someParameter));
    }
    if (anotherParam == null)
    {
        throw new Exception($"{nameof(anotherParam)} should not be null");
    }
}

Exceptions

================================================ FILE: analyzers/rspec/cs/S2302.json ================================================ { "title": "\"nameof\" should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2302", "sqKey": "S2302", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2306.html ================================================

Why is this an issue?

Since C# 5.0, async and await are contextual keywords. Contextual keywords do have a particular meaning in some contexts, but are not reserved and therefore can be used as variable names.

int await = 42; // Noncompliant, but compiles
int async = 42; // Noncompliant, but compiles

Keywords, on the other hand, are always reserved and therefore are not valid variable names.

int abstract = 42; // Error CS1585: Member modifier 'abstract' must precede the member type and name
int foreach = 42; // Error CS1519: Invalid token 'foreach' in class, struct, or interface member declaration

To avoid any confusion, it is best to not use async and await as identifiers.

int someVariableName = 42;
int someOtherVariableName = 42;

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2306.json ================================================ { "title": "\"async\" and \"await\" should not be used as identifiers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2306", "sqKey": "S2306", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2325.html ================================================

Why is this an issue?

Methods and properties that don’t access instance data should be marked as static for the following reasons:

Exceptions

Methods with the following names are excluded because they can’t be made static:

Event handler methods part of a Windows Forms or Windows Presentation Foundation class are excluded because they can’t be made static.

How to fix it

Code examples

Noncompliant code example

public class Utilities
{
    public int MagicNum // Noncompliant - only returns a constant value
    {
        get
        {
            return 42;
        }
    }

    private static string magicWord = "please";
    public string MagicWord  // Noncompliant - only accesses a static field
    {
        get
        {
            return magicWord;
        }
        set
        {
            magicWord = value;
        }
    }

    public int Sum(int a, int b)  // Noncompliant - doesn't access instance data, only the method parameters
    {
        return a + b;
    }
}

Compliant solution

public class Utilities
{
    public static int MagicNum
    {
        get
        {
            return 42;
        }
    }

    private static string magicWord = "please";
    public static string MagicWord
    {
        get
        {
            return magicWord;
        }
        set
        {
            magicWord = value;
        }
    }

    public static int Sum(int a, int b)
    {
        return a + b;
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2325.json ================================================ { "title": "Methods and properties that don\u0027t access instance data should be static", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2325", "sqKey": "S2325", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2326.html ================================================

Why is this an issue?

Type parameters that aren’t used are dead code, which can only distract and possibly confuse developers during maintenance. Therefore, unused type parameters should be removed.

Noncompliant code example

public class MoreMath<T>   // Noncompliant; <T> is ignored
{
  public int Add<T>(int a, int b) // Noncompliant; <T> is ignored
  {
    return a + b;
  }
}

Compliant solution

public class MoreMath
{
  public int Add (int a, int b)
  {
    return a + b;
  }
}
================================================ FILE: analyzers/rspec/cs/S2326.json ================================================ { "title": "Unused type parameters should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2326", "sqKey": "S2326", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2327.html ================================================

Why is this an issue?

When multiple, adjacent try statements have duplicate catch and/or finally blocks, they should be merged to consolidate the catch/finally logic for cleaner, more readable code. Note that this applies even when there is intervening code outside any try block.

Noncompliant code example

try
{
  DoTheFirstThing(a, b);
}
catch (InvalidOperationException ex)
{
  HandleException(ex);
}

DoSomeOtherStuff();

try  // Noncompliant; catch is identical to previous
{
  DoTheSecondThing();
}
catch (InvalidOperationException ex)
{
  HandleException(ex);
}

try  // Compliant; catch handles exception differently
{
  DoTheThirdThing(a);
}
catch (InvalidOperationException ex)
{
  LogAndDie(ex);
}

Compliant solution

try
{
  DoTheFirstThing(a, b);
  DoSomeOtherStuff();
  DoTheSecondThing();
}
catch (InvalidOperationException ex)
{
  HandleException(ex);
}

try  // Compliant; catch handles exception differently
{
  DoTheThirdThing(a);
}
catch (InvalidOperationException ex)
{
  LogAndDie(ex);
}
================================================ FILE: analyzers/rspec/cs/S2327.json ================================================ { "title": "\"try\" statements with identical \"catch\" and\/or \"finally\" blocks should be merged", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2327", "sqKey": "S2327", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2328.html ================================================

Why is this an issue?

GetHashCode is used to file an object in a Dictionary or Hashtable. If GetHashCode uses non-readonly fields and those fields change after the object is stored, the object immediately becomes mis-filed in the Hashtable. Any subsequent test to see if the object is in the Hashtable will return a false negative.

Exceptions

This rule does not raise if the type implementing GetHashCode is a value type, for example a struct or a record struct, since when a value type is stored in a Dictionary or Hashtable, a copy of the value is stored, not a reference to the value.

How to fix it

Code examples

Noncompliant code example

public class Person
{
  public int age;
  public string name;

  public override int GetHashCode()
  {
    int hash = 12;
    hash += this.age.GetHashCode(); // Noncompliant
    hash += this.name.GetHashCode(); // Noncompliant
    return hash;
  }

Compliant solution

public class Person
{
  public readonly DateTime birthday;
  public string name;

  public override int GetHashCode()
  {
    int hash = 12;
    hash += this.birthday.GetHashCode();
    return hash;
  }
================================================ FILE: analyzers/rspec/cs/S2328.json ================================================ { "title": "\"GetHashCode\" should not reference mutable fields", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2328", "sqKey": "S2328", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2330.html ================================================

Why is this an issue?

Array covariance is the principle that if an implicit or explicit reference conversion exits from type A to B, then the same conversion exists from the array type A[] to B[].

While this array conversion can be useful in readonly situations to pass instances of A[] where B[] is expected, it must be used with care, since assigning an instance of B into an array of A will cause an ArrayTypeMismatchException to be thrown at runtime.

Noncompliant code example

abstract class Fruit { }
class Apple : Fruit { }
class Orange : Fruit { }

class Program
{
  static void Main(string[] args)
  {
    Fruit[] fruits = new Apple[1]; // Noncompliant - array covariance is used
    FillWithOranges(fruits);
  }

  // Just looking at the code doesn't reveal anything suspicious
  static void FillWithOranges(Fruit[] fruits)
  {
    for (int i = 0; i < fruits.Length; i++)
    {
      fruits[i] = new Orange(); // Will throw an ArrayTypeMismatchException
    }
  }
}

Compliant solution

abstract class Fruit { }
class Apple : Fruit { }
class Orange : Fruit { }

class Program
{
  static void Main(string[] args)
  {
    Orange[] fruits = new Orange[1]; // Compliant
    FillWithOranges(fruits);
  }

  static void FillWithOranges(Orange[] fruits)
  {
    for (int i = 0; i < fruits.Length; i++)
    {
      fruits[i] = new Orange();
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S2330.json ================================================ { "title": "Array covariance should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2330", "sqKey": "S2330", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2333.html ================================================

Why is this an issue?

Unnecessary keywords simply clutter the code and should be removed. Specifically:

Noncompliant code example

public partial class MyClass // Noncompliant
{
  public virtual void Method()
  {
  }
}

public sealed class MyOtherClass : MyClass
{
  public sealed override void Method() // Noncompliant
  {
  }
}

Compliant solution

public class MyClass
{
  public virtual void Method()
  {
  }
}

public sealed class MyOtherClass : MyClass
{
  public override void Method()
  {
  }
}
================================================ FILE: analyzers/rspec/cs/S2333.json ================================================ { "title": "Redundant modifiers should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused", "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2333", "sqKey": "S2333", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2339.html ================================================

Why is this an issue?

Constant members are copied at compile time to the call sites, instead of being fetched at runtime.

As an example, say you have a library with a constant Version member set to 1.0, and a client application linked to it. This library is then updated and Version is set to 2.0. Unfortunately, even after the old DLL is replaced by the new one, Version will still be 1.0 for the client application. In order to see 2.0, the client application would need to be rebuilt against the new version of the library.

This means that you should use constants to hold values that by definition will never change, such as Zero. In practice, those cases are uncommon, and therefore it is generally better to avoid constant members.

This rule only reports issues on public constant fields, which can be reached from outside the defining assembly.

Noncompliant code example

public class Foo
{
    public const double Version = 1.0;           // Noncompliant
}

Compliant solution

public class Foo
{
    public static double Version
    {
      get { return 1.0; }
    }
}
================================================ FILE: analyzers/rspec/cs/S2339.json ================================================ { "title": "Public constant members should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2339", "sqKey": "S2339", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2342.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently. This rule checks that all enum names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression for non-flags enums: ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$

public enum foo // Noncompliant
{
    FooValue = 0
}

With the default regular expression for flags enums: ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$

[Flags]
public enum Option // Noncompliant
{
    None = 0,
    Option1 = 1,
    Option2 = 2
}

Compliant solution

public enum Foo
{
    FooValue = 0
}
[Flags]
public enum Options
{
    None = 0,
    Option1 = 1,
    Option2 = 2
}
================================================ FILE: analyzers/rspec/cs/S2342.json ================================================ { "title": "Enumeration types should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2342", "sqKey": "S2342", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2344.html ================================================

Why is this an issue?

The information that an enumeration type is actually an enumeration or a set of flags should not be duplicated in its name.

Noncompliant code example

enum FooFlags // Noncompliant
{
    Foo = 1
    Bar = 2
    Baz = 4
}

Compliant solution

enum Foo
{
    Foo = 1
    Bar = 2
    Baz = 4
}
================================================ FILE: analyzers/rspec/cs/S2344.json ================================================ { "title": "Enumeration type names should not have \"Flags\" or \"Enum\" suffixes", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2344", "sqKey": "S2344", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2345.html ================================================

Why is this an issue?

When you annotate an Enum with the Flags attribute, you must not rely on the values that are automatically set by the language to the Enum members, but you should define the enumeration constants in powers of two (1, 2, 4, 8, and so on). Automatic value initialization will set the first member to zero and increment the value by one for each subsequent member. As a result, you won’t be able to use the enum members with bitwise operators.

Exceptions

The default initialization of 0, 1, 2, 3, 4, …​ matches 0, 1, 2, 4, 8 …​ in the first three values, so no issue is reported if the first three members of the enumeration are not initialized.

How to fix it

Define enumeration constants in powers of two, that is, 1, 2, 4, 8, and so on.

Code examples

Noncompliant code example

var bananaAndStrawberry = FruitType.Banana | FruitType.Strawberry;
Console.WriteLine(bananaAndStrawberry.ToString());  // Will display only "Strawberry"

[Flags]
enum FruitType    // Noncompliant
{
  None,
  Banana,
  Orange,
  Strawberry
}

Compliant solution

var bananaAndStrawberry = FruitType.Banana | FruitType.Strawberry;
Console.WriteLine(bananaAndStrawberry.ToString()); // Will display "Banana, Strawberry"

[Flags]
enum FruitType
{
  None = 0,
  Banana = 1,
  Orange = 2,
  Strawberry = 4
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2345.json ================================================ { "title": "Flags enumerations should explicitly initialize all their members", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2345", "sqKey": "S2345", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2346.html ================================================

Why is this an issue?

An enumeration can be decorated with the FlagsAttribute to indicate that it can be used as a bit field: a set of flags, that can be independently set and reset.

For example, the following definition of the day of the week:

[Flags]
enum Days
{
    Monday = 1,    // 0b00000001
    Tuesday = 2,   // 0b00000010
    Wednesday = 4, // 0b00000100
    Thursday = 8,  // 0b00001000
    Friday = 16,   // 0b00010000
    Saturday = 32, // 0b00100000
    Sunday = 64    // 0b01000000
}

allows to define special set of days, such as WeekDays and Weekend using the | operator:

[Flags]
enum Days
{
    // ...
    None = 0,                                                    // 0b00000000
    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday, // 0b00011111
    Weekend = Saturday | Sunday,                                 // 0b01100000
    All = Weekdays | Weekend                                     // 0b01111111
}

These can be used to write more expressive conditions, taking advantage of bitwise operators and Enum.HasFlag:

var someDays = Days.Wednesday | Days.Weekend;  // 0b01100100
someDays.HasFlag(Days.Wednesday);              // someDays contains Wednesday

var mondayAndWednesday = Days.Monday | Days.Wednesday;
someDays.HasFlag(mondayAndWednesday);          // someDays contains Monday and Wednesday
someDays.HasFlag(Days.Monday) || someDays.HasFlag(Days.Wednesday); // someDays contains Monday or Wednesday
someDays & Days.Weekend != Days.None;          // someDays overlaps with the weekend
someDays & Days.Weekdays == Days.Weekdays;     // someDays is only made of weekdays

Consistent use of None in flag enumerations indicates that all flag values are cleared. The value 0 should not be used to indicate any other state since there is no way to check that the bit 0 is set.

[Flags]
enum Days
{
    Monday = 0,    // 0 is used to indicate Monday
    Tuesday = 1,
    Wednesday = 2,
    Thursday = 4,
    Friday = 8,
    Saturday = 16,
    Sunday = 32,
    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekend = Saturday | Sunday,
    All = Weekdays | Weekend
}

var someDays = Days.Wednesday | Days.Thursday;
someDays & Days.Tuesday == Days.Tuesday // False, because someDays doesn't contains Tuesday
someDays & Days.Monday == Days.Monday   // True, even though someDays doesn't contains Monday!
someDays.HasFlag(Days.Monday)           // Same issue as above

How to fix it

Code examples

Noncompliant code example

[Flags]
enum FruitType
{
    Void = 0,        // Non-Compliant
    Banana = 1,
    Orange = 2,
    Strawberry = 4
}

Compliant solution

[Flags]
enum FruitType
{
    None = 0,        // Compliant
    Banana = 1,
    Orange = 2,
    Strawberry = 4
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2346.json ================================================ { "title": "Flags enumerations zero-value members should be named \"None\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2346", "sqKey": "S2346", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2357.html ================================================

Why is this an issue?

Fields should not be part of an API, and therefore should always be private. Indeed, they cannot be added to an interface for instance, and validation cannot be added later on without breaking backward compatibility. Instead, developers should encapsulate their fields into properties. Explicit property getters and setters can be introduced for validation purposes or to smooth the transition to a newer system.

Noncompliant code example

public class Foo
{
  public int MagicNumber = 42;
}

Compliant solution

public class Foo
{
  public int MagicNumber
  {
    get { return 42; }
  }
}

or

public class Foo
{
  private int MagicNumber = 42;
}

Exceptions

structs are ignored, as are static and const fields in classes.

Further, an issue is only raised when the real accessibility is public, taking into account the class accessibility.

================================================ FILE: analyzers/rspec/cs/S2357.json ================================================ { "title": "Fields should be private", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2357", "sqKey": "S2357", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2360.html ================================================

Why is this an issue?

The overloading mechanism should be used in place of optional parameters for several reasons:

Noncompliant code example

void Notify(string company, string office = "QJZ") // Noncompliant
{
}

Compliant solution

void Notify(string company)
{
  Notify(company, "QJZ");
}
void Notify(string company, string office)
{
}

Exceptions

The rule ignores non externally visible methods.

================================================ FILE: analyzers/rspec/cs/S2360.json ================================================ { "title": "Optional parameters should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2360", "sqKey": "S2360", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2365.html ================================================

Why is this an issue?

Most developers expect property access to be as efficient as field access. However, if a property returns a copy of an array or collection, it will be much slower than a simple field access, contrary to the caller’s likely expectations. Therefore, such properties should be refactored into methods so that callers are not surprised by the unexpectedly poor performance.

This rule tracks calls to the following methods inside properties:

How to fix it

Code examples

Noncompliant code example

private List<string> foo = new List<string> { "a", "b", "c" };
private string[] bar = new string[] { "a", "b", "c" };

public IEnumerable<string> Foo => foo.ToList(); // Noncompliant: collection foo is copied

public IEnumerable<string> Bar => (string[])bar.Clone(); // Noncompliant: array bar is copied

Compliant solution

private List<string> foo = new List<string> { "a", "b", "c" };
private string[] bar = new string[] { "a", "b", "c" };

public IEnumerable<string> GetFoo() => foo.ToList();

public IEnumerable<string> GetBar() => (string[])bar.Clone();

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2365.json ================================================ { "title": "Properties should not make collection or array copies", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design", "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2365", "sqKey": "S2365", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2368.html ================================================

Why is this an issue?

Using multidimensional and jagged arrays as method parameters in C# can be challenging for developers.

When these methods are exposed to external users, it requires advanced language knowledge for effective usage.

Determining the appropriate data to pass to these parameters may not be intuitive.

public class Program
{
    public void WriteMatrix(int[][] matrix) // Noncompliant: data type is not intuitive
    {
        // ...
    }
}

In this example, it cannot be inferred easily what the matrix should look like. Is it a 2x2 Matrix or even a triangular Matrix?

Using a collection, data structure, or class that provides a more suitable representation of the required data is recommended instead of a multidimensional array or jagged array to enhance code readability.

public class Matrix2x2
{
    // ...
}

public class Program
{
    public void WriteMatrix(Matrix2x2 matrix) // Compliant: data type is intuitive
    {
        // ...
    }
}

As a result, avoiding exposing such methods to external users is recommended.

Exceptions

However, using multidimensional and jagged array method parameters internally, such as in private or internal methods or within internal classes, is compliant since they are not publicly exposed.

public class FirstClass
{
    private void UpdateMatrix(int[][] matrix) // Compliant: method is private
    {
        // ...
    }
}

internal class SecondClass
{
    public void UpdateMatrix(int[][] matrix) // Compliant: class is internal
    {
        // ...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2368.json ================================================ { "title": "Public methods should not have multidimensional array parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2368", "sqKey": "S2368", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2372.html ================================================

Why is this an issue?

Property getters should be simple operations that are always safe to call. If exceptions need to be thrown, it is best to convert the property to a method.

It is valid to throw exceptions from indexed property getters and from property setters, which are not detected by this rule.

Noncompliant code example

public int Foo
{
    get
    {
        throw new Exception(); // Noncompliant
    }
}

Compliant solution

public int Foo
{
    get
    {
        return 42;
    }
}

Exceptions

No issue is raised when the thrown exception derives from or is of type NotImplementedException, NotSupportedException or InvalidOperationException.

================================================ FILE: analyzers/rspec/cs/S2372.json ================================================ { "title": "Exceptions should not be thrown from property getters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2372", "sqKey": "S2372", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2376.html ================================================

Why is this an issue?

Properties with only setters are confusing and counterintuitive. Instead, a property getter should be added if possible, or the property should be replaced with a setter method.

Noncompliant code example

class Program
{
    public int Foo  //Non-Compliant
    {
        set
        {
            // ... some code ...
        }
    }
}

Compliant solution

class Program
{
    private int foo;

    public void SetFoo(int value)
    {
        // ... some code ...
        foo = value;
    }
}

or

class Program
{
  public int Foo { get; set; } // Compliant
}
================================================ FILE: analyzers/rspec/cs/S2376.json ================================================ { "title": "Write-only properties should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2376", "sqKey": "S2376", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2386.html ================================================

Why is this an issue?

public static mutable fields of classes which are accessed directly should be protected to the degree possible. This can be done by reducing the accessibility of the field or by changing the return type to an immutable type.

This rule raises issues for public static fields with a type inheriting/implementing System.Array or System.Collections.Generic.ICollection<T>.

Noncompliant code example

public class A
{
    public static string[] strings1 = {"first","second"};  // Noncompliant
    public static List<String> strings3 = new List<String>();  // Noncompliant
}

Compliant solution

public class A
{
    protected static string[] strings1 = {"first","second"};
    protected static List<String> strings3 = new List<String>();
}

Exceptions

No issue is reported:

Resources

================================================ FILE: analyzers/rspec/cs/S2386.json ================================================ { "title": "Mutable fields should not be \"public static\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "unpredictable" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2386", "sqKey": "S2386", "scope": "Main", "securityStandards": { "CWE": [ 607, 582 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2387.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

Having a variable with the same name in two unrelated classes is fine, but do the same thing within a class hierarchy and you’ll get confusion at best, chaos at worst.

Noncompliant code example

public class Fruit
{
  protected Season ripe;
  protected Color flesh;

  // ...
}

public class Raspberry : Fruit
{
  private bool ripe; // Noncompliant
  private static Color FLESH; // Noncompliant
}

Compliant solution

public class Fruit
{
  protected Season ripe;
  protected Color flesh;

  // ...
}

public class Raspberry : Fruit
{
  private bool ripened;
  private static Color FLESH_COLOR;
}

Exceptions

This rule ignores same-name fields that are static in both the parent and child classes. It also ignores private parent class fields, but in all other such cases, the child class field should be renamed.

public class Fruit
{
  private Season ripe;
  // ...
}

public class Raspberry : Fruit
{
  private Season ripe;  // Compliant as parent field 'ripe' is anyway not visible from Raspberry
  // ...
}
================================================ FILE: analyzers/rspec/cs/S2387.json ================================================ { "title": "Child class fields should not shadow parent class fields", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2387", "sqKey": "S2387", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2436.html ================================================

Why is this an issue?

A method or class with too many type parameters has likely aggregated too many responsibilities and should be split.

Noncompliant code example

With the default parameter value of 2:

<S, T, U, V> void foo() {} // Noncompliant; not really readable
<String, Integer, Object, String>foo(); // especially on invocations
================================================ FILE: analyzers/rspec/cs/S2436.json ================================================ { "title": "Types and methods should not have too many generic parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2436", "sqKey": "S2436", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2437.html ================================================

Why is this an issue?

Certain bitwise operations are not needed and should not be performed because their results are predictable.

Specifically, using & -1 with any value always results in the original value.

That is because the binary representation of -1 on a integral numeric type supporting negative numbers, such as int or long, is based on two’s complement and made of all 1s: 0b111…​111.

Performing & between a value and 0b111…​111 means applying the & operator to each bit of the value and the bit 1, resulting in a value equal to the provided one, bit by bit.

anyValue & -1 // Noncompliant
anyValue      // Compliant

Similarly, anyValue | 0 always results in anyValue, because the binary representation of 0 is always 0b000…​000 and the | operator returns its first input when the second is 0.

anyValue | 0  // Noncompliant
anyValue      // Compliant

The same applies to anyValue ^ 0: the ^ operator returns 1 when its two input bits are different (1 and 0 or 0 and 1) and returns 0 when its two input bits are the same (both 0 or both 1). When ^ is applied with 0, the result would be 1 if the other input is 1, because the two input bits are different, and 0 if the other input bit is 0, because the two input are the same. That results in returning anyValue.

anyValue ^ 0  // Noncompliant
anyValue      // Compliant

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2437.json ================================================ { "title": "Unnecessary bit operations should not be performed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2437", "sqKey": "S2437", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2445.html ================================================

Why is this an issue?

Locking on a class field synchronizes not on the field itself, but on the object assigned to it. Thus, there are some good practices to follow to avoid problems related to thread synchronization.

How to fix it

Code examples

Noncompliant code example

private Color color = new Color("red");
private void DoSomething()
{
  // Synchronizing access via "color"
  lock (color) // Noncompliant: lock is actually on object instance "red" referred to by the "color" field
  {
    //...
    color = new Color("green"); // other threads now allowed into this block
    // ...
  }
}

Compliant solution

private Color color = new Color("red");
private readonly object lockObj = new object();

private void DoSomething()
{
  lock (lockObj)
  {
    //...
    color = new Color("green");
    // ...
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S2445.json ================================================ { "title": "Blocks should be synchronized on read-only fields", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "multi-threading" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2445", "sqKey": "S2445", "scope": "All", "securityStandards": { "CWE": [ 412, 413 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2479.html ================================================

Why is this an issue?

Non-encoded control characters and whitespace characters are often injected in the source code because of a bad manipulation. They are either invisible or difficult to recognize, which can result in bugs when the string is not what the developer expects. If you actually need to use a control character use their encoded version:

This rule raises an issue when the following characters are seen in a string literal:

Exceptions

How to fix it

Code examples

Noncompliant code example

string tabInside = "A	B";                 // Noncompliant: contains a tabulation
string zeroWidthSpaceInside = "foo​bar";     // Noncompliant: contains a U+200B character inside
Console.WriteLine(zeroWidthSpaceInside);    // Prints "foo?bar"

Compliant solution

string tabInside = "A\tB";                      // Compliant: escaped value
string zeroWidthSpaceInside = "foo\u200Bbar";   // Compliant: escaped value
Console.WriteLine(zeroWidthSpaceInside);        // Prints "foo?bar"

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2479.json ================================================ { "title": "Whitespace and control characters in string literals should be explicit", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2479", "sqKey": "S2479", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2486.html ================================================

Why is this an issue?

When exceptions occur, it is usually a bad idea to simply ignore them. Instead, it is better to handle them properly, or at least to log them.

This rule only reports on empty catch clauses that catch generic Exceptions.

Noncompliant code example

string text = "";
try
{
    text = File.ReadAllText(fileName);
}
catch (Exception exc) // Noncompliant
{
}

Compliant solution

string text = "";
try
{
    text = File.ReadAllText(fileName);
}
catch (Exception exc)
{
    logger.Log(exc);
}

Exceptions

When a block contains a comment, it is not considered to be empty.

Resources

================================================ FILE: analyzers/rspec/cs/S2486.json ================================================ { "title": "Generic exceptions should not be ignored", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "cwe", "error-handling", "suspicious" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2486", "sqKey": "S2486", "scope": "Main", "securityStandards": { "CWE": [ 390 ], "OWASP": [ "A10" ], "OWASP Top 10 2021": [ "A9" ], "PCI DSS 3.2": [ "6.5.5", "10.1", "10.2", "10.3" ], "PCI DSS 4.0": [ "6.2.4", "10.2" ], "ASVS 4.0": [ "11.1.8" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2551.html ================================================

The instance passed to the lock statement should be a dedicated private field.

Why is this an issue?

If the instance representing an exclusively acquired lock is publicly accessible, another thread in another part of the program could accidentally attempt to acquire the same lock. This increases the likelihood of deadlocks.

For example, a string should never be used for locking. When a string is interned by the runtime, it can be shared by multiple threads, breaking the locking mechanism.

Instead, a dedicated private Lock object instance (or object instance, for frameworks before .Net 9) should be used for locking. This minimizes access to the lock instance and therefore prevents accidential lock sharing.

The following objects are considered potentially prone to accidental lock sharing:

How to fix it

Code examples

Noncompliant code example

void MyLockingMethod()
{
    lock (this) // Noncompliant
    {
        // ...
    }
}

Compliant solution

#if NET9_0_OR_GREATER
private readonly Lock lockObj = new();
#else
private readonly object lockObj = new();
#endif

void MyLockingMethod()
{
    lock (lockObj)
    {
        // ...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2551.json ================================================ { "title": "Shared resources should not be used for locking", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "multi-threading" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2551", "sqKey": "S2551", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2583.html ================================================

Why is this an issue?

Conditional expressions which are always true or false can lead to unreachable code.

In the case below, the call of Dispose() never happens.

var a = false;
if (a)
{
    Dispose(); // Never reached
}

Exceptions

This rule will not raise an issue in either of these cases:

In these cases, it is obvious the code is as intended.

How to fix it

The conditions should be reviewed to decide whether:

Code examples

Noncompliant code example

public void Sample(bool b)
{
    bool a = false;
    if (a)                  // Noncompliant: The true branch is never reached
    {
        DoSomething();      // Never reached
    }

    if (!a || b)            // Noncompliant: "!a" is always "true" and the false branch is never reached
    {
        DoSomething();
    }
    else
    {
        DoSomethingElse();  // Never reached
    }

    var c = "xxx";
    var res = c ?? "value"; // Noncompliant: c is always not null, "value" is never used
}

Compliant solution

public void Sample(bool b)
{
    bool a = false;
    if (Foo(a))             // Condition was updated
    {
        DoSomething();
    }

    if (b)                  // Parts of the condition were removed.
    {
        DoSomething();
    }
    else
    {
        DoSomethingElse();
    }

    var c = "xxx";
    var res = c;            // ?? "value" was removed
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2583.json ================================================ { "title": "Conditionally executed code should be reachable", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "unused", "suspicious", "pitfall", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2583", "sqKey": "S2583", "scope": "All", "securityStandards": { "CWE": [ 489, 571, 570 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2589.html ================================================

Gratuitous boolean expressions are conditions that do not change the evaluation of a program. This issue can indicate logical errors and affect the correctness of an application, as well as its maintainability.

Why is this an issue?

Control flow constructs like if-statements allow the programmer to direct the flow of a program depending on a boolean expression. However, if the condition is always true or always false, only one of the branches will ever be executed. In that case, the control flow construct and the condition no longer serve a purpose; they become gratuitous.

What is the potential impact?

The presence of gratuitous conditions can indicate a logical error. For example, the programmer intended to have the program branch into different paths but made a mistake when formulating the branching condition. In this case, this issue might result in a bug and thus affect the reliability of the application. For instance, it might lead to the computation of incorrect results.

Additionally, gratuitous conditions and control flow constructs introduce unnecessary complexity. The source code becomes harder to understand, and thus, the application becomes more difficult to maintain.

This rule looks for operands of a boolean expression never changing the result of the expression. It also applies to the null coalescing operator when one of the operands always evaluates to null.

string d = null;
var v1 = d ?? "value";      // Noncompliant

Exceptions

This rule will not raise an issue in either of these cases:

In these cases, it is obvious the code is as intended.

How to fix it

Gratuitous boolean expressions are suspicious and should be carefully removed from the code.

First, the boolean expression in question should be closely inspected for logical errors. If a mistake was made, it can be corrected so the condition is no longer gratuitous.

If it becomes apparent that the condition is actually unnecessary, it can be removed. The associated control flow construct (e.g., the if-statement containing the condition) will be adapted or even removed, leaving only the necessary branches.

Code examples

Noncompliant code example

public void Sample(bool b, bool c)
{
    var a = true;
    if (a)                  // Noncompliant: "a" is always "true"
    {
        DoSomething();
    }

    if (b && a)             // Noncompliant: "a" is always "true"
    {
        DoSomething();
    }

    if (c || !a)            // Noncompliant: "!a" is always "false"
    {
        DoSomething();
    }

    string d = null;
    var v1 = d ?? "value";  // Noncompliant: "d" is always null and v1 is always "value".
    var v2 = s ?? d;        // Noncompliant: "d" is always null and v2 is always equal to s.
}

Compliant solution

The unnecessary operand is updated:

public void Sample(bool b, bool c, string s)
{
    var a = IsAllowed();
    if (a)                  // Compliant
    {
        DoSomething();
    }

    if (b && a)             // Compliant
    {
        DoSomething();
    }

    if (c || !a)            // Compliant
    {
        DoSomething();
    }

    string d = GetStringData();
    var v1 = d ?? "value";  // Compliant
    var v2 = s ?? d;        // Compliant
}

The unnecessary operand is removed:

public void Sample(bool b, bool c, string s)
{
    DoSomething();

    if (b)                  // Compliant
    {
        DoSomething();
    }

    if (c)                  // Compliant
    {
        DoSomething();
    }

    var v1 = "value";       // Compliant
    var v2 = s;             // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2589.json ================================================ { "title": "Boolean expressions should not be gratuitous", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "suspicious", "redundant", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2589", "sqKey": "S2589", "scope": "All", "securityStandards": { "CWE": [ 489, 571, 570 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2612.html ================================================

Why is this an issue?

In Windows, "Everyone" group is similar and includes all members of the Authenticated Users group as well as the built-in Guest account, and several other built-in security accounts.

Granting permissions to this category can lead to unintended access to files or directories that could allow attackers to obtain sensitive information, disrupt services or elevate privileges.

What is the potential impact?

Unauthorized access to sensitive information

When file or directory permissions grant access to all users on a system (often represented as "others" or "everyone" in permission models), attackers who gain access to any user account can read sensitive files containing credentials, configuration data, API keys, database passwords, personal information, or proprietary business data. This exposure can lead to data breaches, identity theft, compliance violations, and competitive disadvantage.

Service disruption and data corruption

Granting write permissions to broad user categories allows any user on the system to modify or delete critical files and directories. Attackers or compromised low-privileged accounts can corrupt application data, modify configuration files to alter system behavior or disrupt services, or delete important resources, leading to service outages, system instability, data loss, and denial of service.

Privilege escalation

When executable files or scripts have overly permissive permissions, especially when combined with special permission bits that allow programs to execute with the permissions of the file owner or group rather than the executing user, attackers can replace legitimate executables with malicious code. When these modified files are executed by privileged users or processes, the attacker’s code runs with elevated privileges, potentially enabling them to escalate from a low-privileged account to root or administrator access, install backdoors, or pivot to other systems in the network.

How to fix it in .NET Framework

Instead of granting access to "Everyone", explicitly deny access to this group or grant permissions only to specific users or groups that require access. Use AccessControlType.Deny to prevent the "Everyone" group from accessing the file.

Code examples

Noncompliant code example

var accessRule = new FileSystemAccessRule(
    "Everyone",
    FileSystemRights.FullControl,
    AccessControlType.Allow);

var fileSecurity = File.GetAccessControl("path");
fileSecurity.AddAccessRule(accessRule); // Noncompliant
File.SetAccessControl("fileName", fileSecurity);

Compliant solution

var accessRule = new FileSystemAccessRule(
    "Everyone",
    FileSystemRights.FullControl,
    AccessControlType.Deny);

var fileSecurity = File.GetAccessControl("path");
fileSecurity.AddAccessRule(accessRule);
File.SetAccessControl("path", fileSecurity);

How to fix it in .NET

Use AccessControlType.Deny instead of AccessControlType.Allow when setting access rules for the "Everyone" group. This prevents the broad group from having write or full control access to files.

Code examples

Noncompliant code example

var accessRule = new FileSystemAccessRule("Everyone", FileSystemRights.Write, AccessControlType.Allow);
var fileInfo = new FileInfo("path");
var fileSecurity = fileInfo.GetAccessControl();

fileSecurity.SetAccessRule(accessRule); // Noncompliant
fileInfo.SetAccessControl(fileSecurity);

Compliant solution

var accessRule = new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Deny);
var fileInfo = new FileInfo("path");
var fileSecurity = fileInfo.GetAccessControl();

fileSecurity.SetAccessRule(accessRule);
fileInfo.SetAccessControl(fileSecurity);

How to fix it in Mono

Avoid setting permissions that grant read, write, or execute access to "others" (all users). Instead, restrict permissions to the file owner or specific groups. Use FileAccessPermissions.UserExecute or other restrictive permission flags that limit access to the owner only.

Code examples

Noncompliant code example

var fsEntry = UnixFileSystemInfo.GetFileSystemEntry("path");
fsEntry.FileAccessPermissions = FileAccessPermissions.OtherReadWriteExecute; // Noncompliant

Compliant solution

var fsEntry = UnixFileSystemInfo.GetFileSystemEntry("path");
fsEntry.FileAccessPermissions = FileAccessPermissions.UserExecute;

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S2612.json ================================================ { "title": "File permissions should not be set to world-accessible values", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "former-hotspot" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2612", "sqKey": "S2612", "scope": "Main", "securityStandards": { "CWE": [ 732, 266 ], "OWASP": [ "A5" ], "OWASP Top 10 2021": [ "A1", "A4" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "4.3.3" ], "STIG ASD_V5R3": [ "V-222430" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2629.html ================================================

Why is this an issue?

Logging arguments should not require evaluation in order to avoid unnecessary performance overhead. When passing concatenated strings or string interpolations directly into a logging method, the evaluation of these expressions occurs every time the logging method is called, regardless of the log level. This can lead to inefficient code execution and increased resource consumption.

Instead, it is recommended to use the overload of the logger that accepts a log format and its arguments as separate parameters. By separating the log format from the arguments, the evaluation of expressions can be deferred until it is necessary, based on the log level. This approach improves performance by reducing unnecessary evaluations and ensures that logging statements are only evaluated when needed.

Furthermore, using a constant log format enhances observability and facilitates searchability in log aggregation and monitoring software.

The rule covers the following logging frameworks:

How to fix it

Use an overload that takes the log format and the parameters as separate arguments. The log format should be a constant string.

Code examples

Noncompliant code example

logger.DebugFormat($"The value of the parameter is: {parameter}.");

Compliant solution

logger.DebugFormat("The value of the parameter is: {Parameter}.", parameter);

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

CastleCoreLoggingTemplateNotConstant

.NET 9.0

230.306 us

2.7116 us

479200 B

CastleCoreLoggingTemplateConstant

.NET 9.0

46.827 us

1.4743 us

560000 B

CastleCoreLoggingTemplateNotConstant

.NET Framework 4.8.1

1,060.413 us

32.3559 us

1115276 B

CastleCoreLoggingTemplateConstant

.NET Framework 4.8.1

93.697 us

1.8201 us

561650 B

MSLoggingTemplateNotConstant

.NET 9.0

333.246 us

12.9214 us

479200 B

MSLoggingTemplateConstant

.NET 9.0

441.118 us

68.7999 us

560000 B

MSLoggingTemplateNotConstant

.NET Framework 4.8.1

1,542.076 us

99.3423 us

1115276 B

MSLoggingTemplateConstant

.NET Framework 4.8.1

698.071 us

18.6319 us

561653 B

NLogLoggingTemplateNotConstant

.NET 9.0

178.789 us

9.2528 us

479200 B

NLogLoggingTemplateConstant

.NET 9.0

6.009 us

1.3303 us

-

NLogLoggingTemplateNotConstant

.NET Framework 4.8.1

1,086.260 us

44.1670 us

1115276 B

NLogLoggingTemplateConstant

.NET Framework 4.8.1

25.132 us

0.5666 us

-

SerilogLoggingTemplateNotConstant

.NET 9.0

234.460 us

7.4831 us

479200 B

SerilogLoggingTemplateConstant

.NET 9.0

49.854 us

1.8232 us

-

SerilogLoggingTemplateNotConstant

.NET Framework 4.8.1

1,103.939 us

47.0203 us

1115276 B

SerilogLoggingTemplateConstant

.NET Framework 4.8.1

35.752 us

0.6022 us

-

Log4NetLoggingTemplateNotConstant

.NET 9.0

255.754 us

5.6046 us

479200 B

Log4NetLoggingTemplateConstant

.NET 9.0

46.425 us

1.7087 us

240000 B

Log4NetLoggingTemplateNotConstant

.NET Framework 4.8.1

1,109.874 us

23.4388 us

1115276 B

Log4NetLoggingTemplateConstant

.NET Framework 4.8.1

92.305 us

2.4161 us

240707 B

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;

[Params(10_000)]
public int Iterations;

private ILogger ms_logger;
private Castle.Core.Logging.ILogger cc_logger;
private log4net.ILog l4n_logger;
private Serilog.ILogger se_logger;
private NLog.ILogger nl_logger;

[GlobalSetup]
public void GlobalSetup()
{
    ms_logger = new LoggerFactory().CreateLogger<LoggingTemplates>();
    cc_logger = new Castle.Core.Logging.NullLogFactory().Create("Castle.Core.Logging");
    l4n_logger = log4net.LogManager.GetLogger(typeof(LoggingTemplates));
    se_logger = Serilog.Log.Logger;
    nl_logger = NLog.LogManager.GetLogger("NLog");
}

[BenchmarkCategory("Microsoft.Extensions.Logging")]
[Benchmark]
public void MSLoggingTemplateNotConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        ms_logger.LogInformation($"Param: {i}");
    }
}

[BenchmarkCategory("Microsoft.Extensions.Logging")]
[Benchmark]
public void MSLoggingTemplateConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        ms_logger.LogInformation("Param: {Parameter}", i);
    }
}

[BenchmarkCategory("Castle.Core.Logging")]
[Benchmark]
public void CastleCoreLoggingTemplateNotConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        cc_logger.Info($"Param: {i}");
    }
}

[BenchmarkCategory("Castle.Core.Logging")]
[Benchmark]
public void CastleCoreLoggingTemplateConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        cc_logger.InfoFormat("Param: {Parameter}", i);
    }
}

[BenchmarkCategory("log4net")]
[Benchmark]
public void Log4NetLoggingTemplateNotConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        l4n_logger.Info($"Param: {i}");
    }
}

[BenchmarkCategory("log4net")]
[Benchmark]
public void Log4NetLoggingTemplateConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        l4n_logger.InfoFormat("Param: {Parameter}", i);
    }
}

[BenchmarkCategory("Serilog")]
[Benchmark]
public void SerilogLoggingTemplateNotConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        se_logger.Information($"Param: {i}");
    }
}

[BenchmarkCategory("Serilog")]
[Benchmark]
public void SerilogLoggingTemplateConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        se_logger.Information("Param: {Parameter}", i);
    }
}

[BenchmarkCategory("NLog")]
[Benchmark]
public void NLogLoggingTemplateNotConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        nl_logger.Info($"Param: {i}");
    }
}

[BenchmarkCategory("NLog")]
[Benchmark]
public void NLogLoggingTemplateConstant()
{
    for (int i = 0; i < Iterations; i++)
    {
        nl_logger.Info("Param: {Parameter}", i);
    }
}

Hardware Configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S2629.json ================================================ { "title": "Logging templates should be constant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance", "logging" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2629", "sqKey": "S2629", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2674.html ================================================

Why is this an issue?

Invoking a stream reading method without verifying the number of bytes read can lead to erroneous assumptions. A Stream can represent any I/O operation, such as reading a file, network communication, or inter-process communication. As such, it is not guaranteed that the byte[] passed into the method will be filled with the requested number of bytes. Therefore, inspecting the value returned by the reading method is important to ensure the number of bytes read.

Neglecting the returned length read can result in a bug that is difficult to reproduce.

This rule raises an issue when the returned value is ignored for the following methods:

How to fix it

Check the return value of stream reading methods to verify the actual number of bytes read, and use this value when processing the data to avoid potential bugs.

Code examples

Noncompliant code example

public byte[] ReadFile(string fileName)
{
    using var stream = File.Open(fileName, FileMode.Open);
    var result = new byte[stream.Length];

    stream.Read(result, 0, (int)stream.Length); // Noncompliant

    return result;
}

Compliant solution

public byte[] ReadFile(string fileName)
{
    using var stream = File.Open(fileName, FileMode.Open);
    using var ms = new MemoryStream();
    var buffer = new byte[1024];
    int read;

    while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
    {
        ms.Write(buffer, 0, read);
    }

    return ms.ToArray();
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2674.json ================================================ { "title": "The length returned from a stream read should be checked", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2674", "sqKey": "S2674", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2681.html ================================================

Why is this an issue?

Having inconsistent indentation and omitting curly braces from a control structure, such as an if statement or for loop, is misleading and can induce bugs.

This rule raises an issue when the indentation of the lines after a control structure indicates an intent to include those lines in the block, but the omission of curly braces means the lines will be unconditionally executed once.

The following patterns are recognized:

if (condition)
  FirstActionInBlock();
  SecondAction();  // Noncompliant: SecondAction is executed unconditionally
ThirdAction();
if(condition) FirstActionInBlock(); SecondAction();  // Noncompliant: SecondAction is executed unconditionally
if (condition) FirstActionInBlock();
  SecondAction();  // Noncompliant: SecondAction is executed unconditionally
string str = null;
for (int i = 0; i < array.Length; i++)
  str = array[i];
  DoTheThing(str);  // Noncompliant: executed only on the last element

Note that this rule considers tab characters to be equivalent to 1 space. When mixing spaces and tabs, a code may look fine in one editor but be confusing in another configured differently.

Resources

================================================ FILE: analyzers/rspec/cs/S2681.json ================================================ { "title": "Multiline blocks should be enclosed in curly braces", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2681", "sqKey": "S2681", "scope": "All", "securityStandards": { "CWE": [ 483 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2688.html ================================================

Why is this an issue?

double.NaN and float.NaN are not equal to anything, not even themselves.

When anything is compared with NaN using one of the comparison operators >, >=, <, or the equality operator ==, the result will always be false. In contrast, when anything is compared with NaN using the inequality operator !=, the result will always be true.

Instead, the best way to see whether a variable is equal to NaN is to use the float.IsNaN and double.IsNaN methods, which work as expected.

How to fix it

Code examples

Noncompliant code example

var a = double.NaN;

if (a == double.NaN) // Noncompliant: always false
{
  Console.WriteLine("a is not a number");
}
if (a != double.NaN)  // Noncompliant: always true
{
  Console.WriteLine("a is not NaN");
}

Compliant solution

var a = double.NaN;

if (double.IsNaN(a))
{
  Console.WriteLine("a is not a number");
}
if (!double.IsNaN(a))
{
  Console.WriteLine("a is not NaN");
}
================================================ FILE: analyzers/rspec/cs/S2688.json ================================================ { "title": "\"NaN\" should not be used in comparisons", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2688", "sqKey": "S2688", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2692.html ================================================

Why is this an issue?

Most checks against an IndexOf value compare it with -1 because 0 is a valid index.

strings.IndexOf(someString) == -1 // Test for "index not found"
strings.IndexOf(someString) < 0   // Test for "index not found"
strings.IndexOf(someString) >= 0  // Test for "index found"

Any checks which look for values > 0 ignore the first element, which is likely a bug. If the intent is merely to check the inclusion of a value in a string, List, or array, consider using the Contains method instead.

strings.Contains(someString) // bool result

This rule raises an issue when the output value of any of the following methods is tested against > 0:

someArray.IndexOf(someItem) > 0        // Noncompliant: index 0 missing
someString.IndexOfAny(charsArray) > 0  // Noncompliant: index 0 missing
someList.LastIndexOf(someItem) > 0     // Noncompliant: index 0 missing
someString.LastIndexOf(charsArray) > 0 // Noncompliant: index 0 missing

How to fix it

Code examples

Noncompliant code example

string color = "blue";
string name = "ishmael";

List<string> strings = new List<string>();
strings.Add(color);
strings.Add(name);
string[] stringArray = strings.ToArray();

if (strings.IndexOf(color) > 0) // Noncompliant
{
  // ...
}

if (name.IndexOf("ish") > 0) // Noncompliant
{
  // ...
}

if (name.IndexOf("ae") > 0) // Noncompliant
{
  // ...
}

if (Array.IndexOf(stringArray, color) > 0) // Noncompliant
{
  // ...
}

Compliant solution

string color = "blue";
string name = "ishmael";

List<string> strings = new List<string>();
strings.Add(color);
strings.Add(name);
string[] stringArray = strings.ToArray();

if (strings.IndexOf(color) > -1)
{
  // ...
}

if (name.IndexOf("ish") >= 0)
{
  // ...
}

if (name.Contains("ae"))
{
  // ...
}

if (Array.IndexOf(stringArray, color) >= 0)
{
  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2692.json ================================================ { "title": "\"IndexOf\" checks should not be for positive numbers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2692", "sqKey": "S2692", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2696.html ================================================

This rule raises an issue each time a static field is updated from a non-static method or property.

Why is this an issue?

Updating a static field from a non-static method introduces significant challenges and potential bugs. Multiple class instances and threads can access and modify the static field concurrently, leading to unintended consequences for other instances or threads (unexpected behavior, race conditions and synchronization problems).

class MyClass
{
  private static int count = 0;

  public void DoSomething()
  {
    //...
    count++;  // Noncompliant: make the enclosing instance property 'static' or remove this set on the 'static' field.
  }
}

interface MyInterface
{
  private static int count = 0;

  public void DoSomething()
  {
    //...
    count++;  // Noncompliant: remove this set, which updates a 'static' field from an instance method.
  }
}

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S2696.json ================================================ { "title": "Instance members should not write to \"static\" fields", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "multi-threading" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2696", "sqKey": "S2696", "scope": "Main", "securityStandards": { "STIG ASD_V5R3": [ "V-222567" ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2699.html ================================================

Why is this an issue?

The rule targets test methods that lack an assertion and consist solely of an action and, optionally, a setup.

[TestMethod]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
}

Such tests only verify that the system under test does not throw any exceptions without providing any guarantees regarding the code’s behavior under test. Those tests increase the coverage without enforcing anything on the covered code, resulting in a false sense of security.

The rule identifies a potential issue when no assertions are present in tests utilizing the following frameworks:

By enforcing the presence of assertions, this rule aims to enhance the reliability and comprehensiveness of tests by ensuring that they provide meaningful validation of the expected behavior.

Exceptions

Test methods that include a call to a custom assertion method will not raise any issues.

How can you fix it?

To address this issue, you should include assertions to validate the expected behavior. Choose an appropriate assertion method provided by your testing framework (such as MSTest, NUnit, xUnit) or select a suitable assertion library like FluentAssertions, NFluent, NSubstitute, Moq, or Shouldly.

In addition to using built-in assertion methods, you also have the option to create custom assertion methods. To do this, declare an attribute named [AssertionMethodAttribute] and apply it to the respective method. This allows you to encapsulate specific validation logic within your custom assertion methods without raising the issue. Here’s an example:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class CustomTestExample
{
    [TestMethod]
    public void Add_SingleNumber_ReturnsSameNumber()
    {
        var stringCalculator = new StringCalculator();
        var actual = stringCalculator.Add("0");
        Validator.AssertCustomEquality(0, actual); // Compliant
    }
}

public static class Validator
{
    [AssertionMethod]
    public static void AssertCustomEquality(int expected, int actual)
    {
        // ...
    }
}

public class AssertionMethodAttribute : Attribute { }

How to fix it in MSTest

Code examples

Noncompliant code example

[TestMethod]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
}

Compliant solution

[TestMethod]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
    Assert.AreEqual(0, actual);
}

How to fix it in NUnit

Code examples

Noncompliant code example

[Test]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
}

Compliant solution

[Test]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
    Assert.That(0, Is.EqualTo(actual));
}

How to fix it in xUnit

Code examples

Noncompliant code example

[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
}

Compliant solution

[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
    var stringCalculator = new StringCalculator();
    var actual = stringCalculator.Add("0");
    Assert.Equal(0, actual);
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S2699.json ================================================ { "title": "Tests should include assertions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "tests" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2699", "sqKey": "S2699", "scope": "Tests", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2701.html ================================================

Why is this an issue?

Using literal boolean values in assertions can lead to less readable and less informative unit tests. When a test fails, it’s important to have a clear understanding of what the test was checking and why it failed. Most of the testing frameworks provide more explicit assertion methods that will provide a more helpful error message if the test fails.

Exceptions

In the context of xUnit, Assert.True and Assert.False are not flagged by this rule. This is because Assert.Fail was only introduced in 2020 with version 2.4.2. Prior to this, developers used Assert.True(false, message) and Assert.False(true, message) as workarounds to simulate the functionality of Assert.Fail().

How to fix it in MSTest

Code examples

Noncompliant code example

bool someResult;

Assert.AreEqual(false, someResult); // Noncompliant: use Assert.IsFalse
Assert.AreEqual(true, someResult); // Noncompliant: use Assert.IsTrue
Assert.AreNotEqual(false, someResult); // Noncompliant: use Assert.IsTrue
Assert.AreNotEqual(true, someResult); // Noncompliant: use Assert.IsFalse
Assert.IsFalse(true, "Should not reach this line!"); // Noncompliant: use Assert.Fail
Assert.IsTrue(false, "Should not reach this line!"); // Noncompliant: use Assert.Fail
Assert.IsFalse(false); // Noncompliant: remove it

Compliant solution

bool someResult;

Assert.IsFalse(someResult);
Assert.IsTrue(someResult);
Assert.IsTrue(someResult);
Assert.IsFalse(someResult);
Assert.Fail("Should not reach this line!");
Assert.Fail("Should not reach this line!");
// Removed

How to fix it in NUnit

Code examples

Noncompliant code example

bool someResult;

Assert.AreEqual(false, someResult); // Noncompliant: use Assert.False
Assert.AreEqual(true, someResult); // Noncompliant: use Assert.True
Assert.AreNotEqual(false, someResult); // Noncompliant: use Assert.True
Assert.AreNotEqual(true, someResult); // Noncompliant: use Assert.False
Assert.False(true, "Should not reach this line!"); // Noncompliant: use Assert.Fail
Assert.True(false, "Should not reach this line!"); // Noncompliant: use Assert.Fail
Assert.False(false); // Noncompliant: remove it

Compliant solution

bool someResult;

Assert.False(someResult);
Assert.True(someResult);
Assert.True(someResult);
Assert.False(someResult);
Assert.Fail("Should not reach this line!");
Assert.Fail("Should not reach this line!");
// Removed

How to fix it in xUnit

Code examples

Noncompliant code example

bool someResult;

Assert.Equal(false, someResult); // Noncompliant: use Assert.False
Assert.Equal(true, someResult); // Noncompliant: use Assert.True
Assert.NotEqual(false, someResult); // Noncompliant: use Assert.True
Assert.NotEqual(true, someResult); // Noncompliant: use Assert.False

Compliant solution

bool someResult;

Assert.False(someResult);
Assert.True(someResult);
Assert.True(someResult);
Assert.False(someResult);

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2701.json ================================================ { "title": "Literal boolean values should not be used in assertions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "tests" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2701", "sqKey": "S2701", "scope": "Tests", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2737.html ================================================

Why is this an issue?

A catch clause that only rethrows the caught exception has the same effect as omitting the catch altogether and letting it bubble up automatically.

string s = "";
try
{
  s = File.ReadAllText(fileName);
}
catch (Exception e)  // Noncompliant
{
  throw;
}

Such clauses should either be removed or populated with the appropriate logic.

string s = File.ReadAllText(fileName);

or

string s = "";
try
{
  s = File.ReadAllText(fileName);
}
catch (Exception e)
{
  logger.LogError(e);
  throw;
}

Exceptions

This rule will not generate issues for catch blocks if they are followed by a catch block for a more general exception type that does more than just rethrowing the exception.

var s = ""
try
{
    s = File.ReadAllText(fileName);
}
catch (IOException) // Compliant by exception: removing it would change the logic
{
    throw;
}
catch (Exception)  // Compliant: does more than just rethrow
{
    logger.LogError(e);
    throw;
}
================================================ FILE: analyzers/rspec/cs/S2737.json ================================================ { "title": "\"catch\" clauses should do more than rethrow", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "error-handling", "unused", "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2737", "sqKey": "S2737", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2743.html ================================================

Why is this an issue?

A static field in a generic type is not shared among instances of different closed constructed types, thus LengthLimitedSingletonCollection<int>.instances and LengthLimitedSingletonCollection<string>.instances will point to different objects, even though instances is seemingly shared among all LengthLimitedSingletonCollection<> generic classes.

If you need to have a static field shared among instances with different generic arguments, define a non-generic base class to store your static members, then set your generic type to inherit from the base class.

Noncompliant code example

public class LengthLimitedSingletonCollection<T> where T : new()
{
  protected const int MaxAllowedLength = 5;
  protected static Dictionary<Type, object> instances = new Dictionary<Type, object>(); // Noncompliant

  public static T GetInstance()
  {
    object instance;

    if (!instances.TryGetValue(typeof(T), out instance))
    {
      if (instances.Count >= MaxAllowedLength)
      {
        throw new Exception();
      }
      instance = new T();
      instances.Add(typeof(T), instance);
    }
    return (T)instance;
  }
}

Compliant solution

public class SingletonCollectionBase
{
  protected static Dictionary<Type, object> instances = new Dictionary<Type, object>();
}

public class LengthLimitedSingletonCollection<T> : SingletonCollectionBase where T : new()
{
  protected const int MaxAllowedLength = 5;

  public static T GetInstance()
  {
    object instance;

    if (!instances.TryGetValue(typeof(T), out instance))
    {
      if (instances.Count >= MaxAllowedLength)
      {
        throw new Exception();
      }
      instance = new T();
      instances.Add(typeof(T), instance);
    }
    return (T)instance;
  }
}

Exceptions

If the static field or property uses a type parameter, then the developer is assumed to understand that the static member is not shared among the closed constructed types.

public class Cache<T>
{
   private static Dictionary<string, T> CacheDictionary { get; set; } // Compliant
}
================================================ FILE: analyzers/rspec/cs/S2743.json ================================================ { "title": "Static fields should not be used in generic types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2743", "sqKey": "S2743", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2755.html ================================================

This vulnerability allows the usage of external entities in XML.

Why is this an issue?

External Entity Processing allows for XML parsing with the involvement of external entities. However, when this functionality is enabled without proper precautions, it can lead to a vulnerability known as XML External Entity (XXE) attack.

What is the potential impact?

Exposing sensitive data

One significant danger of XXE vulnerabilities is the potential for sensitive data exposure. By crafting malicious XML payloads, attackers can reference external entities that contain sensitive information, such as system files, database credentials, or configuration files. When these entities are processed during XML parsing, the attacker can extract the contents and gain unauthorized access to sensitive data. This poses a severe threat to the confidentiality of critical information.

Exhausting system resources

Another consequence of XXE vulnerabilities is the potential for denial-of-service attacks. By exploiting the ability to include external entities, attackers can construct XML payloads that cause resource exhaustion. This can overwhelm the system’s memory, CPU, or other critical resources, leading to system unresponsiveness or crashes. A successful DoS attack can disrupt the availability of services and negatively impact the user experience.

Forging requests

XXE vulnerabilities can also enable Server-Side Request Forgery (SSRF) attacks. By leveraging the ability to include external entities, an attacker can make the vulnerable application send arbitrary requests to other internal or external systems. This can result in unintended actions, such as retrieving data from internal resources, scanning internal networks, or attacking other systems. SSRF attacks can lead to severe consequences, including unauthorized data access, system compromise, or even further exploitation within the network infrastructure.

How to fix it in .NET

Code examples

The following code contains examples of XML parsers that have external entity processing enabled. As a result, the parsers are vulnerable to XXE attacks if an attacker can control the XML file that is processed.

Noncompliant code example

using System.Xml;

public static void decode()
{
    XmlDocument parser = new XmlDocument();
    parser.XmlResolver = new XmlUrlResolver(); // Noncompliant
    parser.LoadXml("xxe.xml");
}

Compliant solution

XmlDocument is safe by default since .NET Framework 4.5.2. For older versions, set XmlResolver explicitly to null.

using System.Xml;

public static void decode()
{
    XmlDocument parser = new XmlDocument();
    parser.XmlResolver = null;
    parser.LoadXml("xxe.xml");
}

How does this work?

Disable external entities

The most effective approach to prevent XXE vulnerabilities is to disable external entity processing entirely, unless it is explicitly required for specific use cases. By default, XML parsers should be configured to reject the processing of external entities. This can be achieved by setting the appropriate properties or options in your XML parser library or framework.

If external entity processing is necessary for certain scenarios, adopt a whitelisting approach to restrict the entities that can be resolved during XML parsing. Create a list of trusted external entities and disallow all others. This approach ensures that only known and safe entities are processed.
You should rely on features provided by your XML parser to restrict the external entities.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S2755.json ================================================ { "title": "XML parsers should not be vulnerable to XXE attacks", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2755", "sqKey": "S2755", "scope": "Main", "securityStandards": { "CWE": [ 611, 827 ], "OWASP": [ "A4" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "6.5.1" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.5.2" ], "STIG ASD_V5R3": [ "V-222608" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2757.html ================================================

Why is this an issue?

Using operator pairs (=+, =-, or =!) that look like reversed single operators (+=, -= or !=) is confusing. They compile and run but do not produce the same result as their mirrored counterpart.

int target = -5;
int num = 3;

target =- num;  // Noncompliant: target = -3. Is that the intended behavior?
target =+ num; // Noncompliant: target = 3

This rule raises an issue when =+, =-, or =! are used without any space between the operators and when there is at least one whitespace after.

Replace the operators with a single one if that is the intention

int target = -5;
int num = 3;

target -= num;  // target = -8

Or fix the spacing to avoid confusion

int target = -5;
int num = 3;

target = -num;  // target = -3
================================================ FILE: analyzers/rspec/cs/S2757.json ================================================ { "title": "Non-existent operators like \"\u003d+\" should not be used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2757", "sqKey": "S2757", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2760.html ================================================

Why is this an issue?

When the same condition is checked twice in a row, it is either confusing - why have separate checks? - or an error - some other condition should have been checked in the second test.

Noncompliant code example

if (a == b)
{
  doTheThing(b);
}
if (a == b) // Noncompliant; is this really what was intended?
{
  doTheThing(c);
}

Compliant solution

if (a == b)
{
  doTheThing(b);
  doTheThing(c);
}

or

if (a == b)
{
  doTheThing(b);
}
if (b == c)
{
  doTheThing(c);
}

Exceptions

Since it is a common pattern to test a variable, reassign it if it fails the test, then re-test it, that pattern is ignored.

================================================ FILE: analyzers/rspec/cs/S2760.json ================================================ { "title": "Sequential tests should not check the same condition", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2760", "sqKey": "S2760", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2761.html ================================================

Why is this an issue?

The repetition of a prefix operator (!, or ~) is usually a typo. The second operator invalidates the first one.

int v1 = 0;
bool v2 = false;

var v3 = !!v1; // Noncompliant: equivalent to "v1"
var v4 = ~~v2; // Noncompliant: equivalent to "v2"
================================================ FILE: analyzers/rspec/cs/S2761.json ================================================ { "title": "Doubled prefix operators \"!!\" and \"~~\" should not be used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2761", "sqKey": "S2761", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2857.html ================================================

Why is this an issue?

When concatenating strings, it is very easy to forget a whitespace.

In some scenarios this might cause runtime errors, one of which is while creating an SQL query via concatenation:

string select = "SELECT p.FirstName, p.LastName, p.PhoneNumber" +
        "FROM Person as p" +    // Noncompliant: concatenation results in "p.PhoneNumberFROM"
        "WHERE p.Id = @Id";     // Noncompliant: concatenation results in "pWHERE"

This rule raises an issue when the spacing around SQL keywords appears to be missing, making the concatenated string invalid SQL syntax. It would require the user to add the appropriate whitespaces:

string select = "SELECT p.FirstName, p.LastName, p.PhoneNumber" +
        " FROM Person as p" +
        " WHERE p.Id = @Id";
================================================ FILE: analyzers/rspec/cs/S2857.json ================================================ { "title": "SQL keywords should be delimited by whitespace", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "sql" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2857", "sqKey": "S2857", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S2925.html ================================================

Why is this an issue?

Using Thread.Sleep in a test might introduce unpredictable and inconsistent results depending on the environment. Furthermore, it will block the thread, which means the system resources are not being fully used.

[TestMethod]
public void SomeTest()
{
    Thread.Sleep(500); // Noncompliant
    // assertions...
}

An alternative is a task-based asynchronous approach, using async and await.

More specifically the Task.Delay method should be used, because of the following advantages:

[TestMethod]
public async Task SomeTest()
{
    await Task.Delay(500);
    // assertions...
}

Another scenario is when some data might need to be mocked using Moq, and a delay needs to be introduced:

[TestMethod]
public void UserService_Test()
{
    var userService = new Mock<UserService>();
    var expected = new User();

    userService
        .Setup(m => m.GetUserById(42))
        .Returns(() =>
        {
            Thread.Sleep(500); // Noncompliant
            return Task.FromResult(expected);
        });

    // assertions...
}

An alternative to Thread.Sleep while mocking with Moq is to use ReturnsAsync and pass the amount of time to delay there:

[TestMethod]
public void UserService_Test()
{
    var userService = new Mock<UserService>();
    var expected = new User();

    userService
        .Setup(m => m.GetUserById(42))
        .ReturnsAsync(expected, TimeSpan.FromMilliseconds(500));

    // assertions...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2925.json ================================================ { "title": "\"Thread.Sleep\" should not be used in tests", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "tests", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2925", "sqKey": "S2925", "scope": "Tests", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2930.html ================================================

Why is this an issue?

When writing managed code, there is no need to worry about memory allocation or deallocation as it is taken care of by the garbage collector. However, certain objects, such as Bitmap, utilize unmanaged memory for specific purposes like pointer arithmetic. These objects may have substantial unmanaged memory footprints while having minimal managed footprints. Unfortunately, the garbage collector only recognizes the small managed footprint and does not promptly reclaim the corresponding unmanaged memory (by invoking the finalizer method of Bitmap) for efficiency reasons.

In addition, it’s essential to manage other system resources besides memory. The operating system has limits on the number of file descriptors (e.g., FileStream) or sockets (e.g., WebClient) that can remain open simultaneously. Therefore, it’s crucial to Dispose of these resources promptly when they are no longer required, instead of relying on the garbage collector to invoke the finalizers of these objects at an unpredictable time in the future.

This rule keeps track of private fields and local variables of specific types that implement IDisposable or IAsyncDisposable. It identifies instances of these types that are not properly disposed, closed, aliased, returned, or passed to other methods. This applies to instances that are either directly created using the new operator or instantiated through a predefined list of factory methods.

Here is the list of the types tracked by this rule:

Here is the list of predefined factory methods tracked by this rule:

Exceptions

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)
{
  var fs = new FileStream(path, FileMode.Open); // Compliant: it is returned
  var bytes = Encoding.UTF8.GetBytes(text);
  fs.Write(bytes, 0, bytes.Length);
  return fs;
}

public void ReadFromStream(Stream s)
{
  var sr = new StreamReader(s); // Compliant: it would close the underlying stream.
  // ...
}

How to fix it

It is essential to identify what kind of disposable resource variable is used to know how to fix this issue.

In the case of a disposable resource store as a member (either as field or property), it should be disposed at the same time as the class. The best way to achieve this is to follow the dispose pattern.

When creating the disposable resource for a one-time use (cases not covered by the exceptions), it should be disposed at the end of its creation scope. The easiest to ensure your resource is disposed when reaching the end of a scope is to either use the using statement or the using declaration

Code examples

Noncompliant code example

public class ResourceHolder
{
  private FileStream fs; // Noncompliant: dispose or close are never called

  public void OpenResource(string path)
  {
    this.fs = new FileStream(path, FileMode.Open);
  }

  public void WriteToFile(string path, string text)
  {
    var fs = new FileStream(path, FileMode.Open); // Noncompliant: not disposed, returned or initialized with another disposable object
    var bytes = Encoding.UTF8.GetBytes(text);
    fs.Write(bytes, 0, bytes.Length);
  }
}

Compliant solution

public class ResourceHolder : IDisposable, IAsyncDisposable
{
  private FileStream fs; // Compliant: disposed in Dispose/DisposeAsync methods

  public void OpenResource(string path)
  {
    this.fs = new FileStream(path, FileMode.Open);
  }

  public void Dispose()
  {
    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)) // Compliant: disposed at the end of the using block
    {
      var bytes = Encoding.UTF8.GetBytes(text);
      fs.Write(bytes, 0, bytes.Length);
    }
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2930.json ================================================ { "title": "\"IDisposables\" should be disposed", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "denial-of-service" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2930", "sqKey": "S2930", "scope": "All", "securityStandards": { "CWE": [ 459 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2931.html ================================================

Why is this an issue?

An IDisposable object should be disposed (there are some rare exceptions where not disposing is fine, most notably Task). If a class has an IDisposable field, there can be two situations:

In the second case, the safest way for the class to ensure Dispose is called is to call it in its own Dispose function, and therefore to be itself IDisposable. A class is considered to own an IDisposable field resource if it created the object referenced by the field.

Noncompliant code example

public class ResourceHolder   // Noncompliant; doesn't implement IDisposable
{
    private FileStream fs;  // This member is never Disposed
    public void OpenResource(string path)
    {
        this.fs = new FileStream(path, FileMode.Open); // I create the FileStream, I'm owning it
    }
    public void CloseResource()
    {
        this.fs.Close();
    }
}

Compliant solution

public class ResourceHolder : IDisposable
{
    private FileStream fs;
    public void OpenResource(string path)
    {
        this.fs = new FileStream(path, FileMode.Open); // I create the FileStream, I'm owning it
    }
    public void CloseResource()
    {
        this.fs.Close();
    }
    public void Dispose()
    {
        this.fs.Dispose();
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S2931.json ================================================ { "title": "Classes with \"IDisposable\" members should implement \"IDisposable\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "denial-of-service" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2931", "sqKey": "S2931", "scope": "All", "securityStandards": { "CWE": [ 459 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2933.html ================================================

Why is this an issue?

readonly fields can only be assigned in a class constructor. If a class has a field that’s not marked readonly but is only set in the constructor, it could cause confusion about the field’s intended use. To avoid confusion, such fields should be marked readonly to make their intended use explicit, and to prevent future maintainers from inadvertently changing their use.

Exceptions

How to fix it

Mark the given field with the readonly modifier.

Code examples

Noncompliant code example

public class Person
{
    private int _birthYear; // Noncompliant

    Person(int birthYear)
    {
        _birthYear = birthYear;
    }
}

Compliant solution

public class Person
{
    private readonly int _birthYear;

    Person(int birthYear)
    {
        _birthYear = birthYear;
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2933.json ================================================ { "title": "Fields that are only assigned in the constructor should be \"readonly\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2933", "sqKey": "S2933", "scope": "All", "quickfix": "partial" } ================================================ FILE: analyzers/rspec/cs/S2934.html ================================================

Why is this an issue?

While the properties of a readonly reference type field can still be changed after initialization, those of a readonly value type field, such as a struct, cannot.

If the member could be either a class or a struct then assignment to its properties could be unreliable, working sometimes but not others.

How to fix it

There are two ways to fix this issue:

Code examples

Noncompliant code example

interface IPoint
{
    int X { get; set; }
    int Y { get; set; }
}

class PointManager<T1, T2>
    where T1 : IPoint
    where T2 : IPoint
{
    readonly T1 point1;  // this could be a struct
    readonly T2 point2;  // this could be a struct

    public PointManager(T1 point1, T2 point2)
    {
        this.point1 = point1;
        this.point2 = point2;
    }

    public void MovePoints(int newX)
    {
        point1.X = newX; //Noncompliant: if point is a struct, then nothing happened
        point2.X = newX; //Noncompliant: if point is a struct, then nothing happened
    }
}

Compliant solution

interface IPoint
{
    int X { get; set; }
    int Y { get; set; }
}

class PointManager<T1, T2>
    where T1 : IPoint
    where T2 : class, IPoint
{
    readonly T1 point1;  // this could be a struct
    readonly T2 point2;  // this is a class

    public PointManager(T1 point1, T2 point2)
    {
        this.point1 = point1;
        this.point2 = point2;
    }

    public void MovePoints(int newX) // assignment to point1 has been removed
    {
        point2.X = newX; // Compliant: point2 is a class
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2934.json ================================================ { "title": "Property assignments should not be made for \"readonly\" fields not constrained to reference types", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2934", "sqKey": "S2934", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2952.html ================================================

Why is this an issue?

It is possible in an IDisposable to call Dispose on class members from any method, but the contract of Dispose is that it will clean up all unmanaged resources. Move disposing of members to some other method, and you risk resource leaks.

This rule also applies for disposable ref structs.

Noncompliant code example

public class ResourceHolder : IDisposable
{
  private FileStream fs;
  public void OpenResource(string path)
  {
    this.fs = new FileStream(path, FileMode.Open);
  }
  public void CloseResource()
  {
    this.fs.Close();
  }

  public void CleanUp()
  {
    this.fs.Dispose(); // Noncompliant; Dispose not called in class' Dispose method
  }

  public void Dispose()
  {
    // method added to satisfy demands of interface
  }
}

Compliant solution

public class ResourceHolder : IDisposable
{
  private FileStream fs;
  public void OpenResource(string path)
  {
    this.fs = new FileStream(path, FileMode.Open);
  }
  public void CloseResource()
  {
    this.fs.Close();
  }

  public void Dispose()
  {
    this.fs.Dispose();
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S2952.json ================================================ { "title": "Classes should \"Dispose\" of members from the classes\u0027 own \"Dispose\" methods", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "denial-of-service" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2952", "sqKey": "S2952", "scope": "Main", "securityStandards": { "CWE": [ 459 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2953.html ================================================

Why is this an issue?

IDisposable is an interface implemented by all types which need to provide a mechanism for releasing unmanaged resources.

Unlike managed memory, which is taken care of by the garbage collection,

The interface declares a Dispose method, which the implementer has to define.

The method name Dispose should be used exclusively to implement IDisposable.Dispose to prevent any confusion.

It may be tempting to create a Dispose method for other purposes, but doing so will result in confusion and likely lead to problems in production.

Exceptions

Methods named Dispose and invoked from the IDisposable.Dispose implementation are not reported.

public class GarbageDisposal : IDisposable
{
  protected virtual void Dispose(bool disposing)
  {
    //...
  }
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
}

How to fix it

First, it is important to determine whether instances of the type defining the Dispose method should support the IDisposable interface or not.

The decision would be based on whether the instance can have unmanaged resources which have to be dealt with, upon destruction or earlier in the lifetime of the object.

The Dispose pattern can help to take the decision.

If the type should not support the pattern, the Dispose method should be renamed to something which is different than Dispose, but still relevant and possibly more specific to the context.

Code examples

Noncompliant code example

public class GarbageDisposal
{
  private int Dispose()  // Noncompliant
  {
    // ...
  }
}

Compliant solution

public class GarbageDisposal : IDisposable
{
  public void Dispose()
  {
    // ...
  }
}

or

public class GarbageDisposal
{
  private int Grind()
  {
    // ...
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2953.json ================================================ { "title": "Methods named \"Dispose\" should implement \"IDisposable.Dispose\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2953", "sqKey": "S2953", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2955.html ================================================

Why is this an issue?

In C#, without constraints on a generic type parameter, both reference and value types can be passed. However, comparing this type parameter to null can be misleading as value types, like struct, can never be null.

How to fix it

To avoid unexpected comparisons:

Code examples

Noncompliant code example

bool IsDefault<T>(T value)
{
  if (value == null) // Noncompliant
  {
    // ...
  }
}

Compliant solution

bool IsDefault<T>(T value)
{
  if (EqualityComparer<T>.Default.Equals(value, default(T)))
  {
    // ...
  }
}

or

bool IsDefault<T>(T value) where T : class
{
  if (value == null)
  {
    // ...
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2955.json ================================================ { "title": "Generic parameters not constrained to reference types should not be compared to \"null\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2955", "sqKey": "S2955", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S2970.html ================================================

Why is this an issue?

This rule addresses the issue of incomplete assertions that can occur when using certain test frameworks. Incomplete assertions can lead to tests that do not effectively verify anything. The rule enforces the use of complete assertions in specific cases, namely:

string actual = "Using Fluent Assertions";
actual.Should(); // Noncompliant
string actual = "Using NFluent";
Check.That(actual); // Noncompliant
command.Received(); // Noncompliant

In such cases, what is intended to be a test doesn’t actually verify anything.

How to fix it in Fluent Assertions

Fluent Assertions provides an interface for writing assertions, and it is important to ensure that Should() is properly used in conjunction with an assertion method.

Code examples

Noncompliant code example

string actual = "Hello World!";
actual.Should(); // Noncompliant

Compliant solution

string actual = "Hello World!";
actual.Should().Contain("Hello");

How to fix it in NFluent

NFluent offers a syntax for assertions, and it’s important to follow Check.That() with an assertion method to complete the assertion.

Code examples

Noncompliant code example

string actual = "Hello World!";
Check.That(actual); // Noncompliant

Compliant solution

string actual = "Hello World!";
Check.That(actual).Contains("Hello");

How to fix it in NSubstitute

NSubstitute is a mocking framework, and Received() is used to verify that a specific method has been called. However, invoking a method on the mock after calling Received() is necessary to ensure the complete assertion.

Code examples

Noncompliant code example

command.Received(); // Noncompliant

Compliant solution

command.Received().Execute();

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2970.json ================================================ { "title": "Assertions should be complete", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "tests" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2970", "sqKey": "S2970", "scope": "Tests", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2971.html ================================================

Why is this an issue?

In the interests of readability, code that can be simplified should be simplified. To that end, there are several ways IEnumerable language integrated queries (LINQ) can be simplified. This not only improves readabilty but can also lead to improved performance.

How to fix it

Simplify the LINQ expressions:

Using Entity Framework may require enforcing client evaluations. Such queries should use AsEnumerable() instead of ToArray() or ToList() in the middle of a query chain.

Code examples

Noncompliant code example

public void Foo(IEnumerable<Vehicle> seq, List<int> list)
{
    var result1 = seq.Select(x => x as Car).Any(x => x != null);               // Noncompliant; use OfType
    var result2 = seq.Select(x => x as Car).Any(x => x != null && x.HasOwner); // Noncompliant; use OfType before calling Any
    var result3 = seq.Where(x => x is Car).Select(x => x as Car);              // Noncompliant; use OfType
    var result4 = seq.Where(x => x is Car).Select(x => (Car)x);                // Noncompliant; use OfType
    var result5 = seq.Where(x => x.HasOwner).Any();                            // Noncompliant; use Any([predicate])

    var num = list.Count();                                                    // Noncompliant; use the Count property
    var arr = seq.ToList().ToArray();                                          // Noncompliant; ToList is not needed
    var count = seq.ToList().Count(x => x.HasOwner);                           // Noncompliant; ToList is not needed
}

Compliant solution

public void Foo(IEnumerable<Vehicle> seq, List<int> list)
{
    var result1 = seq.OfType<Car>().Any();
    var result2 = seq.OfType<Car>().Any(x => x.HasOwner);
    var result3 = seq.OfType<Car>();
    var result4 = seq.OfType<Car>();
    var result5 = seq.Any(x => x.HasOwner);

    var num = list.Count;
    var arr = seq.ToArray();
    var count = seq.Count(x => x.HasOwner);
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2971.json ================================================ { "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2971", "sqKey": "S2971", "scope": "All", "quickfix": "unknown", "title": "LINQ expressions should be simplified" } ================================================ FILE: analyzers/rspec/cs/S2995.html ================================================

Why is this an issue?

In C#, the Object.ReferenceEquals method is used to compare two reference type variables. If you use this method to compare two value types, such as int, float, or bool you will not get the expected results because value type variables contain an instance of the type and not a reference to it.

Due to value type variables containing directly an instance of the type, they can’t have the same reference, and using Object.ReferenceEquals to compare them will always return false even if the compared variables have the same value.

How to fix it

When comparing value types, prefer using the Object.Equals.

Note that in the case of structure types, it is recommended to implement value equality. If not, {rule:csharpsquid:S3898} might raise.

Code examples

Noncompliant code example

using System;

struct MyStruct
{
    int valueA;
    int valueB;
}

static class MyClass
{
    public static void Method(MyStruct struct1, MyStruct struct2)
    {
        if (Object.ReferenceEquals(struct1, struct2)) // Noncompliant: this will be always false
        {
            // ...
        }
    }
}

Compliant solution

using System;

struct MyStruct : IEquatable<MyStruct>
{
    int valueA;
    int valueB;

    public bool Equals(MyStruct other) => valueA == other.valueA && valueB == other.valueB;

    public override bool Equals(object obj) => obj is MyStruct other && Equals(other);

    public override int GetHashCode() => HashCode.Combine(valueA, valueB);

    public static bool operator ==(MyStruct lhs, MyStruct rhs) => lhs.Equals(rhs);

    public static bool operator !=(MyStruct lhs, MyStruct rhs) => !(lhs == rhs);
}

static class MyClass
{
    public static void Method(MyStruct struct1, MyStruct struct2)
    {
        if (struct1.Equals(struct2)) // Compliant: value are compared
        {
            // ...
        }
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2995.json ================================================ { "title": "\"Object.ReferenceEquals\" should not be used for value types", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2995", "sqKey": "S2995", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S2996.html ================================================

Why is this an issue?

When an object has a field annotated with ThreadStatic, that field is shared within a given thread, but unique across threads. Since a class' static initializer is only invoked for the first thread created, it also means that only the first thread will have the expected initial values.

Instead, allow such fields to be initialized to their default values or make the initialization lazy.

Noncompliant code example

public class Foo
{
  [ThreadStatic]
  public static object PerThreadObject = new object(); // Noncompliant. Will be null in all the threads except the first one.
}

Compliant solution

public class Foo
{
  [ThreadStatic]
  public static object _perThreadObject;
  public static object PerThreadObject
  {
    get
    {
      if (_perThreadObject == null)
      {
        _perThreadObject = new object();
      }
      return _perThreadObject;
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S2996.json ================================================ { "title": "\"ThreadStatic\" fields should not be initialized", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "multi-threading" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2996", "sqKey": "S2996", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S2997.html ================================================

Why is this an issue?

When you use a using statement, the goal is to ensure the correct disposal of an IDisposable instance when the control leaves the using statement block.

If you return that IDisposable instance inside the block, using will dispose it before the caller can use it, likely causing exceptions at runtime. You should either remove using statement or avoid returning the IDisposable in the using statement block.

How to fix it

Code examples

Noncompliant code example

public FileStream WriteToFile(string path, string text)
{
  using (var fs = File.Create(path)) // Noncompliant: 'fs' is disposed at the end of the using scope
  {
    var bytes = Encoding.UTF8.GetBytes(text);
    fs.Write(bytes, 0, bytes.Length);
    return fs;
  }
}

Compliant solution

public FileStream WriteToFile(string path, string text)
{
  var fs = File.Create(path);
  var bytes = Encoding.UTF8.GetBytes(text);
  fs.Write(bytes, 0, bytes.Length);
  return fs; // Compliant: 'fs' is not disposed once the end of the scope is reached and the caller can use it
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S2997.json ================================================ { "title": "\"IDisposables\" created in a \"using\" statement should not be returned", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2997", "sqKey": "S2997", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3005.html ================================================

Why is this an issue?

When you annotate a field with the ThreadStatic attribute, it is an indication that the value of this field is unique for each thread. But if you don’t mark the field as static, then the ThreadStatic attribute is ignored.

The ThreadStatic attribute should either be removed or replaced with the use of ThreadLocal<T> class, which gives a similar behavior for non-static fields.

How to fix it

Code examples

Noncompliant code example

public class MyClass
{
  [ThreadStatic]  // Noncompliant
  private int count = 0;

  // ...
}

Compliant solution

public class MyClass
{
  private int count = 0;

  // ...
}

or

public class MyClass
{
  private readonly ThreadLocal<int> count = new ThreadLocal<int>();
  public int Count
  {
    get { return count.Value; }
    set { count.Value = value; }
  }
  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3005.json ================================================ { "title": "\"ThreadStatic\" should not be used on non-static fields", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3005", "sqKey": "S3005", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3010.html ================================================

Why is this an issue?

Assigning a value to a static field in a constructor could cause unreliable behavior at runtime since it will change the value for all instances of the class.

Instead remove the field’s static modifier, or initialize it statically.

Noncompliant code example

public class Person
{
  private static DateTime dateOfBirth;
  private static int expectedFingers;

  public Person(DateTime birthday)
  {
    dateOfBirth = birthday;  // Noncompliant; now everyone has this birthday
    expectedFingers = 10;  // Noncompliant
  }
}

Compliant solution

public class Person
{
  private DateTime dateOfBirth;
  private static int expectedFingers = 10;

  public Person(DateTime birthday)
  {
    this.dateOfBirth = birthday;
  }
}
================================================ FILE: analyzers/rspec/cs/S3010.json ================================================ { "title": "Static fields should not be updated in constructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3010", "sqKey": "S3010", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3011.html ================================================

Why is this an issue?

Altering or bypassing the accessibility of classes, methods, or fields through reflection violates the encapsulation principle. This can break the internal contracts of the accessed target and lead to maintainability issues and runtime errors.

This rule raises an issue when reflection is used to change the visibility of a class, method or field, and when it is used to directly update a field value.

using System.Reflection;

Type dynClass = Type.GetType("MyInternalClass");
// Noncompliant. Using BindingFlags.NonPublic will return non-public members
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Static;
MethodInfo dynMethod = dynClass.GetMethod("mymethod", bindingAttr);
object result = dynMethod.Invoke(dynClass, null);

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3011.json ================================================ { "title": "Reflection should not be used to increase accessibility of classes, methods, or fields", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3011", "sqKey": "S3011", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3052.html ================================================

Why is this an issue?

The compiler automatically initializes class fields, auto-properties and events to their default values before setting them with any initialization values, so there is no need to explicitly set a member to its default value. Further, under the logic that cleaner code is better code, it’s considered poor style to do so.

Noncompliant code example

class X
{
  public int field = 0; // Noncompliant
  public object o = null; // Noncompliant
  public object MyProperty { get; set; } = null; // Noncompliant
  public event EventHandler MyEvent = null;  // Noncompliant
}

Compliant solution

class X
{
  public int field;
  public object o;
  public object MyProperty { get; set; }
  public event EventHandler MyEvent;
}

Exceptions

const fields are ignored.

================================================ FILE: analyzers/rspec/cs/S3052.json ================================================ { "title": "Members should not be initialized to default values", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention", "finding" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3052", "sqKey": "S3052", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3059.html ================================================

Why is this an issue?

There’s no point in having a public member in a non-public type because objects that can’t access the type will never have the chance to access the member.

This rule raises an issue when a type has methods, fields, or inner types with higher visibility than the type itself has.

Noncompliant code example

internal class MyClass
{
    public static decimal PI = 3.14m;  // Noncompliant

    public int GetOne() // Noncompliant
    {
        return 1;
    }

    protected record NestedType // Noncompliant: outer class is internal
    {
        public bool FlipCoin() // Noncompliant: outer class is internal
        {
            return false;
        }
        // ...
    }
}

Compliant solution

public class MyClass // Class visibility upgrade makes members compliant
{
    public static decimal PI = 3.14m;

    public int GetOne()
    {
        return 1;
    }

    protected record NestedType
    {
        public bool FlipCoin() // Outer type is public
        {
            return false;
        }
        // ...
    }
}

Exceptions

User defined operators need to be public:

public static implicit operator byte(MyClass a) => 1; // Compliant
public static explicit operator MyClass(byte a) => new MyClass(a); // Compliant

Nested types, even if private, can be used and inherited in the parent type. In this case, the visibility of the outer type is considered.

internal class MyClass
{
    private class NestedClass
    {
        public int PublicProperty { get; } // Noncompliant: should be internal
        protected internal int ProtectedInternalProperty { get; } // Compliant: can be used in `InternalsVisibleTo` assemblies
        internal int InternalProperty { get; } // Compliant: can be used in `InternalsVisibleTo` assemblies
        protected int ProtectedProperty { get; } // Compliant: can be used in derived type
        private protected int PrivateProtectedProperty { get; } // Compliant: can be used in derived type
        private int PrivateProperty { get; }
    }
}
================================================ FILE: analyzers/rspec/cs/S3059.json ================================================ { "title": "Types should not have members with visibility set higher than the type\u0027s visibility", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3059", "sqKey": "S3059", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3060.html ================================================

Why is this an issue?

One of the possible ways of performing type-testing is via the is operator: food is Pizza.

The is operator is often used before a direct cast to the target type, as a more flexible and powerful alternative to the as operator, especially when used to perform pattern matching.

if (food is Pizza pizza)

There’s no valid reason to test this with is. The only plausible explanation for such a test is that you’re executing code in a parent class conditionally based on the kind of child class this is.

public class Food
{
  public void DoSomething()
  {
    if (this is Pizza) // Noncompliant
    {
      // Code specific to Pizza...
    }
  }
}

However, code that’s specific to a child class should be in that child class, not in the parent.

How to fix it

One way is to take advantage of the object-orientation of C# and use polymorphism.

For example, when simple method polymorphism is not enough because it is necessary to reuse multiple sections of the parent method, the Template method pattern might help.

Code examples

Noncompliant code example

public class Food
{
  public void DoSomething()
  {
    // Code shared by all Food...
    if (this is Pizza) // Noncompliant
    {
      // Code specific to Pizza...
    }
  }
}

Compliant solution

public class Food
{
  public virtual void DoSomething()
  {
    // Code shared by all Food...
  }
}

public class Pizza : Food
{
  public override void DoSomething()
  {
    base.DoSomething();
    // Code specific to Pizza...
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3060.json ================================================ { "title": "\"is\" should not be used with \"this\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "api-design", "bad-practice" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3060", "sqKey": "S3060", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3063.html ================================================

Why is this an issue?

StringBuilder instances that never build a string clutter the code and worse are a drag on performance. Either they should be removed, or the missing ToString() call should be added.

Noncompliant code example

public void DoSomething(List<string> strings) {
  var sb = new StringBuilder();  // Noncompliant
  sb.Append("Got: ");
  foreach(var str in strings) {
    sb.Append(str).Append(", ");
    // ...
  }
}

Compliant solution

public void DoSomething(List<string> strings) {
  foreach(var str in strings) {
    // ...
  }
}

or

public void DoSomething(List<string> strings) {
  var sb = new StringBuilder();
  sb.Append("Got: ");
  foreach(var str in strings) {
    sb.Append(str).Append(", ");
    // ...
  }
  logger.LogInformation(sb.ToString());
}

Exceptions

No issue is reported when StringBuilder is:

================================================ FILE: analyzers/rspec/cs/S3063.json ================================================ { "title": "\"StringBuilder\" data should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3063", "sqKey": "S3063", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3168.html ================================================

Why is this an issue?

An async method with a void return type does not follow the task asynchronous programming (TAP) model since the return type should be Task or Task<TResult>

Doing so prevents control over the asynchronous execution, such as:

Exceptions

How to fix it

Update the return type of the method from void to Task.

Code examples

Noncompliant code example

private async void ThrowExceptionAsync() // Noncompliant: async method return type is 'void'
{
  throw new InvalidOperationException();
}

public void Method()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here
    throw;
  }
}

Compliant solution

private async Task ThrowExceptionAsync() // Compliant: async method return type is 'Task'
{
  throw new InvalidOperationException();
}

public async Task Method()
{
  try
  {
    await ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is caught here
    throw;
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3168.json ================================================ { "title": "\"async\" methods should not return \"void\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "multi-threading", "async-await" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3168", "sqKey": "S3168", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3169.html ================================================

Why is this an issue?

There’s no point in chaining multiple OrderBy calls in a LINQ; only the last one will be reflected in the result because each subsequent call completely reorders the list. Thus, calling OrderBy multiple times is a performance issue as well, because all of the sorting will be executed, but only the result of the last sort will be kept.

How to fix it

Instead, use ThenBy for each call after the first.

Code examples

Noncompliant code example

var x = personList
  .OrderBy(person => person.Age)
  .OrderBy(person => person.Name)  // Noncompliant
  .ToList();  // x is sorted by Name, not sub-sorted

Compliant solution

var x = personList
  .OrderBy(person => person.Age)
  .ThenBy(person => person.Name)
  .ToList();

Resources

Benchmarks

Method Runtime Mean StdDev Allocated

OrderByAge

.NET 9.0

12.84 ms

0.804 ms

1.53 MB

OrderByAgeOrderBySize

.NET 9.0

24.08 ms

0.267 ms

3.05 MB

OrderByAgeThenBySize

.NET 9.0

18.58 ms

0.747 ms

1.91 MB

OrderByAge

.NET Framework 4.8.1

22.99 ms

0.228 ms

1.53 MB

OrderByAgeOrderBySize

.NET Framework 4.8.1

44.90 ms

0.581 ms

4.3 MB

OrderByAgeThenBySize

.NET Framework 4.8.1

31.72 ms

0.402 ms

1.91 MB

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Size { get; set; }
}

private Random random = new Random(1);
private Consumer consumer = new Consumer();
private Person[] array;

[Params(100_000)]
public int N { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
    array = Enumerable.Range(0, N).Select(x => new Person
    {
      Name = Path.GetRandomFileName(),
      Age = random.Next(0, 100),
      Size = random.Next(0, 200)
    }).ToArray();
}

[Benchmark(Baseline = true)]
public void OrderByAge() =>
    array.OrderBy(x => x.Age).Consume(consumer);

[Benchmark]
public void OrderByAgeOrderBySize() =>
    array.OrderBy(x => x.Age).OrderBy(x => x.Size).Consume(consumer);

[Benchmark]
public void OrderByAgeThenBySize() =>
    array.OrderBy(x => x.Age).ThenBy(x => x.Size).Consume(consumer);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
Intel Core Ultra 7 165H, 1 CPU, 22 logical and 16 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S3169.json ================================================ { "title": "Multiple \"OrderBy\" calls should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3169", "sqKey": "S3169", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3172.html ================================================

Why is this an issue?

In C#, delegates can be added together to chain their execution, and subtracted to remove their execution from the chain.

Subtracting a chain of delegates from another one might yield unexpected results as shown hereunder - and is likely to be a bug.

Noncompliant code example

MyDelegate first, second, third, fourth;
first = () => Console.Write("1");
second = () => Console.Write("2");
third = () => Console.Write("3");
fourth = () => Console.Write("4");

MyDelegate chain1234 = first + second + third + fourth; // Compliant - chain sequence = "1234"
MyDelegate chain12 = chain1234 - third - fourth; // Compliant - chain sequence = "12"


MyDelegate chain14 = first + fourth; // creates a new MyDelegate instance which is a list under the covers
MyDelegate chain23 = chain1234 - chain14; // Noncompliant; (first + fourth) doesn't exist in chain1234


// The chain sequence of "chain23" will be "1234" instead of "23"!
// Indeed, the sequence "1234" does not contain the subsequence "14", so nothing is subtracted
// (but note that "1234" contains both the "1" and "4" subsequences)
chain23 = chain1234 - (first + fourth); // Noncompliant

chain23(); // will print "1234"!

Compliant solution

MyDelegate chain23 = chain1234 - first - fourth; // Compliant - "1" is first removed, followed by "4"

chain23(); // will print "23"
================================================ FILE: analyzers/rspec/cs/S3172.json ================================================ { "title": "Delegates should not be subtracted", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3172", "sqKey": "S3172", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3215.html ================================================

Why is this an issue?

Needing to cast from an interface to a concrete type indicates that something is wrong with the abstractions in use, likely that something is missing from the interface. Instead of casting to a discrete type, the missing functionality should be added to the interface. Otherwise there is a risk of runtime exceptions.

Noncompliant code example

public interface IMyInterface
{
  void DoStuff();
}

public class MyClass1 : IMyInterface
{
  public int Data { get { return new Random().Next(); } }

  public void DoStuff()
  {
    // TODO...
  }
}

public static class DowncastExampleProgram
{
  static void EntryPoint(IMyInterface interfaceRef)
  {
    MyClass1 class1 = (MyClass1)interfaceRef;  // Noncompliant
    int privateData = class1.Data;

    class1 = interfaceRef as MyClass1;  // Noncompliant
    if (class1 != null)
    {
      // ...
    }
  }
}

Exceptions

Casting to object doesn’t raise an issue, because it can never fail.

static void EntryPoint(IMyInterface interfaceRef)
{
  var o = (object)interfaceRef;
  ...
}
================================================ FILE: analyzers/rspec/cs/S3215.json ================================================ { "title": "\"interface\" instances should not be cast to concrete types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "design" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3215", "sqKey": "S3215", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3216.html ================================================

Why is this an issue?

After an awaited Task has executed, you can continue execution in the original, calling thread or any arbitrary thread. Unless the rest of the code needs the context from which the Task was spawned, Task.ConfigureAwait(false) should be used to keep execution in the Task thread to avoid the need for context switching and the possibility of deadlocks.

This rule raises an issue when code in a class library targeting .Net Framework awaits a Task and continues execution in the original calling thread.

The rule does not raise for .Net Core libraries as there is no SynchronizationContext in .Net Core.

Noncompliant code example

var response = await httpClient.GetAsync(url);  // Noncompliant

Compliant solution

var response = await httpClient.GetAsync(url).ConfigureAwait(false);
================================================ FILE: analyzers/rspec/cs/S3216.json ================================================ { "title": "\"ConfigureAwait(false)\" should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "multi-threading", "async-await", "suspicious", "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3216", "sqKey": "S3216", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3217.html ================================================

Why is this an issue?

The foreach statement was introduced in the C# language prior to generics to make it easier to work with the non-generic collections available at that time such as ArrayList. The foreach statements allow you to downcast elements of a collection of Objects to any other type.

The problem is that to achieve the cast, the foreach statements silently perform explicit type conversion, which at runtime can result in an InvalidCastException.

C# code iterating on generic collections or arrays should not rely on foreach statement’s silent explicit conversions.

public class Fruit { }
public class Orange : Fruit { }
public class Apple : Fruit { }

class MyTest
{
  public void Test()
  {
    var fruitBasket = new List<Fruit>();
    fruitBasket.Add(new Orange());
    fruitBasket.Add(new Orange());
    fruitBasket.Add(new Apple());

    foreach (Orange orange in fruitBasket) // Noncompliant
    {
      //...
    }
  }
}
public class Fruit { }
public class Orange : Fruit { }
public class Apple : Fruit { }

class MyTest
{
  public void Test()
  {
    var fruitBasket = new List<Fruit>();
    fruitBasket.Add(new Orange());
    fruitBasket.Add(new Orange());
    fruitBasket.Add(new Apple());

    foreach (Orange orange in fruitBasket.OfType<Orange>())
    {
      //...
    }
  }
}

Exceptions

The rule ignores iterations on collections of objects. This includes legacy code that uses ArrayList. Furthermore, the rule does not report on cases when user-defined conversions are being called.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3217.json ================================================ { "title": "\"Explicit\" conversions of \"foreach\" loops should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3217", "sqKey": "S3217", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3218.html ================================================

Why is this an issue?

Naming the members of an inner class the same as the static members of its enclosing class is possible but generally considered a bad practice. That’s because maintainers may be confused about which members are being used in a given context. Instead the inner class member should be given distinct and descriptive name, and all references to it should be updated accordingly.

class Outer
{
  public static int A;

  public class Inner
  {
    public int A; // Noncompliant

    public int MyProp
    {
      get => A; // Returns inner A. Was that intended?
    }
  }
}

Here’s an example of compliant code after renaming the inner class member, this way the property will return the Outer A:

class Outer
{
  public static int A;

  public class Inner
  {
    public int B; // Compliant

    public int MyProp
    {
      get => A; // Returns outer A
    }
  }
}

Or if you want to reference the Inner A field:

class Outer
{
  public static int B;

  public class Inner
  {
    public int A; // Compliant

    public int MyProp
    {
      get => A; // Returns inner A
    }
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3218.json ================================================ { "title": "Inner class members should not shadow outer class \"static\" or type members", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "design", "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3218", "sqKey": "S3218", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3220.html ================================================

Why is this an issue?

The rules for method resolution are complex and perhaps not properly understood by all coders. The params keyword can make method declarations overlap in non-obvious ways, so that slight changes in the argument types of an invocation can resolve to different methods.

This rule raises an issue when an invocation resolves to a method declaration with params, but could also resolve to another non-params method too.

Noncompliant code example

public class MyClass
{
    private void Format(string a, params object[] b) { }

    private void Format(object a, object b, object c) { }
}

// ...
MyClass myClass = new MyClass();

myClass.Format("", null, null); // Noncompliant, resolves to the first Format with params, but was that intended?
================================================ FILE: analyzers/rspec/cs/S3220.json ================================================ { "title": "Method calls should not resolve ambiguously to overloads with \"params\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3220", "sqKey": "S3220", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3234.html ================================================

Why is this an issue?

GC.SuppressFinalize asks the Common Language Runtime not to call the finalizer of an object. This is useful when implementing the dispose pattern where object finalization is already handled in IDisposable.Dispose. However, it has no effect if there is no finalizer defined in the object’s type, so using it in such cases is just confusing.

This rule raises an issue when GC.SuppressFinalize is called for objects of sealed types without a finalizer.

Note: {rule:csharpsquid:S3971} is a stricter version of this rule. Typically it makes sense to activate only one of these 2 rules.

Noncompliant code example

sealed class MyClass
{
  public void Method()
  {
    ...
    GC.SuppressFinalize(this); //Noncompliant
  }
}

Compliant solution

sealed class MyClass
{
  public void Method()
  {
    ...
  }
}
================================================ FILE: analyzers/rspec/cs/S3234.json ================================================ { "title": "\"GC.SuppressFinalize\" should not be invoked for types without destructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused", "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3234", "sqKey": "S3234", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3235.html ================================================

Why is this an issue?

Redundant parentheses are simply wasted keystrokes, and should be removed.

Noncompliant code example

[MyAttribute()] //Noncompliant
class MyClass
{
  public int MyProperty { get; set; }
  public static MyClass CreateNew(int propertyValue)
  {
    return new MyClass() //Noncompliant
    {
      MyProperty = propertyValue
    };
  }
}

Compliant solution

[MyAttribute]
class MyClass
{
  public int MyProperty { get; set; }
  public static MyClass CreateNew(int propertyValue)
  {
    return new MyClass
    {
      MyProperty = propertyValue
    };
  }
}
================================================ FILE: analyzers/rspec/cs/S3235.json ================================================ { "title": "Redundant parentheses should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused", "finding" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3235", "sqKey": "S3235", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3236.html ================================================

Why is this an issue?

Caller information attributes: CallerFilePathAttribute, CallerLineNumberAttribute, and CallerArgumentExpressionAttribute provide a way to get information about the caller of a method through optional parameters. But the arguments for these optional parameters are only generated if they are not explicitly defined in the call. Thus, specifying the argument values defeats the purpose of the attributes.

Noncompliant code example

void TraceMessage(string message,
  [CallerFilePath] string filePath = null,
  [CallerLineNumber] int lineNumber = 0)
{
  /* ... */
}

void MyMethod()
{
  TraceMessage("my message", "A.B.C.Foo.cs", 42); // Noncompliant
}

Compliant solution

void TraceMessage(string message,
  [CallerFilePath] string filePath = "",
  [CallerLineNumber] int lineNumber = 0)
{
  /* ... */
}

void MyMethod()
{
  TraceMessage("my message");
}

Exceptions

================================================ FILE: analyzers/rspec/cs/S3236.json ================================================ { "title": "Caller information arguments should not be provided explicitly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3236", "sqKey": "S3236", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3237.html ================================================

Why is this an issue?

When you need to get external input for set and init methods defined for properties and indexers or for remove and add methods for events, you should always get this input throught the value contextual keyword.

The contextual keyword value is similar to an input parameter of a method; it references the value that the client code is attempting to assign to the property, indexer or event.

The keyword value holds the value the accessor was called with. Not using it means that the accessor ignores the caller’s intent which could cause unexpected results at runtime.

Noncompliant code example

private int count;
public int Count
{
  get { return count; }
  set { count = 42; } // Noncompliant
}

Compliant solution

private int count;
public int Count
{
  get { return count; }
  set { count = value; }
}

Exceptions

This rule doesn’t raise an issue when the setter is empty and part of the implementation of an interface. The assumption is that this part of the interface is not meaningful to that particular implementation. A good example of that would be a "sink" logger that discards any logs.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3237.json ================================================ { "title": "\"value\" contextual keyword should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3237", "sqKey": "S3237", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3240.html ================================================

Why is this an issue?

In the interests of keeping code clean, the simplest possible conditional syntax should be used. That means

Noncompliant code example

object a = null, b = null, x;

if (a != null) // Noncompliant; needlessly verbose
{
  x = a;
}
else
{
  x = b;
}

x = a != null ? a : b; // Noncompliant; better but could still be simplified

x = (a == null) ? new object() : a; // Noncompliant

if (condition) // Noncompliant
{
  x = a;
}
else
{
  x = b;
}

if (a == null)  // Noncompliant
    a = new object();

var y = null ?? new object(); // Noncompliant

a = a ?? new object();  // Noncompliant for C# 8

Compliant solution

object x;

x = a ?? b;
x = a ?? b;
x = a ?? new object();
x = condition ? a : b;
a ??= new object();
var y = new object();
a ??= new object();
================================================ FILE: analyzers/rspec/cs/S3240.json ================================================ { "title": "The simplest possible condition syntax should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3240", "sqKey": "S3240", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3241.html ================================================

Why is this an issue?

Private methods are intended for use only within their scope. If these methods return values that are not utilized by any calling functions, it indicates that the return operation is unnecessary. Removing such returns can enhance both efficiency and code clarity.

Noncompliant code example

class SomeClass
{
     private int PrivateMethod() => 42;

     public void PublicMethod()
     {
          PrivateMethod(); // Noncompliant: the result of PrivateMethod is not used
     }
}
================================================ FILE: analyzers/rspec/cs/S3241.json ================================================ { "title": "Methods should not return values that are never used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "design", "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3241", "sqKey": "S3241", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3242.html ================================================

Why is this an issue?

When a derived type is used as a parameter instead of the base type, it limits the uses of the method. If the additional functionality that is provided in the derived type is not required then that limitation isn’t required, and should be removed.

This rule raises an issue when a method declaration includes a parameter that is a derived type and accesses only members of the base type.

Noncompliant code example

using System;
using System.IO;

namespace MyLibrary
{
  public class Foo
  {
    public void ReadStream(FileStream stream) // Noncompliant: Uses only System.IO.Stream methods
    {
      int a;
      while ((a = stream.ReadByte()) != -1)
      {
            // Do something.
      }
    }
  }
}

Compliant solution

using System;
using System.IO;

namespace MyLibrary
{
  public class Foo
  {
    public void ReadStream(Stream stream)
    {
      int a;
      while ((a = stream.ReadByte()) != -1)
      {
            // Do something.
      }
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S3242.json ================================================ { "title": "Method parameters should be declared with base types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3242", "sqKey": "S3242", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3244.html ================================================

Why is this an issue?

When working with anonymous functions, it is important to keep in mind that each time you create one, it is a completely new instance.

In this example, even though the same lambda expression is used, the expressions are stored separately in the memory and are therefore not equal or the same.

Func<int, int> lambda1 = x => x + 1;
Func<int, int> lambda2 = x => x + 1;

var result = lambda1 == lambda2; // result is false here

This is even more true when working with events since they are multicast delegates that offer ways of subscribing and unsubscribing to them. If an anonymous function is used to subscribe to an event, it is impossible to unsubscribe from it. This happens because to remove the entry from the subscription list, a reference to the original method is needed, but if the anonymous function has not been stored before subscribing, there is no way to find a reference to it.

Instead, store the callback to a variable or a named method and use the variable or method to subscribe and unsubscribe.

How to fix it

Store the callback to a variable or a named method and use the variable or method to subscribe and unsubscribe.

Code examples

Noncompliant code example

event EventHandler myEvent;

void DoWork()
{
        myEvent += (s, e) => Console.WriteLine($"Event raised with sender {s} and arguments {e}!");
        // ...
        myEvent -= (s, e) => Console.WriteLine($"Event raised with sender {s} and arguments {e}!"); // Noncompliant: this callback was never subscribed
}

Compliant solution

event EventHandler myEvent;
void LogEvent(object s, EventArgs e) => Console.WriteLine($"Event raised with sender {s} and arguments {e}!");

void DoWork()
{
        myEvent += LogEvent;
        // ...
        myEvent -= LogEvent; // Compliant: LogEvent points to the same callback used for subscribing
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3244.json ================================================ { "title": "Anonymous delegates should not be used to unsubscribe from Events", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3244", "sqKey": "S3244", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3246.html ================================================

Why is this an issue?

In the interests of making code as usable as possible, interfaces and delegates with generic parameters should use the out and in modifiers when possible to make the interfaces and delegates covariant and contravariant, respectively.

The out keyword can be used when the type parameter is used only as a return type in the interface or delegate. Doing so makes the parameter covariant, and allows interface and delegate instances created with a sub-type to be used as instances created with a base type. The most notable example of this is IEnumerable<out T>, which allows the assignment of an IEnumerable<string> instance to an IEnumerable<object> variable, for instance.

The in keyword can be used when the type parameter is used only as a method parameter in the interface or a parameter in the delegate. Doing so makes the parameter contravariant, and allows interface and delegate instances created with a base type to be used as instances created with a sub-type. I.e. this is the inversion of covariance. The most notable example of this is the Action<in T> delegate, which allows the assignment of an Action<object> instance to a Action<string> variable, for instance.

Noncompliant code example

interface IConsumer<T>  // Noncompliant
{
    bool Eat(T fruit);
}

Compliant solution

interface IConsumer<in T>
{
    bool Eat(T fruit);
}
================================================ FILE: analyzers/rspec/cs/S3246.json ================================================ { "title": "Generic type parameters should be co\/contravariant when possible", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3246", "sqKey": "S3246", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3247.html ================================================

Why is this an issue?

In C#, the is type testing operator can be used to check if the run-time type of an object is compatible with a given type. If the object is not null, then the is operator performs a cast, and so performing another cast following the check result is redundant.

This can impact:

How to fix it

Use pattern macthing to perform the check and retrieve the cast result.

Code examples

Noncompliant code example

if (x is Fruit)  // Noncompliant
{
  var f = (Fruit)x; // or x as Fruit
  // ...
}

Compliant solution

if (x is Fruit fruit)
{
  // ...
}

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation

IsPattern_Class

.NET 9.0

176.48 ns

0.765 ns

IsWithCast_Class

.NET 9.0

246.12 ns

22.391 ns

IsPattern_Class

.NET Framework 4.8.1

325.11 ns

14.435 ns

IsWithCast_Class

.NET Framework 4.8.1

311.22 ns

11.145 ns

IsPattern_Interface

.NET 9.0

26.77 ns

1.123 ns

IsWithCast_Interface

.NET 9.0

26.45 ns

2.115 ns

IsPattern_Interface

.NET Framework 4.8.1

119.80 ns

5.411 ns

IsWithCast_Interface

.NET Framework 4.8.1

119.33 ns

4.380 ns

IsPattern_ValueType

.NET 9.0

22.58 ns

1.161 ns

IsWithCast_ValueType

.NET 9.0

19.41 ns

2.675 ns

IsPattern_ValueType

.NET Framework 4.8.1

39.66 ns

0.645 ns

IsWithCast_ValueType

.NET Framework 4.8.1

41.34 ns

0.462 ns

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private Random random = new Random(1);

private object ReturnSometimes<T>() where T : new() =>
    random.Next(2) switch
    {
        0 => new T(),
        1 => new object(),
    };

[BenchmarkCategory("ValueType"), Benchmark(Baseline = true)]
public int IsPattern_ValueType()
{
    var i = ReturnSometimes<int>();
    return i is int d
        ? d
        : default;
}

[BenchmarkCategory("ValueType"), Benchmark]
public int IsWithCast_ValueType()
{
    var i = ReturnSometimes<int>();
    return i is int
        ? (int)i
        : default;
}

[BenchmarkCategory("Class"), Benchmark(Baseline = true)]
public DuplicateCasts IsPattern_Class()
{
    var i = ReturnSometimes<DuplicateCasts>();
    return i is DuplicateCasts d
        ? d
        : default;
}

[BenchmarkCategory("Class"), Benchmark]
public DuplicateCasts IsWithCast_Class()
{
    var i = ReturnSometimes<DuplicateCasts>();
    return i is DuplicateCasts
        ? (DuplicateCasts)i
        : default;
}

[BenchmarkCategory("Interface"), Benchmark(Baseline = true)]
public IReadOnlyList<int> IsPattern_Interface()
{
    var i = ReturnSometimes<List<int>>();
    return i is IReadOnlyList<int> d
        ? d
        : default;
}

[BenchmarkCategory("Interface"), Benchmark]
public IReadOnlyList<int> IsWithCast_Interface()
{
    var i = ReturnSometimes<List<int>>();
    return i is IReadOnlyList<int>
        ? (IReadOnlyList<int>)i
        : default;
}

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
Intel Core Ultra 7 165H, 1 CPU, 22 logical and 16 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
  .NET 9.0             : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S3247.json ================================================ { "title": "Duplicate casts should not be made", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3247", "sqKey": "S3247", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3249.html ================================================

Why is this an issue?

Making a base call when overriding a method is generally a good idea, but not in the case of GetHashCode and Equals for classes that directly extend Object. These methods are based on the object’s reference, meaning that no two objects that use those base methods can be equal or have the same hash.

Exceptions

This rule doesn’t report on guard conditions checking for reference equality. For example:

public override bool Equals(object obj)
{
  if (base.Equals(obj)) // Compliant, it's a guard condition.
  {
    return true;
  }
  ...
}

How to fix it

Code examples

Noncompliant code example

var m1 = new MyClass(2);
var m2 = new MyClass(2);

m1.Equals(m2) // False
m1.GetHashCode(); // 43942919
m2.GetHashCode(); // 59941935

class MyClass
{
    private readonly int x;
    public MyClass(int x) =>
        this.x = x;

    public override bool Equals(Object obj) =>
        base.Equals();

    public override int GetHashCode() =>
        x.GetHashCode() ^ base.GetHashCode(); // Noncompliant, base.GetHashCode returns a code based on the objects reference
}

Compliant solution

var m1 = new MyClass(2);
var m2 = new MyClass(2);

m1.Equals(m2) // True
m1.GetHashCode(); // 2
m2.GetHashCode(); // 2

class MyClass
{
    private readonly int x;
    public MyClass(int x) =>
        this.x = x;

    public override bool Equals(Object obj) =>
        this.x == ((MyClass)obj).x;

    public override int GetHashCode() =>
        x.GetHashCode()
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3249.json ================================================ { "title": "Classes directly extending \"object\" should not call \"base\" in \"GetHashCode\" or \"Equals\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3249", "sqKey": "S3249", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3251.html ================================================

Why is this an issue?

partial methods allow an increased degree of flexibility in programming a system. Hooks can be added to generated code by invoking methods that define their signature, but might not have an implementation yet. But if the implementation is still missing when the code makes it to production, the compiler silently removes the call. In the best case scenario, such calls simply represent cruft, but in they worst case they are critical, missing functionality, the loss of which will lead to unexpected results at runtime.

This rule raises an issue for partial methods for which no implementation can be found in the assembly.

Noncompliant code example

partial class C
{
  partial void M(); //Noncompliant

  void OtherM()
  {
    M(); //Noncompliant. Will be removed.
  }
}
================================================ FILE: analyzers/rspec/cs/S3251.json ================================================ { "title": "Implementations should be provided for \"partial\" methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "suspicious" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3251", "sqKey": "S3251", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3253.html ================================================

Why is this an issue?

Since the compiler will automatically invoke the base type’s no-argument constructor, there’s no need to specify its invocation explicitly. Also, when only a single public parameterless constructor is defined in a class, then that constructor can be removed because the compiler would generate it automatically. Similarly, empty static constructors and empty destructors are also wasted keystrokes.

Noncompliant code example

class X
{
  public X() { } // Noncompliant
  static X() { }  // Noncompliant
  ~X() { } // Noncompliant

  ...
}

class Y : X
{
  public Y(int parameter) : base() // Noncompliant
  {
    /* does something with the parameter */
  }
}

Compliant solution

class X
{
  ...
}

class Y : X
{
  public Y(int parameter)
  {
    /* does something with the parameter */
  }
}
================================================ FILE: analyzers/rspec/cs/S3253.json ================================================ { "title": "Constructor and destructor declarations should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3253", "sqKey": "S3253", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3254.html ================================================

Why is this an issue?

Specifying the default parameter values in a method call is redundant. Such values should be omitted in the interests of readability.

Noncompliant code example

public void M(int x, int y=5, int z = 7) { /* ... */ }

// ...
M(1, 5); // Noncompliant, y has the default value
M(1, z: 7); // Noncompliant, z has the default value

Compliant solution

public void M(int x, int y=5, int z = 7) { /* ... */ }

// ...
M(1);
M(1);
================================================ FILE: analyzers/rspec/cs/S3254.json ================================================ { "title": "Default parameter values should not be passed as arguments", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3254", "sqKey": "S3254", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3256.html ================================================

Why is this an issue?

Using string.Equals to determine if a string is empty is significantly slower than using string.IsNullOrEmpty() or checking for string.Length == 0. string.IsNullOrEmpty() is both clear and concise, and therefore preferred to laborious, error-prone, manual null- and emptiness-checking.

Noncompliant code example

"".Equals(name); // Noncompliant
!name.Equals(""); // Noncompliant
name.Equals(string.Empty); // Noncompliant

Compliant solution

name != null && name.Length > 0 // Compliant but more error prone
!string.IsNullOrEmpty(name)
string.IsNullOrEmpty(name)
================================================ FILE: analyzers/rspec/cs/S3256.json ================================================ { "title": "\"string.IsNullOrEmpty\" should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3256", "sqKey": "S3256", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3257.html ================================================

Why is this an issue?

In C#, the type of a variable can often be inferred by the compiler. The use of the [var keyword](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/implicitly-typed-local-variables) allows you to avoid repeating the type name in a variable declaration and object instantiation because the declared type can often be inferred by the compiler.

Additionally, initializations providing the default value can also be omitted, helping to make the code more concise and readable.

Unnecessarily verbose declarations and initializations should be simplified. Specifically, the following should be omitted when they can be inferred:

How to fix it

Remove any unneeded code. C# provides many features designed to help you write more concise code.

Code examples

Noncompliant code example

var l = new List<int>() {}; // Noncompliant, {} can be removed
var o = new object() {}; // Noncompliant, {} can be removed

var ints = new int[] {1, 2, 3}; // Noncompliant, int can be omitted
ints = new int[3] {1, 2, 3}; // Noncompliant, the size specification can be removed

int? i = new int?(5); // Noncompliant new int? could be omitted, it can be inferred from the declaration, and there's implicit conversion from T to T?
var j = new int?(5);

Func<int, int> f1 = (int i) => 1; //Noncompliant, can be simplified

class Class
{
    private event EventHandler MyEvent;

    public Class()
    {
        MyEvent += new EventHandler((a,b)=>{ }); // Noncompliant, needlessly verbose
    }
}

Compliant solution

var l = new List<int>();
var o = new object();

var ints = new [] {1, 2, 3};
ints = new [] {1, 2, 3};

int? i = 5;
var j = new int?(5);

Func<int, int> f1 = (i) => 1;

class Class
{
    private event EventHandler MyEvent;

    public Class()
    {
        MyEvent += (a,b)=>{ };
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3257.json ================================================ { "title": "Declarations and initializations should be as concise as possible", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3257", "sqKey": "S3257", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3260.html ================================================

Why is this an issue?

Classes and records with either private or file access modifiers aren’t visible outside of their assemblies or files, so if they’re not extended inside their scope, they should be made explicitly non-extensible with the addition of the sealed keyword.

What is the potential impact?

We measured at least 4x improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The code can be improved by adding the sealed keyword in front of the class or record types that have no inheritors.

Code examples

Noncompliant code example

private class MyClass  // Noncompliant
{
  // ...
}

private record MyRecord  // Noncompliant
{
  // ...
}
file class MyClass  // Noncompliant
{
  // ...
}

file record MyRecord  // Noncompliant
{
  // ...
}

Compliant solution

private sealed class MyClass
{
  // ...
}

private sealed record MyRecord
{
  // ...
}
file sealed class MyClass
{
  // ...
}

file sealed record MyRecord
{
  // ...
}

Resources

Documentation

Articles & blog posts

Benchmarks

Method Runtime Mean Standard Deviation

UnsealedType

.NET 5.0

918.7 us

10.72 us

SealedType

.NET 5.0

231.2 us

3.20 us

UnsealedType

.NET 6.0

867.9 us

5.65 us

SealedType

.NET 6.0

218.4 us

0.59 us

UnsealedType

.NET 7.0

1,074.5 us

3.15 us

SealedType

.NET 7.0

216.1 us

1.19 us

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

[Params(1_000_000)]
public int Iterations { get; set; }

private readonly UnsealedClass unsealedType = new UnsealedClass();
private readonly SealedClass sealedType = new SealedClass();

[Benchmark(Baseline = true)]
public void UnsealedType()
{
    for (int i = 0; i < Iterations; i++)
    {
        unsealedType.DoNothing();
    }
}

[Benchmark]
public void SealedType()
{
    for (int i = 0; i < Iterations; i++)
    {
        sealedType.DoNothing();
    }
}

private class BaseType
{
    public virtual void DoNothing() { }
}

private class UnsealedClass : BaseType
{
    public override void DoNothing() { }
}

private sealed class SealedClass : BaseType
{
    public override void DoNothing() { }
}

Hardware Configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.203
  [Host]   : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 5.0 : .NET 5.0.17 (5.0.1722.21314), X64 RyuJIT AVX2
  .NET 6.0 : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2
  .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S3260.json ================================================ { "title": "Non-derived \"private\" classes and records should be \"sealed\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3260", "sqKey": "S3260", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3261.html ================================================

Why is this an issue?

Namespaces with no lines of code clutter a project and should be removed.

Noncompliant code example

namespace MyEmptyNamespace // Noncompliant
{

}
================================================ FILE: analyzers/rspec/cs/S3261.json ================================================ { "title": "Namespaces should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3261", "sqKey": "S3261", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3262.html ================================================

Why is this an issue?

Overriding methods automatically inherit the params behavior. To ease readability, this modifier should be explicitly used in the overriding method as well.

Noncompliant code example

class Base
{
  public virtual void Method(params int[] numbers)
  {
    ...
  }
}
class Derived : Base
{
  public override void Method(int[] numbers) // Noncompliant, the params is missing.
  {
    ...
  }
}

Compliant solution

class Base
{
  public virtual void Method(params int[] numbers)
  {
    ...
  }
}
class Derived : Base
{
  public override void Method(params int[] numbers)
  {
    ...
  }
}
================================================ FILE: analyzers/rspec/cs/S3262.json ================================================ { "title": "\"params\" should be used on overrides", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3262", "sqKey": "S3262", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3263.html ================================================

Why is this an issue?

Static field initializers are executed in the order in which they appear in the class from top to bottom. Thus, placing a static field in a class above the field or fields required for its initialization will yield unexpected results.

Noncompliant code example

class MyClass
{
  public static int X = Y; // Noncompliant; Y at this time is still assigned default(int), i.e. 0
  public static int Y = 42;
}

Compliant solution

class MyClass
{
  public static int Y = 42;
  public static int X = Y;
}

or

class MyClass
{
  public static int X;
  public static int Y = 42;

  static MyClass()
  {
    X = Y;
  }
}
================================================ FILE: analyzers/rspec/cs/S3263.json ================================================ { "title": "Static fields should appear in the order they must be initialized ", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3263", "sqKey": "S3263", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3264.html ================================================

Why is this an issue?

Events that are not invoked anywhere are dead code, and there’s no good reason to keep them in the source.

Noncompliant code example

class UninvokedEventSample
{
    private event Action<object, EventArgs> Happened; // Noncompliant

    public void RegisterEventHandler(Action<object, EventArgs> handler)
    {
        Happened += handler; // we register some event handlers
    }

    public void RaiseEvent()
    {
        if (Happened != null)
        {
            // Happened(this, null); // the event is never triggered, because this line is commented out.
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S3264.json ================================================ { "title": "Events should be invoked", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3264", "sqKey": "S3264", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3265.html ================================================

Why is this an issue?

Enumerations are commonly used to identify distinct elements from a set of values.

However, they can also serve as bit flags, enabling bitwise operations to combine multiple elements within a single value.

// Saturday = 0b00100000, Sunday = 0b01000000, weekend = 0b01100000
var weekend = Days.Saturday | Days.Sunday;  // Combining elements

When enumerations are used as bit flags, it is considered good practice to annotate the enum type with the FlagsAttribute:

enum Permissions
{
  None = 0,
  Read = 1,
  Write = 2,
  Execute = 4
}

// ...

var x = Permissions.Read | Permissions.Write;  // Noncompliant: enum is not annotated with [Flags]

The FlagsAttribute explicitly marks an enumeration as bit flags, making it clear that it uses bit fields and is intended to be used as flags.

[Flags]
enum Permissions
{
  None = 0,
  Read = 1,
  Write = 2,
  Execute = 4
}

// ...

var x = Permissions.Read | Permissions.Write;  // Compliant: enum is annotated with [Flags]

Additionally, adding the FlagsAttribute to the enumeration enable a better string representation when using the Enum.ToString method.

Exception

This rule will not raise for MethodImplAttribute because, despite not having the FlagsAttribute, it is often used in a similar fashion.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3265.json ================================================ { "title": "Non-flags enums should not be used in bitwise operations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3265", "sqKey": "S3265", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3267.html ================================================

Why is this an issue?

Using explicit loops for filtering, selecting, or aggregating elements can make code more verbose and harder to read. LINQ expressions provide a more concise and expressive way to perform these operations, improving code clarity and maintainability.

Performance Considerations

If the affected code is part of a performance-critical hot path and that the fix would negatively impact performance, you can self-declare the PerformanceSensitiveAttribute in your codebase, or use the one provided by Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers:

[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class PerformanceSensitiveAttribute() : Attribute;

[PerformanceSensitiveAttribute]
List<string> Method(IEnumerable<string> collection, Predicate<string> condition)
{
  var result = new List<string>();
  foreach (var element in collection)  // Without the attribute, this would raise an issue
  {
    if (condition(element))
    {
      result.Add(element);
    }
  }
  return result;
}

The rule will respect the AllowGenericEnumeration property:

[PerformanceSensitive("Enumeration", AllowGenericEnumeration = true)]
List<string> Method(IEnumerable<string> collection, Predicate<string> condition) { }

In this case, the rule will not be disabled even if the method is marked with the PerformanceSensitiveAttribute attribute.

How to fix it

Replace explicit loops and conditional blocks with equivalent LINQ expressions.

Use the System.Linq.Async package to enable LINQ operations on IAsyncEnumerable prior to .NET 10.

Code examples

Noncompliant code example

List<string> Method(IEnumerable<string> collection, Predicate<string> condition)
{
  var result = new List<string>();
  foreach (var element in collection)  // Noncompliant
  {
    if (condition(element))
    {
      result.Add(element);
    }
  }
  return result;
}
List<string> Method(IEnumerable<MyDto> collection)
{
  var result = new List<string>();
  foreach (var element in collection) // Noncompliant
  {
    var someValue = element.Property;
    if (someValue != null)
    {
      result.Add(someValue);
    }
  }
  return result;
}
public void Method(List<string> list)
{
  foreach (var element in list)
  {
    var someValue = element.Length;
  }
}
async void Method(IAsyncEnumerable<int> collection)
{
  await foreach (var element in collection)
  {
    if (element is 42)
    {
      Console.WriteLine("The meaning of Life.");
    }
  }
}

Compliant solution

List<string> Method(IEnumerable<string> collection, Predicate<string> condition) =>
  collection.Where(x => condition(x)).ToList();
List<string> Method(IEnumerable<MyDto> collection) =>
  collection.Select(x => x.Property).Where(y => y != null).ToList();
void Method(List<int> list)
{
  foreach (var length in list.Select(x => x.Length))
  {
    var someValue = length;
  }
}
async void Method(IAsyncEnumerable<int> collection)
{
  await foreach (var element in collection.Where(x => x is 42)))
  {
    Console.WriteLine("The meaning of Life.");
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3267.json ================================================ { "title": "Loops should be simplified with \"LINQ\" expressions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3267", "sqKey": "S3267", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3329.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

In the mode Cipher Block Chaining (CBC), each block is used as cryptographic input for the next block. For this reason, the first block requires an initialization vector (IV), also called a "starting variable" (SV).

If the same IV is used for multiple encryption sessions or messages, each new encryption of the same plaintext input would always produce the same ciphertext output. This may allow an attacker to detect patterns in the ciphertext.

What is the potential impact?

After retrieving encrypted data and performing cryptographic attacks on it on a given timeframe, attackers can recover the plaintext that encryption was supposed to protect.

Depending on the recovered data, the impact may vary.

Below are some real-world scenarios that illustrate the potential impact of an attacker exploiting the vulnerability.

Additional attack surface

By modifying the plaintext of the encrypted message, an attacker may be able to trigger additional vulnerabilities in the code. An attacker can further exploit a system to obtain more information.
Encrypted values are often considered trustworthy because it would not be possible for a third party to modify them under normal circumstances.

Breach of confidentiality and privacy

When encrypted data contains personal or sensitive information, its retrieval by an attacker can lead to privacy violations, identity theft, financial loss, reputational damage, or unauthorized access to confidential systems.

In this scenario, a company, its employees, users, and partners could be seriously affected.

The impact is twofold, as data breaches and exposure of encrypted data can undermine trust in the organization, as customers, clients and stakeholders may lose confidence in the organization’s ability to protect their sensitive data.

Legal and compliance issues

In many industries and locations, there are legal and compliance requirements to protect sensitive data. If encrypted data is compromised and the plaintext can be recovered, companies face legal consequences, penalties, or violations of privacy laws.

How to fix it in .NET

Code examples

Noncompliant code example

using System.IO;
using System.Security.Cryptography;

public void Encrypt(byte[] key, byte[] dataToEncrypt, MemoryStream target)
{
    var aes = new AesCryptoServiceProvider();

    byte[] iv     = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
    var encryptor = aes.CreateEncryptor(key, iv); // Noncompliant

    var cryptoStream = new CryptoStream(target, encryptor, CryptoStreamMode.Write);
    var swEncrypt    = new StreamWriter(cryptoStream);

    swEncrypt.Write(dataToEncrypt);
}

Compliant solution

In this example, the code implicitly uses a number generator that is considered strong, thanks to aes.IV.

using System.IO;
using System.Security.Cryptography;

public void Encrypt(byte[] key, byte[] dataToEncrypt, MemoryStream target)
{
    var aes = new AesCryptoServiceProvider();

    var encryptor = aes.CreateEncryptor(key, aes.IV);

    var cryptoStream = new CryptoStream(target, encryptor, CryptoStreamMode.Write);
    var swEncrypt    = new StreamWriter(cryptoStream);

    swEncrypt.Write(dataToEncrypt);
}

How does this work?

Use unique IVs

To ensure high security, initialization vectors must meet two important criteria:

The IV does not need be secret, so the IV or information sufficient to determine the IV may be transmitted along with the ciphertext.

In the previous non-compliant example, the problem is not that the IV is hard-coded.
It is that the same IV is used for multiple encryption attempts.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S3329.json ================================================ { "title": "Cipher Block Chaining IVs should be unpredictable", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3329", "sqKey": "S3329", "scope": "Main", "securityStandards": { "CWE": [ 327, 780 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3330.html ================================================

When a cookie is configured with the HttpOnly attribute set to true, the browser guaranties that no client-side script will be able to read it. In most cases, when a cookie is created, the default value of HttpOnly is false and it’s up to the developer to decide whether or not the content of the cookie can be read by the client-side script. As a majority of Cross-Site Scripting (XSS) attacks target the theft of session-cookies, the HttpOnly attribute can help to reduce their impact as it won’t be possible to exploit the XSS vulnerability to steal session-cookies.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

When the HttpCookie.HttpOnly property is set to false then the cookie can be accessed by client side code:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
myCookie.HttpOnly = false; // Sensitive: this cookie is created with the httponly flag set to false and so it can be stolen easily in case of XSS vulnerability

The default value of HttpOnly flag is false, unless overwritten by an application’s configuration file:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
// Sensitive: this cookie is created without the httponly flag  (by default set to false) and so it can be stolen easily in case of XSS vulnerability

Compliant Solution

Set the HttpCookie.HttpOnly property to true:

HttpCookie myCookie = new HttpCookie("Sensitive cookie");
myCookie.HttpOnly = true; // Compliant: the sensitive cookie is protected against theft thanks to the HttpOnly property set to true (HttpOnly = true)

Or change the default flag values for the whole application by editing the Web.config configuration file:

<httpCookies httpOnlyCookies="true" requireSSL="true" />

See

================================================ FILE: analyzers/rspec/cs/S3330.json ================================================ { "title": "Creating cookies without the \"HttpOnly\" flag is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3330", "sqKey": "S3330", "scope": "Main", "securityStandards": { "CWE": [ 1004 ], "OWASP": [ "A7" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "3.4.2" ], "STIG ASD_V5R3": [ "V-222575" ] } } ================================================ FILE: analyzers/rspec/cs/S3343.html ================================================

Why is this an issue?

Caller information attributes provide a way to get information about the caller of a method through optional parameters. But they only work right if their values aren’t provided explicitly. So if you define a method with caller info attributes in the middle of the parameter list, the caller is forced to use named arguments if they want to use the method properly.

This rule raises an issue when the following attributes are used on parameters before the end of the parameter list:

How to fix it

Move the decorated parameters to the end of the parameter list.

Code examples

Noncompliant code example

void TraceMessage([CallerMemberName] string memberName = "",
  [CallerFilePath] string filePath = "",
  [CallerLineNumber] int lineNumber = 0,
  string message = null)  // Noncompliant: decorated parameters appear before "message" parameter
{
  /* ... */
}

Compliant solution

void TraceMessage(string message = null,
  [CallerMemberName] string memberName = "",
  [CallerFilePath] string filePath = "",
  [CallerLineNumber] int lineNumber = 0)
{
  /* ... */
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3343.json ================================================ { "title": "Caller information parameters should come at the end of the parameter list", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3343", "sqKey": "S3343", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3346.html ================================================

Why is this an issue?

An assertion is a piece of code that’s used during development when the compilation debug mode is activated. It allows a program to check itself as it runs. When an assertion is true, that means everything is operating as expected.

In non-debug mode, all Debug.Assert calls are automatically left out (via the Conditional("DEBUG") mechanism). So, by contract, the boolean expressions that are evaluated by those assertions must not contain any side effects. Otherwise, when leaving the debug mode, the functional behavior of the application is not the same anymore.

The rule will raise if the method name starts with any of the following remove, delete, add, pop, update, retain, insert, push, append, clear, dequeue, enqueue, dispose, put, or set, although SetEquals will be ignored.

How to fix it

In the following example, the assertion checks the return value of the remove method in the argument. Because the whole line is skipped in non-debug builds, the call to Remove never happens in such builds.

Code examples

Noncompliant code example

Debug.Assert(list.Remove("dog"));

Compliant solution

The Remove call must be extracted and the return value needs to be asserted instead.

bool result = list.Remove("dog");
Debug.Assert(result);

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3346.json ================================================ { "title": "Expressions used in \"Debug.Assert\" should not produce side effects", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3346", "sqKey": "S3346", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3353.html ================================================

Why is this an issue?

If a variable that is not supposed to change is not marked as const, it could be accidentally reassigned elsewhere in the code, leading to unexpected behavior and bugs that can be hard to track down.

By declaring a variable as const, you ensure that its value remains constant throughout the code. It also signals to other developers that this value is intended to remain constant. This can make the code easier to understand and maintain.

In some cases, using const can lead to performance improvements. The compiler might be able to make optimizations knowing that the value of a const variable will not change.

How to fix it

Mark the given variable with the const modifier.

Code examples

Noncompliant code example

public bool Seek(int[] input)
{
  var target = 32;  // Noncompliant
  foreach (int i in input)
  {
    if (i == target)
    {
      return true;
    }
  }
  return false;
}

Compliant solution

public bool Seek(int[] input)
{
  const int target = 32;
  foreach (int i in input)
  {
    if (i == target)
    {
      return true;
    }
  }
  return false;
}

Noncompliant code example

public class Sample
{
  public void Method()
  {
    var context = $"{nameof(Sample)}.{nameof(Method)}";  // Noncompliant (C# 10 and above only)
  }
}

Compliant solution

public class Sample
{
  public void Method()
  {
    const string context = $"{nameof(Sample)}.{nameof(Method)}";
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3353.json ================================================ { "title": "Unchanged variables should be marked as \"const\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3353", "sqKey": "S3353", "scope": "Main", "quickfix": "partial" } ================================================ FILE: analyzers/rspec/cs/S3358.html ================================================

Why is this an issue?

Nested ternaries are hard to read and can make the order of operations complex to understand.

public string GetReadableStatus(Job j)
{
  return j.IsRunning ? "Running" : j.HasErrors ? "Failed" : "Succeeded";  // Noncompliant
}

Instead, use another line to express the nested operation in a separate statement.

public string GetReadableStatus(Job j)
{
  if (j.IsRunning)
  {
    return "Running";
  }
  return j.HasErrors ? "Failed" : "Succeeded";
}
================================================ FILE: analyzers/rspec/cs/S3358.json ================================================ { "title": "Ternary operators should not be nested", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3358", "sqKey": "S3358", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3363.html ================================================

You should only set a property of a temporal type (like DateTime or DateTimeOffset) as the primary key of a table if the values are guaranteed to be unique.

Why is this an issue?

Using temporal types as the primary key of a table is risky. When these types are used as primary keys, it usually means that each new key is created with the use of .Now or .UtcNow properties from DateTime and DateTimeOffset classes. In those cases, duplicate keys exceptions may occur in many ways:

The rule raises an issue if:

How to fix it

Either use a GUID or the auto generated ID as a primary key.

Code examples

Noncompliant code example

internal class Account
{
    public DateTime Id { get; set; }

    public string Name { get; set; }
    public string Surname { get; set; }
}

Compliant solution

internal class Account
{
    public Guid Id { get; set; }

    public string Name { get; set; }
    public string Surname { get; set; }
}

or

Noncompliant code example

internal class Person
{
    [Key]
    public DateTime PersonIdentifier { get; set; }

    public string Name { get; set; }
    public string Surname { get; set; }
}

Compliant solution

internal class Person
{
    [Key]
    public Guid PersonIdentifier { get; set; }

    public string Name { get; set; }
    public string Surname { get; set; }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3363.json ================================================ { "title": "Date and time should not be used as a type for primary keys", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3363", "sqKey": "S3363", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3366.html ================================================

Why is this an issue?

In single-threaded environments, the use of this in constructors is normal, and expected. But in multi-threaded environments, it could expose partially-constructed objects to other threads, and should be used with caution.

The classic example is a class with a static list of its instances. If the constructor stores this in the list, another thread could access the object before it’s fully-formed. Even when the storage of this is the last instruction in the constructor, there’s still a danger if the class is not final. In that case, the initialization of subclasses won’t be complete before this is exposed.

This rule raises an issue when this is assigned to any globally-visible object in a constructor, and when it is passed to the method of another object in a constructor

Noncompliant code example

public class Monument
{
  public static readonly List<Monument> ALL_MONUMENTS = new List<Monument>();
  // ...

  public Monument(string location, ...)
  {
    ALL_MONUMENTS.Add(this);  // Noncompliant; passed to a method of another object

    this.location = location;
    // ...
  }
}

Exceptions

This rule ignores instances of assigning this directly to a static field of the same class because that case is covered by {rule:csharpsquid:S3010} .

================================================ FILE: analyzers/rspec/cs/S3366.json ================================================ { "title": "\"this\" should not be exposed from constructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "multi-threading", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3366", "sqKey": "S3366", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3376.html ================================================

Why is this an issue?

Adherence to the standard naming conventions makes your code not only more readable, but more usable. For instance, class FirstAttribute : Attribute can be used simply with First, but you must use the full name for class AttributeOne : Attribute.

This rule raises an issue when classes extending Attribute, EventArgs, or Exception, do not end with their parent class names.

Noncompliant code example

class AttributeOne : Attribute  // Noncompliant
{
}

Compliant solution

class FirstAttribute : Attribute
{
}

Exceptions

If a class' direct base class doesn’t follow the convention, then no issue is reported on the class itself, regardless of whether or not it conforms to the convention.

class Timeout : Exception // Noncompliant
{
}
class ExtendedTimeout : Timeout // Ignored; doesn't conform to convention, but the direct base doesn't conform either
{
}
================================================ FILE: analyzers/rspec/cs/S3376.json ================================================ { "title": "Attribute, EventArgs, and Exception type names should end with the type being extended", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3376", "sqKey": "S3376", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3397.html ================================================

Why is this an issue?

object.Equals() overrides can be optimized by checking first for reference equality between this and the parameter. This check can be implemented by calling object.ReferenceEquals() or base.Equals(), where base is object. However, using base.Equals() is a maintenance hazard because while it works if you extend Object directly, if you introduce a new base class that overrides Equals, it suddenly stops working.

This rule raises an issue if base.Equals() is used but base is not object.

Noncompliant code example

class Base
{
  private int baseField;

  public override bool Equals(object other)
  {
    if (base.Equals(other)) // Okay; base is object
    {
      return true;
    }

    return this.baseField == ((Base)other).baseField;
  }
}

class Derived : Base
{
  private int derivedField;

  public override bool Equals(object other)
  {
    if (base.Equals(other))  // Noncompliant
    {
      return true;
    }

    return this.derivedField == ((Derived)other).derivedField;
  }
}

Compliant solution

class Base
{
  private int baseField;

  public override bool Equals(object other)
  {
    if (object.ReferenceEquals(this, other))  // base.Equals is okay here, but object.ReferenceEquals is better
    {
      return true;
    }

    return this.baseField == ((Base)other).baseField;
  }
}

class Derived : Base
{
  private int derivedField;

  public override bool Equals(object other)
  {
    if (object.ReferenceEquals(this, other))
    {
      return true;
    }

    return base.Equals(other) && this.derivedField == ((Derived)other).derivedField;
  }
}
================================================ FILE: analyzers/rspec/cs/S3397.json ================================================ { "title": "\"base.Equals\" should not be used to check for reference equality in \"Equals\" if \"base\" is not \"object\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3397", "sqKey": "S3397", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3398.html ================================================

Why is this an issue?

When a private static method is only invoked by a nested class, there’s no reason not to move it into that class. It will still have the same access to the outer class' static members, but the outer class will be clearer and less cluttered.

Noncompliant code example

public class Outer
{
    private const int base = 42;

    private static void Print(int num)  // Noncompliant - static method is only used by the nested class, should be moved there
    {
        Console.WriteLine(num + base);
    }

    public class Nested
    {
        public void SomeMethod()
        {
            Outer.Print(1);
        }
    }
}

Compliant solution

public class Outer
{
    private const int base = 42;

    public class Nested
    {
        public void SomeMethod()
        {
            Print(1);
        }

        private static void Print(int num)
        {
            Console.WriteLine(num + base);
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S3398.json ================================================ { "title": "\"private\" methods called only by inner classes should be moved to those classes", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3398", "sqKey": "S3398", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3400.html ================================================

Why is this an issue?

There’s no point in forcing the overhead of a method call for a method that always returns the same constant value. Even worse, the fact that a method call must be made will likely mislead developers who call the method thinking that something more is done. Declare a constant instead.

This rule raises an issue if on methods that contain only one statement: the return of a constant value.

Noncompliant code example

int GetBestNumber()
{
  return 12;  // Noncompliant
}

Compliant solution

const int BestNumber = 12;

or

static readonly int BestNumber = 12;
================================================ FILE: analyzers/rspec/cs/S3400.json ================================================ { "title": "Methods should not return constants", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3400", "sqKey": "S3400", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3415.html ================================================

Why is this an issue?

The standard assertions library methods such as AreEqual and AreSame in MSTest and NUnit, or Equal and Same in XUnit, expect the first argument to be the expected value and the second argument to be the actual value.

What is the potential impact?

Having the expected value and the actual value in the wrong order will not alter the outcome of tests, (succeed/fail when it should) but the error messages will contain misleading information.

This rule raises an issue when the actual argument to an assertions library method is a hard-coded value and the expected argument is not.

How to fix it

You should provide the assertion methods with a hard-coded value as the expected value, while the actual value of the assertion should derive from the portion of code that you want to test.

Code examples

Noncompliant code example

Assert.AreEqual(runner.ExitCode, 0, "Unexpected exit code"); // Noncompliant; Yields error message like: Expected:<-1>. Actual:<0>.

Compliant solution

Assert.AreEqual(0, runner.ExitCode, "Unexpected exit code");
================================================ FILE: analyzers/rspec/cs/S3415.json ================================================ { "title": "Assertion arguments should be passed in the correct order", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "tests", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3415", "sqKey": "S3415", "scope": "Tests", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3416.html ================================================

Why is this an issue?

It is a well-established convention to name each logger after its enclosing type. This rule raises an issue when the convention is not respected.

class EnclosingType
{
    private readonly ILogger logger;

    public EnclosingType(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger<AnotherType>();   // Noncompliant
        logger = loggerFactory.CreateLogger<EnclosingType>(); // Compliant
    }
}

Not following such a convention can result in confusion and logging misconfiguration.

For example, the person configuring the log may attempt to change the logging behavior for the MyNamespace.EnclosingType type, by overriding defaults for the logger named after that type.

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "MyNamespace.EnclosingType": "Debug"
        }
    }
}

However, if the convention is not in place, the override would not affect logs from MyNamespace.EnclosingType, since they are made via a logger with a different name.

Moreover, using the same logger name for multiple types prevents the granular configuration of each type’s logger, since there is no way to distinguish them in configuration.

The rule targets the following logging frameworks: * Microsoft Extensions Logging * Apache log4net * NLog

Exceptions

The rule doesn’t raise issues when custom handling of logging names is in place, and the logger name is not derived from a Type.

class EnclosingType
{
    private readonly ILogger logger;

    EnclosingType(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger("My cross-type logging category");   // Compliant
        logger = loggerFactory.CreateLogger(AComplexLogicToFindTheRightType());  // Compliant
    }
}

How to fix it

When the logger name is defined by a generic type parameter:

class EnclosingType
{
    private readonly ILogger logger;

    public EnclosingType(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger<AnotherType>();   // Noncompliant
        logger = loggerFactory.CreateLogger<EnclosingType>(); // Compliant
    }
}

When the logger name is defined by an input parameter of type Type:

class EnclosingType
{
    private readonly ILogger logger;

    public EnclosingType(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger(typeof(AnotherType));   // Noncompliant
        logger = loggerFactory.CreateLogger(typeof(EnclosingType)); // Compliant
        logger = loggerFactory.CreateLogger(GetType());             // Compliant
    }
}

When the logger name is a string, derived from a Type:

class EnclosingType
{
    private readonly ILogger logger;

    public EnclosingType(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger(typeof(AnotherType).Name);       // Noncompliant
        logger = loggerFactory.CreateLogger(typeof(AnotherType).FullName);   // Noncompliant
        logger = loggerFactory.CreateLogger(nameof(AnotherType));            // Noncompliant
        // Fix by referring to the right type
        logger = loggerFactory.CreateLogger(typeof(EnclosingType).Name);     // Compliant
        logger = loggerFactory.CreateLogger(typeof(EnclosingType).FullName); // Compliant
        logger = loggerFactory.CreateLogger(nameof(EnclosingType));          // Compliant
        // or by retrieving the right type dynamically
        logger = loggerFactory.CreateLogger(GetType().FullName);             // Compliant
    }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3416.json ================================================ { "title": "Loggers should be named for their enclosing types", "type": "CODE_SMELL", "status": "ready", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing", "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3416", "sqKey": "S3416", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3427.html ================================================

Why is this an issue?

The rules for method resolution can be complex and may not be fully understood by all developers. The situation becomes even more challenging when dealing with method overloads that have optional parameter values.

This rule raises an issue when an overload with default parameter values is hidden by another overload that does not have the optional parameters.

What is the potential impact?

See the following example:

MyClass.Print(1);  // which overload of Print will be called?

public static class MyClass
{
  public static void Print(int number) { }
  public static void Print(int number, string delimiter = "\n") { } // Noncompliant, default parameter value is hidden by overload
}

In this code snippet, the Print method is overloaded with two versions, where the first one hides the second one. This can lead to confusion and uncertainty about which overload of the method will be invoked when calling it.

How to fix it

To address the problem you have a couple of options:

Code examples

Noncompliant code example

MyClass.Print(1);  // which overload of Print will be called?

public static class MyClass
{
  public static void Print(int number) { }
  public static void Print(int number, string delimiter = "\n") { } // Noncompliant: default parameter value is hidden by overload
}

Compliant solution

MyClass.PrintWithDelimiter(1);

public static class MyClass
{
  public static void Print(int number) { }
  public static void PrintWithDelimiter(int number, string delimiter = "\n") { } // Compliant
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3427.json ================================================ { "title": "Method overloads with default parameter values should not overlap", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "unused", "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3427", "sqKey": "S3427", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3431.html ================================================

Why is this an issue?

It should be clear to a casual reader what code a test is testing and what results are expected. Unfortunately, that’s not usually the case with the ExpectedException attribute since an exception could be thrown from almost any line in the method.

This rule detects MSTest and NUnit ExpectedException attribute.

Exceptions

This rule ignores:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void UsingTest()
{
    Console.ForegroundColor = ConsoleColor.Black;
    try
    {
        using var _ = new ConsoleAlert();
        Assert.AreEqual(ConsoleColor.Red, Console.ForegroundColor);
        throw new InvalidOperationException();
    }
    finally
    {
        Assert.AreEqual(ConsoleColor.Black, Console.ForegroundColor); // The exception itself is not relevant for the test.
    }
}

public sealed class ConsoleAlert : IDisposable
{
    private readonly ConsoleColor previous;

    public  ConsoleAlert()
    {
        previous = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Red;
    }

    public void Dispose() =>
        Console.ForegroundColor = previous;
}

How to fix it in MSTest

Remove the ExpectedException attribute in favor of using the Assert.ThrowsException assertion.

Code examples

Noncompliant code example

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]  // Noncompliant
public void Method_NullParam()
{
    var sut = new MyService();
    sut.Method(null);
}

Compliant solution

[TestMethod]
public void Method_NullParam()
{
    var sut = new MyService();
    Assert.ThrowsException<ArgumentNullException>(() => sut.Method(null));
}

How to fix it in NUnit

Remove the ExpectedException attribute in favor of using the Assert.Throws assertion.

Code examples

Noncompliant code example

[Test]
[ExpectedException(typeof(ArgumentNullException))]  // Noncompliant
public void Method_NullParam()
{
    var sut = new MyService();
    sut.Method(null);
}

Compliant solution

[Test]
public void Method_NullParam()
{
    var sut = new MyService();
    Assert.Throws<ArgumentNullException>(() => sut.Method(null));
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3431.json ================================================ { "title": "\"[ExpectedException]\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "tests" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3431", "sqKey": "S3431", "scope": "Tests", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3433.html ================================================

Why is this an issue?

A method is identified as a test method if it is marked with one of the following attributes:

However, non-public methods are not considered test methods and will not be executed, regardless of whether they have a test attribute. Additionally, methods with the async void modifier or methods that contain generics <T> anywhere in their signatures are also excluded from being recognized as tests and will not be executed.

[TestMethod]
void TestNullArg()  // Noncompliant, method is not public
{  /* ... */  }

[TestMethod]
public async void MyIgnoredTestMethod()  // Noncompliant, this is an 'async void' method
{ /* ... */ }

[TestMethod]
public void MyIgnoredGenericTestMethod<T>(T foo)  // Noncompliant, method has generics in its signature
{ /* ... */ }
[TestMethod]
public void TestNullArg()
{  /* ... */  }

[TestMethod]
public async Task MyIgnoredTestMethod()
{ /* ... */ }

[TestMethod]
public void MyIgnoredGenericTestMethod(int foo)
{ /* ... */ }

Exceptions

For xUnit, accessibility is disregarded when it comes to [Fact] test methods, as they do not necessarily need to be declared as public.

In xUnit, [Theory] test methods, as well as [TestCase] and [TestCaseSource] test methods in NUnit, have the flexibility to be generic, allowing for a wider range of test scenarios.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3433.json ================================================ { "title": "Test method signatures should be correct", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "tests" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3433", "sqKey": "S3433", "scope": "Tests", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3440.html ================================================

Why is this an issue?

There’s no point in checking a variable against the value you’re about to assign it. Save the cycles and lines of code, and simply perform the assignment.

Noncompliant code example

if (x != a)  // Noncompliant; why bother?
{
    x = a;
}

Compliant solution

x = a;

Exceptions

Properties and checks inside setters are excluded from this rule because they could have side effects and removing the check could lead to undesired side effects.

if (MyProperty != a)
{
    MyProperty = a; // Compliant because the setter could be expensive call
}
private int myField;
public int SomeProperty
{
    get
    {
        return myField;
    }
    set
    {
        if (myField != value)
        {
            myField = value;
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S3440.json ================================================ { "title": "Variables should not be checked against the values they\u0027re about to be assigned", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3440", "sqKey": "S3440", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3441.html ================================================

Why is this an issue?

When an anonymous type’s properties are copied from properties or variables with the same names, it yields cleaner code to omit the new type’s property name and the assignment operator.

Noncompliant code example

var X = 5;

var anon = new
{
  X = X, //Noncompliant, the new object would have the same property without the "X =" part.
  Y = "my string"
};

Compliant solution

var X = 5;

var anon = new
{
  X,
  Y = "my string"
};
================================================ FILE: analyzers/rspec/cs/S3441.json ================================================ { "title": "Redundant property names should be omitted in anonymous classes", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3441", "sqKey": "S3441", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3442.html ================================================

Why is this an issue?

The abstract modifier in a class declaration is used to indicate that a class is intended only to be a base class of other classes, not instantiated on its own.

Since abstract classes cannot be instantiated, there is no need for public or internal constructors. If there is basic initialization logic that should run when an extending class instance is created, you can add it in a private, private protected or protected constructor.

How to fix it

Restrict the constructor visibility to the minimum: private, private protected or protected, depending on the usage.

Code examples

Noncompliant code example

abstract class Base
{
    public Base() // Noncompliant: should be private, private protected or protected.
    {
      //...
    }
}

Compliant solution

abstract class Base
{
    protected Base()
    {
      //...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3442.json ================================================ { "title": "\"abstract\" classes should not have \"public\" constructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3442", "sqKey": "S3442", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3443.html ================================================

Why is this an issue?

Calling GetType on a Type variable will always return the System.Type representation, which is equivalent to typeof(System.Type). This also applies to passing a Type argument to IsInstanceOfType which always returns false.

In both cases, the results are entirely predictable and should be avoided.

Exceptions

Calling GetType on System.Type is considered compliant to get an instance of System.RuntimeType, as demonstrated in the following example:

typeof(Type).GetType(); // Can be used by convention to get an instance of 'System.RuntimeType'

How to fix it

Make sure the usage of GetType or IsInstanceOfType is invoked with the correct variable or argument type.

Code examples

Noncompliant code example

void ExamineSystemType(string str)
{
    Type stringType = str.GetType();
    Type runtimeType = stringType.GetType(); // Noncompliant

    if (stringType.IsInstanceOfType(typeof(string))) // Noncompliant; will always return false
    { /* ... */ }
}

Compliant solution

void ExamineSystemType(string str)
{
    Type stringType = str.GetType();

    if (stringType.IsInstanceOfType(str)) // Compliant
    { /* ... */ }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3443.json ================================================ { "title": "Type should not be examined on \"System.Type\" instances", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3443", "sqKey": "S3443", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3444.html ================================================

Why is this an issue?

When an interface inherits from two interfaces that both define a member with the same name, trying to access that member through the derived interface will result in the compiler error CS0229 Ambiguity between 'IBase1.SomeProperty' and 'IBase2.SomeProperty'.

So instead, every caller will be forced to cast instances of the derived interface to one or the other of its base interfaces to resolve the ambiguity and be able to access the member. Instead, it is better to resolve the ambiguity in the definition of the derived interface either by:

Noncompliant code example

public interface IBase1
{
  string SomeProperty { get; set; }
}

public interface IBase2
{
  string SomeProperty { get; set; }
}

public interface IDerived : IBase1, IBase2 // Noncompliant, accessing IDerived.SomeProperty is ambiguous
{
}

public class MyClass : IDerived
{
  // Implements both IBase1.SomeProperty and IBase2.SomeProperty
  public string SomeProperty { get; set; } = "Hello";

  public static void Main()
  {
    MyClass myClass = new MyClass();
    Console.WriteLine(myClass.SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IBase1)myClass).SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IBase2)myClass).SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IDerived)myClass).SomeProperty); // Error CS0229 Ambiguity between 'IBase1.SomeProperty' and 'IBase2.SomeProperty'
  }
}

Compliant solution

public interface IDerived : IBase1, IBase2
{
  new string SomeProperty { get; set; }
}

public class MyClass : IDerived
{
  // Implements IBase1.SomeProperty, IBase2.SomeProperty and IDerived.SomeProperty
  public string SomeProperty { get; set; } = "Hello";

  public static void Main()
  {
    MyClass myClass = new MyClass();
    Console.WriteLine(myClass.SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IBase1)myClass).SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IBase2)myClass).SomeProperty); // Writes "Hello" as expected
    Console.WriteLine(((IDerived)myClass).SomeProperty); // Writes "Hello" as expected
  }
}

or

public interface IBase1
{
  string SomePropertyOne { get; set; }
}

public interface IBase2
{
  string SomePropertyTwo { get; set; }
}

public interface IDerived : IBase1, IBase2
{
}
================================================ FILE: analyzers/rspec/cs/S3444.json ================================================ { "title": "Interfaces should not simply inherit from base interfaces with colliding members", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3444", "sqKey": "S3444", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3445.html ================================================

Why is this an issue?

In C#, the throw statement can be used in two different ways:

By specifying an expression

In the software development context, an expression is a value or anything that executes and ends up being a value. The expression shall be implicitly convertible to System.Exception, and the result of evaluating the expression is converted to System.Exception before being thrown.

try
{
}
catch(Exception exception)
{
  // code that uses the exception
  throw exception; // The exception stack trace is cleared up to this point.
}

In this case, the stack trace, will be cleared, losing the list of method calls between the original method that threw the exception and the current method.

Without specifying an expression

This syntax is supported only in a catch block, in which case, that statement re-throws the exception currently being handled by that catch block, preserving the stack trace.

try
{
}
catch(Exception exception)
{
  // code that uses the exception
  throw; // The stack trace of the initial exception is preserved.
}

Exceptions

It is allowed using the thrown exception as an argument and wrapping it in another exception.

try
{
}
catch(Exception exception)
{
  throw new Exception("Additional information", exception);
}

How to fix it

The recommended way to re-throw an exception is to use the throw statement without including an expression. This ensures that all call stack information is preserved when the exception is propagated to the caller, making debugging easier.

Code examples

Noncompliant code example

try
{
}
catch(Exception exception)
{
  throw exception;
}

Compliant solution

try
{
}
catch(Exception)
{
  throw;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3445.json ================================================ { "title": "Exceptions should not be explicitly rethrown", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "error-handling", "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3445", "sqKey": "S3445", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3447.html ================================================

Why is this an issue?

The use of ref or out in combination with Optional attribute is both confusing and contradictory. [Optional] indicates that the parameter doesn’t have to be provided, while out and ref mean that the parameter will be used to return data to the caller (ref additionally indicates that the parameter may also be used to pass data into the method).

Thus, making it [Optional] to provide the parameter in which you will be passing back the method results doesn’t make sense. In fact, the compiler will raise an error on such code. Unfortunately, it raises the error on method calls where the [Optional] parameter has been omitted, not the source of the problem, the method declaration.

Noncompliant code example

class MyClass
{
  public void DoStuff([Optional] ref int i) // Noncompliant
  {
    Console.WriteLine(i);
  }

  public static void Main()
  {
    new MyClass().DoStuff(); // Compilation Error [CS7036]
  }
}

Compliant solution

class MyClass
{
  public void DoStuff(ref int i)
  {
    Console.WriteLine(i);
  }

  public static void Main()
  {
    var i = 42;
    new MyClass().DoStuff(ref i);
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3447.json ================================================ { "title": "\"[Optional]\" should not be used on \"ref\" or \"out\" parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3447", "sqKey": "S3447", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3449.html ================================================

Why is this an issue?

Numbers can be shifted with the << and >> operators, but the right operand of the operation needs to be an int or a type that has an implicit conversion to int. However, when the left operand is dynamic, the compiler’s type checking is turned off, so you can pass anything to the right of a shift operator and have it compile. And if the argument can’t be implicitly converted to int at runtime, then a RuntimeBinderException will be raised.

dynamic d = 5;
var x = d >> 5.4;   // Noncompliant
x = d << null;      // Noncompliant
x <<= new object(); // Noncompliant

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3449.json ================================================ { "title": "Right operands of shift operators should be integers", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3449", "sqKey": "S3449", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3450.html ================================================

Why is this an issue?

There is no point in providing a default value for a parameter if callers are required to provide a value for it anyway. Thus, [DefaultParameterValue] should always be used in conjunction with [Optional].

Noncompliant code example

public void MyMethod([DefaultParameterValue(5)] int j) //Noncompliant, useless
{
  Console.WriteLine(j);
}

Compliant solution

public void MyMethod(int j = 5)
{
  Console.WriteLine(j);
}

or

public void MyMethod([DefaultParameterValue(5)][Optional] int j)
{
  Console.WriteLine(j);
}
================================================ FILE: analyzers/rspec/cs/S3450.json ================================================ { "title": "Parameters with \"[DefaultParameterValue]\" attributes should also be marked \"[Optional]\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3450", "sqKey": "S3450", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3451.html ================================================

Why is this an issue?

DefaultValue does not make the compiler set the default value, as its name may suggest. What you probably wanted to use is DefaultParameterValue.

The DefaultValue attribute from the System.ComponentModel namespace, is sometimes used to declare a member’s default value. This can be used, for instance, by the reset feature of a visual designer or by a code generator.

public void DoStuff([DefaultValue(4)] int i)
{
    // i is not automatically assigned 4
}

The Optional attribute from the System.Runtime.InteropServices namespace is sometimes used to indicate that a parameter is optional, as an alternative to the language-specific construct.

public void DoStuff([Optional] int i)
{
    // i would be assigned default(int) = 0
}

The use of [DefaultValue] with [Optional] has no more effect than [Optional] alone. That’s because [DefaultValue] doesn’t actually do anything; it merely indicates the intent for the value.

class MyClass
{
    public void DoStuff([Optional][DefaultValue(4)] int i, int j = 5)  // Noncompliant
    {
        Console.WriteLine(i);
    }

    public static void Main()
    {
        new MyClass().DoStuff(); // prints 0, since [DefaultValue] doesn't actually set the default, and default(int) is used instead
    }
}

More than likely, [DefaultValue] was used in confusion instead of [DefaultParameterValue], the language-agnostic version of the default parameter initialization mechanism provided by C#.

class MyClass
{
    public void DoStuff([Optional][DefaultParameterValue(4)] int i, int j = 5)
    {
        Console.WriteLine(i);
    }

    public static void Main()
    {
        new MyClass().DoStuff(); // prints 4
    }
}

Notice that you can’t use both [DefaultParameterValue] and default parameter initialization on the same parameter.

void DoStuff([Optional][DefaultParameterValue(4)] int i = 5) // Error CS1745 Cannot specify default parameter value in conjunction with DefaultParameterAttribute or OptionalAttribute

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3451.json ================================================ { "title": "\"[DefaultValue]\" should not be used when \"[DefaultParameterValue]\" is meant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3451", "sqKey": "S3451", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3453.html ================================================

Why is this an issue?

When a class has only a private constructor, it can’t be instantiated except within the class itself. Such classes can be considered dead code and should be fixed

Exceptions

How to fix it

Code examples

Noncompliant code example

public class MyClass // Noncompliant: the class contains only private constructors
{
  private MyClass() { ... }
}

Compliant solution

public class MyClass // Compliant: the class contains at least one non-private constructor
{
  public MyClass() { ... }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3453.json ================================================ { "title": "Classes should not have only \"private\" constructors", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3453", "sqKey": "S3453", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3456.html ================================================

Why is this an issue?

The string type offers an indexer property that allows you to treat it as a char array. Therefore, if you just need to access a specific character or iterate over all of them, the ToCharArray call should be omitted. For these cases, not omitting makes the code harder to read and less efficient as ToCharArray copies the characters from the string object into a new Unicode character array.

The same principle applies to utf-8 literals types (ReadOnlySpan<byte>, Span<byte>) and the ToArray method.

How to fix it

Code examples

Noncompliant code example

string str = "some string";
foreach (var c in str.ToCharArray()) // Noncompliant
{
  // ...
}

ReadOnlySpan<byte> span = "some UTF-8 string literal"u8;
foreach (var c in span.ToArray()) // Noncompliant
{
  // ...
}

Compliant solution

string str = "some string";
foreach (var c in str)
{
  // ...
}

ReadOnlySpan<byte> span = "some UTF-8 string literal"u8;
foreach (var b in span) // Compliant
{
  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3456.json ================================================ { "title": "\"string.ToCharArray()\" and \"ReadOnlySpan\u003cT\u003e.ToArray()\" should not be called redundantly", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3456", "sqKey": "S3456", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3457.html ================================================

Why is this an issue?

A composite format string is a string that contains placeholders, represented by indices inside curly braces "{0}", "{1}", etc. These placeholders are replaced by values when the string is printed or logged.

Because composite format strings are interpreted at runtime, rather than validated by the compiler, they can contain errors that lead to unexpected behaviors or runtime errors.

This rule validates the correspondence between arguments and composite formats when calling the following methods:

Exceptions

var pattern = "{0} {1} {2}";
var res = string.Format(pattern, 1, 2); // Incorrect, but the analyzer doesn't raise any warnings here
var array = new int[] {};
var res = string.Format("{0} {1}", array); // Compliant; we don't know the size of the array

How to fix it

A composite format string contains placeholders, replaced by values when the string is printed or logged. Mismatch in the format specifiers and the arguments provided can lead to incorrect strings being created.

To avoid issues, a developer should ensure that the provided arguments match format specifiers.

Moreover, use string interpolation when possible.

Instead of

string str = string.Format("Hello {0} {1}!", firstName, lastName);

use

string str = $"Hello {firstName} {lastName}!";

With string interpolation:

Code examples

Noncompliant code example

s = string.Format("{0}", arg0, arg1); // Noncompliant, arg1 is declared but not used.
s = string.Format("{0} {2}", arg0, arg1, arg2); // Noncompliant, the format item with index 1 is missing, so arg1 will not be used.
s = string.Format("foo"); // Noncompliant; there is no need to use "string.Format" here.

Compliant solution

s = string.Format("{0}", arg0);
s = string.Format("{0} {1}", arg0, arg2);
s = "foo";
================================================ FILE: analyzers/rspec/cs/S3457.json ================================================ { "title": "Composite format strings should be used correctly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3457", "sqKey": "S3457", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3458.html ================================================

Why is this an issue?

Empty case clauses that fall through to the default are useless. Whether or not such a case is present, the default clause will be invoked. Such cases simply clutter the code, and should be removed.

Noncompliant code example

switch(ch)
{
  case 'a' :
    HandleA();
    break;
  case 'b' :
    HandleB();
    break;
  case 'c' :  // Noncompliant
  default:
    HandleTheRest();
    break;
}

Compliant solution

switch(ch)
{
  case 'a' :
    HandleA();
    break;
  case 'b' :
    HandleB();
    break;
  default:
    HandleTheRest();
    break;
}
================================================ FILE: analyzers/rspec/cs/S3458.json ================================================ { "title": "Empty \"case\" clauses that fall through to the \"default\" should be omitted", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3458", "sqKey": "S3458", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3459.html ================================================

Why is this an issue?

Fields and auto-properties that are never assigned to hold the default values for their types. They are either pointless code or, more likely, mistakes.

Noncompliant code example

class MyClass
{
  private int field; // Noncompliant, shouldn't it be initialized? This way the value is always default(int), 0.
  private int Property { get; set; }  // Noncompliant
  public void Print()
  {
    Console.WriteLine(field); //Will always print 0
    Console.WriteLine(Property); //Will always print 0
  }
}

Compliant solution

class MyClass
{
  private int field = 1;
  private int Property { get; set; } = 42;
  public void Print()
  {
    field++;
    Console.WriteLine(field);
    Console.WriteLine(Property);
  }
}

Exceptions

================================================ FILE: analyzers/rspec/cs/S3459.json ================================================ { "title": "Unassigned members should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3459", "sqKey": "S3459", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3464.html ================================================

Why is this an issue?

Recursion is a technique used to define a problem in terms of the problem itself, usually in terms of a simpler version of the problem itself.

For example, the implementation of the generator for the n-th value of the Fibonacci sequence comes naturally from its mathematical definition, when recursion is used:

int NthFibonacciNumber(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return NthFibonacciNumber(n - 1) + NthFibonacciNumber(n - 2);
    }
}

As opposed to:

int NthFibonacciNumber(int n)
{
    int previous = 0;
	int last = 1;
	for (var i = 0; i < n; i++)
	{
        (previous, last) = (last, last + previous);
	}
	return last;
}

The use of recursion is acceptable in methods, like the one above, where you can break out of it.

int NthFibonacciNumber(int n)
{
    if (n <= 1)
    {
        return 1; // Base case: stop the recursion
    }
    // ...
}

It is also acceptable and makes sense in some type definitions:

class Box : IComparable<Box>
{
    public int CompareTo(Box? other)
    {
        // Compare the two Box instances...
    }
}

With types, some invalid recursive definitions are caught by the compiler:

class C2<T> : C2<T>     // Error CS0146: Circular base type dependency
{
}

class C2<T> : C2<C2<T>> // Error CS0146: Circular base type dependency
{
}

In more complex scenarios, however, the code will compile but execution will result in a TypeLoadException if you try to instantiate the class.

class C1<T>
{
}

class C2<T> : C1<C2<C2<T>>> // Noncompliant
{
}

var c2 = new C2<int>();     // This would result into a TypeLoadException

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3464.json ================================================ { "title": "Type inheritance should not be recursive", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3464", "sqKey": "S3464", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3466.html ================================================

Why is this an issue?

When optional parameter values are not passed to base method calls, the value passed in by the caller is ignored. This can cause the function to behave differently than expected, leading to errors and making the code difficult to debug.

How to fix it

Code examples

Noncompliant code example

public class BaseClass
{
    public virtual void MyMethod(int i = 1)
    {
        Console.WriteLine(i);
    }
}

public class DerivedClass : BaseClass
{
    public override void MyMethod(int i = 1)
    {
        // ...
        base.MyMethod(); // Noncompliant: caller's value is ignored
    }

    static int Main(string[] args)
    {
        DerivedClass dc = new DerivedClass();
        dc.MyMethod(12);  // prints 1
    }
}

Compliant solution

public class BaseClass
{
    public virtual void MyMethod(int i = 1)
    {
        Console.WriteLine(i);
    }
}

public class DerivedClass : BaseClass
{
    public override void MyMethod(int i = 1)
    {
        // ...
        base.MyMethod(i);
    }

    static int Main(string[] args)
    {
        DerivedClass dc = new DerivedClass();
        dc.MyMethod(12);  // prints 12
    }
}

Resources

Documentation

Microsoft Learn - Optional Arguments

================================================ FILE: analyzers/rspec/cs/S3466.json ================================================ { "title": "Optional parameters should be passed to \"base\" calls", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3466", "sqKey": "S3466", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3532.html ================================================

Why is this an issue?

The default clause should take appropriate action. Having an empty default is a waste of keystrokes.

Noncompliant code example

enum Fruit
{
  Apple,
  Orange,
  Banana
}

void PrintName(Fruit fruit)
{
  switch(fruit)
  {
    case Fruit.Apple:
      Console.WriteLine("apple");
      break;
    default:  //Noncompliant
      break;
  }
}

Compliant solution

enum Fruit
{
  Apple,
  Orange,
  Banana
}

void PrintName(Fruit fruit)
{
  switch(fruit)
  {
    case Fruit.Apple:
      Console.WriteLine("apple");
      break;
    default:
      throw new NotSupportedException();
  }
}

or

void PrintName(Fruit fruit)
{
  switch(fruit)
  {
    case Fruit.Apple:
      Console.WriteLine("apple");
      break;
  }
}

Exceptions

default clauses containing only a comment are ignored with the assumption that they are empty on purpose and the comment documents why.

================================================ FILE: analyzers/rspec/cs/S3532.json ================================================ { "title": "Empty \"default\" clauses should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": " 1min" }, "tags": [ "unused", "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3532", "sqKey": "S3532", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3597.html ================================================

Why is this an issue?

The ServiceContract attribute specifies that a class or interface defines the communication contract of a Windows Communication Foundation (WCF) service. The service operations of this class or interface are defined by OperationContract attributes added to methods. It doesn’t make sense to define a contract without any service operations; thus, in a ServiceContract class or interface at least one method should be annotated with OperationContract. Similarly, WCF only serves OperationContract methods that are defined inside ServiceContract classes or interfaces; thus, this rule also checks that ServiceContract is added to the containing type of OperationContract methods.

Noncompliant code example

[ServiceContract]
interface IMyService // Noncompliant
{
  int MyServiceMethod();
}

Compliant solution

[ServiceContract]
interface IMyService
{
  [OperationContract]
  int MyServiceMethod();
}
================================================ FILE: analyzers/rspec/cs/S3597.json ================================================ { "title": "\"ServiceContract\" and \"OperationContract\" attributes should be used together", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3597", "sqKey": "S3597", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3598.html ================================================

Why is this an issue?

When declaring a Windows Communication Foundation (WCF) OperationContract method as one-way, that service method won’t return any result, not even an underlying empty confirmation message. These are fire-and-forget methods that are useful in event-like communication. Therefore, specifying a return type has no effect and can confuse readers.

Exceptions

The rule doesn’t report if OperationContractAttribute.AsyncPattern is set to true.

How to fix it

Code examples

Noncompliant code example

[ServiceContract]
interface IMyService
{
  [OperationContract(IsOneWay = true)]
  int SomethingHappened(int parameter); // Noncompliant
}

Compliant solution

[ServiceContract]
interface IMyService
{
  [OperationContract(IsOneWay = true)]
  void SomethingHappened(int parameter);
}

Resources

Documentation

Microsoft Learn - OperationContractAttribute

================================================ FILE: analyzers/rspec/cs/S3598.json ================================================ { "title": "One-way \"OperationContract\" methods should have \"void\" return type", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3598", "sqKey": "S3598", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3600.html ================================================

Why is this an issue?

Adding params to a method override has no effect. The compiler accepts it, but the callers won’t be able to benefit from the added modifier.

Noncompliant code example

class Base
{
  public virtual void Method(int[] numbers)
  {
    ...
  }
}
class Derived : Base
{
  public override void Method(params int[] numbers) // Noncompliant, method can't be called with params syntax.
  {
    ...
  }
}

Compliant solution

class Base
{
  public virtual void Method(int[] numbers)
  {
    ...
  }
}
class Derived : Base
{
  public override void Method(int[] numbers)
  {
    ...
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3600.json ================================================ { "title": "\"params\" should not be introduced on overrides", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "confusing" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3600", "sqKey": "S3600", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S3603.html ================================================

Why is this an issue?

Marking a method with the Pure attribute indicates that the method doesn’t make any visible state changes. Therefore, a Pure method should return a result. Otherwise, it indicates a no-operation call.

Using Pure on a void method is either by mistake or the method is not doing a meaningful task.

How to fix it

Code examples

Noncompliant code example

class Person
{
  private int age;

  [Pure] // Noncompliant: The method makes a state change
  void ConfigureAge(int age) =>
    this.age = age;
}

Compliant solution

class Person
{
  private int age;

  void ConfigureAge(int age) =>
    this.age = age;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3603.json ================================================ { "title": "Methods with \"Pure\" attribute should return a value ", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3603", "sqKey": "S3603", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3604.html ================================================

Why is this an issue?

Fields, properties and events can be initialized either inline or in the constructor. Initializing them inline and in the constructor at the same time is redundant; the inline initialization will be overridden.

Noncompliant code example

class Person
{
  int age = 42; // Noncompliant
  public Person(int age)
  {
    this.age = age;
  }
}

Compliant solution

class Person
{
  int age;
  public Person(int age)
  {
    this.age = age;
  }
}

Exceptions

This rule doesn’t report an issue if not all constructors initialize the field. If the field is initialized inline to its default value, then {rule:csharpsquid:S3052} already reports an issue on the initialization.

================================================ FILE: analyzers/rspec/cs/S3604.json ================================================ { "title": "Member initializer values should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3604", "sqKey": "S3604", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3610.html ================================================

Why is this an issue?

Calling GetType() on a nullable value type object returns the underlying value type. Therefore, comparing the returned Type object to typeof(Nullable<SomeType>) will either throw an NullReferenceException or the result will always be true or false and can be known at compile time.

How to fix it

Code examples

Noncompliant code example

void DoChecks<T>(Nullable<T> value) where T : struct
{
    bool areEqual = value.GetType() == typeof(Nullable<int>); // Noncompliant: always false
    bool areNotEqual = value.GetType() != typeof(Nullable<int>); // Noncompliant: always true

    Nullable<int> nullable = null;
    bool nullComparison = nullable.GetType() != typeof(Nullable<int>); // Noncompliant: throws NullReferenceException
}

Compliant solution

void DoChecks<T>(Nullable<T> value) where T : struct
{
    bool areEqual = value.GetType() == typeof(int); // Compliant: can be true or false
    bool areNotEqual = value.GetType() != typeof(int); // Compliant: can be true or false

    Nullable<int> nullable = null;
    bool nullComparison = nullable is not null && nullable.GetType() == typeof(int); // Compliant: does not throw NullReferenceException
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3610.json ================================================ { "title": "Nullable type comparison should not be redundant", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "redundant" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3610", "sqKey": "S3610", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3626.html ================================================

Why is this an issue?

Jump statements, such as return, yield break, goto, and continue are used to change the normal flow of execution in a program. However, redundant jump statements can make code difficult to read and maintain.

Noncompliant code example

void Foo()
{
  goto A; // Noncompliant
  A:
  while (condition1)
  {
    if (condition2)
    {
      continue; // Noncompliant
    }
    else
    {
      DoTheThing();
    }
  }
  return; // Noncompliant; this is a void method
}

Compliant solution

void Foo()
{
  while (condition1)
  {
    if (!condition2)
    {
      DoTheThing();
    }
  }
}

Exceptions

================================================ FILE: analyzers/rspec/cs/S3626.json ================================================ { "title": "Jump statements should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "redundant", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3626", "sqKey": "S3626", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3655.html ================================================

Why is this an issue?

Nullable value types can hold either a value or null.

The value held in the nullable type can be accessed with the Value property or by casting it to the underlying type. Still, both operations throw an InvalidOperationException when the value is null. A nullable type should always be tested before accessing the value to avoid raising exceptions.

How to fix it

Code examples

Noncompliant code example

void Sample(bool condition)
{
    int? nullableValue = condition ? 42 : null;
    Console.WriteLine(nullableValue.Value); // Noncompliant: InvalidOperationException is raised

    int? nullableCast = condition ? 42 : null;
    Console.WriteLine((int)nullableCast);   // Noncompliant: InvalidOperationException is raised
}

Compliant solution

void Sample(bool condition)
{
    int? nullableValue = condition ? 42 : null;
    if (nullableValue.HasValue)
    {
      Console.WriteLine(nullableValue.Value);
    }

    int? nullableCast = condition ? 42 : null;
    if (nullableCast is not null)
    {
      Console.WriteLine((int)nullableCast);
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3655.json ================================================ { "title": "Empty nullable value should not be accessed", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3655", "sqKey": "S3655", "scope": "All", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3717.html ================================================

Why is this an issue?

NotImplementedException is often used to mark methods which must be implemented for the overall functionality to be complete, but which the developer wants to implement later. That’s as opposed to the NotSupportedException which is thrown by methods which are required by base classes or interfaces, but which are not appropriate to the current class.

This rule raises an exception when NotImplementedException is thrown.

Noncompliant code example

void doTheThing()
{
    throw new NotImplementedException();
}

Exceptions

Exceptions derived from NotImplementedException are ignored.

================================================ FILE: analyzers/rspec/cs/S3717.json ================================================ { "title": "Track use of \"NotImplementedException\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3717", "sqKey": "S3717", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3776.html ================================================

This rule raises an issue when the code cognitive complexity of a function is above a certain threshold.

Why is this an issue?

Cognitive Complexity is a measure of how hard it is to understand the control flow of a unit of code. Code with high cognitive complexity is hard to read, understand, test, and modify.

As a rule of thumb, high cognitive complexity is a sign that the code should be refactored into smaller, easier-to-manage pieces.

Which syntax in code does impact cognitive complexity score?

Here are the core concepts:

The method of computation is fully detailed in the pdf linked in the resources.

What is the potential impact?

Developers spend more time reading and understanding code than writing it. High cognitive complexity slows down changes and increases the cost of maintenance.

How to fix it

Reducing cognitive complexity can be challenging.
Here are a few suggestions:

Code examples

Extraction of a complex condition in a new function.

Noncompliant code example

The code is using a complex condition and has a cognitive cost of 3.

decimal CalculateFinalPrice(User user, Cart cart)
{
    decimal total = CalculateTotal(cart);
    if (user.HasMembership()               // +1 (if)
        && user.OrdersCount > 10           // +1 (more than one condition)
        && user.AccountActive
        && !user.HasDiscount
        || user.OrdersCount == 1)          // +1 (change of operator in condition)
    {

        total = ApplyDiscount(user, total);
    }
    return total;
}

Compliant solution

Even if the cognitive complexity of the whole program did not change, it is easier for a reader to understand the code of the calculateFinalPrice function, which now only has a cognitive cost of 1.

decimal CalculateFinalPrice(User user, Cart cart)
{
    decimal total = CalculateTotal(cart);
    if (IsEligibleForDiscount(user))       // +1 (if)
    {
        total = applyDiscount(user, total);
    }
    return total;
}

bool IsEligibleForDiscount(User user)
{
    return user.HasMembership()
            && user.OrdersCount > 10       // +1 (more than one condition)
            && user.AccountActive
            && !user.HasDiscount
            || user.OrdersCount == 1;      // +1 (change of operator in condition)
}

Break down large functions.

Noncompliant code example

For example, consider a function that calculates the total price of a shopping cart, including sales tax and shipping.
Note: The code is simplified here, to illustrate the purpose. Please imagine there is more happening in the foreach loops.

decimal CalculateTotal(Cart cart)
{
    decimal total = 0;
    foreach (Item item in cart.Items) // +1 (foreach)
    {
        total += item.Price;
    }

    // calculateSalesTax
    foreach (Item item in cart.Items) // +1 (foreach)
    {
        total += 0.2m * item.Price;
    }

    //calculateShipping
    total += 5m * cart.Items.Count;

    return total;
}

This function could be refactored into smaller functions: The complexity is spread over multiple functions and the complex CalculateTotal has now a complexity score of zero.

Compliant solution

decimal CalculateTotal(Cart cart)
{
    decimal total = 0;
    total = CalculateSubtotal(cart, total);
    total += CalculateSalesTax(cart, total);
    total += CalculateShipping(cart, total);
    return total;
}

decimal CalculateSubtotal(Cart cart, decimal total)
{
    foreach (Item item in cart.Items) // +1 (foreach)
    {
        total += item.Price;
    }

    return total;
}

decimal CalculateSalesTax(Cart cart, decimal total)
{
    foreach (Item item in cart.Items)  // +1 (foreach)
    {
        total += 0.2m * item.Price;
    }

    return total;
}

decimal CalculateShipping(Cart cart, decimal total)
{
    total += 5m * cart.Items.Count;
    return total;
}

Avoid deep nesting by returning early.

Noncompliant code example

The below code has a cognitive complexity of 6.

decimal CalculateDiscount(decimal price, User user)
{
    if (IsEligibleForDiscount(user))    // +1 ( if )
    {
        if (user.HasMembership())       // +2 ( nested if )
        {
            return price * 0.9m;
        }
        else if (user.OrdersCount == 1) // +1 ( else )
        {
            return price * 0.95m;
        }
        else                            // +1 ( else )
        {
            return price;
        }
    }
    else                                // +1 ( else )
    {
        return price;
    }
}

Compliant solution

Checking for the edge case first flattens the if statements and reduces the cognitive complexity to 3.

decimal CalculateDiscount(decimal price, User user)
{
    if (!IsEligibleForDiscount(user)) // +1 ( if )
    {
        return price;
    }

    if (user.HasMembership())         // +1 (  if )
    {
        return price * 0.9m;
    }

    if (user.OrdersCount == 1)        // +1 ( else )
    {
        return price * 0.95m;
    }

    return price;
}

Use the null-conditional operator to access data.

In the below code, the cognitive complexity is increased due to the multiple checks required to access the manufacturer’s name. This can be simplified using the optional chaining operator.

Noncompliant code example

string GetManufacturerName(Product product)
{
    string manufacturerName = null;
    if (product != null && product.Details != null &&
        product.Details.Manufacturer != null) // +1 (if) +1 (multiple condition)
    {
        manufacturerName = product.Details.Manufacturer.Name;
    }

    if (manufacturerName != null) // +1 (if)
    {
        return manufacturerName;
    }

    return "Unknown";
}

Compliant solution

The optional chaining operator will return null if any reference in the chain is null, avoiding multiple checks. The ?? operator allows to provide the default value to use.

string GetManufacturerName(Product product)
{
    return product?.Details?.Manufacturer?.Name ?? "Unknown";
}

Pitfalls

As this code is complex, ensure that you have unit tests that cover the code before refactoring.

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3776.json ================================================ { "title": "Cognitive Complexity of methods should not be too high", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Linear with offset", "linearDesc": "per complexity point over the threshold", "linearOffset": "5min", "linearFactor": "1min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3776", "sqKey": "S3776", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3869.html ================================================

Why is this an issue?

The SafeHandle.DangerousGetHandle method poses significant risks and should be used carefully. This method carries the inherent danger of potentially returning an invalid handle, which can result in resource leaks and security vulnerabilities. Although it is technically possible to utilize this method without encountering issues, doing so correctly requires a high level of expertise. Therefore, it is recommended to avoid using this method altogether.

What is the potential impact?

The SafeHandle.DangerousGetHandle method is potentially prone to leaks and vulnerabilities due to its nature and usage. Here are a few reasons why:

static void Main(string[] args)
{
    System.Reflection.FieldInfo fieldInfo = ...;
    SafeHandle handle = (SafeHandle)fieldInfo.GetValue(rKey);
    IntPtr dangerousHandle = handle.DangerousGetHandle(); // Noncompliant
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3869.json ================================================ { "title": "\"SafeHandle.DangerousGetHandle\" should not be called", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "leak", "unpredictable" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3869", "sqKey": "S3869", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3871.html ================================================

Why is this an issue?

The point of having custom exception types is to convey more information than is available in standard types. But custom exception types must be public for that to work.

If a method throws a non-public exception, the best you can do on the caller’s side is to catch the closest public base of the class. However, you lose all the information that the new exception type carries.

This rule will raise an issue if you directly inherit one of the following exception types in a non-public class:

How to fix it

Code examples

Noncompliant code example

internal class MyException : Exception   // Noncompliant
{
  // ...
}

Compliant solution

public class MyException : Exception
{
  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3871.json ================================================ { "title": "Exception types should be \"public\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "error-handling", "api-design" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3871", "sqKey": "S3871", "scope": "All", "securityStandards": { "OWASP": [ "A10" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3872.html ================================================

Why is this an issue?

The name of a method should communicate what it does, and the names of its parameters should indicate how they’re used. If a method and its parameter have the same name it is an indication that one of these rules of thumb has been broken, if not both. Even if by some trick of language that’s not the case, it is still likely to confuse callers and maintainers.

Noncompliant code example

public void Login(string login)  // Noncompliant
{
  //...
}

Compliant solution

public void Login(string userName)
{
  //...
}
================================================ FILE: analyzers/rspec/cs/S3872.json ================================================ { "title": "Parameter names should not duplicate the names of their methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3872", "sqKey": "S3872", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3874.html ================================================

Why is this an issue?

Passing a parameter by reference, which is what happens when you use the out or ref parameter modifiers, means that the method will receive a pointer to the argument, rather than the argument itself. If the argument was a value type, the method will be able to change the argument’s values. If it was a reference type, then the method receives a pointer to a pointer, which is usually not what was intended. Even when it is what was intended, this is the sort of thing that’s difficult to get right, and should be used with caution.

This rule raises an issue when out or ref is used on a non-Optional parameter in a public method. Optional parameters are covered by {rule:csharpsquid:S3447}.

Noncompliant code example

public void GetReply(
         ref MyClass input, // Noncompliant
         out string reply)  // Noncompliant
{ ... }

Compliant solution

public string GetReply(MyClass input)
{ ... }

public bool TryGetReply(MyClass input, out string reply)
{ ... }

public ReplyData GetReply(MyClass input)
{ ... }

internal void GetReply(ref MyClass input, out string reply)
{ ... }

Exceptions

This rule will not raise issues for:

================================================ FILE: analyzers/rspec/cs/S3874.json ================================================ { "title": "\"out\" and \"ref\" parameters should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3874", "sqKey": "S3874", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3875.html ================================================

Why is this an issue?

The use of == to compare two objects is expected to do a reference comparison. That is, it is expected to return true if and only if they are the same object instance. Overloading the operator to do anything else will inevitably lead to the introduction of bugs by callers.

public static bool operator ==(MyType x, MyType y) // Noncompliant: confusing for the caller
{
    // custom implementation
}

On the other hand, overloading it to do exactly that is pointless; that’s what == does by default.

public static bool operator ==(MyType x, MyType y) // Noncompliant: redundant
{
    if (x == null)
    {
        return y == null;
    }

    return object.ReferenceEquals(x,y);
}

Exceptions

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3875.json ================================================ { "title": "\"operator\u003d\u003d\" should not be overloaded on reference types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3875", "sqKey": "S3875", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3876.html ================================================

Why is this an issue?

Strings and integral types are typically used as indexers. When some other type is required, it typically indicates design problems, and potentially a situation where a method should be used instead.

Noncompliant code example

public int this[MyCustomClass index]  // Noncompliant
{
    // get and set accessors
}
================================================ FILE: analyzers/rspec/cs/S3876.json ================================================ { "title": "Strings or integral types should be used for indexers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3876", "sqKey": "S3876", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3877.html ================================================

Why is this an issue?

The rule is reporting when an exception is thrown from certain methods and constructors. These methods are expected to behave in a specific way and throwing an exception from them can lead to unexpected behavior and break the calling code.

public override string ToString()
{
  if (string.IsNullOrEmpty(Name))
  {
    throw new ArgumentException(nameof(Name));  // Noncompliant
  }
  //...
}

An issue is raised when an exception is thrown from any of the following:

Exceptions

Certain exceptions will be ignored in specific contexts, thus not raising the issue:

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3877.json ================================================ { "title": "Exceptions should not be thrown from unexpected methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3877", "sqKey": "S3877", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3878.html ================================================

Why is this an issue?

Creating an array or using a collection expression solely for the purpose of passing it to a params parameter is unnecessary. Simply pass the elements directly, and they will be automatically consolidated into the appropriate collection type.

How to fix it

Code examples

Noncompliant code example

public void Base()
{
    Method(new string[] { "s1", "s2" }); // Noncompliant: resolves to string[] overload
    Method(new string[] { });            // Noncompliant: resolves to string[] overload
    Method(["s3", "s4"]);                // Noncompliant: resolves to ReadOnlySpan overload
    Method(new string[12]);              // Compliant: resolves to string[] overload
}

public void Method(params string[] args)
{
    // ...
}

public void Method(params ReadOnlySpan<string> args) // C# 13 params collections
{
    // C# 13 params collection
}

Compliant solution

public void Base()
{
    Method("s1", "s2");     // resolves to ReadOnlySpan overload
    Method();               // resolves to ReadOnlySpan overload
    Method("s3", "s4");     // resolves to ReadOnlySpan overload
    Method(new string[12]); // resolves to string[]  overload
}

public void Method(params string[] args)
{
    // ..
}

public void Method(params ReadOnlySpan<string> args) // C# 13 params collections
{
    // ..
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3878.json ================================================ { "title": "Arrays should not be created for params parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3878", "sqKey": "S3878", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3880.html ================================================

Why is this an issue?

Finalizers come with a performance cost due to the overhead of tracking the life cycle of objects. An empty one is consequently costly with no benefit or justification.

Noncompliant code example

public class Foo
{
    ~Foo() // Noncompliant
    {
    }
}
================================================ FILE: analyzers/rspec/cs/S3880.json ================================================ { "title": "Finalizers should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3880", "sqKey": "S3880", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3881.html ================================================

Why is this an issue?

The IDisposable interface is a mechanism to release unmanaged resources, if not implemented correctly this could result in resource leaks or more severe bugs.

This rule raises an issue when the recommended dispose pattern, as defined by Microsoft, is not adhered to. See the Compliant Solution section for examples.

Satisfying the rule’s conditions will enable potential derived classes to correctly dispose the members of your class:

Noncompliant code example

public class Foo1 : IDisposable // Noncompliant - provide protected overridable implementation of Dispose(bool) on Foo or mark the type as sealed.
{
    public void Dispose() // Noncompliant - should contain only a call to Dispose(true) and then GC.SuppressFinalize(this)
    {
        // Cleanup
    }
}

public class Foo2 : IDisposable
{
    void IDisposable.Dispose() // Noncompliant - Dispose() should be public
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public virtual void Dispose() // Noncompliant - Dispose() should be sealed
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class Foo3 : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Cleanup
    }

    ~Foo3() // Noncompliant - Modify Foo.~Foo() so that it calls Dispose(false) and then returns.
    {
        // Cleanup
    }
}

Compliant solution

// Sealed class
public sealed class Foo1 : IDisposable
{
    public void Dispose()
    {
        // Cleanup
    }
}

// Simple implementation
public class Foo2 : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Cleanup
    }
}

// Implementation with a finalizer
public class Foo3 : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        // Cleanup
    }

    ~Foo3()
    {
        Dispose(false);
    }
}

// Base disposable class
public class Foo4 : DisposableBase
{
    protected override void Dispose(bool disposing)
    {
        // Cleanup
        // Do not forget to call base
        base.Dispose(disposing);
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3881.json ================================================ { "title": "\"IDisposable\" should be implemented correctly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3881", "sqKey": "S3881", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3884.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

CoSetProxyBlanket and CoInitializeSecurity both work to set the permissions context in which the process invoked immediately after is executed. Calling them from within that process is useless because it’s too late at that point; the permissions context has already been set.

Specifically, these methods are meant to be called from non-managed code such as a C++ wrapper that then invokes the managed, i.e. C# or VB.NET, code.

Noncompliant code example

[DllImport("ole32.dll")]
static extern int CoSetProxyBlanket([MarshalAs(UnmanagedType.IUnknown)]object pProxy, uint dwAuthnSvc, uint dwAuthzSvc,
	[MarshalAs(UnmanagedType.LPWStr)] string pServerPrincName, uint dwAuthnLevel, uint dwImpLevel, IntPtr pAuthInfo,
	uint dwCapabilities);

public enum RpcAuthnLevel
{
	Default = 0,
	None = 1,
	Connect = 2,
	Call = 3,
	Pkt = 4,
	PktIntegrity = 5,
	PktPrivacy = 6
}

public enum RpcImpLevel
{
	Default = 0,
	Anonymous = 1,
	Identify = 2,
	Impersonate = 3,
	Delegate = 4
}

public enum EoAuthnCap
{
	None = 0x00,
	MutualAuth = 0x01,
	StaticCloaking = 0x20,
	DynamicCloaking = 0x40,
	AnyAuthority = 0x80,
	MakeFullSIC = 0x100,
	Default = 0x800,
	SecureRefs = 0x02,
	AccessControl = 0x04,
	AppID = 0x08,
	Dynamic = 0x10,
	RequireFullSIC = 0x200,
	AutoImpersonate = 0x400,
	NoCustomMarshal = 0x2000,
	DisableAAA = 0x1000
}

[DllImport("ole32.dll")]
public static extern int CoInitializeSecurity(IntPtr pVoid, int cAuthSvc, IntPtr asAuthSvc, IntPtr pReserved1,
	RpcAuthnLevel level, RpcImpLevel impers, IntPtr pAuthList, EoAuthnCap dwCapabilities, IntPtr pReserved3);

static void Main(string[] args)
{
	var hres1 = CoSetProxyBlanket(null, 0, 0, null, 0, 0, IntPtr.Zero, 0); // Noncompliant

	var hres2 = CoInitializeSecurity(IntPtr.Zero, -1, IntPtr.Zero, IntPtr.Zero, RpcAuthnLevel.None,
		RpcImpLevel.Impersonate, IntPtr.Zero, EoAuthnCap.None, IntPtr.Zero); // Noncompliant
}

Resources

================================================ FILE: analyzers/rspec/cs/S3884.json ================================================ { "title": "\"CoSetProxyBlanket\" and \"CoInitializeSecurity\" should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3884", "sqKey": "S3884", "scope": "All", "securityStandards": { "CWE": [ 648 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3885.html ================================================

Why is this an issue?

The parameter to Assembly.Load includes the full specification of the dll to be loaded. Use another method, and you might end up with a dll other than the one you expected.

This rule raises an issue when Assembly.LoadFrom, Assembly.LoadFile, or Assembly.LoadWithPartialName is called.

Exceptions

The rule does not raise an issue when the methods are used within an AssemblyResolve event handler. In this context, using Assembly.Load can cause a StackOverflowException due to recursive event firing, making Assembly.LoadFrom or Assembly.LoadFile the appropriate choices.

static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}

static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
    return Assembly.LoadFrom("MyAssembly.dll"); // Compliant: within AssemblyResolve handler
}

How to fix it

Code examples

Noncompliant code example

static void Main(string[] args)
{
    Assembly.LoadFrom("MyAssembly.dll"); // Noncompliant
}
static void Main(string[] args)
{
    Assembly.LoadFile(@"C:\MyPath\MyAssembly.dll"); // Noncompliant
}
static void Main(string[] args)
{
    Assembly.LoadWithPartialName("MyAssembly"); // Noncompliant
}

Compliant solution

static void Main(string[] args)
{
    Assembly.Load("MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); // Compliant
}
static void Main(string[] args)
{
    Assembly.Load("MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); // Compliant
}
static void Main(string[] args)
{
    Assembly.Load("MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3885.json ================================================ { "title": "\"Assembly.Load\" should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unpredictable" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3885", "sqKey": "S3885", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3887.html ================================================

Why is this an issue?

Using the readonly keyword on a field means it can’t be changed after initialization. However, that’s only partly true when applied to collections or arrays. The readonly keyword enforces that another instance can’t be assigned to the field, but it cannot keep the contents from being updated. In practice, the field value can be changed, and the use of readonly on such a field is misleading, and you’re likely not getting the behavior you expect.

This rule raises an issue when a non-private, readonly field is an array or collection.

How to fix it

To fix this, you should either use an immutable or frozen collection or remove the readonly modifier to clarify the behavior.

Code examples

Noncompliant code example

public class MyClass
{
  public readonly string[] strings1;  // Noncompliant
  public readonly string[] strings2;  // Noncompliant
  public readonly string[] strings3;  // Noncompliant
  // ...
}

Compliant solution

public class MyClass
{
  public string[] strings1;                         // Compliant: remove readonly modifier
  public readonly ImmutableArray<string> strings;   // Compliant: use an Immutable collection
  private readonly string[] strings;                // Compliant: reduced accessibility to private

  // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3887.json ================================================ { "title": "Mutable, non-private fields should not be \"readonly\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3887", "sqKey": "S3887", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3889.html ================================================

Why is this an issue?

Thread.Suspend and Thread.Resume can give unpredictable results, and both methods have been deprecated. Indeed, if Thread.Suspend is not used very carefully, a thread can be suspended while holding a lock, thus leading to a deadlock.

There are other synchronization mechanisms that are safer and should be used instead, such as:

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3889.json ================================================ { "title": "\"Thread.Resume\" and \"Thread.Suspend\" should not be used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "multi-threading", "unpredictable" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3889", "sqKey": "S3889", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3897.html ================================================

Why is this an issue?

The IEquatable<T> interface has only one method in it: Equals(<T>). If you’ve already written Equals(T), there’s no reason not to explicitly implement IEquatable<T>. Doing so expands the utility of your class by allowing it to be used where an IEquatable is called for.

Note: Classes that implement IEquatable<T> should also be sealed.

Noncompliant code example

class MyClass  // Noncompliant
{
  public bool Equals(MyClass other)
  {
    //...
  }
}

Compliant solution

sealed class MyClass : IEquatable<MyClass>
{
  public override bool Equals(object other)
  {
    return Equals(other as MyClass);
  }

  public bool Equals(MyClass other)
  {
    //...
  }
}
================================================ FILE: analyzers/rspec/cs/S3897.json ================================================ { "title": "Classes that provide \"Equals(\u003cT\u003e)\" should implement \"IEquatable\u003cT\u003e\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "api-design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3897", "sqKey": "S3897", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3898.html ================================================

Why is this an issue?

If you’re using a struct, 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

struct MyStruct  // Noncompliant
{
    public int Value { get; set; }
}

Compliant solution

struct MyStruct : IEquatable<MyStruct>
{
    public int Value { get; set; }

    public bool Equals(MyStruct other)
    {
        // ...
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S3898.json ================================================ { "title": "Value types should implement \"IEquatable\u003cT\u003e\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3898", "sqKey": "S3898", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3900.html ================================================

Why is this an issue?

Methods declared as public, protected, or protected internal can be accessed from other assemblies, which means you should validate parameters to be within the expected constraints. In general, checking against null is recommended in defensive programming.

This rule raises an issue when a parameter of a publicly accessible method is not validated against null before being dereferenced.

Noncompliant code example

public class MyClass
{
    private MyOtherClass other;

    public void Foo(MyOtherClass other)
    {
        this.other = other.Clone(); // Noncompliant
    }

    protected void Bar(MyOtherClass other)
    {
        this.other = other.Clone(); // Noncompliant
    }
}

Compliant solution

public class MyClass
{
    private MyOtherClass other;

    public void Foo(MyOtherClass other)
    {
        if (other != null)
        {
            this.other = other.Clone();
        }
    }

    protected void Bar(MyOtherClass other)
    {
        if (other != null)
        {
            this.other = other.Clone();
        }
    }

    public void Baz(MyOtherClass other)
    {
        ArgumentNullException.ThrowIfNull(other);

        this.other = other.Clone();
    }

    public void Qux(MyOtherClass other)
    {
        this.other = other; // Compliant: "other" is not being dereferenced
    }

    private void Xyzzy(MyOtherClass other)
    {
        this.other = other.Clone(); // Compliant: method is not publicly accessible
    }
}

Exceptions

using System;

[AttributeUsage(AttributeTargets.Parameter, Inherited=false)]
public sealed class ValidatedNotNullAttribute : Attribute { }

public static class Guard
{
    public static void NotNull<T>([ValidatedNotNullAttribute] T value, [CallerArgumentExpression("value")] string name = "") where T : class
    {
        if (value == null)
            throw new ArgumentNullException(name);
    }
}

public static class Utils
{
    public static string ToUpper(string value)
    {
        Guard.NotNull(value);

        return value.ToUpper(); // Compliant
    }
}
================================================ FILE: analyzers/rspec/cs/S3900.json ================================================ { "title": "Arguments of public methods should be validated against null", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3900", "sqKey": "S3900", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3902.html ================================================

Why is this an issue?

Using Type.Assembly to get the current assembly is nearly free in terms of performance; it’s a simple property access. On the other hand, Assembly.GetExecutingAssembly() can take up to 30 times as long because it walks up the call stack to find the assembly.

Note that Assembly.GetExecutingAssembly() is different than Type.Assembly because it dynamically returns the assembly that contains the startup object of the currently executed application. For example, if executed from an application it will return the application assembly, but if executed from a unit test project it could return the unit test assembly. Type.Assembly always returns the assembly that contains the specified type.

Noncompliant code example

public class Example
{
   public static void Main()
   {
      Assembly assem = Assembly.GetExecutingAssembly(); // Noncompliant
      Console.WriteLine("Assembly name: {0}", assem.FullName);
   }
}

Compliant solution

public class Example
{
   public static void Main()
   {
      Assembly assem = typeof(Example).Assembly; // Here we use the type of the current class
      Console.WriteLine("Assembly name: {0}", assem.FullName);
   }
}
================================================ FILE: analyzers/rspec/cs/S3902.json ================================================ { "title": "\"Assembly.GetExecutingAssembly\" should not be called", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3902", "sqKey": "S3902", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3903.html ================================================

Why is this an issue?

Types are declared in namespaces in order to prevent name collisions and as a way to organize them into the object hierarchy. Types that are defined outside any named namespace are in a global namespace that cannot be referenced in code.

Noncompliant code example

public class Foo // Noncompliant
{
}

public struct Bar // Noncompliant
{
}

Compliant solution

namespace SomeSpace
{
    public class Foo
    {
    }

    public struct Bar
    {
    }
}

or alternatively in C#10 and above

namespace SomeSpace;
public class Foo
{
}

public struct Bar
{
}
================================================ FILE: analyzers/rspec/cs/S3903.json ================================================ { "title": "Types should be defined in named namespaces", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3903", "sqKey": "S3903", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3904.html ================================================

Why is this an issue?

The AssemblyVersion attribute is used to specify the version number of an assembly. An assembly is a compiled unit of code, which can be marked with a version number by applying the attribute to an assembly’s source code file.

The AssemblyVersion attribute is useful for many reasons:

If no AssemblyVersion is provided, the same default version will be used for every build. Since the version number is used by .NET Framework to uniquely identify an assembly, this can lead to broken dependencies.

How to fix it

Code examples

Noncompliant code example

using System.Reflection;

[assembly: AssemblyTitle("MyAssembly")] // Noncompliant
namespace MyLibrary
{
    // ...
}

Compliant solution

using System.Reflection;

[assembly: AssemblyTitle("MyAssembly")]
[assembly: AssemblyVersion("42.1.125.0")]
namespace MyLibrary
{
    // ...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3904.json ================================================ { "title": "Assemblies should have version information", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3904", "sqKey": "S3904", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3906.html ================================================

Why is this an issue?

Delegate event handlers (i.e. delegates used as type of an event) should have a very specific signature:

This rule raises an issue whenever a delegate declaration doesn’t match that signature.

Noncompliant code example

public delegate void AlarmEventHandler(object s);

public class Foo
{
    public event AlarmEventHandler AlarmEvent; // Noncompliant
}

Compliant solution

public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

public class Foo
{
    public event AlarmEventHandler AlarmEvent; // Compliant
}

Resources

Handling and Raising Events

================================================ FILE: analyzers/rspec/cs/S3906.json ================================================ { "title": "Event Handlers should have the correct signature", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3906", "sqKey": "S3906", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3908.html ================================================

Why is this an issue?

Since .Net Framework version 2.0 it is not necessary to declare a delegate that specifies a class derived from System.EventArgs. The System.EventHandler<TEventArgs> delegate mechanism should be used instead as it allows any class derived from EventArgs to be used with that handler.

This rule raises an issue when an old style delegate is used as an event handler.

Noncompliant code example

public class MyEventArgs : EventArgs
{
}

public delegate void MyEventHandler(object sender, MyEventArgs e); // Noncompliant

public class EventProducer
{
  public event MyEventHandler MyEvent;

  protected virtual void OnMyEvent(MyEventArgs e)
  {
    if (MyEvent != null)
    {
      MyEvent(e);
    }
  }
}

public class EventConsumer
{
  public EventConsumer(EventProducer producer)
  {
      producer.MyEvent += HandleEvent;
  }

  private void HandleEvent(object sender, MyEventArgs e)
  {
    // Do something...
  }
}

Compliant solution

public class MyEventArgs : EventArgs
{
}

public class EventProducer
{
  public event EventHandler<MyEventArgs> MyEvent;

  protected virtual void OnMyEvent(MyEventArgs e)
  {
    if (MyEvent != null)
    {
      MyEvent(e);
    }
  }
}

public class EventConsumer
{
  public EventConsumer(EventProducer producer)
  {
      producer.MyEvent += HandleEvent;
  }

  private void HandleEvent(object sender, MyEventArgs e)
  {
    // Do something...
  }
}
================================================ FILE: analyzers/rspec/cs/S3908.json ================================================ { "title": "Generic event handlers should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3908", "sqKey": "S3908", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3909.html ================================================

Why is this an issue?

The NET Framework 2.0 introduced the generic interface System.Collections.Generic.IEnumerable<T> and it should be preferred over the older, non generic, interfaces.

This rule raises an issue when a public type implements System.Collections.IEnumerable.

Noncompliant code example

using System;
using System.Collections;

public class MyData
{
  public MyData()
  {
  }
}

public class MyList : CollectionBase // Noncompliant
{
  public void Add(MyData data)
  {
    InnerList.Add(data);
  }

  // ...
}

Compliant solution

using System;
using System.Collections.ObjectModel;

public class MyData
{
  public MyData()
  {
  }
}

public class MyList : Collection<MyData>
{
  // Implementation...
}
================================================ FILE: analyzers/rspec/cs/S3909.json ================================================ { "title": "Collections should implement the generic interface", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3909", "sqKey": "S3909", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3923.html ================================================

Why is this an issue?

Having all branches of a switch or if chain with the same implementation indicates a problem.

In the following code:

if (b == 0)  // Noncompliant
{
    DoTheThing();
}
else
{
    DoTheThing();
}

int b = a > 12 ? 4 : 4;  // Noncompliant

switch (i) // Noncompliant
{
    case 1:
        DoSomething();
        break;
    case 2:
        DoSomething();
        break;
    case 3:
        DoSomething();
        break;
    default:
        DoSomething();
}

Either there is a copy-paste error that needs fixing or an unnecessary switch or if chain that should be removed.

Exceptions

This rule does not apply to if chains without else, nor to switch without a default clause.

if (b == 0)    //no issue, this could have been done on purpose to make the code more readable
{
    DoSomething();
}
else if (b == 1)
{
    DoSomething();
}
================================================ FILE: analyzers/rspec/cs/S3923.json ================================================ { "title": "All branches in a conditional structure should not have exactly the same implementation", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3923", "sqKey": "S3923", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3925.html ================================================

Why is this an issue?

The ISerializable interface is the mechanism to control the type serialization process. If not implemented correctly this could result in an invalid serialization and hard-to-detect bugs.

This rule raises an issue on types that implement ISerializable without following the serialization pattern recommended by Microsoft.

Specifically, this rule checks for these problems:

Classes that inherit from Exception are implementing ISerializable. Make sure the [Serializable] attribute is used and that ISerializable is correctly implemented. Even if you don’t plan to explicitly serialize the object yourself, it might still require serialization, for instance when crossing the boundary of an AppDomain.

This rule only raises an issue on classes that indicate that they are interested in serialization (see the Exceptions section). That is to reduce noise because a lot of classes in the base class library are implementing ISerializable, including the following classes: Exception, Uri, Hashtable, Dictionary<TKey,TValue>, DataSet, HttpWebRequest, Regex TreeNode, and others. There is often no need to add serialization support in classes derived from these types.

Exceptions

[Serializable]                                                                                 // 1.
public class SerializationOptIn_Attribute
{
}

public class SerializationOptIn_Interface : ISerializable                                      // 2.
{
}

public class SerializationOptIn_Constructor
{
    protected SerializationOptIn_Constructor(SerializationInfo info, StreamingContext context) // 3.
    {
    }
}

How to fix it

Make sure to follow the recommended guidelines when implementing ISerializable.

Code examples

Noncompliant code example

public class Bar
{
}

public class Foo : ISerializable // Noncompliant: serialization constructor is missing
                                 // Noncompliant: the [Serializable] attribute is missing
{
    private readonly Bar bar; // Noncompliant: the field is not marked with [NonSerialized]
}

public sealed class SealedFoo : Foo
{
    private int val; // Noncompliant: 'val' is serializable and GetObjectData is not overridden

    public SealedFoo()
    {
        // ...
    }

    public SealedFoo(SerializationInfo info, StreamingContext context) // Noncompliant: serialization constructor is not `private`
                                                                       // Noncompliant: serialization constructor does not call base constructor
    {
        // ...
    }
}

public class UnsealedFoo : Foo
{
    public UnsealedFoo()
    {
        // ...
    }

    public UnsealedFoo(SerializationInfo info, StreamingContext context) // Noncompliant: serialization constructor is not `protected`
        : base(info, context)
    {
        // ...
    }

    protected void GetObjectData(SerializationInfo info, StreamingContext context) // Noncompliant: GetObjectData is not public virtual
    {
        // Noncompliant: does not call base.GetObjectData(info, context)
    }
}

Compliant solution

public class Bar
{
}

[Serializable]
public class Foo : ISerializable // Compliant: the class is marked with [Serializable]
{
    [NonSerialized]
    private readonly Bar bar; // Compliant: the field is marked with [NonSerialized]

    public Foo()
    {
        // ...
    }

    protected Foo(SerializationInfo info, StreamingContext context) // Compliant: serialization constructor is present
    {
        // ...
    }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        // ...
    }
}

[Serializable]
public sealed class SealedFoo : Foo
{
    private int val; // Compliant: 'val' is serializable and GetObjectData is overridden

    public SealedFoo()
    {
        // ...
    }

    private SealedFoo(SerializationInfo info, StreamingContext context) // Compliant: serialization constructor is `private`
        : base(info, context) // Compliant: serialization constructor calls base constructor
    {
        // ...
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        // ...
    }
}

[Serializable]
public class UnsealedFoo : Foo
{
    public UnsealedFoo()
    {
        // ...
    }

    protected UnsealedFoo(SerializationInfo info, StreamingContext context) // Compliant: serialization constructor is `protected`
        : base(info, context)
    {
        // ...
    }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context) // Compliant: GetObjectData is public virtual
    {
        base.GetObjectData(info, context); // Compliant: calls base.GetObjectData(info, context)
        // ...

    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3925.json ================================================ { "title": "\"ISerializable\" should be implemented correctly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3925", "sqKey": "S3925", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3926.html ================================================

Why is this an issue?

Fields marked with System.Runtime.Serialization.OptionalFieldAttribute are serialized just like any other field. But such fields are ignored on deserialization, and retain the default values associated with their types. Therefore, deserialization event handlers should be declared to set such fields during the deserialization process.

This rule raises when at least one field with the System.Runtime.Serialization.OptionalFieldAttribute attribute is declared but one (or both) of the following event handlers System.Runtime.Serialization.OnDeserializingAttribute or System.Runtime.Serialization.OnDeserializedAttribute are not present.

Noncompliant code example

[Serializable]
public class Foo
{
    [OptionalField(VersionAdded = 2)]
    int optionalField = 5;
}

Compliant solution

[Serializable]
public class Foo
{
    [OptionalField(VersionAdded = 2)]
    int optionalField = 5;

    [OnDeserializing]
    void OnDeserializing(StreamingContext context)
    {
	    optionalField = 5;
    }

    [OnDeserialized]
    void OnDeserialized(StreamingContext context)
    {
        // Set optionalField if dependent on other deserialized values.
    }
}
================================================ FILE: analyzers/rspec/cs/S3926.json ================================================ { "title": "Deserialization methods should be provided for \"OptionalField\" members", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "serialization" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3926", "sqKey": "S3926", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3927.html ================================================

Why is this an issue?

Serialization event handlers that don’t have the correct signature will not be called, bypassing augmentations to automated serialization and deserialization events.

A method is designated a serialization event handler by applying one of the following serialization event attributes:

Serialization event handlers take a single parameter of type StreamingContext, return void, and have private visibility.

This rule raises an issue when any of these constraints are not respected.

How to fix it

Code examples

Noncompliant code example

[Serializable]
public class Foo
{
    [OnSerializing]
    public void OnSerializing(StreamingContext context) {} // Noncompliant: should be private

    [OnSerialized]
    int OnSerialized(StreamingContext context) {} // Noncompliant: should return void

    [OnDeserializing]
    void OnDeserializing() {} // Noncompliant: should have a single parameter of type StreamingContext

    [OnSerializing]
    public void OnSerializing2<T>(StreamingContext context) {} // Noncompliant: should have no type parameters

    [OnDeserialized]
    void OnDeserialized(StreamingContext context, string str) {} // Noncompliant: should have a single parameter of type StreamingContext
}

Compliant solution

[Serializable]
public class Foo
{
    [OnSerializing]
    private void OnSerializing(StreamingContext context) {}

    [OnSerialized]
    private void OnSerialized(StreamingContext context) {}

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context) {}

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context) {}
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3927.json ================================================ { "title": "Serialization event handlers should be implemented correctly", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3927", "sqKey": "S3927", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3928.html ================================================

Why is this an issue?

Some constructors of the ArgumentException, ArgumentNullException, ArgumentOutOfRangeException and DuplicateWaitObjectException classes must be fed with a valid parameter name. This rule raises an issue in two cases:

Noncompliant code example

public void Foo(Bar a, int[] b)
{
  throw new ArgumentException();                                        // Noncompliant
  throw new ArgumentException("My error message", "c");                 // Noncompliant
  throw new ArgumentException("My error message", "c", innerException); // Noncompliant

  throw new ArgumentNullException("c");                     // Noncompliant
  throw new ArgumentNullException(nameof(c));               // Noncompliant
  throw new ArgumentNullException("My error message", "a"); // Noncompliant

  throw new ArgumentOutOfRangeException("c");                           // Noncompliant
  throw new ArgumentOutOfRangeException("c", "My error message");       // Noncompliant
  throw new ArgumentOutOfRangeException("c", b, "My error message");    // Noncompliant

  throw new DuplicateWaitObjectException("c", "My error message");      // Noncompliant
}

Compliant solution

public void Foo(Bar a, int[] b)
{
  throw new ArgumentException("My error message", "a");
  throw new ArgumentException("My error message", "b", innerException);

  throw new ArgumentNullException("a");
  throw new ArgumentNullException(nameof(a));
  throw new ArgumentNullException("a", "My error message");

  throw new ArgumentOutOfRangeException("b");
  throw new ArgumentOutOfRangeException("b", "My error message");
  throw new ArgumentOutOfRangeException("b", b, "My error message");

  throw new DuplicateWaitObjectException("b", "My error message");
}

Exceptions

The rule won’t raise an issue if the parameter name is not a constant value.

================================================ FILE: analyzers/rspec/cs/S3928.json ================================================ { "title": "Parameter names used into ArgumentException constructors should match an existing one ", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3928", "sqKey": "S3928", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3937.html ================================================

Why is this an issue?

The use of punctuation characters to separate subgroups in a number can make the number more readable. For instance consider 1,000,000,000 versus 1000000000. But when the grouping is irregular, such as 1,000,00,000; it indicates an error.

This rule raises an issue when underscores (_) are used to break a number into irregular subgroups.

Noncompliant code example

int thousand = 100_0;
int tenThousand = 100_00;
int million = 1_000_00_000;

Compliant solution

int thousand = 1000;
int tenThousand = 10_000;
int tenThousandWithout = 10000;
int duos = 1_00_00;
int million = 100_000_000;
================================================ FILE: analyzers/rspec/cs/S3937.json ================================================ { "title": "Number patterns should be regular", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3937", "sqKey": "S3937", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3949.html ================================================

Why is this an issue?

Numbers are infinite, but the types that hold them are not. Each numeric type has hard upper and lower bounds. Try to calculate or assign numbers beyond those bounds, and the result will be a value that has silently wrapped around from the expected positive value to a negative one, or vice versa.

Noncompliant code example

public int Transform(int value)
{
    if (value <= 0)
    {
        return value;
    }
    int number = int.MaxValue;
    return number + value;  // Noncompliant
}

Compliant solution

public long Transform(int value)
{
    if (value <= 0)
    {
        return value;
    }
    long number = int.MaxValue;
    return number + value;
}

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S3949.json ================================================ { "title": "Calculations should not overflow", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "overflow", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3949", "sqKey": "S3949", "scope": "All", "securityStandards": { "STIG ASD_V5R3": [ "V-222612" ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S3956.html ================================================

Why is this an issue?

System.Collections.Generic.List<T> is a generic collection that is designed for performance and not inheritance. For example, it does not contain virtual members that make it easier to change the behavior of an inherited class. That means that future attempts to expand the behavior will be spoiled because the extension points simply aren’t there. Instead, one of the following generic collections should be used:

This rule raises an issue every time a System.Collections.Generic.List<T> is exposed:

Noncompliant code example

namespace Foo
{
   public class Bar
   {
      public List<T> Method1(T arg) // Noncompliant
      {
           //...
      }
   }
}

Compliant solution

namespace Foo
{
   public class Bar
   {
      public Collection<T> Method1(T arg)
      {
           //...
      }
   }
}
================================================ FILE: analyzers/rspec/cs/S3956.json ================================================ { "title": "\"Generic.List\" instances should not be part of public APIs", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3956", "sqKey": "S3956", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3962.html ================================================

Why is this an issue?

The value of a static readonly field is computed at runtime while the value of a const field is calculated at compile time, which improves performance.

This rule raises an issue when a static readonly field is initialized with a value that is computable at compile time.

As specified by Microsoft, the list of types that can have a constant value are:

C# type .Net Fwk type

bool

System.Boolean

byte

System.Byte

sbyte

System.SByte

char

System.Char

decimal

System.Decimal

double

System.Double

float

System.Single

int

System.Int32

uint

System.UInt32

long

System.Int64

ulong

System.UInt64

short

System.Int16

ushort

System.UInt16

string

System.String

Noncompliant code example

namespace myLib
{
  public class Foo
  {
    static readonly int x = 1;  // Noncompliant
    static readonly int y = x + 4; // Noncompliant
    static readonly string s = "Bar";  // Noncompliant
  }
}

Compliant solution

namespace myLib
{
  public class Foo
  {
    const int x = 1;
    const int y = x + 4;
    const string s = "Bar";
  }
}
================================================ FILE: analyzers/rspec/cs/S3962.json ================================================ { "title": "\"static readonly\" constants should be \"const\" instead", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3962", "sqKey": "S3962", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3963.html ================================================

Why is this an issue?

When a static constructor serves no other purpose that initializing static fields, it comes with an unnecessary performance cost because the compiler generates a check before each static method or instance constructor invocation.

Instead, inline initialization is highly recommended.

Noncompliant code example

namespace myLib
{
  public class Foo
  {
    static int i;
    static string s;

    static Foo() // Noncompliant
    {
      i = 3;
      ResourceManager sm =  new ResourceManager("strings", Assembly.GetExecutingAssembly());
      s = sm.GetString("mystring");
    }
  }
}

Compliant solution

namespace myLib
{
  public class Foo
  {
    static int i =3;
    static string s = InitString();

    static string InitString()
    {
      ResourceManager sm = new ResourceManager("strings", Assembly.GetExecutingAssembly());
      return sm.GetString("mystring");
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S3963.json ================================================ { "title": "\"static\" fields should be initialized inline", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3963", "sqKey": "S3963", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3966.html ================================================

Why is this an issue?

Disposing an object twice in the same method, either with the using keyword or by calling Dispose directly, is confusing and error-prone. For example, another developer might try to use an already-disposed object, or there can be runtime errors for specific paths in the code.

In addition, even if the documentation explicitly states that calling the Dispose method multiple times should not throw an exception, some implementations still do it. Thus it is safer to not dispose of an object twice when possible.

How to fix it

Code examples

Noncompliant code example

var foo = new Disposable();
foo.Dispose();
foo.Dispose(); // Noncompliant
using (var bar = new Disposable()) // Noncompliant
{
    bar.Dispose();
}

Compliant solution

var foo = new Disposable();
foo.Dispose();
using (var bar = new Disposable()) // Compliant
{

}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3966.json ================================================ { "title": "Objects should not be disposed more than once", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "confusing", "pitfall", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3966", "sqKey": "S3966", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3967.html ================================================

Why is this an issue?

A jagged array is an array whose elements are arrays. It is recommended over a multidimensional array because the arrays that make up the elements can be of different sizes, which avoids wasting memory space.

Noncompliant code example

int [,] myArray =  // Noncompliant
    {
        {1,2,3,4},
        {5,6,7,0},
        {8,0,0,0},
        {9,0,0,0}
    };
// ...
myArray[1,1] = 0;

Compliant solution

int[][] myArray =
    {
        new int[] {1,2,3,4},
        new int[] {5,6,7},
        new int[] {8},
        new int[] {9}
    };
// ...
myArray[1][1] = 0;
================================================ FILE: analyzers/rspec/cs/S3967.json ================================================ { "title": "Multidimensional arrays should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3967", "sqKey": "S3967", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3971.html ================================================

Why is this an issue?

GC.SuppressFinalize requests that the system not call the finalizer for the specified object. This should only be done when implementing Dispose as part of the Dispose Pattern.

This rule raises an issue when GC.SuppressFinalize is called outside that pattern.

================================================ FILE: analyzers/rspec/cs/S3971.json ================================================ { "title": "\"GC.SuppressFinalize\" should not be called", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3971", "sqKey": "S3971", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3972.html ================================================

Why is this an issue?

Placing an if statement on the same line as the closing } from a preceding if, else, or else if block can lead to confusion and potential errors. It may indicate a missing else statement or create ambiguity for maintainers who might fail to understand that the two statements are unconnected.

The following code snippet is confusing:

if (condition1) {
  // ...
} if (condition2) {  // Noncompliant
  //...
}

Either the two conditions are unrelated and they should be visually separated:

if (condition1) {
  // ...
}

if (condition2) {
  //...
}

Or they were supposed to be exclusive and you should use else if instead:

if (condition1) {
  // ...
} else if (condition2) {
  //...
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3972.json ================================================ { "title": "Conditionals should start on new lines", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3972", "sqKey": "S3972", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3973.html ================================================

Why is this an issue?

When the line immediately after conditional statements has neither curly braces nor indentation, the intent of the code is unclear and perhaps not executed as expected. Additionally, such code is confusing to maintainers.

The rule will check the line indentation after the following conditional statements:

if (condition)  // Noncompliant
DoTheThing();
DoTheOtherThing(); // Was the intent to call this function unconditionally?

It becomes even more confusing and bug-prone if lines get commented out.

if (condition)  // Noncompliant
//   DoTheThing();
DoTheOtherThing(); // Was the intent to call this function conditionally?

Indentation alone or together with curly braces makes the intent clear.

if (condition)
  DoTheThing();
DoTheOtherThing(); // Clear intent to call this function unconditionally

// or

if (condition)
{
  DoTheThing();
}
DoTheOtherThing(); // Clear intent to call this function unconditionally
================================================ FILE: analyzers/rspec/cs/S3973.json ================================================ { "title": "A conditionally executed single line should be denoted by indentation", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "confusing", "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3973", "sqKey": "S3973", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3981.html ================================================

Why is this an issue?

The size of a collection and the length of an array are always greater than or equal to zero. Testing it doesn’t make sense, since the result is always true.

if(collection.Count >= 0){...} // Noncompliant: always true

if(array.Length >= 0){...} // Noncompliant: always true

Similarly testing that it is less than zero will always return false.

if(enumerable.Count() < 0){...} // Noncompliant: always false

Fix the code to properly check for emptiness if it was the intent, or remove the redundant code to keep the current behavior.

================================================ FILE: analyzers/rspec/cs/S3981.json ================================================ { "title": "Collection sizes and array length comparisons should make sense", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3981", "sqKey": "S3981", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3984.html ================================================

Why is this an issue?

Creating a new Exception without actually throwing does not achieve the intended purpose.

if (x < 0)
{
    new ArgumentException("x must be nonnegative");
}

Ensure to throw the Exception with a throw statement.

if (x < 0)
{
    throw new ArgumentException("x must be nonnegative");
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3984.json ================================================ { "title": "Exceptions should not be created without being thrown", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3984", "sqKey": "S3984", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S3990.html ================================================

Why is this an issue?

Assemblies should conform with the Common Language Specification (CLS) in order to be usable across programming languages. To be compliant an assembly has to indicate it with System.CLSCompliantAttribute.

Compliant solution

using System;

[assembly:CLSCompliant(true)]
namespace MyLibrary
{
}
================================================ FILE: analyzers/rspec/cs/S3990.json ================================================ { "title": "Assemblies should be marked as CLS compliant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3990", "sqKey": "S3990", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3992.html ================================================

Why is this an issue?

Assemblies should explicitly indicate whether they are meant to be COM visible or not. If the ComVisibleAttribute is not present, the default is to make the content of the assembly visible to COM clients.

Note that COM visibility can be overridden for individual types and members.

Noncompliant code example

using System;

namespace MyLibrary  // Noncompliant
{
}

Compliant solution

using System;

[assembly: System.Runtime.InteropServices.ComVisible(false)]
namespace MyLibrary
{
}
================================================ FILE: analyzers/rspec/cs/S3992.json ================================================ { "title": "Assemblies should explicitly specify COM visibility", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3992", "sqKey": "S3992", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3993.html ================================================

Why is this an issue?

When defining custom attributes, AttributeUsageAttribute must be used to indicate where the attribute can be applied. This will:

How to fix it

Code examples

Noncompliant code example

public sealed class MyAttribute : Attribute // Noncompliant - AttributeUsage is missing
{
    private string text;

    public MyAttribute(string text)
    {
        this.text = text;
    }

    public string Text => text;
}

Compliant solution

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Delegate)]
public sealed class MyAttribute : Attribute
{
    private string text;

    public MyAttribute(string text)
    {
        this.text = text;
    }

    public string Text => text;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S3993.json ================================================ { "title": "Custom attributes should be marked with \"System.AttributeUsageAttribute\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3993", "sqKey": "S3993", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3994.html ================================================

Why is this an issue?

String representations of URIs or URLs are prone to parsing and encoding errors which can lead to vulnerabilities. The System.Uri class is a safe alternative and should be preferred. At minimum, an overload of the method taking a System.Uri as a parameter should be provided in each class that contains a method with an apparent Uri passed as a string.

This rule raises issues when a method has a string parameter with a name containing "uri", "Uri", "urn", "Urn", "url" or "Url", and the type doesn’t declare a corresponding overload taking an System.Uri parameter instead.

Noncompliant code example

using System;

namespace MyLibrary
{
   public class MyClass
   {

      public void FetchResource(string uriString) { } // Noncompliant
   }
}

Compliant solution

using System;

namespace MyLibrary
{
   public class MyClass
   {

      public void FetchResource(string uriString)
      {
          FetchResource(new Uri(uriString));
      }

      public void FetchResource(Uri uri) { }
   }
}
================================================ FILE: analyzers/rspec/cs/S3994.json ================================================ { "title": "URI Parameters should not be strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3994", "sqKey": "S3994", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3995.html ================================================

Why is this an issue?

String representations of URIs or URLs are prone to parsing and encoding errors which can lead to vulnerabilities. The System.Uri class is a safe alternative and should be preferred.

This rule raises an issue when a method has a string return type and its name contains "Uri", "Urn", or "Url" or begins with "uri", "urn", or "url".

Noncompliant code example

using System;

namespace MyLibrary
{
   public class MyClass
   {
      public string GetParentUri() // Noncompliant
      {
         return "http://www.mysite.com";
      }
   }
}

Compliant solution

using System;

namespace MyLibrary
{
   public class MyClass
   {

      public Uri GetParentUri()
      {
         return new URI("http://www.mysite.com");
      }
   }
}
================================================ FILE: analyzers/rspec/cs/S3995.json ================================================ { "title": "URI return values should not be strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3995", "sqKey": "S3995", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3996.html ================================================

Why is this an issue?

String representations of URIs or URLs are prone to parsing and encoding errors which can lead to vulnerabilities. The System.Uri class is a safe alternative and should be preferred.

This rule raises an issue when a property is a string type and its name contains "uri", "Uri", "urn", "Urn", "url" or "Url".

Noncompliant code example

using System;

namespace MyLibrary
{
   public class MyClass
   {
      string myUri;

      public string MyUri // Noncompliant
      {
         get { return myURI; }
         set { myUri = value; }
      }
   }
}

Compliant solution

using System;

namespace MyLibrary
{
   public class MyClass
   {
      Uri myUri;

      public Uri MyUri
      {
         get { return myURI; }
         set { myUri = value; }
      }
   }
}
================================================ FILE: analyzers/rspec/cs/S3996.json ================================================ { "title": "URI properties should not be strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3996", "sqKey": "S3996", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3997.html ================================================

Why is this an issue?

String representations of URIs or URLs are prone to parsing and encoding errors which can lead to vulnerabilities. The System.Uri class is a safe alternative and should be preferred.

This rule raises an issue when two overloads differ only by the string / Uri parameter and the string overload doesn’t call the Uri overload. It is assumed that the string parameter represents a URI because of the exact match besides that parameter type. It stands to reason that the safer overload should be used.

Noncompliant code example

using System;

namespace MyLibrary
{
   public class MyClass
   {
      public void FetchResource(string uriString) // Noncompliant
      {
         // No calls to FetResource(Uri)
      }

      public void FetchResource(Uri uri) { }
   }
}

Compliant solution

using System;

namespace MyLibrary
{
   public class MyClass
   {
      public void FetchResource(string uriString)
      {
          FetchResource(new Uri(uriString));
      }

      public void FetchResource(Uri uri) { }
   }
}
================================================ FILE: analyzers/rspec/cs/S3997.json ================================================ { "title": "String URI overloads should call \"System.Uri\" overloads", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3997", "sqKey": "S3997", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S3998.html ================================================

Why is this an issue?

Objects that can be accessed across application domain boundaries are said to have weak identity. This means that these objects can be considered shared resources outside of the domain, which can be lead to them being accessed or modified by multiple threads or concurrent parts of a program, outside of the domain.

A thread acquiring a lock on such an object runs the risk of being blocked by another thread in a different application domain, leading to poor performance and potentially thread starvation and deadlocks.

Types with weak identity are:

How to fix it

Code examples

Noncompliant code example

public class Sample
{
    private readonly StackOverflowException myLock = new();

    public void Go()
    {
        lock (myLock) // Noncompliant
        {
            // ...
        }
    }
}

Compliant solution

public class Sample
{
    private readonly object myLock = new();

    public void Go()
    {
        lock (myLock)
        {
            // ...
        }
    }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S3998.json ================================================ { "title": "Threads should not lock on objects with weak identity", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "multi-threading", "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3998", "sqKey": "S3998", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4000.html ================================================

Why is this an issue?

Pointer and unmanaged function pointer types such as IntPtr, UIntPtr, int* etc. are used to access unmanaged memory, usually in order to use C or C++ libraries. If such a pointer is not secured by making it private, internal or readonly, it can lead to a vulnerability allowing access to arbitrary locations.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class MyClass
  {
    public IntPtr myPointer;  // Noncompliant
    protected UIntPtr myOtherPointer; // Noncompliant
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class MyClass
  {
    private IntPtr myPointer;
    protected readonly UIntPtr myOtherPointer;
  }
}
================================================ FILE: analyzers/rspec/cs/S4000.json ================================================ { "title": "Pointers to unmanaged memory should not be visible", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4000", "sqKey": "S4000", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4002.html ================================================

Why is this an issue?

This rule raises an issue when a disposable type contains fields of the following types and does not implement a finalizer:

Noncompliant code example

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
  public class Foo : IDisposable // Noncompliant: Doesn't have a finalizer
  {
    private IntPtr myResource;
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
      if (!disposed)
      {
        // Dispose of resources held by this instance.
        FreeResource(myResource);
        disposed = true;

        // Suppress finalization of this disposed instance.
        if (disposing)
        {
          GC.SuppressFinalize(this);
        }
      }
    }

    public void Dispose() {
      Dispose(true);
    }
  }
}

Compliant solution

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
  public class Foo : IDisposable
  {
    private IntPtr myResource;
    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
      if (!disposed)
      {
        // Dispose of resources held by this instance.
        FreeResource(myResource);
        disposed = true;

        // Suppress finalization of this disposed instance.
        if (disposing)
        {
          GC.SuppressFinalize(this);
        }
      }
    }

    ~Foo()
    {
      Dispose(false);
    }
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S4002.json ================================================ { "title": "Disposable types should declare finalizers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4002", "sqKey": "S4002", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4004.html ================================================

Why is this an issue?

A writable collection property can be replaced by a completely different collection. Making it readonly prevents that while still allowing individual members to be set. If you want to allow the replacement of the whole collection the recommended pattern is to implement a method to remove all the elements (e.g. System.Collections.List<T>.Clear) and a method to populate the collection (e.g. System.Collections.List<T>.AddRange).

This rule raises an issue when an externally visible writable property is of a type that implements System.Collections.ICollection or System.Collections.Generic.ICollection<T>.

Noncompliant code example

using System;
using System.Collections;

namespace MyLibrary
{
  public class Foo
  {
    List<string> strings;

    public List<string> SomeStrings
    {
      get { return strings; }
      set { strings = value; } // Noncompliant
    }
  }
}

Compliant solution

using System;
using System.Collections;

namespace MyLibrary
{
  public class Foo
  {
    List<string> strings;

    public List<string> SomeStrings
    {
      get { return strings; }
    }
  }
}

Exceptions

This rule does not raise issues for

 

================================================ FILE: analyzers/rspec/cs/S4004.json ================================================ { "title": "Collection properties should be readonly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4004", "sqKey": "S4004", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4005.html ================================================

Why is this an issue?

String representations of URIs or URLs are prone to parsing and encoding errors which can lead to vulnerabilities. The System.Uri class is a safe alternative and should be preferred.

This rule raises an issue when a called method has a string parameter with a name containing "uri", "Uri", "urn", "Urn", "url" or "Url" and the declaring type contains a corresponding overload that takes a System.Uri as a parameter.

When there is a choice between two overloads that differ only regarding the representation of a URI, the user should choose the overload that takes a System.Uri argument.

Noncompliant code example

using System;

namespace MyLibrary
{
   public class Foo
   {
      public void FetchResource(string uriString) { }
      public void FetchResource(Uri uri) { }

      public string ReadResource(string uriString, string name, bool isLocal) { }
      public string ReadResource(Uri uri, string name, bool isLocal) { }

      public void Main() {
        FetchResource("http://www.mysite.com"); // Noncompliant
        ReadResource("http://www.mysite.com", "foo-resource", true); // Noncompliant
      }
   }
}

Compliant solution

using System;

namespace MyLibrary
{
   public class Foo
   {
      public void FetchResource(string uriString) { }
      public void FetchResource(Uri uri) { }

      public string ReadResource(string uriString, string name, bool isLocal) { }
      public string ReadResource(Uri uri, string name, bool isLocal) { }

      public void Main() {
        FetchResource(new Uri("http://www.mysite.com"));
        ReadResource(new Uri("http://www.mysite.com"), "foo-resource", true);
      }
   }
}
================================================ FILE: analyzers/rspec/cs/S4005.json ================================================ { "title": "\"System.Uri\" arguments should be used instead of strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4005", "sqKey": "S4005", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4015.html ================================================

Why is this an issue?

Decreasing the accessibility level of an inherited method that is not overridable to private will shadow the name of the base method and can lead to confusion.

public class Base
{
    public void SomeMethod(int count) { }
}
public class Derived : Base
{
    private void SomeMethod(int count) { } // Noncompliant
}

class Program
{
    public void DoWork()
    {
        var derived = new Derived();
        derived.SomeMethod(42); // Base.SomeMethod is accessed here
    }
}

Another potential problem is the case of a class deriving from Derived and accessing SomeMethod. In this scenario, the method accessed will instead be the Base implementation, which might not be what was expected.

public class Base
{
    public void SomeMethod(int count) { }
}
public class Derived : Base
{
    private void SomeMethod(int count) { } // Noncompliant
}

public class SecondDerived : Derived
{
    public void DoWork()
    {
        SomeMethod(42); // Base.SomeMethod is accessed here
    }
}

One way to mitigate this, is by sealing the Derived class by using the sealed modifier, thus preventing inheritance from this point on.

public class Base
{
    public void SomeMethod(int count) { }
}
public sealed class Derived : Base
{
    private void SomeMethod(int count) { } // Compliant: class is marked as sealed
}

Another way to mitigate this, is by having the Derived implementation match the accessibility modifier of the Base implementation of SomeMethod. From a caller’s perspective, this is closer to the expected behavior.

using System;

namespace MyLibrary
{
    public class Base
    {
        public void SomeMethod(int count) { }
    }
    public class Derived : Base
    {
        public void SomeMethod(int count) { } // Compliant: same accessibility as Base.SomeMethod
    }

    public class Program
    {
        public void DoWork()
        {
            var derived = new Derived();
            derived.SomeMethod(42); // Derived.SomeMethod is called
        }
    }
}

Last but not least, consider using a different name for the Derived method, thus completely eliminating any confusion caused by the naming collision.

public class Base
{
    public void SomeMethod(int count) { }
}
public class Derived : Base
{
    private void SomeOtherMethod(int count) { } // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4015.json ================================================ { "title": "Inherited member visibility should not be decreased", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4015", "sqKey": "S4015", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4016.html ================================================

Why is this an issue?

If an enum member’s name contains the word "reserved" it implies it is not currently used and will be change in the future. However changing an enum member is a breaking change and can create significant problems. There is no need to reserve an enum member since a new member can be added in the future, and such an addition will usually not be a breaking change.

This rule raises an issue when the name of an enumeration member contains "reserved".

Noncompliant code example

using System;

namespace MyLibrary
{
  public enum Color
  {
        None,
        Red,
        Orange,
        Yellow,
        ReservedColor  // Noncompliant
    }
}
================================================ FILE: analyzers/rspec/cs/S4016.json ================================================ { "title": "Enumeration members should not be named \"Reserved\"", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4016", "sqKey": "S4016", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4017.html ================================================

Why is this an issue?

A nested type is a type argument that is also a generic type. Calling a method with such a nested type argument requires complicated and confusing code. It should be avoided as much as possible.

Noncompliant code example

using System;
using System.Collections.Generic;

namespace MyLibrary
{
  public class Foo
  {
    public void DoSomething(ICollection<ICollection<int>> outerCollect) // Noncompliant
    {
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4017.json ================================================ { "title": "Method signatures should not contain nested generic types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4017", "sqKey": "S4017", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4018.html ================================================

Why is this an issue?

Type inference enables the call of a generic method without explicitly specifying its type arguments. This is not possible when a parameter type is missing from the argument list.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class Foo
  {
    public void MyMethod<T>()  // Noncompliant
    {
        // this method can only be invoked by providing the type argument e.g. 'MyMethod<int>()'
    }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class Foo
  {
    public void MyMethod<T>(T param)
    {
        // type inference allows this to be invoked 'MyMethod(arg)'
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4018.json ================================================ { "title": "All type parameters should be used in the parameter list to enable type inference", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4018", "sqKey": "S4018", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4019.html ================================================

Why is this an issue?

When a method in a derived class has:

the result is that the base method becomes hidden.

As shown in the following code snippet, when an instance of the derived class is used, invoking the method with an argument that matches the less derived parameter type will invoke the derived class method instead of the base class method:

class BaseClass
{
  internal void MyMethod(string str) => Console.WriteLine("BaseClass: Method(string)");
}

class DerivedClass : BaseClass
{
  internal void MyMethod(object str) => Console.WriteLine("DerivedClass: Method(object)"); // Noncompliant
}

// ...
BaseClass baseObj = new BaseClass();
baseObj.MyMethod("Hello"); // Output: BaseClass: Method(string)

DerivedClass derivedObj = new DerivedClass();
derivedObj.MyMethod("Hello"); // Output: DerivedClass: Method(object) - DerivedClass method is hiding the BaseClass method

BaseClass derivedAsBase = new DerivedClass();
derivedAsBase.MyMethod("Hello"); // Output: BaseClass: Method(string)
class BaseClass
{
  internal void MyMethod(string str) => Console.WriteLine("BaseClass: Method(string)");
}

class DerivedClass : BaseClass
{
  internal void MyOtherMethod(object str) => Console.WriteLine("DerivedClass: Method(object)"); // Compliant
}

// ...
BaseClass baseObj = new BaseClass();
baseObj.MyMethod("Hello"); // Output: BaseClass: Method(string)

DerivedClass derivedObj = new DerivedClass();
derivedObj.MyMethod("Hello"); // Output: BaseClass: Method(string)

BaseClass derivedAsBase = new DerivedClass();
derivedAsBase.MyMethod("Hello"); // Output: BaseClass: Method(string)

Keep in mind that you cannot fix this issue by using the new keyword or by marking the method in the base class as virtual and overriding it in the DerivedClass because the parameter types are different.

Exceptions

The rule is not raised when the two methods have the same parameter types.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4019.json ================================================ { "title": "Base class methods should not be hidden", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4019", "sqKey": "S4019", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4022.html ================================================

Why is this an issue?

By default the storage type of an enum is Int32. In most cases it is not necessary to change this. In particular you will not achieve any performance gain by using a smaller data type (e.g. Byte) and may limit future uses.

Noncompliant code example

using System;

namespace MyLibrary
{
    public enum Visibility : sbyte // Noncompliant
    {
        Visible = 0,
        Invisible = 1,
    }
}

Compliant solution

using System;

namespace MyLibrary
{
    public enum Visibility
    {
        Visible = 0,
        Invisible = 1,
    }
}
================================================ FILE: analyzers/rspec/cs/S4022.json ================================================ { "title": "Enumerations should have \"Int32\" storage", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4022", "sqKey": "S4022", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4023.html ================================================

Empty interfaces should be avoided as they do not provide any functional requirements for implementing classes.

Why is this an issue?

Empty interfaces are either useless or used as a marker. Custom attributes are a better alternative to marker interfaces. See the How to fix it section for more information.

Exceptions

This rule doesn’t raise in any of the following cases:

Aggregation of multiple interfaces

public interface IAggregate: IComparable, IFormattable { } // Compliant: Aggregates two interfaces

Generic specialization

An empty interface with a single base interface is compliant as long as the resulting interface binds a generic parameter or constrains it.

// Compliant: Bound to a concrete type
public interface IStringEquatable: IEquatable<string> { }
// Compliant: Specialized by type parameter constraint
public interface ICreateableEquatable<T>: IEquatable<T> where T: new() { }

Custom attribute

An empty interface is compliant if a custom attribute is applied to the interface.

[Obsolete]
public interface ISorted { } // Compliant: An attribute is applied to the interface declaration

How to fix it

Do any of the following to fix the issue:

Code examples

Noncompliant code example

The empty interface does not add any functionality.

public interface IFoo // Noncompliant
{

}

Compliant solution

Add members to the interface to be compliant.

public interface IFoo
{
    void Bar();
}

Noncompliant code example

A typical use case for marker interfaces is doing type inspection via reflection as shown below.

The IIncludeFields marker interface is used to configure the JSON serialization of an object.

// An example marker interface
public interface IIncludeFields { }

public class OptInToIncludeFields: IIncludeFields { }

Serialize(new OptInToIncludeFields());

void Serialize<T>(T o)
{
    // Use reflection to check if the interface is applied to the type
    var includeFields = o.GetType()
        .GetInterfaces().Any(i => i == typeof(IIncludeFields));
    var options = new JsonSerializerOptions()
    {
        // Take decisions based on the presence of the marker
        IncludeFields = includeFields,
    };
}

The same example can be rewritten using custom attributes. This approach is preferable because it is more fine-grained, allows parameterization, and is more flexible in type hierarchies.

// A custom attribute used as a marker
[AttributeUsage(AttributeTargets.Class)]
public sealed class IncludeFieldsAttribute: Attribute { }

[IncludeFields]
public class OptInToIncludeFields { }

Serialize(new OptInToIncludeFields());

void Serialize<T>(T o)
{
    // Use reflection to check if the attribute is applied to the type
    var includeFields = o.GetType()
        .GetCustomAttributes(typeof(IncludeFieldsAttribute), inherit: true).Any();
    var options = new JsonSerializerOptions()
    {
        // Take decisions based on the presence of the marker
        IncludeFields = includeFields,
    };
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4023.json ================================================ { "title": "Interfaces should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4023", "sqKey": "S4023", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4025.html ================================================

Why is this an issue?

Having a field in a child class with a name that differs from a parent class' field only by capitalization is sure to cause confusion. Such child class fields should be renamed.

Noncompliant code example

public class Fruit
{
  protected string plantingSeason;
  //...
}

public class Raspberry : Fruit
{
  protected string plantingseason;  // Noncompliant
  // ...
}

Compliant solution

public class Fruit
{
  protected string plantingSeason;
  //...
}

public class Raspberry : Fruit
{
  protected string whenToPlant;
  // ...
}

Or

public class Fruit
{
  protected string plantingSeason;
  //...
}

public class Raspberry : Fruit
{
  // field removed; parent field will be used instead
  // ...
}

Exceptions

This rule ignores same-name fields that are static in both the parent and child classes. It also ignores private parent class fields, but in all other such cases, the child class field should be renamed.

================================================ FILE: analyzers/rspec/cs/S4025.json ================================================ { "title": "Child class fields should not differ from parent class fields only by capitalization", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4025", "sqKey": "S4025", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4026.html ================================================

Why is this an issue?

It is important to inform the ResourceManager of the language used to display the resources of the neutral culture for an assembly. This improves lookup performance for the first resource loaded.

This rule raises an issue when an assembly contains a ResX-based resource but does not have the System.Resources.NeutralResourcesLanguageAttribute applied to it.

Noncompliant code example

using System;

public class MyClass // Noncompliant
{
   public static void Main()
   {
      string[] cultures = { "de-DE", "en-us", "fr-FR" };
      Random rnd = new Random();
      int index = rnd.Next(0, cultures.Length);
      Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultures[index]);

      ResourceManager rm = new ResourceManager("MyResources" ,
                                               typeof(MyClass).Assembly);
      string greeting = rm.GetString("Greeting");

      Console.Write("Enter your name: ");
      string name = Console.ReadLine();
      Console.WriteLine("{0} {1}!", greeting, name);
   }
}

Compliant solution

using System;

[assembly:NeutralResourcesLanguageAttribute("en")]
public class MyClass
{
   public static void Main()
   {
      string[] cultures = { "de-DE", "en-us", "fr-FR" };
      Random rnd = new Random();
      int index = rnd.Next(0, cultures.Length);
      Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultures[index]);

      ResourceManager rm = new ResourceManager("MyResources" ,
                                               typeof(MyClass).Assembly);
      string greeting = rm.GetString("Greeting");

      Console.Write("Enter your name: ");
      string name = Console.ReadLine();
      Console.WriteLine("{0} {1}!", greeting, name);
   }
}
================================================ FILE: analyzers/rspec/cs/S4026.json ================================================ { "title": "Assemblies should be marked with \"NeutralResourcesLanguageAttribute\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4026", "sqKey": "S4026", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4027.html ================================================

Why is this an issue?

Exceptions types should provide the following constructors:

The absence of these constructors can complicate exception handling and limit the information that can be provided when an exception is thrown.

How to fix it

Code examples

Noncompliant code example

public class MyException : Exception // Noncompliant: several constructors are missing
{
    public MyException()
    {
    }
}

Compliant solution

public class MyException : Exception
{
    public MyException()
    {
    }

    public MyException(string message)
        : base(message)
    {
    }

    public MyException(string message, Exception innerException)
        : base(message, innerException)
    {
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4027.json ================================================ { "title": "Exceptions should provide standard constructors", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4027", "sqKey": "S4027", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4035.html ================================================

Why is this an issue?

When a class implements the IEquatable<T> interface, it enters a contract that, in effect, states "I know how to compare two instances of type T or any type derived from T for equality.". However if that class is derived, it is very unlikely that the base class will know how to make a meaningful comparison. Therefore that implicit contract is now broken.

Alternatively IEqualityComparer<T> provides a safer interface and is used by collections or Equals could be made virtual.

This rule raises an issue when an unsealed, public or protected class implements IEquatable<T> and the Equals is neither virtual nor abstract.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class Base : IEquatable<Base> // Noncompliant
  {
    public bool Equals(Base other)
    {
      if (other == null) { return false; }
      // do comparison of base properties
      return true;
    }

    public override bool Equals(object other)  => Equals(other as Base);
  }

  class A : Base
  {
    public bool Equals(A other)
    {
      if (other == null) { return false; }
      // do comparison of A properties
      return base.Equals(other);
    }

    public override bool Equals(object other)  => Equals(other as A);
  }

  class B : Base
  {
    public bool Equals(B other)
    {
      if (other == null) { return false; }
      // do comparison of B properties
      return base.Equals(other);
    }

    public override bool Equals(object other)  => Equals(other as B);
  }

  internal class Program
  {
    static void Main(string[] args)
    {
        A a = new A();
        B b = new B();
         Console.WriteLine(a.Equals(b)); // This calls the WRONG equals. This causes Base.Equals(Base)
                                         // to be called which only compares the properties in Base and ignores the fact that
                                         // a and b are different types. In the working example A.Equals(Object) would have been
                                         // called and Equals would return false because it correctly recognizes that a and b are
                                         // different types. If a and b have the same base properties they will be returned as equal.
    }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
    public sealed class Foo : IEquatable<Foo>
    {
        public bool Equals(Foo other)
        {
            // Your code here
        }
    }
}

Resources

IEqualityComparer<T> Interface

================================================ FILE: analyzers/rspec/cs/S4035.json ================================================ { "title": "Classes implementing \"IEquatable\u003cT\u003e\" should be sealed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4035", "sqKey": "S4035", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4036.html ================================================

When you run an OS command, it is always important to protect yourself against the risk of accidental or malicious replacement of the executables in the production system.

To do so, it is important to point to the specific executable that should be used.

For example, if you call git (without specifying a path), the operating system will search for the executable in the directories specified in the PATH environment variable.
An attacker could have added, in a permissive directory covered by PATH , another executable called git, but with a completely different behavior, for example exfiltrating data or exploiting a vulnerability in your own code.

However, by calling /usr/bin/git or ../git (relative path) directly, the operating system will always use the intended executable.
Note that you still need to make sure that the executable is not world-writeable and potentially overwritten. This is not the scope of this rule.

Ask Yourself Whether

There is a risk if you answered no to this question.

Recommended Secure Coding Practices

If you wish to rely on the PATH environment variable to locate the OS command, make sure that each of its listed directories is fixed, not susceptible to change, and not writable by unprivileged users.

If you determine that these folders cannot be altered, and that you are sure that the program you intended to use will be used, then you can determine that these risks are under your control.

A good practice you can use is to also hardcode the PATH variable you want to use, if you can do so in the framework you use.

If the previous recommendations cannot be followed due to their complexity or other requirements, then consider using the absolute path of the command instead.

$ whereis git
git: /usr/bin/git /usr/share/man/man1/git.1.gz
$ ls -l /usr/bin/git
-rwxr-xr-x 1 root root 3376112 Jan 28 10:13 /usr/bin/git

Sensitive Code Example

Process p = new Process();
p.StartInfo.FileName = "binary"; // Sensitive

Compliant Solution

Process p = new Process();
p.StartInfo.FileName = @"C:\Apps\binary.exe";

See

================================================ FILE: analyzers/rspec/cs/S4036.json ================================================ { "title": "Searching OS commands in PATH is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4036", "sqKey": "S4036", "scope": "Main", "securityStandards": { "CWE": [ 427, 426 ], "OWASP": [ "A1" ], "OWASP Top 10 2021": [ "A8" ] } } ================================================ FILE: analyzers/rspec/cs/S4039.html ================================================

Why is this an issue?

When a base type explicitly implements a public interface method, property or event, that member is only accessible in derived types through a reference to the current instance (namely this). If the derived type explicitly overrides that interface member, the base implementation becomes inaccessible.

This rule raises an issue when an unsealed, externally visible type provides an explicit member implementation of an interface and does not provide an alternate, externally visible member with the same name.

Exceptions

This rule does not report a violation for an explicit implementation of IDisposable.Dispose when an externally visible Close() or System.IDisposable.Dispose(Boolean) method is provided.

How to fix it

Make the class sealed, change the class member to a non-explicit declaration, or provide a new class member exposing the functionality of the explicit interface member.

Code examples

Noncompliant code example

public interface IMyInterface
{
    void MyMethod();
}

public class Foo : IMyInterface
{
    void IMyInterface.MyMethod() // Noncompliant
    {
        MyMethod();
    }
}

Compliant solution

public interface IMyInterface
{
    void MyMethod();
}

public class Foo : IMyInterface
{
    void IMyInterface.MyMethod()
    {
        MyMethod();
    }

    // This method can be public or protected
    protected void MyMethod()
    {
        // Do something ...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4039.json ================================================ { "title": "Interface methods should be callable by derived types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4039", "sqKey": "S4039", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4040.html ================================================

Why is this an issue?

Certain characters, once normalized to lowercase, cannot make a round trip. That is, they can not be converted from one locale to another and then accurately restored to their original characters.

It is therefore strongly recommended to normalize characters and strings to uppercase instead.

Noncompliant code example

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
var areStringEqual = "INTEGER".ToLower() == "integer"; // Noncompliant, the result is false as the ToLower will resolve to "ınteger"
var areCharEqual = char.ToLower('I') == 'i'; // Noncompliant, the result is false as the ToLower will resolve to "ı"

var incorrectRoundtrip = "İ".ToLowerInvariant().ToUpper() == "I".ToLowerInvariant().ToUpper(); // Noncompliant, because of the lower we lose the information about the correct uppercase character

Compliant solution

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
var areStringEqual = "ınteger".ToUpperInvariant() == "ıNTEGER";
var areCharEqual = char.ToUpperInvariant('ı') == 'ı';
var correctRoundtrip = "İ".ToUpperInvariant().ToLower() != "I".ToUpperInvariant().ToLower();

Resources

================================================ FILE: analyzers/rspec/cs/S4040.json ================================================ { "title": "Strings should be normalized to uppercase", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4040", "sqKey": "S4040", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4041.html ================================================

Why is this an issue?

When a type name matches the name of a publicly defined namespace, for instance one in the .NET framework class library, it leads to confusion and makes the library that much harder to use.

This rule raises an issue when a name of a public type matches the name of a .NET Framework namespace, or a namespace of the project assembly, in a case-insensitive comparison.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class Text   // Noncompliant: Collides with System.Text
  {
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class MyText
  {
  }
}
================================================ FILE: analyzers/rspec/cs/S4041.json ================================================ { "title": "Type names should not match namespaces", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4041", "sqKey": "S4041", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4047.html ================================================

Why is this an issue?

When a reference parameter (keyword ref) is used, the passed argument type must exactly match the reference parameter type. This means that to be able to pass a derived type, it must be cast and assigned to a variable of the proper type. Use of generic methods eliminates that cumbersome down casting and should therefore be preferred.

This rule raises an issue when a method contains a ref parameter of type System.Object.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class Foo
  {
    public void Bar(ref object o1, ref object o2) // Noncompliant
    {
    }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class Foo
  {
    public void Bar<T>(ref T ref1, ref T ref2)
    {
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4047.json ================================================ { "title": "Generics should be used when appropriate", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4047", "sqKey": "S4047", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4049.html ================================================

Why is this an issue?

Properties are accessed like fields which makes them easier to use.

This rule raises an issue when the name of a public or protected method starts with Get, takes no parameter, and returns a value that is not an array.

Noncompliant code example

using System;

namespace MyLibrary
{
    public class Foo
    {
        private string name;

        public string GetName()  // Noncompliant
        {
            return name;
        }
    }
}

Compliant solution

using System;

namespace MyLibrary
{
    public class Foo
    {
        private string name;

        public string Name
        {
            get
            {
                return name;
            }
        }
    }
}

Exceptions

The rule doesn’t raise an issue when the method:

================================================ FILE: analyzers/rspec/cs/S4049.json ================================================ { "title": "Properties should be preferred", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4049", "sqKey": "S4049", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4050.html ================================================

Why is this an issue?

When overloading some arithmetic operator overloads, it is very important to make sure that all related operators and methods are consistent in their implementation.

The following guidelines should be followed:

This rule raises an issue when any of these guidelines are not followed on a publicly-visible class or struct (public, protected or protected internal).

How to fix it

Make sure to implement all related operators.

Code examples

Noncompliant code example

public class Foo // Noncompliant
{
    private int left;
    private int right;

    public Foo(int l, int r)
    {
        this.left = l;
        this.right = r;
    }

    public static Foo operator +(Foo a, Foo b)
    {
        return new Foo(a.left + b.left, a.right + b.right);
    }

    public static Foo operator -(Foo a, Foo b)
    {
        return new Foo(a.left - b.left, a.right - b.right);
    }
}

Compliant solution

public class Foo
{
    private int left;
    private int right;

    public Foo(int l, int r)
    {
      this.left = l;
      this.right = r;
    }

    public override bool Equals(Object obj)
    {
        var a = obj as Foo;
        if (a == null)
          return false;
        return this == a;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(right, left);
    }

    public static Foo operator +(Foo a, Foo b)
    {
        return new Foo(a.left + b.left, a.right + b.right);
    }

    public static Foo operator -(Foo a, Foo b)
    {
        return new Foo(a.left - b.left, a.right - b.right);
    }

    public static bool operator ==(Foo a, Foo b)
    {
        return a.left == b.left && a.right == b.right;
    }

    public static bool operator !=(Foo a, Foo b)
    {
        return !(a == b);
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4050.json ================================================ { "title": "Operators should be overloaded consistently", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4050", "sqKey": "S4050", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4052.html ================================================

Why is this an issue?

With the advent of .NET Framework 2.0, certain practices and types have become obsolete.

In particular, exceptions should now extend System.Exception instead of System.ApplicationException. Similarly, generic collections should be used instead of the older, non-generic, ones. Finally when creating an XML view, you should not extend System.Xml.XmlDocument. This rule raises an issue when an externally visible type extends one of these types:

How to fix it

Code examples

Noncompliant code example

using System;
using System.Collections;

namespace MyLibrary
{
  public class MyCollection : CollectionBase  // Noncompliant
  {
  }
}

Compliant solution

using System;
using System.Collections.ObjectModel;

namespace MyLibrary
{
  public class MyCollection : Collection<T>
  {
  }
}
================================================ FILE: analyzers/rspec/cs/S4052.json ================================================ { "title": "Types should not extend outdated base types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4052", "sqKey": "S4052", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4055.html ================================================

Why is this an issue?

String literals embedded in the source code will not be localized properly.

This rule raises an issue when a literal string is passed as a parameter or property and one or more of the following cases is true:

Noncompliant code example

using System;
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;

[assembly: NeutralResourcesLanguageAttribute("en-US")]
namespace MyLibrary
{
    public class Foo
    {
        public void SetHour(int hour)
        {
            if (hour < 0 || hour > 23)
            {
                MessageBox.Show("The valid range is 0 - 23."); // Noncompliant
            }
        }
    }
}

Compliant solution

using System;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Windows.Forms;



[assembly: NeutralResourcesLanguageAttribute("en-US")]
namespace MyLibrary
{
    public class Foo
    {
        ResourceManager rm;
        public Foo()
        {
            rm = new ResourceManager("en-US", Assembly.GetExecutingAssembly());
        }

        public void SetHour(int hour)
        {
            if (hour < 0 || hour > 23)
            {
                MessageBox.Show(
                rm.GetString("OutOfRangeMessage", CultureInfo.CurrentUICulture));
            }
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S4055.json ================================================ { "title": "Literals should not be passed as localized parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "localisation", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4055", "sqKey": "S4055", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4056.html ================================================

Why is this an issue?

When a System.Globalization.CultureInfo or IFormatProvider object is not supplied, the default value that is supplied by the overloaded member might not have the effect that you want in all locales.

You should supply culture-specific information according to the following guidelines:

This rule raises an issue when a method or constructor calls one or more members that have overloads that accept a System.IFormatProvider parameter, and the method or constructor does not call the overload that takes the IFormatProvider parameter. This rule ignores calls to .NET Framework methods that are documented as ignoring the IFormatProvider parameter as well as the following methods:

Noncompliant code example

using System;

namespace MyLibrary
{
    public class Foo
    {
        public void Bar(String string1)
        {
            if(string.Compare(string1, string2, false) == 0) // Noncompliant
            {
                Console.WriteLine(string3.ToLower()); // Noncompliant
            }
        }
    }
}

Compliant solution

using System;
using System.Globalization;

namespace MyLibrary
{
    public class Foo
    {
        public void Bar(String string1, String string2, String string3)
        {
            if(string.Compare(string1, string2, false,
                              CultureInfo.InvariantCulture) == 0)
            {
                Console.WriteLine(string3.ToLower(CultureInfo.CurrentCulture));
            }
        }
    }
}

Exceptions

This rule will not raise an issue when the overload is marked as obsolete.

================================================ FILE: analyzers/rspec/cs/S4056.json ================================================ { "title": "Overloads with a \"CultureInfo\" or an \"IFormatProvider\" parameter should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "localisation", "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4056", "sqKey": "S4056", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4057.html ================================================

Why is this an issue?

When you create a DataTable or DataSet, you should set the locale explicitly. By default, the locale for these types is the current culture. For data that is stored in a database or file and is shared globally, the locale should ordinarily be set to the invariant culture (CultureInfo.InvariantCulture).

This rule raises an issue when System.Data.DataTable or System.Data.DataSet instances are created without explicitly setting the locale property (DataTable.Locale or DataSet.Locale).

Noncompliant code example

using System;
using System.Data;

namespace MyLibrary
{
    public class Foo
    {
        public DataTable CreateTable()
        {
            DataTable table = new DataTable("Customers"); // Noncompliant table.Locale not set
            DataColumn key = table.Columns.Add("ID", typeof(Int32));

            key.AllowDBNull = false;
            key.Unique = true;
            table.Columns.Add("LastName", typeof(String));
            table.Columns.Add("FirstName", typeof(String));
            return table;
        }
    }
}

Compliant solution

using System;
using System.Data;
using System.Globalization;

namespace MyLibrary
{
    public class Foo
    {
        public DataTable CreateTable()
        {
            DataTable table = new DataTable("Customers");
            table.Locale = CultureInfo.InvariantCulture;
            DataColumn key = table.Columns.Add("ID", typeof(Int32));

            key.AllowDBNull = false;
            key.Unique = true;
            table.Columns.Add("LastName", typeof(String));
            table.Columns.Add("FirstName", typeof(String));
            return table;
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S4057.json ================================================ { "title": "Locales should be set for data types", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "localisation" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4057", "sqKey": "S4057", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4058.html ================================================

Why is this an issue?

Many string operations, the Compare and Equals methods in particular, provide an overload that accepts a StringComparison enumeration value as a parameter. Calling these overloads and explicitly providing this parameter makes your code clearer and easier to maintain.

This rule raises an issue when a string comparison operation doesn’t use the overload that takes a StringComparison parameter.

Noncompliant code example

using System;

namespace MyLibrary
{
  public class Foo
  {
    public bool HaveSameNames(string name1, string name2)
    {
      return string.Compare(name1, name2) == 0; // Noncompliant
    }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class Foo
  {
    public bool HaveSameNames(string name1, string name2)
    {
      return string.Compare(name1, name2, StringComparison.OrdinalIgnoreCase) == 0;
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4058.json ================================================ { "title": "Overloads with a \"StringComparison\" parameter should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4058", "sqKey": "S4058", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4059.html ================================================

Why is this an issue?

Properties and Get method should have names that makes them clearly distinguishable.

This rule raises an issue when the name of a public or protected member starts with 'Get' and otherwise matches the name of a public or protected property.

Noncompliant code example

using System;

namespace MyLibrary
{
    public class Foo
    {
        public DateTime Date
        {
            get { return DateTime.Today; }
        }

        public string GetDate() // Noncompliant
        {
            return this.Date.ToString();
        }
    }
}

Compliant solution

using System;

namespace MyLibrary
{
    public class Foo
    {
        public DateTime Date
        {
            get { return DateTime.Today; }
        }

        public string GetDateAsString()
        {
            return this.Date.ToString();
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S4059.json ================================================ { "title": "Property names should not match get methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4059", "sqKey": "S4059", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4060.html ================================================

Why is this an issue?

The .NET framework class library provides methods for retrieving custom attributes. Sealing the attribute eliminates the search through the inheritance hierarchy, and can improve performance.

This rule raises an issue when a public type inherits from System.Attribute, is not abstract, and is not sealed.

Noncompliant code example

using System;

public class MyAttribute: Attribute // Noncompliant
{
    public string Name { get; }

    public MyAttribute(string name) =>
        Name = name;
}

Compliant solution

using System;

public sealed class MyAttribute : Attribute
{
    public string Name { get; }

    public MyAttribute(string name) =>
        Name = name;
}
================================================ FILE: analyzers/rspec/cs/S4060.json ================================================ { "title": "Non-abstract attributes should be sealed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4060", "sqKey": "S4060", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4061.html ================================================

Why is this an issue?

A method using the VarArgs calling convention is not Common Language Specification (CLS) compliant and might not be accessible across programming languages, while the params keyword works the same way and is CLS compliant.

This rule raises an issue when a public or protected type contains a public or protected method that uses the VarArgs calling convention.

Noncompliant code example

using System;

namespace MyLibrary
{
    public class Foo
    {
        public void Bar(__arglist) // Noncompliant
        {
            ArgIterator argumentIterator = new ArgIterator(__arglist);
            for(int i = 0; i < argumentIterator.GetRemainingCount(); i++)
            {
                Console.WriteLine(
                    __refvalue(argumentIterator.GetNextArg(), string));
            }
        }
    }
}

Compliant solution

using System;

[assembly: CLSCompliant(true)]
namespace MyLibrary
{
    public class Foo
    {
        public void Bar(params string[] wordList)
        {
            for(int i = 0; i < wordList.Length; i++)
            {
                Console.WriteLine(wordList[i]);
            }
        }
    }
}

Exceptions

Interop methods using VarArgs calling convention do not raise an issue.

[DllImport("msvcrt40.dll")]
public static extern int printf(string format, __arglist); // Compliant
================================================ FILE: analyzers/rspec/cs/S4061.json ================================================ { "title": "\"params\" should be used instead of \"varargs\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4061", "sqKey": "S4061", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4069.html ================================================

Why is this an issue?

Operator overloading is convenient but unfortunately not portable across languages. To be able to access the same functionality from another language you need to provide an alternate named method following the convention:

Operator Method Name

+ (binary)

Add

&

BitwiseAnd

|

BitwiseOr

/

Divide

==

Equals

^

Xor

>

Compare

>=

Compare

!=

Equals

<

Compare

<=

Compare

!

LogicalNot

%

Mod

* (binary)

Multiply

~

OnesComplement

- (binary)

Subtract

- (unary)

Negate

+ (unary)

Plus

This rule raises an issue when there is an operator overload without the expected named alternative method.

Exceptions

This rule does not raise an issue when the class implementing the comparison operators >, <, >= and <= contains a method named CompareTo.

================================================ FILE: analyzers/rspec/cs/S4069.json ================================================ { "title": "Operator overloads should have named alternatives", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4069", "sqKey": "S4069", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4070.html ================================================

Why is this an issue?

This rule raises an issue when an externally visible enumeration is marked with FlagsAttribute and one, or more, of its values is not a power of 2 or a combination of the other defined values.

Noncompliant code example

using System;

namespace MyLibrary
{
    [Flags]
    public enum Color // Noncompliant, Orange is neither a power of two, nor a combination of any of the defined values
    {
        None    = 0,
        Red     = 1,
        Orange  = 3,
        Yellow  = 4
    }
}

Compliant solution

using System;

namespace MyLibrary
{
    public enum Color // Compliant - no FlagsAttribute
    {
        None = 0,
        Red = 1,
        Orange = 3,
        Yellow = 4
    }

    [Flags]
    public enum Days
    {
        None = 0,
        Monday = 1,
        Tuesday = 2,
        Wednesday = 4,
        Thursday = 8,
        Friday = 16,
        All = Monday| Tuesday | Wednesday | Thursday | Friday    // Compliant - combination of other values
    }
}
================================================ FILE: analyzers/rspec/cs/S4070.json ================================================ { "title": "Non-flags enums should not be marked with \"FlagsAttribute\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4070", "sqKey": "S4070", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4136.html ================================================

Why is this an issue?

For clarity, all overloads of the same method should be grouped together. That lets both users and maintainers quickly understand all the current available options.

Noncompliant code example

interface IMyInterface
{
  int DoTheThing(); // Noncompliant - overloaded method declarations are not grouped together
  string DoTheOtherThing();
  int DoTheThing(string s);
}

Compliant solution

interface IMyInterface
{
  int DoTheThing();
  int DoTheThing(string s);
  string DoTheOtherThing();
}

Exceptions

As it is common practice to group method declarations by implemented interface, no issue will be raised for implicit and explicit interface implementations if grouped together with other members of that interface.

As it is also a common practice to group method declarations by accessibility level, no issue will be raised for method overloads having different access modifiers.

Example:

class MyClass
{
  private void DoTheThing(string s) // Ok - this method is declared as private while the other one is public
  {
    // ...
  }

  private string DoTheOtherThing(string s)
  {
    // ...
  }

  public void DoTheThing()
  {
    // ...
  }
}
================================================ FILE: analyzers/rspec/cs/S4136.json ================================================ { "title": "Method overloads should be grouped together", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4136", "sqKey": "S4136", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4143.html ================================================

Why is this an issue?

Storing a value inside a collection at a given key or index and then unconditionally overwriting it without reading the initial value is a case of a "dead store".

list[index] = "value 1";
list[index] = "value 2";  // Noncompliant

dictionary.Add(key, "value 1");
dictionary[key] = "value 2"; // Noncompliant

This practice is redundant and will cause confusion for the reader. More importantly, it is often an error and not what the developer intended to do.

================================================ FILE: analyzers/rspec/cs/S4143.json ================================================ { "title": "Collection elements should not be replaced unconditionally", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4143", "sqKey": "S4143", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4144.html ================================================

Why is this an issue?

Two methods having the same implementation are suspicious. It might be that something else was intended. Or the duplication is intentional, which becomes a maintenance burden.

private const string CODE = "secret";
private int callCount = 0;

public string GetCode()
{
  callCount++;
  return CODE;
}

public string GetName() // Noncompliant: duplicates GetCode
{
  callCount++;
  return CODE;
}

If the identical logic is intentional, the code should be refactored to avoid duplication. For example, by having both methods call the same method or by having one implementation invoke the other.

private const string CODE = "secret";
private int callCount = 0;

public string GetCode()
{
  callCount++;
  return CODE;
}

public string GetName() // Intent is clear
{
  return GetCode();
}

Exceptions

Empty methods, methods with only one line of code and methods with the same name (overload) are ignored.

================================================ FILE: analyzers/rspec/cs/S4144.json ================================================ { "title": "Methods should not have identical implementations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "confusing", "duplicate", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4144", "sqKey": "S4144", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4158.html ================================================

Why is this an issue?

When a collection is empty, iterating it has no effect. Doing so anyway is likely a bug; either population was accidentally omitted, or the iteration needs to be revised.

How to fix it

Code examples

Noncompliant code example

public void Method()
{
    var values = new List<string>();
    values.Remove("bar");              // Noncompliant
    if (values.Contains("foo")) { }    // Noncompliant
    foreach (var str in values) { }    // Noncompliant
}

Compliant solution

public void Method()
{
    var values = LoadValues();
    values.Remove("bar");
    if (values.Contains("foo")) { }
    foreach (var str in values) { }
}
================================================ FILE: analyzers/rspec/cs/S4158.json ================================================ { "title": "Empty collections should not be accessed or iterated", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4158", "sqKey": "S4158", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4159.html ================================================

Why is this an issue?

The Attributed Programming Model, also known as Attribute-oriented programming (@OP), is a programming model used to embed attributes within codes.

In this model, objects are required to conform to a specific structure so that they can be used by the Managed Extensibility Framework (MEF).

MEF provides a way to discover available components implicitly, via composition. A MEF component, called a part, declaratively specifies:

The ExportAttribute declares that a part "exports", or provides to the composition container, an object that fulfills a particular contract.

During composition, parts with imports that have matching contracts will have those dependencies filled by the exported object.

If the type doesn’t implement the interface it is exporting there will be an issue at runtime (either a cast exception or just a container not filled with the exported type) leading to unexpected behaviors/crashes.

The rule raises an issue when a class doesn’t implement or inherit the type declared in the ExportAttribute.

How to fix it

Code examples

Noncompliant code example

[Export(typeof(ISomeType))]
public class SomeType // Noncompliant: doesn't implement 'ISomeType'.
{
}

Compliant solution

[Export(typeof(ISomeType))]
public class SomeType : ISomeType
{
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4159.json ================================================ { "title": "Classes should implement their \"ExportAttribute\" interfaces", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-4159", "sqKey": "S4159", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4200.html ================================================

Why is this an issue?

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

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
  class Foo
  {
    [DllImport("mynativelib")]
    extern public static void Bar(string s, int x); // Noncompliant
  }
}

Compliant solution

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
  class Foo
  {
    [DllImport("mynativelib")]
    extern private static void Bar(string s, int x);

    public void BarWrapper(string s, int x)
    {
      if (s != null && x >= 0  && x < s.Length)
      {
        Bar(s, x);
      }
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4200.json ================================================ { "title": "Native methods should be wrapped", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4200", "sqKey": "S4200", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4201.html ================================================

Why is this an issue?

There’s no need to null test in conjunction with an is test. null is not an instance of anything, so a null check is redundant.

Noncompliant code example

if (x != null && x is MyClass) { ... }  // Noncompliant

if (x == null || !(x is MyClass)) { ... } // Noncompliant

Compliant solution

if (x is MyClass) { ... }

if (!(x is MyClass)) { ... }
================================================ FILE: analyzers/rspec/cs/S4201.json ================================================ { "title": "Null checks should not be combined with \"is\" operator checks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "redundant" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4201", "sqKey": "S4201", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S4210.html ================================================

Why is this an issue?

When an assembly uses Windows Forms (classes and interfaces from the System.Windows.Forms namespace) its entry point should be marked with the STAThreadAttribute to indicate that the threading model should be "Single-Threaded Apartment" (STA) which is the only one supported by Windows Forms.

This rule raises an issue when the entry point (static void Main method) of an assembly using Windows Forms is not marked as STA.

Noncompliant code example

using System;
using System.Windows.Forms;

namespace MyLibrary
{
    public class MyForm: Form
    {
        public MyForm()
        {
            this.Text = "Hello World!";
        }

        public static void Main()  // Noncompliant
        {
            var form = new MyForm();
            Application.Run(form);
        }
    }
}

Compliant solution

using System;
using System.Windows.Forms;

namespace MyLibrary
{
    public class MyForm: Form
    {
        public MyForm()
        {
            this.Text = "Hello World!";
        }

        [STAThread]
        public static void Main()
        {
            var form = new MyForm();
            Application.Run(form);
        }
    }
}
================================================ FILE: analyzers/rspec/cs/S4210.json ================================================ { "title": "Windows Forms entry points should be marked with STAThread", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "winforms", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4210", "sqKey": "S4210", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4211.html ================================================

Transparency attributes in the .NET Framework, designed to protect security-critical operations, can lead to ambiguities and vulnerabilities when declared at different levels such as both for the class and a method.

Why is this an issue?

Transparency attributes can be declared at several levels. If two different attributes are declared at two different levels, the attribute that prevails is the one in the highest level. For example, you can declare that a class is SecuritySafeCritical and that a method of this class is SecurityCritical. In this case, the method will be SecuritySafeCritical and the SecurityCritical attribute attached to it is ignored.

What is the potential impact?

Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.

Elevation of Privileges

An attacker could potentially exploit conflicting transparency attributes to perform actions with higher privileges than intended.

Data Exposure

If a member with conflicting attributes is involved in handling sensitive data, an attacker could exploit the vulnerability to gain unauthorized access to this data. This could lead to breaches of confidentiality and potential data loss.

How to fix it

Code examples

Noncompliant code example

using System;
using System.Security;

namespace MyLibrary
{
    [SecuritySafeCritical]
    public class Foo
    {
        [SecurityCritical] // Noncompliant
        public void Bar()
        {
        }
    }
}

Compliant solution

using System;
using System.Security;

namespace MyLibrary
{
    public class Foo
    {
        [SecurityCritical]
        public void Bar()
        {
        }
    }
}

How does this work?

Never set class-level annotations

A class should never have class-level annotations if some functions have different permission levels. Instead, make sure every function has its own correct annotation.

If no function needs a particularly distinct security annotation in a class, just set a class-level [SecurityCritical].

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S4211.json ================================================ { "title": "Members should not have conflicting transparency annotations", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4211", "sqKey": "S4211", "scope": "Main", "securityStandards": { "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "CWE": [ 269 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4212.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

Because serialization constructors allocate and initialize objects, security checks that are present on regular constructors must also be present on a serialization constructor. Failure to do so would allow callers that could not otherwise create an instance to use the serialization constructor to do this.

This rule raises an issue when a type implements the System.Runtime.Serialization.ISerializable interface, is not a delegate or interface, is declared in an assembly that allows partially trusted callers and has a constructor that takes a System.Runtime.Serialization.SerializationInfo object and a System.Runtime.Serialization.StreamingContext object which is not secured by a security check, but one or more of the regular constructors in the type is secured.

Noncompliant code example

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;

[assembly: AllowPartiallyTrustedCallersAttribute()]
namespace MyLibrary
{
    [Serializable]
    public class Foo : ISerializable
    {
        private int n;

        [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
        public Foo()
        {
           n = -1;
        }

        protected Foo(SerializationInfo info, StreamingContext context) // Noncompliant
        {
           n = (int)info.GetValue("n", typeof(int));
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
           info.AddValue("n", n);
        }
    }
}

Compliant solution

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;

[assembly: AllowPartiallyTrustedCallersAttribute()]
namespace MyLibrary
{
    [Serializable]
    public class Foo : ISerializable
    {
        private int n;

        [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
        public Foo()
        {
           n = -1;
        }

        [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)]
        protected Foo(SerializationInfo info, StreamingContext context)
        {
           n = (int)info.GetValue("n", typeof(int));
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
           info.AddValue("n", n);
        }
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S4212.json ================================================ { "title": "Serialization constructors should be secured", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "serialization" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4212", "sqKey": "S4212", "scope": "Main", "securityStandards": { "OWASP": [ "A8" ], "OWASP Top 10 2021": [ "A8" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4214.html ================================================

This rule is deprecated; use {rule:csharpsquid:S4200} instead.

Why is this an issue?

Methods marked with the System.Runtime.InteropServices.DllImportAttribute attribute use Platform Invocation Services to access unmanaged code and should not be exposed. Keeping them private or internal makes sure that their access is controlled and properly managed.

This rule raises an issue when a method declared with DllImport is public or protected.

Noncompliant code example

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
    public class Foo
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern bool RemoveDirectory(string name);  // Noncompliant
    }
}

Compliant solution

using System;
using System.Runtime.InteropServices;

namespace MyLibrary
{
    public class Foo
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        private static extern bool RemoveDirectory(string name);
    }
}
================================================ FILE: analyzers/rspec/cs/S4214.json ================================================ { "title": "\"P\/Invoke\" methods should not be visible", "type": "CODE_SMELL", "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4214", "sqKey": "S4214", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4220.html ================================================

Why is this an issue?

When raising an event, two arguments are expected by the EventHandler delegate: Sender and event-data. There are three guidelines regarding these parameters:

This rule raises an issue when any of these guidelines is not met.

Noncompliant code example

using System;

namespace MyLibrary
{
  class Foo
  {
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(null, e); // Noncompliant
    }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  class Foo
  {
    public event EventHandler ThresholdReached;

    protected virtual void OnThresholdReached(EventArgs e)
    {
        ThresholdReached?.Invoke(this, e);
    }
  }
}
================================================ FILE: analyzers/rspec/cs/S4220.json ================================================ { "title": "Events should have proper arguments", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "event", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4220", "sqKey": "S4220", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4225.html ================================================

Why is this an issue?

Creating an extension method that extends object is not recommended because it makes the method available on every type. Extensions should be applied at the most specialized level possible, and that is very unlikely to be object.

Noncompliant code example

public static class MyExtensions
{
    public static void SomeExtension(this object obj) // Noncompliant
    {
        // ...
    }
}
================================================ FILE: analyzers/rspec/cs/S4225.json ================================================ { "title": "Extension methods should not extend \"object\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4225", "sqKey": "S4225", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4226.html ================================================

Why is this an issue?

It makes little sense to create an extension method when it is possible to just add that method to the class itself.

This rule raises an issue when an extension is declared in the same namespace as the class it is extending.

Noncompliant code example

namespace MyLibrary
{
    public class Foo
    {
        // ...
    }

    public static class MyExtensions
    {
        public static void Bar(this Foo a) // Noncompliant
        {
            // ...
        }
    }
}

Compliant solution

Using separate namespace:

namespace MyLibrary
{
    public class Foo
    {
        // ...
    }
}

namespace Helpers
{
    public static class MyExtensions
    {
        public static void Bar(this Foo a)
        {
            // ...
        }
    }
}

Merging the method in the class:

namespace MyLibrary
{
    public class Foo
    {
        // ...
        public void Bar()
        {
            // ...
        }
    }
}

Exceptions

================================================ FILE: analyzers/rspec/cs/S4226.json ================================================ { "title": "Extensions should be in separate namespaces", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "confusing" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4226", "sqKey": "S4226", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4260.html ================================================

Why is this an issue?

When creating a custom Markup Extension that accepts parameters in WPF, the ConstructorArgument markup must be used to identify the discrete properties that match these parameters. However since this is done via a string, the compiler won’t give you any warning in case there are typos.

This rule raises an issue when the string argument to ConstructorArgumentAttribute doesn’t match any parameter of any constructor.

How to fix it

Code examples

Noncompliant code example

using System;

namespace MyLibrary
{
  public class MyExtension : MarkupExtension
  {
    public MyExtension() { }

    public MyExtension(object value1)
    {
      Value1 = value1;
    }

    [ConstructorArgument("value2")]   // Noncompliant
    public object Value1 { get; set; }
  }
}

Compliant solution

using System;

namespace MyLibrary
{
  public class MyExtension : MarkupExtension
  {
    public MyExtension() { }

    public MyExtension(object value1)
    {
      Value1 = value1;
    }

    [ConstructorArgument("value1")]
    public object Value1 { get; set; }
  }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4260.json ================================================ { "title": "\"ConstructorArgument\" parameters should exist in constructors", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "xaml", "wpf" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4260", "sqKey": "S4260", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4261.html ================================================

Why is this an issue?

According to the Task-based Asynchronous Pattern (TAP), methods returning either a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult> are considered "asynchronous". Such methods should use the Async suffix. Conversely methods which do not return such Tasks should not have an "Async" suffix in their names.

Noncompliant code example

using System;
using  System.Threading.Tasks;

namespace myLibrary
{

  public class Foo
  {
    public Task Read(byte [] buffer, int offset, int count, // Noncompliant
                                CancellationToken cancellationToken)
  }
}

Compliant solution

using System;
using  System.Threading.Tasks;

namespace myLibrary
{

  public class Foo
  {
    public Task ReadAsync(byte [] buffer, int offset, int count, CancellationToken cancellationToken)
  }
}

Exceptions

This rule doesn’t raise an issue when the method is an override or part of the implementation of an interface since it can not be renamed.

Resources

================================================ FILE: analyzers/rspec/cs/S4261.json ================================================ { "title": "Methods should be named according to their synchronicities", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4261", "sqKey": "S4261", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4275.html ================================================

Why is this an issue?

Properties provide a way to enforce encapsulation by providing accessors that give controlled access to private fields. However, in classes with multiple fields, it is not unusual that copy-and-paste is used to quickly create the needed properties, which can result in the wrong field being accessed by a getter or setter.

class C
{
    private int x;
    private int y;
    public int Y => x; // Noncompliant: The returned field should be 'y'
}

This rule raises an issue in any of these cases:

For simple properties, it is better to use auto-implemented properties (C# 3.0 or later).

Field and property names are compared as case-insensitive. All underscore characters are ignored.

How to fix it

Code examples

Noncompliant code example

class A
{
    private int x;
    private int y;

    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return x; }  // Noncompliant: field 'y' is not used in the return value
        set { x = value; } // Noncompliant: field 'y' is not updated
    }
}

Compliant solution

class A
{
    private int x;
    private int y;

    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return y; }
        set { y = value; }
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S4275.json ================================================ { "title": "Getters and setters should access the expected fields", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4275", "sqKey": "S4275", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4277.html ================================================

Why is this an issue?

Marking a class with PartCreationPolicy(CreationPolicy.Shared), which is part of Managed Extensibility Framework (MEF), means that a single, shared instance of the exported object will be created. Therefore it doesn’t make sense to create new instances using the constructor and it will most likely result in unexpected behaviours.

This rule raises an issue when a constructor of a class marked shared with a PartCreationPolicyAttribute is invoked.

How to fix it

Code examples

Noncompliant code example

[Export(typeof(IFooBar))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class FooBar : IFooBar
{
}

public class Program
{
    public static void Main()
    {
        var fooBar = new FooBar(); // Noncompliant;
    }
}

Compliant solution

[Export(typeof(IFooBar))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class FooBar : IFooBar
{
}

public class Program
{
    public static void Main()
    {
        var fooBar = serviceProvider.GetService<IFooBar>();
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4277.json ================================================ { "title": "\"Shared\" parts should not be created with \"new\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4277", "sqKey": "S4277", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4347.html ================================================

Cryptographic operations often rely on unpredictable random numbers to enhance security. These random numbers are created by cryptographically secure pseudo-random number generators (CSPRNG). It is important not to use a predictable seed with these random number generators otherwise the random numbers will also become predictable.

Why is this an issue?

Random number generators are often used to generate random values for cryptographic algorithms. When a random number generator is used for cryptographic purposes, the generated numbers must be as random and unpredictable as possible. When the random number generator is improperly seeded with a constant or a predictable value, its output will also be predictable.

This can have severe security implications for cryptographic operations that rely on the randomness of the generated numbers. By using a predictable seed, an attacker can potentially guess or deduce the generated numbers, compromising the security of whatever cryptographic algorithm relies on the random number generator.

What is the potential impact?

It is crucial to understand that the strength of cryptographic algorithms heavily relies on the quality of the random numbers used. By improperly seeding a CSPRNG, we introduce a significant weakness that can be exploited by attackers.

Insecure cryptographic keys

One of the primary use cases for CSPRNGs is generating cryptographic keys. If an attacker can predict the seed used to initialize the random number generator, they may be able to derive the same keys. Depending on the use case, this can lead to multiple severe outcomes, such as:

Session hijacking and man-in-the-middle attack

Another scenario where this vulnerability can be exploited is in the generation of session tokens or nonces for secure communication protocols. If an attacker can predict the seed used to generate these tokens, they can impersonate legitimate users or intercept sensitive information.

How to fix it in BouncyCastle

BouncyCastle provides several random number generators implementations. Most of these will automatically create unpredictable output.

The remaining random number generators require seeding with an unpredictable value before they will produce unpredictable outputs. These should be seeded with at least 16 bytes of random data to ensure unpredictable output and that the random seed cannot be guessed using a brute-force attack.

Code examples

Noncompliant code example

SecureRandom instances created with GetInstance() are seeded by default. Disabling seeding results in predictable output.

using Org.BouncyCastle.Security;

byte[] random = new byte[8];

SecureRandom sr = SecureRandom.GetInstance("SHA256PRNG", false);
sr.NextBytes(random); // Noncompliant

DigestRandomGenerator and VmpcRandomGenerator instances require seeding before use. Predictable seed values will result in predictable outputs.

using Org.BouncyCastle.Crypto.Digest;
using Org.BouncyCastle.Crypto.Prng;

byte[] random = new byte[8];

IRandomGenerator digest = new DigestRandomGenerator(new Sha256Digest());
digest.AddSeedMaterial(Encoding.UTF8.GetBytes("predictable seed value"));
digest.NextBytes(random); // Noncompliant

IRandomGenerator vmpc = new VmpcRandomGenerator();
vmpc.AddSeedMaterial(Convert.FromBase64String("2hq9pkyqLQJkrYTrEv1eNw=="));
vmpc.NextBytes(random); // Noncompliant

When a SecureRandom is created using an unseeded DigestRandomGenerator and VmpcRandomGenerator instance, the SecureRandom will create predictable outputs.

using Org.BouncyCastle.Crypto.Digest;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Security;

byte[] random = new byte[8];

IRandomGenerator digest = new DigestRandomGenerator(new Sha256Digest());
SecureRandom sr = new SecureRandom(digest);
sr.NextBytes(random); // Noncompliant

Compliant solution

Allow SecureRandom.GetInstance() to automatically seed new SecureRandom instances.

using Org.BouncyCastle.Security;

byte[] random = new byte[8];

SecureRandom sr = SecureRandom.GetInstance("SHA256PRNG");
sr.NextBytes(random);

Use unpredictable values to seed DigestRandomGenerator and VmpcRandomGenerator instances. The SecureRandom.GenerateSeed() method is designed for this purpose.

using Org.BouncyCastle.Crypto.Digest;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Security;

byte[] random = new byte[8];

IRandomGenerator digest = new DigestRandomGenerator(new Sha256Digest());
digest.AddSeedMaterial(SecureRandom.GenerateSeed(16));
digest.NextBytes(random);

IRandomGenerator vmpc = new VmpcRandomGenerator();
vmpc.AddSeedMaterial(SecureRandom.GenerateSeed(16));
vmpc.NextBytes(random);

An overload of the SecureRandom constructor will automatically seed the underlying IRandomGenerator with an unpredictable value.

using Org.BouncyCastle.Crypto.Digest;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Security;

byte[] random = new byte[8];

IRandomGenerator digest = new DigestRandomGenerator(new Sha256Digest());
SecureRandom sr = new SecureRandom(digest, 16);
sr.NextBytes(random);

Resources

Documentation

Standards

Implementation Specification

(visible only on this page)

Message

When the random number generator’s output is not predictable by default:

Change this seed value to something unpredictable, or remove the seed.

When the random number generator’s output is predictable by default:

Set an unpredictable seed before generating random values.

Highlighting

When the random number generator’s output is not predictable by default:

When the random number generator’s output is predictable by default:

If the factory method or constructor is not already highlighted, it should become a secondary highlight.

================================================ FILE: analyzers/rspec/cs/S4347.json ================================================ { "title": "Secure random number generators should not output predictable values", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "pitfall", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4347", "sqKey": "S4347", "scope": "Main", "securityStandards": { "CWE": [ 330, 332, 336, 337 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A2" ], "ASVS 4.0": [ "2.3.1", "2.6.2", "2.9.2" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4423.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

To provide communication security over a network, SSL and TLS are generally used. However, it is important to note that the following protocols are all considered weak by the cryptographic community, and are officially deprecated:

When these unsecured protocols are used, it is best practice to expect a breach: that a user or organization with malicious intent will perform mathematical attacks on this data after obtaining it by other means.

What is the potential impact?

After retrieving encrypted data and performing cryptographic attacks on it on a given timeframe, attackers can recover the plaintext that encryption was supposed to protect.

Depending on the recovered data, the impact may vary.

Below are some real-world scenarios that illustrate the potential impact of an attacker exploiting the vulnerability.

Additional attack surface

By modifying the plaintext of the encrypted message, an attacker may be able to trigger additional vulnerabilities in the code. An attacker can further exploit a system to obtain more information.
Encrypted values are often considered trustworthy because it would not be possible for a third party to modify them under normal circumstances.

Breach of confidentiality and privacy

When encrypted data contains personal or sensitive information, its retrieval by an attacker can lead to privacy violations, identity theft, financial loss, reputational damage, or unauthorized access to confidential systems.

In this scenario, the company, its employees, users, and partners could be seriously affected.

The impact is twofold, as data breaches and exposure of encrypted data can undermine trust in the organization, as customers, clients and stakeholders may lose confidence in the organization’s ability to protect their sensitive data.

Legal and compliance issues

In many industries and locations, there are legal and compliance requirements to protect sensitive data. If encrypted data is compromised and the plaintext can be recovered, companies face legal consequences, penalties, or violations of privacy laws.

How to fix it in .NET

Code examples

Noncompliant code example

These samples use TLSv1.0 as the default TLS algorithm, which is cryptographically weak.

using System.Net;

public void encrypt()
{
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; // Noncompliant
}
using System.Net.Http;
using System.Security.Authentication;

public void encrypt()
{
    new HttpClientHandler
    {
        SslProtocols = SslProtocols.Tls // Noncompliant
    };
}

Compliant solution

Using System.Net;

public void encrypt()
{
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
}
using System.Net.Http;
using System.Security.Authentication;

public void encrypt()
{
    new HttpClientHandler
    {
        SslProtocols = SslProtocols.Tls12
    };
}

How does this work?

As a rule of thumb, by default you should use the cryptographic algorithms and mechanisms that are considered strong by the cryptographic community.

The best choices at the moment are the following.

Use TLS v1.2 or TLS v1.3

Even though TLS V1.3 is available, using TLS v1.2 is still considered good and secure practice by the cryptography community.

The use of TLS v1.2 ensures compatibility with a wide range of platforms and enables seamless communication between different systems that do not yet have TLS v1.3 support.

The only drawback depends on whether the framework used is outdated: its TLS v1.2 settings may enable older and insecure cipher suites that are deprecated as insecure.

On the other hand, TLS v1.3 removes support for older and weaker cryptographic algorithms, eliminates known vulnerabilities from previous TLS versions, and improves performance.

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S4423.json ================================================ { "title": "Weak SSL\/TLS protocols should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4423", "sqKey": "S4423", "scope": "Main", "securityStandards": { "CWE": [ 327, 326, 295 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2", "A7" ], "PCI DSS 3.2": [ "4.1", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "8.3.7", "9.1.2", "9.1.3" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4426.html ================================================

This vulnerability exposes encrypted data to attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

In today’s cryptography, the length of the key directly affects the security level of cryptographic algorithms.

Note that depending on the algorithm, the term key refers to a different mathematical property. For example:

If an application uses a key that is considered short and insecure, the encrypted data is exposed to attacks aimed at getting at the plaintext.

In general, it is best practice to expect a breach: that a user or organization with malicious intent will perform cryptographic attacks on this data after obtaining it by other means.

What is the potential impact?

After retrieving encrypted data and performing cryptographic attacks on it on a given timeframe, attackers can recover the plaintext that encryption was supposed to protect.

Depending on the recovered data, the impact may vary.

Below are some real-world scenarios that illustrate the potential impact of an attacker exploiting the vulnerability.

Additional attack surface

By modifying the plaintext of the encrypted message, an attacker may be able to trigger additional vulnerabilities in the code. An attacker can further exploit a system to obtain more information.
Encrypted values are often considered trustworthy because it would not be possible for a third party to modify them under normal circumstances.

Breach of confidentiality and privacy

When encrypted data contains personal or sensitive information, its retrieval by an attacker can lead to privacy violations, identity theft, financial loss, reputational damage, or unauthorized access to confidential systems.

In this scenario, the company, its employees, users, and partners could be seriously affected.

The impact is twofold, as data breaches and exposure of encrypted data can undermine trust in the organization, as customers, clients and stakeholders may lose confidence in the organization’s ability to protect their sensitive data.

Legal and compliance issues

In many industries and locations, there are legal and compliance requirements to protect sensitive data. If encrypted data is compromised and the plaintext can be recovered, companies face legal consequences, penalties, or violations of privacy laws.

How to fix it in .NET

Code examples

The following code examples either explicitly or implicitly generate keys. Note that there are differences in the size of the keys depending on the algorithm.

Due to the mathematical properties of the algorithms, the security requirements for the key size vary depending on the algorithm.
For example, a 256-bit ECC key provides about the same level of security as a 3072-bit RSA key and a 128-bit symmetric key.

Noncompliant code example

Here is an example of a private key generation with RSA:

using System;
using System.Security.Cryptography;

public void encrypt()
{
    var RsaCsp = new RSACryptoServiceProvider(); // Noncompliant
}

Here is an example of a key generation with the Digital Signature Algorithm (DSA):

using System;
using System.Security.Cryptography;

public void encrypt()
{
    var DsaCsp = new DSACryptoServiceProvider(); // Noncompliant
}

Here is an example of an Elliptic Curve (EC) initialization. It implicitly generates a private key whose size is indicated in the elliptic curve name:

using System;
using System.Security.Cryptography;

public void encrypt()
{
    ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.brainpoolP160t1); // Noncompliant
}

Compliant solution

using System;
using System.Security.Cryptography;

public void encrypt()
{
    var RsaCsp = new RSACryptoServiceProvider(2048);
}
using System;
using System.Security.Cryptography;

public void encrypt()
{
    var Dsa = new DSACng(2048);
}
using System;
using System.Security.Cryptography;

public void encrypt()
{
    ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
}

How does this work?

As a rule of thumb, use the cryptographic algorithms and mechanisms that are considered strong by the cryptography community.

The appropriate choices are the following.

RSA (Rivest-Shamir-Adleman) and DSA (Digital Signature Algorithm)

The security of these algorithms depends on the difficulty of attacks attempting to solve their underlying mathematical problem.

In general, a minimum key size of 2048 bits is recommended for both. It provides 112 bits of security. A key length of 3072 or 4096 should be preferred when possible.

Elliptic Curve Cryptography (ECC)

Elliptic curve cryptography is also used in various algorithms, such as ECDSA, ECDH, or ECMQV. The length of keys generated with elliptic curve algorithms is mentioned directly in their names. For example, secp256k1 generates a 256-bits long private key.

Currently, a minimum key size of 224 bits is recommended for EC-based algorithms.

Additionally, some curves that theoretically provide sufficiently long keys are still discouraged. This can be because of a flaw in the curve parameters, a bad overall design, or poor performance. It is generally advised to use a NIST-approved elliptic curve wherever possible. Such curves currently include:

Pitfalls

The KeySize Property is not a setter

The following code is invalid:

 ----
     var RsaCsp = new RSACryptoServiceProvider();
     RsaCsp.KeySize = 2048;
----

The KeySize property of CryptoServiceProviders cannot be updated because the setter simply does not exist. This means that this line will not perform any update on KeySize, and the compiler won’t raise an Exception when compiling it. This should not be considered a workaround.
To change the key size, use one of the overloaded constructors with the desired key size instead.

Going the extra mile

Pre-Quantum Cryptography

Encrypted data and communications recorded today could be decrypted in the future by an attack from a quantum computer.
It is important to keep in mind that NIST-approved digital signature schemes, key agreement, and key transport may need to be replaced with secure quantum-resistant (or "post-quantum") counterpart.

Thus, if data is to remain secure beyond 2030, proactive measures should be taken now to ensure its safety.

Learn more here.

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S4426.json ================================================ { "title": "Cryptographic keys should be robust", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4426", "sqKey": "S4426", "scope": "Main", "securityStandards": { "CWE": [ 326 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2" ], "ASVS 4.0": [ "6.2.3" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4428.html ================================================

Why is this an issue?

To customize the default behavior for an export in the Managed Extensibility Framework (MEF), applying the PartCreationPolicyAttribute is necessary. For the PartCreationPolicyAttribute to be meaningful in the context of an export, the class must also be annotated with the ExportAttribute.

This rule raises an issue when a class is annotated with the PartCreationPolicyAttribute but not with the ExportAttribute.

How to fix it

Code examples

Noncompliant code example

using System.ComponentModel.Composition;

[PartCreationPolicy(CreationPolicy.Any)] // Noncompliant
public class FooBar : IFooBar { }

Compliant solution

using System.ComponentModel.Composition;

[Export(typeof(IFooBar))]
[PartCreationPolicy(CreationPolicy.Any)]
public class FooBar : IFooBar { }

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S4428.json ================================================ { "title": "\"PartCreationPolicyAttribute\" should be used with \"ExportAttribute\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4428", "sqKey": "S4428", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4433.html ================================================

Lightweight Directory Access Protocol (LDAP) servers provide two main authentication methods: the SASL and Simple ones. The Simple Authentication method also breaks down into three different mechanisms:

A server that accepts either the Anonymous or Unauthenticated mechanisms will accept connections from clients not providing credentials.

Why is this an issue?

When configured to accept the Anonymous or Unauthenticated authentication mechanism, an LDAP server will accept connections from clients that do not provide a password or other authentication credentials. Such users will be able to read or modify part or all of the data contained in the hosted directory.

What is the potential impact?

An attacker exploiting unauthenticated access to an LDAP server can access the data that is stored in the corresponding directory. The impact varies depending on the permission obtained on the directory and the type of data it stores.

Authentication bypass

If attackers get write access to the directory, they will be able to alter most of the data it stores. This might include sensitive technical data such as user passwords or asset configurations. Such an attack can typically lead to an authentication bypass on applications and systems that use the affected directory as an identity provider.

In such a case, all users configured in the directory might see their identity and privileges taken over.

Sensitive information leak

If attackers get read-only access to the directory, they will be able to read the data it stores. That data might include security-sensitive pieces of information.

Typically, attackers might get access to user account lists that they can use in further intrusion steps. For example, they could use such lists to perform password spraying, or related attacks, on all systems that rely on the affected directory as an identity provider.

If the directory contains some Personally Identifiable Information, an attacker accessing it might represent a violation of regulatory requirements in some countries. For example, this kind of security event would go against the European GDPR law.

How to fix it

Code examples

The following code indicates an anonymous LDAP authentication vulnerability because it binds to a remote server using an Anonymous Simple authentication mechanism.

Noncompliant code example

DirectoryEntry myDirectoryEntry = new DirectoryEntry(adPath);
myDirectoryEntry.AuthenticationType = AuthenticationTypes.None; // Noncompliant

DirectoryEntry myDirectoryEntry = new DirectoryEntry(adPath, "u", "p", AuthenticationTypes.None); // Noncompliant

Compliant solution

DirectoryEntry myDirectoryEntry = new DirectoryEntry(myADSPath); // Compliant; default DirectoryEntry.AuthenticationType property value is "Secure" since .NET Framework 2.0

DirectoryEntry myDirectoryEntry = new DirectoryEntry(myADSPath, "u", "p", AuthenticationTypes.Secure);

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S4433.json ================================================ { "title": "LDAP connections should be authenticated", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4433", "sqKey": "S4433", "scope": "Main", "securityStandards": { "CWE": [ 521 ], "OWASP": [ "A2" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "9.2.2", "9.2.3" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4456.html ================================================

Why is this an issue?

Because of the way yield methods are rewritten by the compiler (they become lazily evaluated state machines) any exceptions thrown during the parameters check will happen only when the collection is iterated over. That could happen far away from the source of the buggy code.

Therefore it is recommended to split the method into two: an outer method handling the validation (no longer lazy) and an inner (lazy) method to handle the iteration.

This rule raises an issue when a method throws any exception derived from ArgumentException and contains the yield keyword.

Noncompliant code example

public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) // Noncompliant
{
    if (source == null) { throw new ArgumentNullException(nameof(source)); }
    if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }

    foreach (var element in source)
    {
        if (!predicate(element)) { break; }
        yield return element;
    }
}
public static async IAsyncEnumerable<TSource> TakeWhileAsync(this IEnumerable<TSource> source, Func<TSource, bool> predicate) // Noncompliant
{
    if (source == null) { throw new ArgumentNullException(nameof(source)); }
    if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }

    foreach (var element in source)
    {
        await Task.Delay(10);
        if (!predicate(element)) { break; }
        yield return element;
    }
}

Compliant solution

public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) { throw new ArgumentNullException(nameof(source)); }
    if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }
    return TakeWhileIterator<TSource>(source, predicate);
}

private static IEnumerable<TSource> TakeWhileIterator<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    foreach (TSource element in source)
    {
        if (!predicate(element)) break;
        yield return element;
    }
}
public static IAsyncEnumerable<TSource> TakeWhileAsync(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) { throw new ArgumentNullException(nameof(source)); }
    if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); }

    return TakeWhileAsyncIterator<TSource>(source, predicate);
}

private static async IAsyncEnumerable<TSource> TakeWhileAsyncIterator<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    foreach (TSource element in source)
    {
        await Task.Delay(10);
        if (!predicate(element)) break;
        yield return element;
    }
}

Resources

Related rules

================================================ FILE: analyzers/rspec/cs/S4456.json ================================================ { "title": "Parameter validation in yielding methods should be wrapped", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "yield" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4456", "sqKey": "S4456", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4457.html ================================================

Why is this an issue?

Because of the way async/await methods are rewritten by the compiler, any exceptions thrown during the parameters check are captured in the returned Task and will only be observed when the task is awaited. That could happen far away from the source of the buggy code or never happen for fire-and-forget tasks.

Therefore it is recommended to split the method into two: an outer method handling the parameter checks (without being async/await) and an inner method to handle the iterator block with the async/await pattern.

This rule raises an issue when an async method throws any exception derived from ArgumentException and contains the await keyword.

Noncompliant code example

public static async Task SkipLinesAsync(this TextReader reader, int linesToSkip) // Noncompliant
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}

var task = SkipLinesAsync(null, -1); // No exception - captured in the task
await task;                          // ArgumentNullException thrown here

Compliant solution

public static Task SkipLinesAsync(this TextReader reader, int linesToSkip)
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    return reader.SkipLinesInternalAsync(linesToSkip);
}

private static async Task SkipLinesInternalAsync(this TextReader reader, int linesToSkip)
{
    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}

var task = SkipLinesAsync(null, -1); // ArgumentNullException thrown here
await task;

Resources

Related rules

================================================ FILE: analyzers/rspec/cs/S4457.json ================================================ { "title": "Parameter validation in \"async\"\/\"await\" methods should be wrapped", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "async-await" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4457", "sqKey": "S4457", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4462.html ================================================

Why is this an issue?

Making blocking calls to async methods transforms code that was intended to be asynchronous into a blocking operation. Doing so can cause deadlocks and unexpected blocking of context threads.

According to the MSDN documentation:

The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.

To Do This … Instead of This … Use This

Retrieve the result of a background task

Task.Wait, Task.Result or Task.GetAwaiter.GetResult

await

Wait for any task to complete

Task.WaitAny

await Task.WhenAny

Retrieve the results of multiple tasks

Task.WaitAll

await Task.WhenAll

Wait a period of time

Thread.Sleep

await Task.Delay

Noncompliant code example

public static class DeadlockDemo
{
    private static async Task DelayAsync()
    {
        await Task.Delay(1000);
    }

    // This method causes a deadlock when called in a GUI or ASP.NET context.
    public static void Test()
    {
        // Start the delay.
        var delayTask = DelayAsync();
        // Wait for the delay to complete.
        delayTask.Wait(); // Noncompliant
    }
}

Compliant solution

public static class DeadlockDemo
{
    private static async Task DelayAsync()
    {
        await Task.Delay(1000);
    }

    public static async Task TestAsync()
    {
        // Start the delay.
        var delayTask = DelayAsync();
        // Wait for the delay to complete.
        await delayTask;
    }
}

Exceptions

Resources

================================================ FILE: analyzers/rspec/cs/S4462.json ================================================ { "title": "Calls to \"async\" methods should not be blocking", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "async-await", "deadlock" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-4462", "sqKey": "S4462", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4487.html ================================================

Why is this an issue?

Private fields which are written but never read are a case of "dead store". Changing the value of such a field is useless and most probably indicates an error in the code.

public class Rectangle
{
  private readonly int length;
  private readonly int width;  // Noncompliant: width is written but never read

  public Rectangle(int length, int width)
  {
    this.length = length;
    this.width = width;
  }

  public int Surface
  {
    get
    {
      return length * length;
    }
  }
}

Remove this field if it doesn’t need to be read, or fix the code to read it.

public class Rectangle
{
  private readonly int length;
  private readonly int width;

  public Rectangle(int length, int width)
  {
    this.length = length;
    this.width = width;
  }

  public int Surface
  {
    get
    {
      return length * width;
    }
  }
}

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S4487.json ================================================ { "title": "Unread \"private\" fields should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "unused" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4487", "sqKey": "S4487", "scope": "All", "securityStandards": { "CWE": [ 563 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4502.html ================================================

A cross-site request forgery (CSRF) attack occurs when a trusted user of a web application can be forced, by an attacker, to perform sensitive actions that he didn’t intend, such as updating his profile or sending a message, more generally anything that can change the state of the application.

The attacker can trick the user/victim to click on a link, corresponding to the privileged action, or to visit a malicious web site that embeds a hidden web request and as web browsers automatically include cookies, the actions can be authenticated and sensitive.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddControllersWithViews(options => options.Filters.Add(new IgnoreAntiforgeryTokenAttribute())); // Sensitive
    // ...
}
[HttpPost, IgnoreAntiforgeryToken] // Sensitive
public IActionResult ChangeEmail(ChangeEmailModel model) => View("~/Views/...");

Compliant Solution

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddControllersWithViews(options => options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));
    // or
    services.AddControllersWithViews(options => options.Filters.Add(new ValidateAntiForgeryTokenAttribute()));
    // ...
}
[HttpPost]
[AutoValidateAntiforgeryToken]
public IActionResult ChangeEmail(ChangeEmailModel model) => View("~/Views/...");

See

================================================ FILE: analyzers/rspec/cs/S4502.json ================================================ { "title": "Disabling CSRF protections is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4502", "sqKey": "S4502", "scope": "Main", "securityStandards": { "CWE": [ 352 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.9" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "13.2.3", "4.2.2" ], "STIG ASD_V5R3": [ "V-222603" ] } } ================================================ FILE: analyzers/rspec/cs/S4507.html ================================================

Development tools and frameworks usually have options to make debugging easier for developers. Although these features are useful during development, they should never be enabled for applications deployed in production. Debug instructions or error messages can leak detailed information about the system, like the application’s path or file names.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Do not enable debugging features on production servers.

The .Net Core framework offers multiple features which help during debug. Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage and Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage are two of them. Make sure that those features are disabled in production.

Use if (env.IsDevelopment()) to disable debug code.

Sensitive Code Example

This rule raises issues when the following .Net Core methods are called: Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage, Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;

namespace mvcApp
{
    public class Startup2
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // Those calls are Sensitive because it seems that they will run in production
            app.UseDeveloperExceptionPage(); // Sensitive
            app.UseDatabaseErrorPage(); // Sensitive
        }
    }
}

Compliant Solution

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;

namespace mvcApp
{
    public class Startup2
    {
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                // The following calls are ok because they are disabled in production
                app.UseDeveloperExceptionPage(); // Compliant
                app.UseDatabaseErrorPage(); // Compliant
            }
        }
    }
}

Exceptions

This rule does not analyze configuration files. Make sure that debug mode is not enabled by default in those files.

See

================================================ FILE: analyzers/rspec/cs/S4507.json ================================================ { "title": "Delivering code in production with debug features activated is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "cwe", "error-handling", "debug", "user-experience" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4507", "sqKey": "S4507", "scope": "Main", "securityStandards": { "CWE": [ 489, 215 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A5" ], "ASVS 4.0": [ "14.3.2" ] } } ================================================ FILE: analyzers/rspec/cs/S4524.html ================================================

Why is this an issue?

The switch statement is a conditional statement that executes a sequence of instructions based on patterns matching the provided value.

switch (temperatureInCelsius)
{
    case < 35.0:
        Console.WriteLine("Hypothermia");
        break;
    case >= 36.5 and <= 37.5:
        Console.WriteLine("Normal");
        break;
    case > 37.5 and <= 40.0:
        Console.WriteLine("Fever or hyperthermia");
        break;
    case > 40.0:
        Console.WriteLine("Hyperpyrexia");
        break;
}

The switch statement can optionally contain a default clause, executed when none of the case clauses are executed (or in presence of a goto default;).

switch (gradeLetter)
{
    case "A+":
    case "A":
    case "A-":
        Console.WriteLine("Excellent");
        break;
    case "B+":
    case "B":
        Console.WriteLine("Very Good");
        break;
    case "B-":
    case "C+":
        Console.WriteLine("Good");
        break;
    case "C":
        Console.WriteLine("Pass");
        break;
    case "F":
        Console.WriteLine("Fail");
        break;
    default:
        Console.WriteLine("Invalid grade letter!");
        break;
}

The default clause can be defined for various reasons:

While C# allows the default clause to appear in any place within a switch statement, and while its position doesn’t alter its behavior, it is recommended to put the default clause either at the beginning or at the end of the switch statement.

That improves readability and helps the developer to quickly find the default behavior of a switch statement.

This rule raises an issue if the default clause is neither the first nor the last one of the switch statement.

How to fix it

Code examples

Noncompliant code example

switch (param)
{
    case 0:
        DoSomething();
        break;
    default: // Noncompliant: default clause should be the first or last one
        Error();
        break;
    case 1:
        DoSomethingElse();
        break;
}

Compliant solution

switch (param)
{
    case 0:
        DoSomething();
        break;
    case 1:
        DoSomethingElse();
        break;
    default:
        Error();
        break;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4524.json ================================================ { "title": "\"default\" clauses should be first or last", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4524", "sqKey": "S4524", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4545.html ================================================

Why is this an issue?

The DebuggerDisplayAttribute is used to determine how an object is displayed in the debugger window.

The DebuggerDisplayAttribute constructor takes a single mandatory argument: the string to be displayed in the value column for instances of the type. Any text within curly braces is evaluated as the name of a field or property, or any complex expression containing method calls and operators.

Naming a non-existent member between curly braces will result in a CS0103 error in the debug window when debugging objects. Although there is no impact on the production code, providing a wrong value can lead to difficulties when debugging the application.

This rule raises an issue when text specified between curly braces refers to members that don’t exist in the current context.

Noncompliant code example

[DebuggerDisplay("Name: {Name}")] // Noncompliant - Name doesn't exist in this context
public class Person
{
    public string FullName { get; private set; }
}

Compliant solution

[DebuggerDisplay("Name: {FullName}")]
public class Person
{
    public string FullName { get; private set; }
}
================================================ FILE: analyzers/rspec/cs/S4545.json ================================================ { "title": "\"DebuggerDisplayAttribute\" strings should reference existing members", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4545", "sqKey": "S4545", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4581.html ================================================

Why is this an issue?

When the syntax new Guid() (i.e. parameterless instantiation) is used, it must be that one of three things is wanted:

  1. An empty GUID, in which case Guid.Empty is clearer.
  2. A randomly-generated GUID, in which case Guid.NewGuid() should be used.
  3. A new GUID with a specific initialization, in which case the initialization parameter is missing.

This rule raises an issue when a parameterless instantiation of the Guid struct is found.

Noncompliant code example

public void Foo()
{
    var g1 = new Guid();    // Noncompliant - what's the intent?
    Guid g2 = new();        // Noncompliant
    var g3 = default(Guid); // Noncompliant
    Guid g4 = default;      // Noncompliant
}

Compliant solution

public void Foo(byte[] bytes)
{
    var g1 = Guid.Empty;
    var g2 = Guid.NewGuid();
    var g3 = new Guid(bytes);
}
================================================ FILE: analyzers/rspec/cs/S4581.json ================================================ { "title": "\"new Guid()\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4581", "sqKey": "S4581", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4583.html ================================================

Why is this an issue?

When calling the BeginInvoke method of a delegate, resources are allocated that are only freed up when EndInvoke is called. Failing to pair BeginInvoke with EndInvoke can lead to resource leaks and incomplete asynchronous calls.

This rule raises an issue in the following scenarios:

How to fix it

Code examples

Noncompliant code example

BeginInvoke without callback:

public delegate string AsyncMethodCaller();

public static void Main()
{
    AsyncExample asyncExample = new AsyncExample();
    AsyncMethodCaller caller = new AsyncMethodCaller(asyncExample.MyMethod);

    // Initiate the asynchronous call.
    IAsyncResult result = caller.BeginInvoke(null, null); // Noncompliant: not paired with EndInvoke
}

BeginInvoke with callback:

public delegate string AsyncMethodCaller();

public static void Main()
{
    AsyncExample asyncExample = new AsyncExample();
    AsyncMethodCaller caller = new AsyncMethodCaller(asyncExample.MyMethod);

    IAsyncResult result = caller.BeginInvoke(
        new AsyncCallback((IAsyncResult ar) => {}),
        null); // Noncompliant: not paired with EndInvoke
}

Compliant solution

BeginInvoke without callback:

public delegate string AsyncMethodCaller();

public static void Main()
{
    AsyncExample asyncExample = new AsyncExample();
    AsyncMethodCaller caller = new AsyncMethodCaller(asyncExample.MyMethod);

    IAsyncResult result = caller.BeginInvoke(null, null);

    string returnValue = caller.EndInvoke(result);
}

BeginInvoke with callback:

public delegate string AsyncMethodCaller();

public static void Main()
{
    AsyncExample asyncExample = new AsyncExample();
    AsyncMethodCaller caller = new AsyncMethodCaller(asyncExample.MyMethod);

    IAsyncResult result = caller.BeginInvoke(
        new AsyncCallback((IAsyncResult ar) =>
            {
                // Call EndInvoke to retrieve the results.
                string returnValue = caller.EndInvoke(ar);
            }), null);
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S4583.json ================================================ { "title": "Calls to delegate\u0027s method \"BeginInvoke\" should be paired with calls to \"EndInvoke\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4583", "sqKey": "S4583", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S4586.html ================================================

Why is this an issue?

Returning null from a non-async Task/Task<TResult> method will cause a NullReferenceException at runtime if the method is awaited. This problem can be avoided by returning Task.CompletedTask or Task.FromResult<TResult>(null) respectively.

public Task DoFooAsync()
{
    return null;               // Noncompliant: Causes a NullReferenceException if awaited.
}

public async Task Main()
{
    await DoFooAsync();        // NullReferenceException
}

How to fix it

Instead of null Task.CompletedTask or Task.FromResult<TResult>(null) should be returned.

Code examples

A Task returning method can be fixed like so:

Noncompliant code example

public Task DoFooAsync()
{
    return null;               // Noncompliant: Causes a NullReferenceException if awaited.
}

Compliant solution

public Task DoFooAsync()
{
    return Task.CompletedTask; // Compliant: Method can be awaited.
}

A Task<TResult> returning method can be fixed like so:

Noncompliant code example

public Task<object> GetFooAsync()
{
    return null;                          // Noncompliant: Causes a NullReferenceException if awaited.
}

Compliant solution

public Task<object> GetFooAsync()
{
    return Task.FromResult<object>(null); // Compliant: Method can be awaited.
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S4586.json ================================================ { "title": "Non-async \"Task\/Task\u003cT\u003e\" methods should not return null", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [ "async-await" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4586", "sqKey": "S4586", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4635.html ================================================

Why is this an issue?

It is important to be careful when searching for characters within a substring. Let’s consider the following example:

if (str.SubString(startIndex).IndexOf(char1) == -1) // Noncompliant: a new string is going to be created by "Substring"
{
   // ...
}

A new string object is created with every call to the Substring method. When chaining it with any of the following methods, it creates a new object for one-time use only:

What is the potential impact?

Creating a new object consumes significant system resources, especially CPU and memory. When using Substring repeatedly, such as in a loop, it can greatly impact the overall performance of the application or program.

To mitigate the creation of new objects while searching for characters within a substring, it is possible to use an overload of the mentioned methods with a startIndex parameter:

if (str.IndexOf(char1, startIndex) == -1)           // Compliant: no new instance of string is created
{
   // ...
}

Using these methods gives the same result while avoiding the creation of additional string objects.

Resources

Documentation

Benchmarks

Method Runtime StringSize Mean StdDev Ratio Allocated

SubstringThenIndexOf

.NET 7.0

10

11.234 ms

0.1319 ms

1.00

40000008 B

IndexOfOnly

.NET 7.0

10

2.477 ms

0.0473 ms

0.22

2 B

SubstringThenIndexOf

.NET Framework 4.6.2

10

8.634 ms

0.4195 ms

1.00

48141349 B

IndexOfOnly

.NET Framework 4.6.2

10

1.724 ms

0.0346 ms

0.19

-

SubstringThenIndexOf

.NET 7.0

100

14.651 ms

0.2977 ms

1.00

176000008 B

IndexOfOnly

.NET 7.0

100

2.464 ms

0.0501 ms

0.17

2 B

SubstringThenIndexOf

.NET Framework 4.6.2

100

13.363 ms

0.2044 ms

1.00

176518761 B

IndexOfOnly

.NET Framework 4.6.2

100

1.689 ms

0.0290 ms

0.13

-

SubstringThenIndexOf

.NET 7.0

1000

78.688 ms

2.4727 ms

1.00

1528000072 B

IndexOfOnly

.NET 7.0

1000

2.456 ms

0.0397 ms

0.03

2 B

SubstringThenIndexOf

.NET Framework 4.6.2

1000

75.994 ms

1.2650 ms

1.00

1532637240 B

IndexOfOnly

.NET Framework 4.6.2

1000

1.699 ms

0.0216 ms

0.02

-

The results were generated by running the following snippet with BenchmarkDotNet:

private string theString;
private int iteration = 1_000_000;

[Params(10, 100, 1_000)]
public int StringSize { get; set; }

[GlobalSetup]
public void Setup() =>
    theString = new string('a', StringSize);

[Benchmark(Baseline = true)]
public void SubstringThenIndexOf()
{
    for (var i = 0; i < iteration; i++)
    {
        _ = theString.Substring(StringSize / 4).IndexOf('a');
    }
}

[Benchmark]
public void IndexOfOnly()
{
    for (var i = 0; i < iteration; i++)
    {
        _ = theString.IndexOf('a', StringSize / 4) - StringSize / 4;
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2965/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S4635.json ================================================ { "title": "Start index should be used instead of calling Substring", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4635", "sqKey": "S4635", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S4663.html ================================================

Why is this an issue?

Empty comments, as shown in the example, hurt readability and might indicate an oversight.

//

/*
*/

///

/**
*/

Some meaningful text should be added to the comment, or the comment markers should be removed.

================================================ FILE: analyzers/rspec/cs/S4663.json ================================================ { "title": "Comments should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4663", "sqKey": "S4663", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S4790.html ================================================

Cryptographic hash algorithms such as MD2, MD4, MD5, MD6, HAVAL-128, DSA (which uses SHA-1), RIPEMD, RIPEMD-128, RIPEMD-160and SHA-1 are no longer considered secure, because it is possible to have collisions (little computational effort is enough to find two or more different inputs that produce the same hash).

Message authentication code (MAC) algorithms such as HMAC-MD5 or HMAC-SHA1 use weak hash functions as building blocks. Although they are not all proven to be weak, they are considered legacy algorithms and should be avoided.

Ask Yourself Whether

The hashed value is used in a security context like:

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

Recommended Secure Coding Practices

Safer alternatives, such as SHA-256, SHA-512, SHA-3 are recommended, and for password hashing, it’s even better to use algorithms that do not compute too "quickly", like bcrypt, scrypt, argon2 or pbkdf2 because it slows down brute force attacks.

Sensitive Code Example

var hashProvider1 = new MD5CryptoServiceProvider(); // Sensitive
var hashProvider2 = (HashAlgorithm)CryptoConfig.CreateFromName("MD5"); // Sensitive
var hashProvider3 = new SHA1Managed(); // Sensitive
var hashProvider4 = HashAlgorithm.Create("SHA1"); // Sensitive

Compliant Solution

var hashProvider1 = new SHA512Managed(); // Compliant
var hashProvider2 = (HashAlgorithm)CryptoConfig.CreateFromName("SHA512Managed"); // Compliant
var hashProvider3 = HashAlgorithm.Create("SHA512Managed"); // Compliant

See

================================================ FILE: analyzers/rspec/cs/S4790.json ================================================ { "title": "Using weak hashing algorithms is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4790", "sqKey": "S4790", "scope": "Main", "securityStandards": { "CWE": [ 1240 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "3.4", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "6.2.4" ] } } ================================================ FILE: analyzers/rspec/cs/S4792.html ================================================

This rule is deprecated, and will eventually be removed.

Configuring loggers is security-sensitive. It has led in the past to the following vulnerabilities:

Logs are useful before, during and after a security incident.

Logs are also a target for attackers because they might contain sensitive information. Configuring loggers has an impact on the type of information logged and how they are logged.

This rule flags for review code that initiates loggers configuration. The goal is to guide security code reviews.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Remember that configuring loggers properly doesn’t make them bullet-proof. Here is a list of recommendations explaining on how to use your logs:

Sensitive Code Example

.Net Core: configure programmatically

using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore;

namespace MvcApp
{
    public class ProgramLogging
    {
        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureLogging((hostingContext, logging) => // Sensitive
                {
                    // ...
                })
                .UseStartup<StartupLogging>();
    }

    public class StartupLogging
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddLogging(logging => // Sensitive
            {
                // ...
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            IConfiguration config = null;
            LogLevel level = LogLevel.Critical;
            Boolean includeScopes = false;
            Func<string,Microsoft.Extensions.Logging.LogLevel,bool> filter = null;
            Microsoft.Extensions.Logging.Console.IConsoleLoggerSettings consoleSettings = null;
            Microsoft.Extensions.Logging.AzureAppServices.AzureAppServicesDiagnosticsSettings azureSettings = null;
            Microsoft.Extensions.Logging.EventLog.EventLogSettings eventLogSettings = null;

            // An issue will be raised for each call to an ILoggerFactory extension methods adding loggers.
            loggerFactory.AddAzureWebAppDiagnostics(); // Sensitive
            loggerFactory.AddAzureWebAppDiagnostics(azureSettings); // Sensitive
            loggerFactory.AddConsole(); // Sensitive
            loggerFactory.AddConsole(level); // Sensitive
            loggerFactory.AddConsole(level, includeScopes); // Sensitive
            loggerFactory.AddConsole(filter); // Sensitive
            loggerFactory.AddConsole(filter, includeScopes); // Sensitive
            loggerFactory.AddConsole(config); // Sensitive
            loggerFactory.AddConsole(consoleSettings); // Sensitive
            loggerFactory.AddDebug(); // Sensitive
            loggerFactory.AddDebug(level); // Sensitive
            loggerFactory.AddDebug(filter); // Sensitive
            loggerFactory.AddEventLog(); // Sensitive
            loggerFactory.AddEventLog(eventLogSettings); // Sensitive
            loggerFactory.AddEventLog(level); // Sensitive
            loggerFactory.AddEventSourceLogger(); // Sensitive

            IEnumerable<ILoggerProvider> providers = null;
            LoggerFilterOptions filterOptions1 = null;
            IOptionsMonitor<LoggerFilterOptions> filterOptions2 = null;

            LoggerFactory factory = new LoggerFactory(); // Sensitive
            new LoggerFactory(providers); // Sensitive
            new LoggerFactory(providers, filterOptions1); // Sensitive
            new LoggerFactory(providers, filterOptions2); // Sensitive
        }
    }
}

Log4Net

using System;
using System.IO;
using System.Xml;
using log4net.Appender;
using log4net.Config;
using log4net.Repository;

namespace Logging
{
    class Log4netLogging
    {
        void Foo(ILoggerRepository repository, XmlElement element, FileInfo configFile, Uri configUri, Stream configStream,
        IAppender appender, params IAppender[] appenders) {
            log4net.Config.XmlConfigurator.Configure(repository); // Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, element); // Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configFile); // Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configUri); // Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configStream); // Sensitive
            log4net.Config.XmlConfigurator.ConfigureAndWatch(repository, configFile); // Sensitive

            log4net.Config.DOMConfigurator.Configure(); // Sensitive
            log4net.Config.DOMConfigurator.Configure(repository); // Sensitive
            log4net.Config.DOMConfigurator.Configure(element); // Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, element); // Sensitive
            log4net.Config.DOMConfigurator.Configure(configFile); // Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, configFile); // Sensitive
            log4net.Config.DOMConfigurator.Configure(configStream); // Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, configStream); // Sensitive
            log4net.Config.DOMConfigurator.ConfigureAndWatch(configFile); // Sensitive
            log4net.Config.DOMConfigurator.ConfigureAndWatch(repository, configFile); // Sensitive

            log4net.Config.BasicConfigurator.Configure(); // Sensitive
            log4net.Config.BasicConfigurator.Configure(appender); // Sensitive
            log4net.Config.BasicConfigurator.Configure(appenders); // Sensitive
            log4net.Config.BasicConfigurator.Configure(repository); // Sensitive
            log4net.Config.BasicConfigurator.Configure(repository, appender); // Sensitive
            log4net.Config.BasicConfigurator.Configure(repository, appenders); // Sensitive
        }
    }
}

NLog: configure programmatically

namespace Logging
{
    class NLogLogging
    {
        void Foo(NLog.Config.LoggingConfiguration config) {
            NLog.LogManager.Configuration = config; // Sensitive, this changes the logging configuration.
        }
    }
}

Serilog

namespace Logging
{
    class SerilogLogging
    {
        void Foo() {
            new Serilog.LoggerConfiguration(); // Sensitive
        }
    }
}

See

================================================ FILE: analyzers/rspec/cs/S4792.json ================================================ { "title": "Configuring loggers is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "deprecated", "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4792", "sqKey": "S4792", "scope": "Main", "securityStandards": { "CWE": [ 117, 532 ], "OWASP": [ "A3", "A10" ], "OWASP Top 10 2021": [ "A9" ], "PCI DSS 3.2": [ "10.1", "10.2", "10.3" ], "PCI DSS 4.0": [ "10.2" ], "ASVS 4.0": [ "7.1.1", "7.1.2" ] } } ================================================ FILE: analyzers/rspec/cs/S4830.html ================================================

This vulnerability makes it possible that an encrypted communication is intercepted.

Why is this an issue?

Transport Layer Security (TLS) provides secure communication between systems over the internet by encrypting the data sent between them. Certificate validation adds an extra layer of trust and security to this process to ensure that a system is indeed the one it claims to be.

When certificate validation is disabled, the client skips a critical security check. This creates an opportunity for attackers to pose as a trusted entity and intercept, manipulate, or steal the data being transmitted.

What is the potential impact?

Establishing trust in a secure way is a non-trivial task. When you disable certificate validation, you are removing a key mechanism designed to build this trust in internet communication, opening your system up to a number of potential threats.

Identity spoofing

If a system does not validate certificates, it cannot confirm the identity of the other party involved in the communication. An attacker can exploit this by creating a fake server and masquerading as a legitimate one. For example, they might set up a server that looks like your bank’s server, tricking your system into thinking it is communicating with the bank. This scenario, called identity spoofing, allows the attacker to collect any data your system sends to them, potentially leading to significant data breaches.

Loss of data integrity

When TLS certificate validation is disabled, the integrity of the data you send and receive cannot be guaranteed. An attacker could modify the data in transit, and you would have no way of knowing. This could range from subtle manipulations of the data you receive to the injection of malicious code or malware into your system. The consequences of such breaches of data integrity can be severe, depending on the nature of the data and the system.

How to fix it in .NET

Code examples

In the following example, the callback change impacts the entirety of HTTP requests made by the application.

The certificate validation gets disabled by overriding ServerCertificateValidationCallback with an empty implementation. It is highly recommended to use the original implementation.

Noncompliant code example

using System.Net;
using System.Net.Http;

public static void connect()
{
    ServicePointManager.ServerCertificateValidationCallback +=
	 (sender, certificate, chain, errors) => {
	     return true; // Noncompliant
    };

    HttpClient httpClient = new HttpClient();
    HttpResponseMessage response = httpClient.GetAsync("https://example.com").Result;
}

How does this work?

Addressing the vulnerability of disabled TLS certificate validation primarily involves re-enabling the default validation.

To avoid running into problems with invalid certificates, consider the following sections.

Using trusted certificates

If possible, always use a certificate issued by a well-known, trusted CA for your server. Most programming environments come with a predefined list of trusted root CAs, and certificates issued by these authorities are validated automatically. This is the best practice, and it requires no additional code or configuration.

Working with self-signed certificates or non-standard CAs

In some cases, you might need to work with a server using a self-signed certificate, or a certificate issued by a CA not included in your trusted roots. Rather than disabling certificate validation in your code, you can add the necessary certificates to your trust store.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S4830.json ================================================ { "title": "Server certificates should be verified during SSL\/TLS connections", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "privacy", "ssl" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4830", "sqKey": "S4830", "scope": "Main", "securityStandards": { "CWE": [ 295 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2", "A5", "A7" ], "PCI DSS 3.2": [ "4.1", "6.5.4", "6.5.10" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "1.9.2", "9.2.1" ], "STIG ASD_V5R3": [ "V-222550" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5034.html ================================================

Why is this an issue?

ValueTask<TResult> provides a value type that wraps a Task<TResult> and the corresponding TResult. It was introduced in .NET Core 2.0 to optimize memory allocation when functions return their results synchronously.

Using ValueTask and ValueTask<TResult> in the following ways is discouraged as it could result in a race condition:

It is recommended to use ValueTask/ValueTask<TResult> either by calling await on the function returning it, optionally calling ConfigureAwait(false) on it, or by calling .AsTask() on it.

This rule raises an issue when the following operations are performed on a ValueTask/ValueTask<TResult> instance unless it happens in a loop:

How to fix it

Code examples

Noncompliant code example

ValueTask<int> vt = ComputeAsync();
int result = await vt;
result = await vt; // Noncompliant, variable is awaited multiple times

int value = ComputeAsync()).GetAwaiter().GetResult(); // Noncompliant, uses GetAwaiter().GetResult() when it's not known to be done

Compliant solution

ValueTask<int> vt = ComputeAsync();
int result = await vt;

int value = await ComputeAsync().AsTask();

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S5034.json ================================================ { "title": "\"ValueTask\" should be consumed correctly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "async-await" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5034", "sqKey": "S5034", "scope": "All", "securityStandards": { "STIG ASD_V5R3": [ "V-222567" ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S5042.html ================================================

Successful Zip Bomb attacks occur when an application expands untrusted archive files without controlling the size of the expanded data, which can lead to denial of service. A Zip bomb is usually a malicious archive file of a few kilobytes of compressed data but turned into gigabytes of uncompressed data. To achieve this extreme compression ratio, attackers will compress irrelevant data (eg: a long string of repeated bytes).

Ask Yourself Whether

Archives to expand are untrusted and:

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

Recommended Secure Coding Practices

Sensitive Code Example

using var zipToOpen = new FileStream(@"ZipBomb.zip", FileMode.Open);
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in archive.Entries)
{
  entry.ExtractToFile("./output_onlyfortesting.txt", true); // Sensitive
}

Compliant Solution

int THRESHOLD_ENTRIES = 10000;
int THRESHOLD_SIZE = 1000000000; // 1 GB
double THRESHOLD_RATIO = 10;
int totalSizeArchive = 0;
int totalEntryArchive = 0;

using var zipToOpen = new FileStream(@"ZipBomb.zip", FileMode.Open);
using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Read);
foreach (ZipArchiveEntry entry in archive.Entries)
{
  totalEntryArchive ++;

  using (Stream st = entry.Open())
  {
    byte[] buffer = new byte[1024];
    int totalSizeEntry = 0;
    int numBytesRead = 0;

    do
    {
      numBytesRead = st.Read(buffer, 0, 1024);
      totalSizeEntry += numBytesRead;
      totalSizeArchive += numBytesRead;
      double compressionRatio = totalSizeEntry / entry.CompressedLength;

      if(compressionRatio > THRESHOLD_RATIO) {
        // ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
        break;
      }
    }
    while (numBytesRead > 0);
  }

  if(totalSizeArchive > THRESHOLD_SIZE) {
      // the uncompressed data size is too much for the application resource capacity
      break;
  }

  if(totalEntryArchive > THRESHOLD_ENTRIES) {
      // too much entries in this archive, can lead to inodes exhaustion of the system
      break;
  }
}

See

================================================ FILE: analyzers/rspec/cs/S5042.json ================================================ { "title": "Expanding archive files without controlling resource consumption is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5042", "sqKey": "S5042", "scope": "Main", "securityStandards": { "CWE": [ 409 ], "OWASP": [ "A5", "A6" ], "OWASP Top 10 2021": [ "A1", "A5" ], "ASVS 4.0": [ "12.1.2" ] } } ================================================ FILE: analyzers/rspec/cs/S5122.html ================================================

Having a permissive Cross-Origin Resource Sharing policy is security-sensitive. It has led in the past to the following vulnerabilities:

Same origin policy in browsers prevents, by default and for security-reasons, a javascript frontend to perform a cross-origin HTTP request to a resource that has a different origin (domain, protocol, or port) from its own. The requested target can append additional HTTP headers in response, called CORS, that act like directives for the browser and change the access control policy / relax the same origin policy.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

ASP.NET Core MVC:

[HttpGet]
public string Get()
{
    Response.Headers.Add("Access-Control-Allow-Origin", "*"); // Sensitive
    Response.Headers.Add(HeaderNames.AccessControlAllowOrigin, "*"); // Sensitive
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddDefaultPolicy(builder =>
        {
            builder.WithOrigins("*"); // Sensitive
        });

        options.AddPolicy(name: "EnableAllPolicy", builder =>
        {
            builder.WithOrigins("*"); // Sensitive
        });

        options.AddPolicy(name: "OtherPolicy", builder =>
        {
            builder.AllowAnyOrigin(); // Sensitive
        });
    });

    services.AddControllers();
}

ASP.NET MVC:

public class HomeController : ApiController
{
    public HttpResponseMessage Get()
    {
        var response = HttpContext.Current.Response;

        response.Headers.Add("Access-Control-Allow-Origin", "*"); // Sensitive
        response.Headers.Add(HeaderNames.AccessControlAllowOrigin, "*"); // Sensitive
        response.AppendHeader(HeaderNames.AccessControlAllowOrigin, "*"); // Sensitive
    }
}
[EnableCors(origins: "*", headers: "*", methods: "GET")] // Sensitive
public HttpResponseMessage Get() => new HttpResponseMessage()
{
    Content = new StringContent("content")
};

User-controlled origin:

String origin = Request.Headers["Origin"];
Response.Headers.Add("Access-Control-Allow-Origin", origin); // Sensitive

Compliant Solution

ASP.NET Core MVC:

[HttpGet]
public string Get()
{
    Response.Headers.Add("Access-Control-Allow-Origin", "https://trustedwebsite.com"); // Safe
    Response.Headers.Add(HeaderNames.AccessControlAllowOrigin, "https://trustedwebsite.com"); // Safe
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddDefaultPolicy(builder =>
        {
            builder.WithOrigins("https://trustedwebsite.com", "https://anothertrustedwebsite.com"); // Safe
        });

        options.AddPolicy(name: "EnableAllPolicy", builder =>
        {
            builder.WithOrigins("https://trustedwebsite.com"); // Safe
        });
    });

    services.AddControllers();
}

ASP.Net MVC:

public class HomeController : ApiController
{
    public HttpResponseMessage Get()
    {
        var response = HttpContext.Current.Response;

        response.Headers.Add("Access-Control-Allow-Origin", "https://trustedwebsite.com");
        response.Headers.Add(HeaderNames.AccessControlAllowOrigin, "https://trustedwebsite.com");
        response.AppendHeader(HeaderNames.AccessControlAllowOrigin, "https://trustedwebsite.com");
    }
}
[EnableCors(origins: "https://trustedwebsite.com", headers: "*", methods: "GET")]
public HttpResponseMessage Get() => new HttpResponseMessage()
{
    Content = new StringContent("content")
};

User-controlled origin validated with an allow-list:

String origin = Request.Headers["Origin"];

if (trustedOrigins.Contains(origin))
{
    Response.Headers.Add("Access-Control-Allow-Origin", origin);
}

See

================================================ FILE: analyzers/rspec/cs/S5122.json ================================================ { "title": "Having a permissive Cross-Origin Resource Sharing policy is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-5122", "sqKey": "S5122", "scope": "Main", "securityStandards": { "CWE": [ 346, 942 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A5", "A7" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "14.5.2", "14.5.3" ] } } ================================================ FILE: analyzers/rspec/cs/S5332.html ================================================

Clear-text protocols such as ftp, telnet, or http lack encryption of transported data, as well as the capability to build an authenticated connection. It means that an attacker able to sniff traffic from the network can read, modify, or corrupt the transported content. These protocols are not secure as they expose applications to an extensive range of risks:

Even in the context of isolated networks like offline environments or segmented cloud environments, the insider threat exists. Thus, attacks involving communications being sniffed or tampered with can still happen.

For example, attackers could successfully compromise prior security layers by:

In such cases, encrypting communications would decrease the chances of attackers to successfully leak data or steal credentials from other network components. By layering various security practices (segmentation and encryption, for example), the application will follow the defense-in-depth principle.

Note that using the http protocol is being deprecated by major web browsers.

In the past, it has led to the following vulnerabilities:

Ask Yourself Whether

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

Recommended Secure Coding Practices

It is recommended to secure all transport channels, even on local networks, as it can take a single non-secure connection to compromise an entire application or system.

Sensitive Code Example

var urlHttp = "http://example.com";                 // Noncompliant
var urlFtp = "ftp://anonymous@example.com";         // Noncompliant
var urlTelnet = "telnet://anonymous@example.com";   // Noncompliant
using var smtp = new SmtpClient("host", 25); // Noncompliant, EnableSsl is not set
using var telnet = new MyTelnet.Client("host", port); // Noncompliant, rule raises Security Hotspot on any member containing "Telnet"

Compliant Solution

var urlHttps = "https://example.com";
var urlSftp = "sftp://anonymous@example.com";
var urlSsh = "ssh://anonymous@example.com";
using var smtp = new SmtpClient("host", 25) { EnableSsl = true };
using var ssh = new MySsh.Client("host", port);

Exceptions

No issue is reported for the following cases because they are not considered sensitive:

See

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S5332.json ================================================ { "title": "Using clear-text protocols is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5332", "sqKey": "S5332", "scope": "Main", "securityStandards": { "CWE": [ 200, 319 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "1.9.1", "9.1.1", "9.2.2" ], "STIG ASD_V5R3": [ "V-222397", "V-222534", "V-222562", "V-222563", "V-222577", "V-222596", "V-222597", "V-222598", "V-222599" ] } } ================================================ FILE: analyzers/rspec/cs/S5344.html ================================================

The improper storage of passwords poses a significant security risk to software applications. This vulnerability arises when passwords are stored in plaintext or with a fast hashing algorithm. To exploit this vulnerability, an attacker typically requires access to the stored passwords.

Why is this an issue?

Attackers who would get access to the stored passwords could reuse them without further attacks or with little additional effort.
Obtaining the plaintext passwords, they could then gain unauthorized access to user accounts, potentially leading to various malicious activities.

What is the potential impact?

Plaintext or weakly hashed password storage poses a significant security risk to software applications.

Unauthorized Access

When passwords are stored in plaintext or with weak hashing algorithms, an attacker who gains access to the password database can easily retrieve and use the passwords to gain unauthorized access to user accounts. This can lead to various malicious activities, such as unauthorized data access, identity theft, or even financial fraud.

Credential Reuse

Many users tend to reuse passwords across multiple platforms. If an attacker obtains plaintext or weakly hashed passwords, they can potentially use these credentials to gain unauthorized access to other accounts held by the same user. This can have far-reaching consequences, as sensitive personal information or critical systems may be compromised.

Regulatory Compliance

Many industries and jurisdictions have specific regulations and standards to protect user data and ensure its confidentiality. Storing passwords in plaintext or with weak hashing algorithms can lead to non-compliance with these regulations, potentially resulting in legal consequences, financial penalties, and damage to the reputation of the software application and its developers.

How to fix it in ASP.NET Core

Code examples

Noncompliant code example

Using Microsoft.AspNetCore.Cryptography.KeyDerivation:

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);

string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: password!,
    salt: salt,
    prf: KeyDerivationPrf.HMACSHA256,
    iterationCount: 1, // Noncompliant
    numBytesRequested: 256 / 8));

Using System.Security.Cryptography:

using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, 128/8); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));

Using Microsoft.AspNetCore.Identity:

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

string password = Request.Query["password"];
IOptions<PasswordHasherOptions> options = Options.Create(new PasswordHasherOptions() {
    IterationCount = 1 // Noncompliant
});
PasswordHasher<User> hasher = new PasswordHasher<User>(options);
string hash = hasher.HashPassword(new User("test"), password);

Compliant solution

Using Microsoft.AspNetCore.Cryptography.KeyDerivation:

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);

string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: password!,
    salt: salt,
    prf: KeyDerivationPrf.HMACSHA256,
    iterationCount: 100_000,
    numBytesRequested: 256 / 8));

Using System.Security.Cryptography

using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));

Using Microsoft.AspNetCore.Identity:

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

string password = Request.Query["password"];
PasswordHasher<User> hasher = new PasswordHasher<User>();
string hash = hasher.HashPassword(new User("test"), password);

How does this work?

Select the correct PBKDF2 parameters

If PBKDF2 must be used, be aware that default values might not be considered secure.
Depending on the algorithm used, the number of iterations should be adjusted to ensure that the derived key is secure. The following are the recommended number of iterations for PBKDF2:

Note that PBKDF2-HMAC-SHA256 is recommended by NIST.
Iterations are also called "rounds" depending on the library used.

When recommended cost factors are too high in the context of the application or if the performance cost is unacceptable, a cost factor reduction might be considered. In that case, it should not be chosen under 100,000.

Going the extra mile

Pepper

In a defense-in-depth security approach, peppering can also be used. This is a security technique where an external secret value is added to a password before it is hashed.
This makes it more difficult for an attacker to crack the hashed passwords, as they would need to know the secret value to generate the correct hash.
Learn more here.

How to fix it in ASP.NET

Code examples

Noncompliant code example

using System.Security.Cryptography;

RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
byte[] salt = new byte[32];
rngCsp.GetBytes(salt);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100, HashAlgorithmName.SHA256); // Noncompliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));

Using using Microsoft.AspNet.Identity:

using Microsoft.AspNet.Identity;

string password = "NotSoSecure";
PasswordHasher hasher = new PasswordHasher();
string hash = hasher.HashPassword(password); // Noncompliant

Compliant solution

using System.Security.Cryptography;

RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
byte[] salt = new byte[32];
rngCsp.GetBytes(salt);
Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256); // Compliant
string hashed = Convert.ToBase64String(kdf.GetBytes(256 / 8));

How does this work?

Select the correct PBKDF2 parameters

If PBKDF2 must be used, be aware that default values might not be considered secure.
Depending on the algorithm used, the number of iterations should be adjusted to ensure that the derived key is secure. The following are the recommended number of iterations for PBKDF2:

Note that PBKDF2-HMAC-SHA256 is recommended by NIST.
Iterations are also called "rounds" depending on the library used.

When recommended cost factors are too high in the context of the application or if the performance cost is unacceptable, a cost factor reduction might be considered. In that case, it should not be chosen under 100,000.

Going the extra mile

Pepper

In a defense-in-depth security approach, peppering can also be used. This is a security technique where an external secret value is added to a password before it is hashed.
This makes it more difficult for an attacker to crack the hashed passwords, as they would need to know the secret value to generate the correct hash.
Learn more here.

How to fix it in BouncyCastle

Code examples

Noncompliant code example

Using SCrypt:

using Org.BouncyCastle.Crypto.Generators;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes

string hashed = Convert.ToBase64String(SCrypt.Generate(Encoding.Unicode.GetBytes(password), salt, 4, 2, 1, 32));  // Noncompliant

Using BCrypt:

using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);

string hashed = OpenBsdBCrypt.Generate(password.ToCharArray(), salt, 4); // Noncompliant

Using PBKDF2:

using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
gen.Init(Encoding.Unicode.GetBytes(password), salt, 100);  // Noncompliant
KeyParameter keyParam = (KeyParameter) gen.GenerateDerivedMacParameters(256);
string hashed = Convert.ToBase64String(keyParam.GetKey());

Compliant solution

Using SCrypt:

using Org.BouncyCastle.Crypto.Generators;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes

string hashed = Convert.ToBase64String(SCrypt.Generate(Encoding.Unicode.GetBytes(password), salt, 1<<12, 8, 1, 32));  // Noncompliant

Using BCrypt:

using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);

string hashed = OpenBsdBCrypt.Generate(password.ToCharArray(), salt, 14); // Noncompliant

Using PBKDF2:

using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;

string password = Request.Query["password"];
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
gen.Init(Encoding.Unicode.GetBytes(password), salt, 100_000);  // Noncompliant
KeyParameter keyParam = (KeyParameter) gen.GenerateDerivedMacParameters(256);
string hashed = Convert.ToBase64String(keyParam.GetKey());

How does this work?

Select the correct Bcrypt parameters

When bcrypt’s hashing function is used, it is important to select a round count that is high enough to make the function slow enough to prevent brute force: More than 12 rounds.

For bcrypt’s key derivation function, the number of rounds should likewise be high enough to make the function slow enough to prevent brute force: More than 4096 rounds (2^12).
This number is not the same coefficient as the first one because it uses a different algorithm.

Select the correct Scrypt parameters

If scrypt must be used, the default values of scrypt are considered secure.

Like Argon2id, scrypt has three different parameters that can be configured. N is the CPU/memory cost parameter and must be a power of two. r is the block size and p is the parallelization factor.

All three parameters affect the memory and CPU usage of the algorithm. Higher values of N, r and p result in safer hashes, but come at the cost of higher resource usage.

For scrypt, OWASP recommends to have a hash length of at least 64 bytes, and to set N, p and r to the values of one of the following rows:

N (cost parameter) p (parallelization factor) r (block size)

217 (1 << 17)

1

8

216 (1 << 16)

2

8

215 (1 << 15)

3

8

214 (1 << 14)

5

8

213 (1 << 13)

10

8

Every row provides the same level of defense. They only differ in the amount of CPU and RAM used: the top row has low CPU usage and high memory usage, while the bottom row has high CPU usage and low memory usage.

Select the correct PBKDF2 parameters

If PBKDF2 must be used, be aware that default values might not be considered secure.
Depending on the algorithm used, the number of iterations should be adjusted to ensure that the derived key is secure. The following are the recommended number of iterations for PBKDF2:

Note that PBKDF2-HMAC-SHA256 is recommended by NIST.
Iterations are also called "rounds" depending on the library used.

When recommended cost factors are too high in the context of the application or if the performance cost is unacceptable, a cost factor reduction might be considered. In that case, it should not be chosen under 100,000.

Going the extra mile

Pepper

In a defense-in-depth security approach, peppering can also be used. This is a security technique where an external secret value is added to a password before it is hashed.
This makes it more difficult for an attacker to crack the hashed passwords, as they would need to know the secret value to generate the correct hash.
Learn more here.

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S5344.json ================================================ { "title": "Passwords should not be stored in plaintext or with a fast hashing algorithm", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "spring" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5344", "sqKey": "S5344", "scope": "Main", "securityStandards": { "CWE": [ 256, 916 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2", "A4" ], "PCI DSS 3.2": [ "6.5.3" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.3", "2.4.1", "2.4.2", "2.4.3", "2.4.4", "2.4.5" ], "STIG ASD_V5R3": [ "V-222542" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5443.html ================================================

Operating systems have global directories where any user has write access. Those folders are mostly used as temporary storage areas like /tmp in Linux based systems. An application manipulating files from these folders is exposed to race conditions on filenames: a malicious user can try to create a file with a predictable name before the application does. A successful attack can result in other files being accessed, modified, corrupted or deleted. This risk is even higher if the application runs with elevated permissions.

In the past, it has led to the following vulnerabilities:

This rule raises an issue whenever it detects a hard-coded path to a publicly writable directory like /tmp (see examples bellow). It also detects access to environment variables that point to publicly writable directories, e.g., TMP, TMPDIR and TEMP.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Out of the box, .NET is missing secure-by-design APIs to create temporary files. To overcome this, one of the following options can be used:

Sensitive Code Example

using var writer = new StreamWriter("/tmp/f"); // Sensitive
var tmp = Environment.GetEnvironmentVariable("TMP"); // Sensitive

Compliant Solution

var randomPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

// Creates a new file with write, non inheritable permissions which is deleted on close.
using var fileStream = new FileStream(randomPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.DeleteOnClose);
using var writer = new StreamWriter(fileStream);

See

================================================ FILE: analyzers/rspec/cs/S5443.json ================================================ { "title": "Using publicly writable directories is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5443", "sqKey": "S5443", "scope": "Main", "securityStandards": { "CWE": [ 377, 379 ], "OWASP": [ "A5", "A3" ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222567" ] } } ================================================ FILE: analyzers/rspec/cs/S5445.html ================================================

Temporary files are considered insecurely created when the file existence check is performed separately from the actual file creation. Such a situation can occur when creating temporary files using normal file handling functions or when using dedicated temporary file handling functions that are not atomic.

Why is this an issue?

Creating temporary files in a non-atomic way introduces race condition issues in the application’s behavior. Indeed, a third party can create a given file between when the application chooses its name and when it creates it.

In such a situation, the application might use a temporary file that it does not entirely control. In particular, this file’s permissions might be different than expected. This can lead to trust boundary issues.

What is the potential impact?

Attackers with control over a temporary file used by a vulnerable application will be able to modify it in a way that will affect the application’s logic. By changing this file’s Access Control List or other operating system-level properties, they could prevent the file from being deleted or emptied. They may also alter the file’s content before or while the application uses it.

Depending on why and how the affected temporary files are used, the exploitation of a race condition in an application can have various consequences. They can range from sensitive information disclosure to more serious application or hosting infrastructure compromise.

Information disclosure

Because attackers can control the permissions set on temporary files and prevent their removal, they can read what the application stores in them. This might be especially critical if this information is sensitive.

For example, an application might use temporary files to store users' session-related information. In such a case, attackers controlling those files can access session-stored information. This might allow them to take over authenticated users' identities and entitlements.

Attack surface extension

An application might use temporary files to store technical data for further reuse or as a communication channel between multiple components. In that case, it might consider those files part of the trust boundaries and use their content without additional security validation or sanitation. In such a case, an attacker controlling the file content might use it as an attack vector for further compromise.

For example, an application might store serialized data in temporary files for later use. In such a case, attackers controlling those files' content can change it in a way that will lead to an insecure deserialization exploitation. It might allow them to execute arbitrary code on the application hosting server and take it over.

How to fix it

Code examples

The following code example is vulnerable to a race condition attack because it creates a temporary file using an unsafe API function.

Noncompliant code example

using System.IO;

public void Example()
{
    var tempPath = Path.GetTempFileName();  // Noncompliant

    using (var writer = new StreamWriter(tempPath))
    {
        writer.WriteLine("content");
    }
}

Compliant solution

using System.IO;

public void Example()
{
    var randomPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

    using (var fileStream = new FileStream(randomPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.DeleteOnClose))
    using (var writer = new StreamWriter(fileStream))
    {
        writer.WriteLine("content");
    }
}

How does this work?

Applications should create temporary files so that no third party can read or modify their content. It requires that the files' name, location, and permissions are carefully chosen and set. This can be achieved in multiple ways depending on the applications' technology stacks.

Strong security controls

Temporary files can be created using unsafe functions and API as long as strong security controls are applied. Non-temporary file-handling functions and APIs can also be used for that purpose.

In general, applications should ensure that attackers can not create a file before them. This turns into the following requirements when creating the files:

Moreover, when possible, it is recommended that applications destroy temporary files after they have finished using them.

Here the example compliant code uses the Path.GetTempPath and Path.GetRandomFileName functions to generate a unique random file name. The file is then open with the FileMode.CreateNew option that will ensure the creation fails if the file already exists. The FileShare.None option will additionally prevent the file from being opened again by any process. To finish, this code ensures the file will get destroyed once the application has finished using it with the FileOptions.DeleteOnClose option.

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S5445.json ================================================ { "title": "Insecure temporary file creation methods should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5445", "sqKey": "S5445", "scope": "Main", "securityStandards": { "CWE": [ 377, 379 ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222567" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5542.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and reducing the difficulty of recovering the original cleartext.

Unauthenticated modes such as CBC (Cipher Block Chaining) may be used but are prone to attacks that manipulate the ciphertext. They must be used with caution.

For RSA, the weakest algorithms are either using it without padding or using the PKCS1v1.5 padding scheme.

What is the potential impact?

The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.

Below are some real-world scenarios that illustrate possible impacts of an attacker exploiting the vulnerability.

Theft of sensitive data

The encrypted message might contain data that is considered sensitive and should not be known to third parties.

By using a weak algorithm the likelihood that an attacker might be able to recover the cleartext drastically increases.

Additional attack surface

By modifying the cleartext of the encrypted message it might be possible for an attacker to trigger other vulnerabilities in the code. Encrypted values are often considered trusted, since under normal circumstances it would not be possible for a third party to modify them.

How to fix it in .NET

Code examples

Noncompliant code example

Example with a symmetric cipher, AES:

using System.Security.Cryptography;

public void encrypt()
{
    AesManaged aes = new AesManaged
    {
        keysize = 128,
        blocksize = 128,
        mode = ciphermode.ecb,        // Noncompliant
        padding = paddingmode.pkcs7
    };
}

Note that Microsoft has marked derived cryptographic types like AesManaged as no longer recommended for use.

Example with an asymmetric cipher, RSA:

using System.Security.Cryptography;

public void encrypt()
{
    RSACryptoServiceProvider RsaCsp = new RSACryptoServiceProvider();
    byte[] encryptedData            = RsaCsp.Encrypt(dataToEncrypt, false); // Noncompliant
}

Compliant solution

For the AES symmetric cipher, use the GCM mode:

using System.Security.Cryptography;

public void encrypt()
{
    AesGcm aes = AesGcm(key);
}

For the RSA asymmetric cipher, use the Optimal Asymmetric Encryption Padding (OAEP):

using System.Security.Cryptography;

public void encrypt()
{
    RSACryptoServiceProvider RsaCsp = new RSACryptoServiceProvider();
    byte[] encryptedData            = RsaCsp.Encrypt(dataToEncrypt, true);
}

How does this work?

As a rule of thumb, use the cryptographic algorithms and mechanisms that are considered strong by the cryptographic community.

Appropriate choices are currently the following.

For AES: use authenticated encryption modes

The best-known authenticated encryption mode for AES is Galois/Counter mode (GCM).

GCM mode combines encryption with authentication and integrity checks using a cryptographic hash function and provides both confidentiality and authenticity of data.

Other similar modes are:

It is also possible to use AES-CBC with HMAC for integrity checks. However, it is considered more straightforward to use AES-GCM directly instead.

For RSA: use the OAEP scheme

The Optimal Asymmetric Encryption Padding scheme (OAEP) adds randomness and a secure hash function that strengthens the regular inner workings of RSA.

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S5542.json ================================================ { "title": "Encryption algorithms should be used with secure mode and padding scheme", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5542", "sqKey": "S5542", "scope": "Main", "securityStandards": { "CWE": [ 327, 780 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5547.html ================================================

This vulnerability makes it possible that the cleartext of the encrypted message might be recoverable without prior knowledge of the key.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communication in various domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

What is the potential impact?

The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.

Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.

Theft of sensitive data

The encrypted message might contain data that is considered sensitive and should not be known to third parties.

By using a weak algorithm the likelihood that an attacker might be able to recover the cleartext drastically increases.

Additional attack surface

By modifying the cleartext of the encrypted message it might be possible for an attacker to trigger other vulnerabilities in the code. Encrypted values are often considered trusted, since under normal circumstances it would not be possible for a third party to modify them.

How to fix it in .NET

Code examples

The following code contains examples of algorithms that are not considered highly resistant to cryptanalysis and thus should be avoided.

Noncompliant code example

using System.Security.Cryptography;

public void encrypt()
{
    var simpleDES = new DESCryptoServiceProvider(); // Noncompliant
}

Compliant solution

using System.Security.Cryptography;

public void encrypt()
{
    using (Aes aes = Aes.Create())
    {
        // ...
    }
}

How does this work?

Use a secure algorithm

It is highly recommended to use an algorithm that is currently considered secure by the cryptographic community. A common choice for such an algorithm is the Advanced Encryption Standard (AES).

For block ciphers, it is not recommended to use algorithms with a block size that is smaller than 128 bits.

How to fix it in BouncyCastle

Code examples

The following code contains examples of algorithms that are not considered highly resistant to cryptanalysis and thus should be avoided.

Noncompliant code example

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;

public void encrypt()
{
    AesFastEngine aesFast = new AesFastEngine(); // Noncompliant
}

Compliant solution

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;

public void encrypt()
{
    var AES = new AESEngine();
}

How does this work?

Use a secure algorithm

It is highly recommended to use an algorithm that is currently considered secure by the cryptographic community. A common choice for such an algorithm is the Advanced Encryption Standard (AES).

For block ciphers, it is not recommended to use algorithms with a block size that is smaller than 128 bits.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S5547.json ================================================ { "title": "Cipher algorithms should be robust", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5547", "sqKey": "S5547", "scope": "Main", "securityStandards": { "CWE": [ 327, 326 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "6.2.2", "6.2.3", "6.2.5", "8.3.7" ], "STIG ASD_V5R3": [ "V-222396" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5659.html ================================================

This vulnerability allows forging of JSON Web Tokens to impersonate other users.

Why is this an issue?

JSON Web Tokens (JWTs), a popular method of securely transmitting information between parties as a JSON object, can become a significant security risk when they are not properly signed with a robust cipher algorithm, left unsigned altogether, or if the signature is not verified. This vulnerability class allows malicious actors to craft fraudulent tokens, effectively impersonating user identities. In essence, the integrity of a JWT hinges on the strength and presence of its signature.

What is the potential impact?

When a JSON Web Token is not appropriately signed with a strong cipher algorithm or if the signature is not verified, it becomes a significant threat to data security and the privacy of user identities.

Impersonation of users

JWTs are commonly used to represent user authorization claims. They contain information about the user’s identity, user roles, and access rights. When these tokens are not securely signed, it allows an attacker to forge them. In essence, a weak or missing signature gives an attacker the power to craft a token that could impersonate any user. For instance, they could create a token for an administrator account, gaining access to high-level permissions and sensitive data.

Unauthorized data access

When a JWT is not securely signed, it can be tampered with by an attacker, and the integrity of the data it carries cannot be trusted. An attacker can manipulate the content of the token and grant themselves permissions they should not have, leading to unauthorized data access.

How to fix it in Jwt.Net

Code examples

The following code contains an example of JWT decoding without verification of the signature.

Noncompliant code example

using JWT;

public static void decode(IJwtDecoder decoder)
{
    decoder.Decode(token, secret, verify: false); // Noncompliant
}
using JWT;

public static void decode()
{
    var jwt = new JwtBuilder()
        .WithSecret(secret)
        .Decode(token); // Noncompliant
}

Compliant solution

using JWT;

public static void decode(IJwtDecoder decoder)
{
    decoder.Decode(token, secret, verify: true);
}

When using JwtBuilder, make sure to call MustVerifySignature().

using JWT;

public static void decode()
{
    var jwt = new JwtBuilder()
        .WithSecret(secret)
        .MustVerifySignature()
        .Decode(token);
}

How does this work?

Verify the signature of your tokens

Resolving a vulnerability concerning the validation of JWT token signatures is mainly about incorporating a critical step into your process: validating the signature every time a token is decoded. Just having a signed token using a secure algorithm is not enough. If you are not validating signatures, they are not serving their purpose.

Every time your application receives a JWT, it needs to decode the token to extract the information contained within. It is during this decoding process that the signature of the JWT should also be checked.

To resolve the issue, follow these instructions:

  1. Use framework-specific functions for signature verification: Most programming frameworks that support JWTs provide specific functions to not only decode a token but also validate its signature simultaneously. Make sure to use these functions when handling incoming tokens.
  2. Handle invalid signatures appropriately: If a JWT’s signature does not validate correctly, it means the token is not trustworthy, indicating potential tampering. The action to take when encountering an invalid token should be denying the request carrying it and logging the event for further investigation.
  3. Incorporate signature validation in your tests: When you are writing tests for your application, include tests that check the signature validation functionality. This can help you catch any instances where signature verification might be unintentionally skipped or bypassed.

By following these practices, you can ensure the security of your application’s JWT handling process, making it resistant to attacks that rely on tampering with tokens. Validation of the signature needs to be an integral and non-negotiable part of your token handling process.

Going the extra mile

Securely store your secret keys

Ensure that your secret keys are stored securely. They should not be hard-coded into your application code or checked into your version control system. Instead, consider using environment variables, secure key management systems, or vault services.

Rotate your secret keys

Even with the strongest cipher algorithms, there is a risk that your secret keys may be compromised. Therefore, it is a good practice to periodically rotate your secret keys. By doing so, you limit the amount of time that an attacker can misuse a stolen key. When you rotate keys, be sure to allow a grace period where tokens signed with the old key are still accepted to prevent service disruptions.

Resources

Standards

================================================ FILE: analyzers/rspec/cs/S5659.json ================================================ { "title": "JWT should be signed and verified with strong cipher algorithms", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5659", "sqKey": "S5659", "scope": "Main", "securityStandards": { "CWE": [ 347 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S5693.html ================================================

Rejecting requests with significant content length is a good practice to control the network traffic intensity and thus resource consumption in order to prevent DoS attacks.

Ask Yourself Whether

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

Recommended Secure Coding Practices

It is recommended to customize the rule with the limit values that correspond to the web application.

Sensitive Code Example

using Microsoft.AspNetCore.Mvc;

public class MyController : Controller
{
    [HttpPost]
    [DisableRequestSizeLimit] // Sensitive: No size  limit
    [RequestSizeLimit(10485760)] // Sensitive: 10485760 B = 10240 KB = 10 MB is more than the recommended limit of 8MB
    public IActionResult PostRequest(Model model)
    {
    // ...
    }

    [HttpPost]
    [RequestFormLimits(MultipartBodyLengthLimit = 10485760)] // Sensitive: 10485760 B = 10240 KB = 10 MB is more than the recommended limit of 8MB
    public IActionResult MultipartFormRequest(Model model)
    {
    // ...
    }
}

In Web.config:

<configuration>
    <system.web>
        <httpRuntime maxRequestLength="81920" executionTimeout="3600" />
        <!-- Sensitive: maxRequestLength is expressed in KB, so 81920 KB = 80 MB  -->
    </system.web>
    <system.webServer>
        <security>
            <requestFiltering>
                <requestLimits maxAllowedContentLength="83886080" />
                <!-- Sensitive: maxAllowedContentLength is expressed in bytes, so 83886080 B = 81920 KB = 80 MB  -->
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

Compliant Solution

using Microsoft.AspNetCore.Mvc;

public class MyController : Controller
{
    [HttpPost]
    [RequestSizeLimit(8388608)] // Compliant: 8388608 B = 8192 KB = 8 MB
    public IActionResult PostRequest(Model model)
    {
    // ...
    }

    [HttpPost]
    [RequestFormLimits(MultipartBodyLengthLimit = 8388608)] // Compliant: 8388608 B = 8192 KB = 8 MB
    public IActionResult MultipartFormRequest(Model model)
    {
    // ...
    }
}

In Web.config:

<configuration>
    <system.web>
        <httpRuntime maxRequestLength="8192" executionTimeout="3600" />
        <!-- Compliant: maxRequestLength is expressed in KB, so 8192 KB = 8 MB  -->
    </system.web>
    <system.webServer>
        <security>
            <requestFiltering>
                <requestLimits maxAllowedContentLength="8388608" />
                <!-- Compliant: maxAllowedContentLength is expressed in bytes, so 8388608 B = 8192 KB = 8 MB  -->
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

See

================================================ FILE: analyzers/rspec/cs/S5693.json ================================================ { "title": "Allowing requests with excessive content length is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5693", "sqKey": "S5693", "scope": "All", "securityStandards": { "CWE": [ 400, 770 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "2.2" ], "PCI DSS 4.0": [ "2.2" ], "ASVS 4.0": [ "12.1.1", "12.1.3" ] } } ================================================ FILE: analyzers/rspec/cs/S5753.html ================================================

ASP.NET 1.1+ comes with a feature called Request Validation, preventing the server to accept content containing un-encoded HTML. This feature comes as a first protection layer against Cross-Site Scripting (XSS) attacks and act as a simple Web Application Firewall (WAF) rejecting requests potentially containing malicious content.

While this feature is not a silver bullet to prevent all XSS attacks, it helps to catch basic ones. It will for example prevent <script type="text/javascript" src="https://malicious.domain/payload.js"> to reach your Controller.

Note: Request Validation feature being only available for ASP.NET, no Security Hotspot is raised on ASP.NET Core applications.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

At Controller level:

[ValidateInput(false)]
public ActionResult Welcome(string name)
{
  ...
}

At application level, configured in the Web.config file:

<configuration>
   <system.web>
      <pages validateRequest="false" />
      ...
      <httpRuntime requestValidationMode="0.0" />
   </system.web>
</configuration>

Compliant Solution

At Controller level:

[ValidateInput(true)]
public ActionResult Welcome(string name)
{
  ...
}

or

public ActionResult Welcome(string name)
{
  ...
}

At application level, configured in the Web.config file:

<configuration>
   <system.web>
      <pages validateRequest="true" />
      ...
      <httpRuntime requestValidationMode="4.5" />
   </system.web>
</configuration>

See

================================================ FILE: analyzers/rspec/cs/S5753.json ================================================ { "title": "Disabling ASP.NET \"Request Validation\" feature is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5753", "sqKey": "S5753", "scope": "Main", "securityStandards": { "CWE": [ 79 ], "OWASP": [ "A7" ], "OWASP Top 10 2021": [ "A3" ], "PCI DSS 3.2": [ "6.5.7" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.3.3" ] } } ================================================ FILE: analyzers/rspec/cs/S5766.html ================================================

When an object is created via deserialization, its constructor is often not executed: the object is instead built directly from its serialized data.
This means an attacker can create a malicious object and completely bypass security checks.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

In the following examples, Serializable classes either omit validation or apply different validation logic during instantiation than during deserialization.

For classes inheriting ISerializable:

[Serializable]
public class InternalUrl : ISerializable
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        if(!tmpUrl.StartsWith("http://localhost/"))
        {
            url = "http://localhost/default";
        }
        else
        {
            url = tmpUrl;
        }
    }

    // Special Deserialization constructor
    protected InternalUrl(SerializationInfo info, StreamingContext context)
    {
       url = (string) info.GetValue("url", typeof(string));
       // Sensitive - no validation
     }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("url", url);
    }
}

For classes inheriting IDeserializationCallback:

[Serializable]
public class InternalUrl : IDeserializationCallback
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        if(!tmpUrl.StartsWith("http://localhost/"))
        {
            url = "http://localhost/default";
        }
        else
        {
            url = tmpUrl;
        }
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
       // Sensitive - no validation
    }
}

For classes inheriting from neither of previous types:

[Serializable]
public class InternalUrl
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        // Sensitive - no validation
        url = tmpUrl;
    }
}

Compliant Solution

For classes inheriting ISerializable:

[Serializable]
public class InternalUrl : ISerializable
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        url = tmpUrl;
        validate();
    }

    // Special Deserialization constructor
    protected InternalUrl(SerializationInfo info, StreamingContext context)
    {
       url = (string) info.GetValue("url", typeof(string));
       validate();
     }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("url", url);
    }

    void validate()
    {
        if(!url.StartsWith("http://localhost/"))
        {
            url = "http://localhost/default";
        }
    }
}

For classes inheriting IDeserializationCallback:

[Serializable]
public class InternalUrl : IDeserializationCallback
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        url = tmpUrl;
        validate();
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        validate();
    }
}

For classes inheriting from neither of previous types:

[Serializable]
public class InternalUrl
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        if(!tmpUrl.StartsWith("http://localhost/"))
        {
            url = "http://localhost/default";
        }
        else
        {
            url = tmpUrl;
        }
    }
}

See

================================================ FILE: analyzers/rspec/cs/S5766.json ================================================ { "title": "Creating Serializable objects without data validation checks is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5766", "sqKey": "S5766", "scope": "All", "securityStandards": { "CWE": [ 502 ], "OWASP": [ "A8" ], "OWASP Top 10 2021": [ "A8" ], "ASVS 4.0": [ "1.5.2", "5.5.1", "5.5.3" ] } } ================================================ FILE: analyzers/rspec/cs/S5773.html ================================================

Deserialization is the process of converting serialized data (such as objects or data structures) back into their original form. Types allowed to be unserialized should be strictly controlled.

Why is this an issue?

During the deserialization process, the state of an object will be reconstructed from the serialized data stream. By allowing unrestricted deserialization of types, the application makes it possible for attackers to use types with dangerous or otherwise sensitive behavior during the deserialization process.

What is the potential impact?

When an application deserializes untrusted data without proper restrictions, an attacker can craft malicious serialized objects. Depending on the affected objects and properties, the consequences can vary.

Remote Code Execution

If attackers can craft malicious serialized objects that contain executable code, this code will run within the application’s context, potentially gaining full control over the system. This can lead to unauthorized access, data breaches, or even complete system compromise.

For example, a well-known attack vector consists in serializing an object of type TempFileCollection with arbitrary files (defined by an attacker) which will be deleted on the application deserializing this object (when the finalize() method of the TempFileCollection object is called). These kinds of specially crafted serialized objects are called "gadgets".

Privilege escalation

Unrestricted deserialization can also enable attackers to escalate their privileges within the application. By manipulating the serialized data, an attacker can modify object properties or bypass security checks, granting them elevated privileges that they should not have. This can result in unauthorized access to sensitive data, unauthorized actions, or even administrative control over the application.

Denial of Service

In some cases, an attacker can abuse the deserialization process to cause a denial of service (DoS) condition. By providing specially crafted serialized data, the attacker can trigger excessive resource consumption, leading to system instability or unresponsiveness. This can disrupt the availability of the application, impacting its functionality and causing inconvenience to users.

How to fix it

Code examples

Noncompliant code example

With BinaryFormatter, NetDataContractSerializer or SoapFormatter:

var myBinaryFormatter = new BinaryFormatter();
myBinaryFormatter.Deserialize(stream); // Noncompliant

With JavaScriptSerializer:

JavaScriptSerializer serializer1 = new JavaScriptSerializer(new SimpleTypeResolver()); // Noncompliant
serializer1.Deserialize<ExpectedType>(json);

Compliant solution

With BinaryFormatter, NetDataContractSerializer or SoapFormatter:

sealed class CustomBinder : SerializationBinder
{
   public override Type BindToType(string assemblyName, string typeName)
   {
       if (!(typeName == "type1" || typeName == "type2" || typeName == "type3"))
       {
          throw new SerializationException("Only type1, type2 and type3 are allowed");
       }
       return Assembly.Load(assemblyName).GetType(typeName);
   }
}

var myBinaryFormatter = new BinaryFormatter();
myBinaryFormatter.Binder = new CustomBinder();
myBinaryFormatter.Deserialize(stream);

With JavaScriptSerializer:

public class CustomSafeTypeResolver : JavaScriptTypeResolver
{
   public override Type ResolveType(string id)
   {
      if(id != "ExpectedType") {
         throw new ArgumentNullException("Only ExpectedType is allowed during deserialization");
      }
      return Type.GetType(id);
   }
}

JavaScriptSerializer serializer = new JavaScriptSerializer(new CustomSafeTypeResolver());
serializer.Deserialize<ExpectedType>(json);

Going the extra mile

Instead of using BinaryFormatter and similar serializers, it is recommended to use safer alternatives in most of the cases, such as XmlSerializer or DataContractSerializer.

If it’s not possible then try to mitigate the risk by restricting the types allowed to be deserialized:

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S5773.json ================================================ { "title": "Types allowed to be deserialized should be restricted", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "quickfix": "targeted", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5773", "sqKey": "S5773", "scope": "Main", "securityStandards": { "CWE": [ 502 ], "OWASP": [ "A8" ], "OWASP Top 10 2021": [ "A8" ], "ASVS 4.0": [ "1.5.2", "5.5.1", "5.5.3" ] } } ================================================ FILE: analyzers/rspec/cs/S5856.html ================================================

Why is this an issue?

Regular expressions have their own syntax that is understood by regular expression engines. Those engines will throw an exception at runtime if they are given a regular expression that does not conform to that syntax.

To avoid syntax errors, special characters should be escaped with backslashes when they are intended to be matched literally and references to capturing groups should use the correctly spelled name or number of the group.

Negative lookaround groups cannot be combined with RegexOptions.NonBacktracking. Such combination would throw an exception during runtime.

How to fix it

Code examples

Noncompliant code example

void Regexes(string input)
{
    var regex = new Regex("[A");                                                    // Noncompliant
    var match = Regex.Match(input, "[A");                                           // Noncompliant
    var negativeLookahead = new Regex("a(?!b)", RegexOptions.NonBacktracking);      // Noncompliant
    var negativeLookbehind = new Regex("(?<!a)b", RegexOptions.NonBacktracking);    // Noncompliant
}

Compliant solution

void Regexes(string input)
{
    var regex = new Regex("[A-Z]");
    var match = Regex.Match(input, "[A-Z]");
    var negativeLookahead = new Regex("a(?!b)");
    var negativeLookbehind = new Regex("(?<!a)b");
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S5856.json ================================================ { "title": "Regular expressions should be syntactically valid", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "regex" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5856", "sqKey": "S5856", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6354.html ================================================

Why is this an issue?

One of the principles of a unit test is that it must have full control of the system under test. This is problematic when production code includes calls to static methods, which cannot be changed or controlled. Date/time functions are usually provided by system libraries as static methods.

This can be improved by wrapping the system calls in an object or service that can be controlled inside the unit test.

Noncompliant code example

public class Foo
{
    public string HelloTime() =>
        $"Hello at {DateTime.UtcNow}";
}

Compliant solution

There are different approaches to solve this problem. One of them is suggested below. There are also open source libraries (such as NodaTime) which already implement an IClock interface and a FakeClock testing class.

public interface IClock
{
    DateTime UtcNow();
}

public class Foo
{
    public string HelloTime(IClock clock) =>
        $"Hello at {clock.UtcNow()}";
}

public class FooTest
{
    public record TestClock(DateTime now) : IClock
    {
        public DateTime UtcNow() => now;
    }

    [Fact]
    public void HelloTime_Gives_CorrectTime()
    {
        var dateTime = new DateTime(2017, 06, 11);
        Assert.Equal((new Foo()).HelloTime(new TestClock(dateTime)), $"Hello at {dateTime}");
    }
}

Another possible solution is using an adaptable static class, ideally supports an IDisposable method, that not only adjusts the time behaviour for the current thread only, but also for scope of the using.

public static class Clock
{
    public static DateTime UtcNow() { /* ... */ }
    public static IDisposable SetTimeForCurrentThread(Func<DateTime> time) { /* ... */ }
}

public class Foo
{
    public string HelloTime() =>
        $"Hello at {Clock.UtcNow()}";
}

public class FooTest
{
    [Fact]
    public void HelloTime_Gives_CorrectTime()
    {
        var dateTime = new DateTime(2017, 06, 11);
        using (Clock.SetTimeForCurrentThread(() => dateTime))
        {
             Assert.Equal((new Foo()).HelloTime(), $"Hello at {dateTime}");
        }
    }
}

Resources

NodaTime testing

================================================ FILE: analyzers/rspec/cs/S6354.json ================================================ { "title": "Use a testable date\/time provider", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6354", "sqKey": "S6354", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6377.html ================================================

XML signatures are a method used to ensure the integrity and authenticity of XML documents. However, if XML signatures are not validated securely, it can lead to potential vulnerabilities.

Why is this an issue?

XML can be used for a wide variety of purposes. Using a signature on an XML message generally indicates this message requires authenticity and integrity. However, if the signature validation is not properly implemented this authenticity can not be guaranteed.

What is the potential impact?

By not enforcing secure validation, the XML Digital Signature API is more susceptible to attacks such as signature spoofing and injections.

Increased Vulnerability to Signature Spoofing

By disabling secure validation, the application becomes more susceptible to signature spoofing attacks. Attackers can potentially manipulate the XML signature in a way that bypasses the validation process, allowing them to forge or tamper with the signature. This can lead to the acceptance of invalid or maliciously modified signatures, compromising the integrity and authenticity of the XML documents.

Risk of Injection Attacks

Disabling secure validation can expose the application to injection attacks. Attackers can inject malicious code or entities into the XML document, taking advantage of the weakened validation process. In some cases, it can also expose the application to denial-of-service attacks. Attackers can exploit vulnerabilities in the validation process to cause excessive resource consumption or system crashes, leading to service unavailability or disruption.

How to fix it in ASP.NET Core

Code examples

The following noncompliant code example verifies an XML signature without providing a trusted public key. This code will validate the signature against the embedded public key, accepting any forged signature.

Noncompliant code example

XmlDocument xmlDoc = new()
{
    PreserveWhitespace = true
};
xmlDoc.Load("/data/login.xml");
SignedXml signedXml = new(xmlDoc);
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement?)nodeList[0]);
if (signedXml.CheckSignature()) {
    // Process the XML content
} else {
    // Raise an error
}

Compliant solution

CspParameters cspParams = new()
{
    KeyContainerName = "MY_RSA_KEY"
};
RSACryptoServiceProvider rsaKey = new(cspParams);

XmlDocument xmlDoc = new()
{
    PreserveWhitespace = true
};
xmlDoc.Load("/data/login.xml");
SignedXml signedXml = new(xmlDoc);
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement?)nodeList[0]);
if (signedXml.CheckSignature(rsaKey)) {
    // Process the XML content
} else {
    // Raise an error
}

How does this work?

Here, the compliant solution provides an RSA public key to the signature validation function. This will ensure only signatures computed with the associated private key will be accepted, preventing signature forgery attacks.

Using the CheckSignature method without providing a key can be risky because it may search the AddressBook store for certificates, which includes all trusted root CA certificates on the machine. This broad trust base can be exploited by attackers. Additionally, if the document is not signed with an X.509 signature, the method will use the key embedded in the signature element, which can lead to accepting signatures from untrusted sources.

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S6377.json ================================================ { "title": "XML signatures should be validated securely", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6377", "sqKey": "S6377", "scope": "Main", "securityStandards": { "CWE": [ 347 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "STIG ASD_V5R3": [ "V-222608" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6418.html ================================================

Why is this an issue?

Hard-coding secrets in source code or binaries makes it easy for attackers to extract sensitive information, especially in distributed or open-source applications. This practice exposes credentials and tokens, increasing the risk of unauthorized access and data breaches.

This rule detects variables/fields/properties having a name matching a list of words (secret, token, credential, auth, api[_.-]?key) being assigned a pseudorandom hard-coded value. The pseudorandomness of the hard-coded value is based on its entropy and the probability to be human-readable. The randomness sensibility can be adjusted if needed. Lower values will detect less random values, raising potentially more false positives.

How to fix it

Secrets should be stored in a configuration file that is not committed to the code repository, in a database, or managed by your cloud provider’s secrets management service. If a secret is exposed in the source code, it must be rotated immediately.

Code Examples

Noncompliant code example

const string mySecret = "47828a8dd77ee1eb9dde2d5e93cb221ce8c32b37";

Compliant solution

static readonly string mySecret = Environment.GetEnvironmentVariable("MY_APP_SECRET");

Resources

================================================ FILE: analyzers/rspec/cs/S6418.json ================================================ { "title": "Secrets should not be hard-coded", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "quickfix": "infeasible", "tags": [ "cwe" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-6418", "sqKey": "S6418", "scope": "Main", "securityStandards": { "CERT": [ "MSC03-J." ], "CWE": [ 798 ], "OWASP": [ "A2" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.4", "3.5.2", "6.4.1" ] } } ================================================ FILE: analyzers/rspec/cs/S6419.html ================================================

Why is this an issue?

An Azure Function should be stateless as there’s no control over where and when function instances are provisioned and de-provisioned. Managing and storing data/state between requests can lead to inconsistencies. If, for any reason, you need to have a stateful function, consider using the Durable Functions extension of Azure Functions.

Noncompliant code example

    public static class HttpExample
    {
        private static readonly int port = 2000;
        private static int numOfRequests = 1;

        [FunctionName("HttpExample")]
        public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest request, ILogger log)
        {
            numOfRequests += 1; // Noncompliant
            log.LogInformation($"Number of POST requests is {numOfRequests}.");

            string responseMessage = $"HttpRequest was made on port {port}."; // Compliant, state is only read.

            return new OkObjectResult(responseMessage);
        }
    }

Compliant solution

    public static class HttpExample
    {
        private static readonly int port = 2000;

        [FunctionName("HttpExample")]
        public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest request, ILogger log)
        {
            // A compliant solution would be to manage the `numOfRequests` with an entity function or would use storage (e.g., Azure Blob storage, Azure Queue Storage)
            // to share the state between functions.

            string responseMessage = $"HttpRequest was made on port {port}.";

            return new OkObjectResult(responseMessage);
        }
    }

Resources

================================================ FILE: analyzers/rspec/cs/S6419.json ================================================ { "title": "Azure Functions should be stateless", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "azure", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6419", "sqKey": "S6419", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6420.html ================================================

Why is this an issue?

To avoid holding more connections than necessary and to avoid potentially exhausting the number of available sockets when using HttpClient, DocumentClient, QueueClient, ConnectionMultiplexer or Azure Storage clients, consider:

These classes typically manage their own connections to the resource, and thus are intended to be instantiated once and reused throughout the lifetime of an application.

Noncompliant code example

    public class HttpExample
    {
        [FunctionName("HttpExample")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest request)
        {
            HttpClient httpClient = new HttpClient(); // Noncompliant

            var response = await httpClient.GetAsync("https://example.com");
            // rest of the function
        }
    }

Compliant solution

    public class HttpExample
    {
        [FunctionName("HttpExample")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest request, IHttpClientFactory clientFactory)
        {
            var httpClient = clientFactory.CreateClient();
            var response = await httpClient.GetAsync("https://example.com");
            // rest of the function
        }
    }

Resources

================================================ FILE: analyzers/rspec/cs/S6420.json ================================================ { "title": "Client instances should not be recreated on each Azure Function invocation", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "azure", "bad-practice", "design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6420", "sqKey": "S6420", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6421.html ================================================

Why is this an issue?

The top-most level of an Azure Function code should include a try/catch block to capture and log all errors so you can monitor the health of the application effectively. In case a retry policy has been defined for your Azure Function, you should rethrow any errors that should result in a retry.

Noncompliant code example

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
    // Noncompliant
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    // do stuff
}

Compliant solution

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
{
    try
    {
        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        // do stuff
    }
    catch (Exception ex)
    {
        // do stuff
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S6421.json ================================================ { "title": "Azure Functions should use Structured Error Handling", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "azure", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6421", "sqKey": "S6421", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6422.html ================================================

Why is this an issue?

Making blocking calls to async methods transforms the code into a synchronous operation. Doing so inside an Azure Function can lead to thread pool exhaustion.

Thread pool exhaustion refers to a situation where all available threads in a thread pool are occupied, and new tasks or work items cannot be scheduled for execution due to the lack of available threads. This can lead to delayed execution and degraded performance.

class RequestParser
{
	[FunctionName(nameof(ParseRequest))]
	public static async Task<IActionResult> ParseRequest([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
	{
		// This can lead to thread exhaustion
		string requestBody = new StreamReader(req.Body).ReadToEndAsync().Result;
		// do stuff...
	}
}

Instead, asynchronous mechanisms should be used:

class RequestParser
{
	[FunctionName(nameof(ParseRequest))]
	public static async Task<IActionResult> ParseRequest([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
	{
		// Non-blocking, asynchronous operation
		string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
		// do stuff...
	}
}

This applies to multiple methods that are available when working with tasks:

Goal Blocking Asynchronous

Wait for the result of a task

Task.Wait, Task.Result or Task.GetAwaiter.GetResult

await

Wait for any of many task to complete

Task.WaitAny

await Task.WhenAny

Wait for all of many tasks to complete

Task.WaitAll

await Task.WhenAll

Wait a period of time

Thread.Sleep

await Task.Delay

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6422.json ================================================ { "title": "Calls to \"async\" methods should not be blocking in Azure Functions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "azure", "async-await" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-6422", "sqKey": "S6422", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6423.html ================================================

Why is this an issue?

Capturing and logging errors is critical to monitoring the health of your Azure Functions application.

Each catch block inside an Azure Function should log helpful details about the failure. Moreover, the logging should not be done at Debug or Trace level.

Consider using the built-in integration with Application Insights for better monitoring of your Application.

Noncompliant code example

[FunctionName("Foo")]
public static async Task<IActionResult> Run(
	[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
	ILogger log)
{
	try
	{
		// do stuff that can fail
	}
	catch (Exception ex)
	{
		// the failure is not logged at all OR is logged at DEBUG/TRACE level
	}
}

Compliant solution

[FunctionName("Foo")]
public static async Task<IActionResult> Run(
	[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
	ILogger log)
{
	try
	{
		// do stuff that can fail
	}
	catch (Exception ex)
	{
		log.LogError(ex, "Give details that will help investigations");
	}
}

Resources

================================================ FILE: analyzers/rspec/cs/S6423.json ================================================ { "title": "Azure Functions should log all failures", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "azure", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6423", "sqKey": "S6423", "scope": "Main", "securityStandards": { "STIG ASD_V5R3": [ "V-222610" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6424.html ================================================

Why is this an issue?

The recommended way to access Azure Durable Entities is through generated proxy objects with the help of interfaces.

The following restrictions, during interface design, are enforced:

If any of these rules are violated, an InvalidOperationException is thrown at runtime when the interface is used as a type argument to IDurableEntityContext.SignalEntity<TEntityInterface>, IDurableEntityClient.SignalEntityAsync<TEntityInterface> or IDurableOrchestrationContext.CreateEntityProxy<TEntityInterface>. The exception message explains which rule was broken.

This rule raises an issue in case any of the restrictions above is not respected.

Noncompliant code example

namespace Foo // Noncompliant, must be defined in the same assembly as the entity class that implements it
{
    public interface ICounter<T> // Noncompliant, interfaces cannot contain generic parameters
    {
        string Name { get; set; } // Noncompliant, interface must only define methods
        void Add(int amount, int secondParameter); // Noncompliant, methods must not have more than one parameter
        int Get(); // Noncompliant, methods must return void, Task, or Task<T>
    }
}

namespace Bar
{
    public class Counter : ICounter
    {
        // do stuff
    }

    public static class AddToCounterFromQueue
    {
        [FunctionName("AddToCounterFromQueue")]
        public static Task Run(
            [QueueTrigger("durable-function-trigger")] string input,
            [DurableClient] IDurableEntityClient client)
        {
            var entityId = new EntityId("Counter", "myCounter");
            int amount = int.Parse(input);
            return client.SignalEntityAsync<ICounter>(entityId, proxy => proxy.Add(amount, 10));
        }
    }
}

Compliant solution

namespace Bar
{
    public interface ICounter
    {
        void Add(int amount);
        Task<int> Get();
    }
}

namespace Bar
{
    public class Counter : ICounter
    {
        // do stuff
    }

    public static class AddToCounterFromQueue
    {
        [FunctionName("AddToCounterFromQueue")]
        public static Task Run(
            [QueueTrigger("durable-function-trigger")] string input,
            [DurableClient] IDurableEntityClient client)
        {
            var entityId = new EntityId("Counter", "myCounter");
            int amount = int.Parse(input);
            return client.SignalEntityAsync<ICounter>(entityId, proxy => proxy.Add(amount));
        }
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S6424.json ================================================ { "title": "Interfaces for durable entities should satisfy the restrictions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "azure", "design" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-6424", "sqKey": "S6424", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6444.html ================================================

Not specifying a timeout for regular expressions can lead to a Denial-of-Service attack. Pass a timeout when using System.Text.RegularExpressions to process untrusted input because a malicious user might craft a value for which the evaluation lasts excessively long.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

public void RegexPattern(string input)
{
    var emailPattern = new Regex(".+@.+", RegexOptions.None);
    var isNumber = Regex.IsMatch(input, "[0-9]+");
    var isLetterA = Regex.IsMatch(input, "(a+)+");
}

Compliant Solution

public void RegexPattern(string input)
{
    var emailPattern = new Regex(".+@.+", RegexOptions.None, TimeSpan.FromMilliseconds(100));
    var isNumber = Regex.IsMatch(input, "[0-9]+", RegexOptions.None, TimeSpan.FromMilliseconds(100));
    var isLetterA = Regex.IsMatch(input, "(a+)+", RegexOptions.NonBacktracking); // .Net 7 and above
    AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100)); // process-wide setting
}

See

================================================ FILE: analyzers/rspec/cs/S6444.json ================================================ { "title": "Not specifying a timeout for regular expressions is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "regex" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6444", "sqKey": "S6444", "scope": "Main", "securityStandards": { "CWE": [ 400, 1333 ], "OWASP": [ "A1" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6507.html ================================================

Why is this an issue?

Locking on a local variable can undermine synchronization because two different threads running the same method in parallel will potentially lock on different instances of the same object, allowing them to access the synchronized block at the same time.

Noncompliant code example

private void DoSomething()
{
  object local = new object();
  // Code potentially modifying the local variable ...

  lock (local) // Noncompliant
  {
    // ...
  }
}

Compliant solution

private readonly object lockObj = new object();

private void DoSomething()
{
  lock (lockObj)
  {
    //...
  }
}

Resources

================================================ FILE: analyzers/rspec/cs/S6507.json ================================================ { "title": "Blocks should not be synchronized on local variables", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "multi-threading" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6507", "sqKey": "S6507", "scope": "All", "securityStandards": { "CWE": [ 412, 413 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6513.html ================================================

Why is this an issue?

The ExcludeFromCodeCoverageAttribute is used to exclude portions of code from code coverage reporting. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the Justification property was added to the ExcludeFromCodeCoverageAttribute as an opportunity to document the rationale for the exclusion. This rule raises an issue when no such justification is given.

Noncompliant code example

public struct Coordinates
{
    public int X { get; }
    public int Y { get; }

    [ExcludeFromCodeCoverage] // Noncompliant
    public override bool Equals(object obj) => obj is Coordinates coordinates && X == coordinates.X && Y == coordinates.Y;

    [ExcludeFromCodeCoverage] // Noncompliant
    public override int GetHashCode()
    {
        var hashCode = 1861411795;
        hashCode = hashCode * -1521134295 + X.GetHashCode();
        hashCode = hashCode * -1521134295 + Y.GetHashCode();
        return hashCode;
    }
}

Compliant solution

public struct Coordinates
{
    public int X { get; }
    public int Y { get; }

    [ExcludeFromCodeCoverage(Justification = "Code generated by Visual Studio refactoring")] // Compliant
    public override bool Equals(object obj) => obj is Coordinates coordinates && X == coordinates.X && Y == coordinates.Y;

    [ExcludeFromCodeCoverage(Justification = "Code generated by Visual Studio refactoring")] // Compliant
    public override int GetHashCode()
    {
        var hashCode = 1861411795;
        hashCode = hashCode * -1521134295 + X.GetHashCode();
        hashCode = hashCode * -1521134295 + Y.GetHashCode();
        return hashCode;
    }
}

Resources

================================================ FILE: analyzers/rspec/cs/S6513.json ================================================ { "title": "\"ExcludeFromCodeCoverage\" attributes should include a justification", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6513", "sqKey": "S6513", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6561.html ================================================

The rule targets the use of DateTime.Now call followed by some arithmetic operation.

Why is this an issue?

Using DateTime.Now calls within a subtraction operation to measure elapsed time is not recommended. This property is subject to changes such as daylight savings transitions, which can invalidate the calculation if the change occurs during the benchmark session, or when updating a timer. Moreover, DateTime.Now is dependent on the system clock, which may have low resolution on older systems (as low as 15 milliseconds).

How to fix it

Code examples

If the purpose is to benchmark something then, instead of the DateTime.Now property, it’s recommended to use Stopwatch, which is not affected by changes in time such as daylight savings (DST) and automatically checks for the existence of high-precision timers. As a bonus, the StopWatch class is also lightweight and computationally faster than DateTime.

Noncompliant code example

var start = DateTime.Now; // First call, on March 26th 2:59 am
MethodToBeBenchmarked();

Console.WriteLine($"{(DateTime.Now - start).TotalMilliseconds} ms"); // Second call happens 2 minutes later but `Now` is March 26th, 4:01 am as there's a shift to summer time

Compliant solution

var stopWatch = Stopwatch.StartNew(); // Compliant
MethodToBeBenchmarked();
stopWatch.Stop();

Console.WriteLine($"{stopWatch.ElapsedMilliseconds} ms");

If, on the other hand, the goal is to refresh a timer prefer using the DateTime.UtcNow property, which guarantees reliable results when doing arithmetic operations during DST transitions.

Noncompliant code example

if ((DateTime.Now - lastRefresh).TotalMilliseconds > MinRefreshInterval)
{
    lastRefresh = DateTime.Now;
    // Refresh
}

Compliant solution

if ((DateTime.UtcNow - lastRefresh).TotalMilliseconds > MinRefreshInterval)
{
    lastRefresh = DateTime.UtcNow;
    // Refresh
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6561.json ================================================ { "title": "Avoid using \"DateTime.Now\" for benchmarking or timing operations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6561", "sqKey": "S6561", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6562.html ================================================

Why is this an issue?

Not knowing the Kind of the DateTime object that an application is using can lead to misunderstandings when displaying or comparing them. Explicitly setting the Kind property helps the application to stay consistent, and its maintainers understand what kind of date is being managed. To achieve this, when instantiating a new DateTime object you should always use a constructor overload that allows you to define the Kind property.

What is the potential impact?

Creating the DateTime object without specifying the property Kind will set it to the default value of DateTimeKind.Unspecified. In this case, calling the method ToUniversalTime will assume that Kind is DateTimeKind.Local and calling the method ToLocalTime will assume that it’s DateTimeKind.Utc. As a result, you might have mismatched DateTime objects in your application.

How to fix it

To resolve this issue, use a constructor overload that lets you specify the DateTimeKind when creating the DateTime object. From .Net 6 onwards, use the DateOnly type if the time portion of the date is not relevant.

Code examples

Noncompliant code example

void CreateNewTime()
{
    var birthDate = new DateTime(1994, 7, 5, 16, 23, 42);
}

Compliant solution

void CreateNewTime()
{
    var birthDate = new DateTime(1994, 7, 5, 16, 23, 42, DateTimeKind.Utc);
    // or from .Net 6 onwards, use DateOnly:
    var birthDate = new DateOnly(1994, 7, 5);
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6562.json ================================================ { "title": "Always set the \"DateTimeKind\" when creating new \"DateTime\" instances", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "localisation", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6562", "sqKey": "S6562", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6563.html ================================================

Why is this an issue?

You should avoid recording time instants with the use of property DateTime.Now. The property DateTime.Now returns the current date and time expressed in the machine’s local time without containing any timezone-related information (for example, the offset from Coordinated Universal Time). Not having this information means that if you need to display this DateTime object or use it for computations in another machine placed in a different time zone, you won’t be able to reconstruct it in the second machine’s local time without knowing the origin’s offset. This will likely lead to confusion and potential bugs.

Instead, you should record the DateTime instants in UTC, which gives you the date and time as it is in the Coordinated Universal Time. UTC is a time standard for all time zones and is not subjected to Daylight Saving Time (DST).

Similarly, the use of the DateTime.Today property should also be avoided, as it can return different date values depending on the time zone.

Generally, unless the purpose is to only display the Date and Time to a user on their local machine, you should always use UTC (for example, when storing dates in a datebase or using them for calculations).

What is the potential impact?

You can end up with DateTime instants that have no meaning for anyone except the machine they were recorded on. Using UTC gives an unambiguous representation of an instant, and this UTC instant can be transformed into any equivalent local time. This operation isn’t reversible as some local times are ambiguous and can be matched to more than one UTC instant (for example, due to daylight savings).

How to fix it

Instead of DateTime.Now use any of the following:

Instead of DateTime.Today use any of the following:

Code examples

Noncompliant code example

void LogDateTime()
{
    using var streamWriter = new StreamWriter("logs.txt", true);
    streamWriter.WriteLine($"DateTime:{DateTime.Now.ToString("o")}"); // This log won't have any meaning if it's reconstructed in a machine in a different timezone.
}

Compliant solution

void LogDateTime()
{
    using var streamWriter = new StreamWriter("logs.txt", true);
    streamWriter.WriteLine($"DateTime:{DateTime.UtcNow.ToString("o")}");
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6563.json ================================================ { "title": "Use UTC when recording DateTime instants", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6563", "sqKey": "S6563", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6566.html ================================================

This rule recommends using DateTimeOffset instead of DateTime for projects targeting .NET Framework 2.0 or later.

Why is this an issue?

You should use DateTimeOffset instead of DateTime as it provides all the information that the DateTime struct has, and additionally, the offset from Coordinated Universal Time (UTC). This way you can avoid potential problems created by the lack of timezone awareness (see the "Pitfalls" section below for more information).

However, it’s important to note that although DateTimeOffset contains more information than DateTime by storing the offset to UTC, it isn’t tied to a specific time zone. This information must be stored separately to have a full picture of the moment in time with the use of TimeZoneInfo.

How to fix it

In most cases, you can directly replace DateTime with DateTimeOffset. When hardcoding dates with local kind, remember that the offset is timezone dependent, so it should be set according to which timezone that data represents. For more information, refer to DateTime and DateTimeOffset documentation from Microsoft (see the "Resources" section below).

Code examples

Noncompliant code example

DateTime myDate = new DateTime(2008, 6, 19, 7, 0, 0, DateTimeKind.Local); // Noncompliant

var now = DateTime.Now; // Noncompliant

Compliant solution

DateTimeOffset myDate = new DateTimeOffset(2008, 6, 19, 7, 0, 0, TimeSpan.FromHours(-7)); // Compliant

var now = DateTimeOffset.Now; // Compliant

Pitfalls

Common DateTime pitfalls include:

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6566.json ================================================ { "title": "Use \"DateTimeOffset\" instead of \"DateTime\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6566", "sqKey": "S6566", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6575.html ================================================

Since .NET 6 you don’t have to use the TimeZoneConverter library to manually do the conversion between IANA and Windows timezones. The .NET 6.0 introduced new Time Zone enhancements, one being the TimeZoneInfo.FindSystemTimeZoneById(string timezone) method now accepts as input both IANA and Windows time zone IDs on any operating system with installed time zone data. TimeZoneInfo.FindSystemTimeZoneById will automatically convert its input from IANA to Windows and vice versa if the requested time zone is not found on the system.

Why is this an issue?

The method TimeZoneInfo.FindSystemTimeZoneById(string timezone) can get both IANA and Windows timezones as input and automatically convert one to the other if the requested time zone is not found on the system. Because one does not need to handle the conversion, the code will be less complex and easier to maintain.

How to fix it

There’s no need to translate manually between time zones; it is enough to call TimeZoneInfo.FindSystemTimeZoneById(string timezone), where the timezone can be IANA or Windows format. Depending on the OS, the equivalent time zone will be returned (Windows Time Zones for Windows and IANA timezones for Linux, macOS).

Code examples

Noncompliant code example

// Assuming we are in Windows OS and we need to get the Tokyo Time Zone.
var ianaTimeZone = "Asia/Tokyo";
var windowsTimeZone = TZConvert.IanaToWindows(ianaTimeZone);
TimeZoneInfo tokyoWindowsTimeZone = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZone);

Compliant solution

// Assuming we are in Windows OS and we need to get the Tokyo Time Zone.
var ianaTimeZone = "Asia/Tokyo";
TimeZoneInfo tokyoWindowsTimeZone = TimeZoneInfo.FindSystemTimeZoneById(ianaTimeZone);

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S6575.json ================================================ { "title": "Use \"TimeZoneInfo.FindSystemTimeZoneById\" without converting the timezones with \"TimezoneConverter\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6575", "sqKey": "S6575", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6580.html ================================================

When converting a string representation of a date and time to a DateTime object or any other temporal type with one of the available system parsing methods, you should always provide an IFormatProvider parameter.

Why is this an issue?

If you try to parse a string representation of a date or time without a format provider, the method will use the machine’s CultureInfo; if the given string does not follow it, you’ll have an object that does not match the string representation or an unexpected runtime error.

This rule raises an issue for the following date and time string representation parsing methods:

Of the following types:

How to fix it

Alway use an overload of the parse method, where you can provide an IFormatProvider parameter.

Code examples

Noncompliant code example

var dateTimeString = "4/12/2023 4:05:48 PM"; // This is an en-US format string - 12 of April 2023
var dateTimeObject = DateTime.Parse(dateTimeString); // This is wrongly parsed as 4th of December, when it's read in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe)

var dateTimeString2 = "4/13/2023 4:05:48 PM"; // This is an en-US format string - 13 of April 2023
var dateTimeObject2 = DateTime.Parse(dateTimeString2); // Runtime Error, when it's parsed in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe).

var timeInSaudiArabia = new TimeOnly(16, 23).ToString(new CultureInfo("ar-SA"));
var timeObject = TimeOnly.Parse(timeInSaudiArabia); // Runtime Error, when it's parsed in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe).

Compliant solution

var dateTimeString = "4/12/2023 4:05:48 PM"; // This is an en-US format string - 12 of April 2023
var dateTimeObject = DateTime.Parse(dateTimeString, new CultureInfo("en-US"));

var dateTimeString2 = "4/13/2023 4:05:48 PM"; // This is an en-US format string - 13 of April 2023
var dateTimeObject2 = DateTime.Parse(dateTimeString2, new CultureInfo("en-US"))

var timeInSaudiArabia = new TimeOnly(16, 23).ToString(new CultureInfo("ar-SA"));
var timeObject = TimeOnly.Parse(timeInSaudiArabia, new CultureInfo("ar-SA"));

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6580.json ================================================ { "title": "Use a format provider when parsing date and time", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall", "bug" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6580", "sqKey": "S6580", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6585.html ================================================

Why is this an issue?

Hardcoding the date and time format strings can lead to formats that consumers misunderstand. Also, if the same format is meant to be used in multiple places, it is easier to make a mistake when it’s hardcoded instead of using a format provided by an IFormatProvider or using one of the standard format strings.

What is the potential impact?

If a non-conventional format is used, the formatted date and time can be misunderstood. Also, if a mistake is made in the format, the formatted date can be incomplete. For example, you might switch the place of the minutes and month parts of a date or simply forget to print the year.

How to fix it

Instead of hardcoding the format, provide one from the available formats through an IFormatProvider or use one of the standard format strings.

Code examples

Noncompliant code example

void PrintTime()
{
    Console.WriteLine(DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss"));

    Console.WriteLine(DateTime.UtcNow.ToString("dd/mm/yyyy HH:MM:ss")); // Months and minutes have changed their places
}

Compliant solution

void PrintTime()
{
    Console.WriteLine(DateTime.UtcNow.ToString(CultureInfo.GetCultureInfo("es-MX")));

    Console.WriteLine(DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)); // Better provide a well known culture, so this kind of issues do not pop up
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6585.json ================================================ { "title": "Don\u0027t hardcode the format when turning dates and times to strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6585", "sqKey": "S6585", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6588.html ================================================

Why is this an issue?

With .NET Core the UnixEpoch field was introduced to DateTime and DateTimeOffset types. Using this field clearly states that the intention is to use the beginning of the Unix epoch.

What is the potential impact?

You should not use the DateTime or DateTimeOffset constructors to set the time to the 1st of January 1970 to represent the beginning of the Unix epoch. Not everyone is familiar with what this particular date is representing and it can be misleading.

How to fix it

To fix this issue, use the UnixEpoch field of DateTime or DateTimeOffset instead of the constructor.

Code examples

Noncompliant code example

void GetEpochTime()
{
    var epochTime = new DateTime(1970, 1, 1);
}

Compliant solution

void GetEpochTime()
{
    var epochTime = DateTime.UnixEpoch;
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6588.json ================================================ { "title": "Use the \"UnixEpoch\" field instead of creating \"DateTime\" instances that point to the beginning of the Unix epoch", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6588", "sqKey": "S6588", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S6602.html ================================================

Why is this an issue?

Both the List.Find method and the Enumerable.FirstOrDefault method can be used to locate the first element that meets a specified condition within a collection. However, for List objects, List.Find may offer superior performance compared to Enumerable.FirstOrDefault. While the performance difference might be negligible for small collections, it can become significant for larger collections. This observation also holds true for ImmutableList and arrays.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought FirstOrDefault closer to the performance of collection-specific Find methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 2x improvement in the execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The Find method is defined on the collection class, and it has the same signature as FirstOrDefault extension method. The function can be replaced in place.

Code examples

Noncompliant code example

int GetValue(List<int> data) =>
    data.FirstOrDefault(x => x % 2 == 0);
int GetValue(int[] data) =>
    data.FirstOrDefault(x => x % 2 == 0);

Compliant solution

int GetValue(List<int> data) =>
    data.Find(x => x % 2 == 0);
int GetValue(int[] data) =>
    Array.Find(data, x => x % 2 == 0);

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayFirstOrDefault

.NET 8.0

Array

10.515 μs

0.1410 μs

32 B

ArrayFind

.NET 8.0

Array

4.417 μs

0.0729 μs

-

ArrayFirstOrDefault

.NET 9.0

Array

2.262 μs

0.0135 μs

-

ArrayFind

.NET 9.0

Array

3.428 μs

0.0206 μs

-

ArrayFirstOrDefault

.NET Framework 4.8.1

Array

45.074 μs

0.7517 μs

32 B

ArrayFind

.NET Framework 4.8.1

Array

13.948 μs

0.1496 μs

-

ImmutableListFirstOrDefault

.NET 8.0

ImmutableList<T>

83.796 μs

1.3199 μs

72 B

ImmutableListFind

.NET 8.0

ImmutableList<T>

59.720 μs

1.0723 μs

-

ImmutableListFirstOrDefault

.NET 9.0

ImmutableList<T>

81.984 μs

1.0886 μs

72 B

ImmutableListFind

.NET 9.0

ImmutableList<T>

58.288 μs

0.8079 μs

-

ImmutableListFirstOrDefault

.NET Framework 4.8.1

ImmutableList<T>

446.893 μs

9.8430 μs

76 B

ImmutableListFind

.NET Framework 4.8.1

ImmutableList<T>

427.476 μs

3.3371 μs

-

ListFirstOrDefault

.NET 8.0

List<T>

14.808 μs

0.1723 μs

40 B

ListFind

.NET 8.0

List<T>

6.040 μs

0.1104 μs

-

ListFirstOrDefault

.NET 9.0

List<T>

2.233 μs

0.0154 μs

-

ListFind

.NET 9.0

List<T>

4.458 μs

0.0745 μs

-

ListFirstOrDefault

.NET Framework 4.8.1

List<T>

57.290 μs

1.0494 μs

40 B

ListFind

.NET Framework 4.8.1

List<T>

18.476 μs

0.0504 μs

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == 1;
private readonly static Predicate<int> ConditionPredicate = static x => x == 1;
private List<int> list;
private ImmutableList<int> immutableList;
private int[] array;
public const int N = 10_000;

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public int ListFirstOrDefault() =>
    list.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark]
public int ListFind() =>
    list.Find(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public int ImmutableListFirstOrDefault() =>
    immutableList.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public int ImmutableListFind() =>
    immutableList.Find(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public int ArrayFirstOrDefault() =>
    array.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public int ArrayFind() =>
    Array.Find(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6602.json ================================================ { "title": "\"Find\" method should be used instead of the \"FirstOrDefault\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6602", "sqKey": "S6602", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6603.html ================================================

Why is this an issue?

Both the List.TrueForAll method and the IEnumerable.All method can be used to check if all list elements satisfy a given condition in a collection. However, List.TrueForAll can be faster than IEnumerable.All for List objects. The performance difference may be minor for small collections, but for large collections, it can be noticeable.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought All closer to the performance of collection-specific TrueForAll methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 4x improvement both in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The TrueForAll method is defined on the collection class, and it has the same signature as the All extension method. The method can be replaced in place.

Code examples

Noncompliant code example

public bool AreAllEven(List<int> data) =>
    data.All(x => x % 2 == 0);
public bool AreAllEven(int[] data) =>
    data.All(x => x % 2 == 0);

Compliant solution

public bool AreAllEven(List<int> data) =>
    data.TrueForAll(x => x % 2 == 0);
public bool AreAllEven(int[] data) =>
    Array.TrueForAll(data, x => x % 2 == 0);

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayAll

.NET 8.0

Array

109.25 μs

1.767 μs

32 B

ArrayTrueForAll

.NET 8.0

Array

45.01 μs

0.547 μs

-

ArrayAll

.NET 9.0

Array

22.28 μs

0.254 μs

-

ArrayTrueForAll

.NET 9.0

Array

37.60 μs

0.382 μs

-

ArrayAll

.NET Framework 4.8.1

Array

495.90 μs

4.342 μs

40 B

ArrayTrueForAll

.NET Framework 4.8.1

Array

164.52 μs

2.030 μs

-

ImmutableListAll

.NET 8.0

ImmutableList<T>

940.29 μs

5.600 μs

72 B

ImmutableListTrueForAll

.NET 8.0

ImmutableList<T>

679.46 μs

2.371 μs

-

ImmutableListAll

.NET 9.0

ImmutableList<T>

922.43 μs

14.564 μs

72 B

ImmutableListTrueForAll

.NET 9.0

ImmutableList<T>

692.31 μs

8.897 μs

-

ImmutableListAll

.NET Framework 4.8.1

ImmutableList<T>

4,578.72 μs

77.920 μs

128 B

ImmutableListTrueForAll

.NET Framework 4.8.1

ImmutableList<T>

4,393.49 μs

122.061 μs

-

ImmutableListBuilderAll

.NET 8.0

ImmutableList<T>.Builder

970.45 μs

13.598 μs

73 B

ImmutableListBuilderTrueForAll

.NET 8.0

ImmutableList<T>.Builder

687.82 μs

6.142 μs

-

ImmutableListBuilderAll

.NET 9.0

ImmutableList<T>.Builder

981.17 μs

12.966 μs

72 B

ImmutableListBuilderTrueForAll

.NET 9.0

ImmutableList<T>.Builder

710.19 μs

16.195 μs

-

ImmutableListBuilderAll

.NET Framework 4.8.1

ImmutableList<T>.Builder

4,780.50 μs

43.282 μs

128 B

ImmutableListBuilderTrueForAll

.NET Framework 4.8.1

ImmutableList<T>.Builder

4,493.82 μs

76.530 μs

-

ListAll

.NET 8.0

List<T>

151.12 μs

2.028 μs

40 B

ListTrueForAll

.NET 8.0

List<T>

58.03 μs

0.493 μs

-

ListAll

.NET 9.0

List<T>

22.14 μs

0.327 μs

-

ListTrueForAll

.NET 9.0

List<T>

46.01 μs

0.327 μs

-

ListAll

.NET Framework 4.8.1

List<T>

619.86 μs

6.037 μs

48 B

ListTrueForAll

.NET Framework 4.8.1

List<T>

208.49 μs

2.340 μs

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == Math.Abs(x);
private readonly static Predicate<int> ConditionPredicate = static x => x == Math.Abs(x);

private List<int> list;
private ImmutableList<int> immutableList;
private ImmutableList<int>.Builder immutableListBuilder;
private int[] array;

[Params(100_000)]
public int N { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    immutableListBuilder = ImmutableList.CreateBuilder<int>();
    immutableListBuilder.AddRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark]
public bool ListAll() =>
    list.All(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public bool ListTrueForAll() =>
    list.TrueForAll(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public bool ImmutableListAll() =>
    immutableList.All(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public bool ImmutableListTrueForAll() =>
    immutableList.TrueForAll(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>.Builder"), Benchmark(Baseline = true)]
public bool ImmutableListBuilderAll() =>
    immutableListBuilder.All(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>.Builder"), Benchmark]
public bool ImmutableListBuilderTrueForAll() =>
    immutableListBuilder.TrueForAll(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public bool ArrayAll() =>
    array.All(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public bool ArrayTrueForAll() =>
    Array.TrueForAll(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6603.json ================================================ { "title": "The collection-specific \"TrueForAll\" method should be used instead of the \"All\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6603", "sqKey": "S6603", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6605.html ================================================

Why is this an issue?

Both the List.Exists method and IEnumerable.Any method can be used to find the first element that satisfies a predicate in a collection. However, List.Exists can be faster than IEnumerable.Any for List objects, as well as requires significantly less memory. For small collections, the performance difference may be negligible, but for large collections, it can be noticeable. The same applies to ImmutableList and arrays too.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought Any closer to the performance of collection-specific Exists methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 3x improvement in execution time. For more details see the Benchmarks section from the More info tab.

Also, no memory allocations were needed for the Exists method, since the search is done in-place.

Exceptions

Since LINQ to Entities relies a lot on System.Linq for query conversion, this rule won’t raise when used within LINQ to Entities syntaxes.

How to fix it

The Exists method is defined on the collection class, and it has the same signature as Any extension method if a predicate is used. The method can be replaced in place.

Code examples

Noncompliant code example

bool ContainsEven(List<int> data) =>
    data.Any(x => x % 2 == 0);
bool ContainsEven(int[] data) =>
    data.Any(x => x % 2 == 0);

Compliant solution

bool ContainsEven(List<int> data) =>
    data.Exists(x => x % 2 == 0);
bool ContainsEven(int[] data) =>
    Array.Exists(data, x => x % 2 == 0);

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayAny

.NET 8.0

Array

1,174.0 ns

16.44 ns

32 B

ArrayExists

.NET 8.0

Array

570.6 ns

7.12 ns

-

ArrayAny

.NET 9.0

Array

358.5 ns

5.57 ns

-

ArrayExists

.NET 9.0

Array

581.6 ns

6.17 ns

-

ArrayAny

.NET Framework 4.8.1

Array

4,896.0 ns

102.83 ns

32 B

ArrayExists

.NET Framework 4.8.1

Array

1,649.4 ns

29.81 ns

-

ImmutableListAny

.NET 8.0

ImmutableList<T>

7,859.3 ns

91.45 ns

72 B

ImmutableListExists

.NET 8.0

ImmutableList<T>

5,898.1 ns

81.69 ns

-

ImmutableListAny

.NET 9.0

ImmutableList<T>

7,748.9 ns

119.10 ns

72 B

ImmutableListExists

.NET 9.0

ImmutableList<T>

5,705.0 ns

31.53 ns

-

ImmutableListAny

.NET Framework 4.8.1

ImmutableList<T>

45,118.5 ns

168.72 ns

72 B

ImmutableListExists

.NET Framework 4.8.1

ImmutableList<T>

41,966.0 ns

631.59 ns

-

ListAny

.NET 8.0

List<T>

1,643.5 ns

13.09 ns

40 B

ListExists

.NET 8.0

List<T>

726.2 ns

11.99 ns

-

ListAny

.NET 9.0

List<T>

398.6 ns

8.20 ns

-

ListExists

.NET 9.0

List<T>

612.4 ns

18.73 ns

-

ListAny

.NET Framework 4.8.1

List<T>

5,621.5 ns

35.80 ns

40 B

ListExists

.NET Framework 4.8.1

List<T>

1,748.0 ns

11.76 ns

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == -1 * Math.Abs(x);
private readonly static Predicate<int> ConditionPredicate = static x => x == -1 * Math.Abs(x);

private List<int> list;
private ImmutableList<int> immutableList;
private int[] array;

[Params(1_000)]
public int N { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark]
public bool ListAny() =>
    list.Any(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public bool ListExists() =>
    list.Exists(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public bool ImmutableListAny() =>
    immutableList.Any(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public bool ImmutableListExists() =>
    immutableList.Exists(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public bool ArrayAny() =>
    array.Any(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public bool ArrayExists() =>
    Array.Exists(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6605.json ================================================ { "title": "Collection-specific \"Exists\" method should be used instead of the \"Any\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6605", "sqKey": "S6605", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6607.html ================================================

Why is this an issue?

When working with LINQ in C#, it is recommended to pay attention to the order in which methods are chained, especially when using Where and OrderBy methods. It is advised to call the Where method before OrderBy because Where filters the elements of the sequence based on a given condition and returns a new sequence containing only the elements that satisfy that condition. Calling OrderBy before Where, may end up sorting elements that will be later discarded, which can lead to inefficiency. Conversely, calling Where before OrderBy, will first filter the sequence to include only the elements of interest, and then sort them based on the specified order.

What is the potential impact?

We measured at least 2x improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The issue can be fixed by calling Where before OrderBy.

Code examples

Noncompliant code example

public IEnumerable<int> GetSortedFilteredList(IEnumerable<int> data) =>
    data.OrderBy(x => x).Where(x => x % 2 == 0);

Compliant solution

public IEnumerable<int> GetSortedFilteredList(IEnumerable<int> data) =>
     data.Where(x => x % 2 == 0).OrderBy(x => x);

Resources

Documentation

Articles & blog posts

Benchmarks

Method Runtime Mean Standard Deviation

OrderByThenWhere

.NET 7.0

175.36 ms

5.101 ms

WhereThenOrderBy

.NET 7.0

85.58 ms

1.697 ms

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private IList<int> data;
private static readonly Random Random = new Random();

[Params(1_000_000)]
public int NumberOfEntries;

[GlobalSetup]
public void Setup() =>
    data = Enumerable.Range(0, NumberOfEntries).Select(x => Random.Next(0, NumberOfEntries)).ToList();

[Benchmark(Baseline = true)]
public void OrderByThenWhere() =>
    _ = data.OrderBy(x => x).Where(x => x % 2 == 0 ).ToList();  // OrderBy followed by Where

[Benchmark]
public void WhereThenOrderBy() =>
    _ = data.Where(x => x % 2 == 0 ).OrderBy(x => x).ToList();  // Where followed by OrderBy

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S6607.json ================================================ { "title": "The collection should be filtered before sorting by using \"Where\" before \"OrderBy\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6607", "sqKey": "S6607", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6608.html ================================================

Why is this an issue?

Indexes in C# provide direct access to an element at a specific position within an array or collection. When compared to Enumerable methods, indexing can be more efficient for certain scenarios, such as iterating over a large collection, due to avoiding the overhead of checking the underlying collection type before accessing it.

This applies to types that implement one of these interfaces:

What is the potential impact?

We measured a significant improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

If the type you are using implements IList, IList<T> or IReadonlyList<T>, it implements this[int index]. This means calls to First, Last, or ElementAt(index) can be replaced with indexing at 0, Count-1 and index respectively.

Code examples

Noncompliant code example

int GetAt(List<int> data, int index)
    => data.ElementAt(index);
int GetFirst(List<int> data)
    => data.First();
int GetLast(List<int> data)
    => data.Last();

Compliant solution

int GetAt(List<int> data, int index)
    => data[index];
int GetFirst(List<int> data)
    => data[0];
int GetLast(List<int> data)
    => data[data.Count-1];

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation

ElementAt

3,403.4 ns

28.52 ns

26.67 ns

Index

478.0 ns

6.93 ns

6.48 ns

First

6,160.0 ns

57.66 ns

53.93 ns

First_Index

485.7 ns

5.81 ns

5.15 ns

Last

6,034.3 ns

20.34 ns

16.98 ns

Last_Index

408.3 ns

2.54 ns

2.38 ns

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private List<byte> data;
private Random random;

[Params(1_000_000)]
public int SampleSize;

[Params(1_000)]
public int LoopSize;

[GlobalSetup]
public void Setup()
{
    random = new Random(42);

    var bytes = new byte[SampleSize];
    random.NextBytes(bytes);
    data = bytes.ToList();
}

[Benchmark]
public int ElementAt()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.ElementAt(i);
    }

    return result;
}

[Benchmark]
public int Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[i];
    }

    return result;
}

[Benchmark]
public int First()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.First();
    }

    return result;
}

[Benchmark]
public int First_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[0];
    }

    return result;
}

[Benchmark]
public int Last()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.Last();
    }

    return result;
}

[Benchmark]
public int Last_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[data.Count - 1];
    }

    return result;
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.4412/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=8.0.301
  [Host]   : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S6608.json ================================================ { "title": "Prefer indexing instead of \"Enumerable\" methods on types implementing \"IList\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6608", "sqKey": "S6608", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6609.html ================================================

Why is this an issue?

Both the Enumerable.Max extension method and the SortedSet<T>.Max property can be used to find the maximum value in a SortedSet<T>. However, SortedSet<T>.Max is much faster than Enumerable.Max. For small collections, the performance difference may be minor, but for large collections, it can be noticeable. The same applies for the Min property as well.

Max and Min in SortedSet<T> exploit the fact that the set is implemented via a Red-Black tree. The algorithm to find the Max/Min is "go left/right whenever possible". The operation has the time complexity of O(h) which becomes O(ln(n)) due to the fact that the tree is balanced. This is much better than the O(n) time complexity of extension methods.

Max and Min in ImmutableSortedSet<T> exploits a tree augmentation technique, storing the Min, Max and Count values on each node of the data structure. The time complexity in this case is O(1) that is significantly better than O(n) of extension methods.

Applies to:

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

The Min and Max properties are defined on the following classes, and the extension method call can be replaced by calling the propery instead:

Code examples

Noncompliant code example

int GetMax(SortedSet<int> data) =>
    data.Max();
int GetMin(SortedSet<int> data) =>
    data.Min();

Compliant solution

int GetMax(SortedSet<int> data) =>
    data.Max;
int GetMin(SortedSet<int> data) =>
    data.Min;

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

MaxMethod

.NET 7.0

68,961.483 us

499.6623 us

248063 B

MaxProperty

.NET 7.0

4.638 us

0.0634 us

-

MaxMethod

.NET Framework 4.6.2

85,827.359 us

1,531.1611 us

281259 B

MaxProperty

.NET Framework 4.6.2

67.682 us

0.3757 us

312919 B

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private SortedSet<string> data;

[Params(1_000)]
public int Iterations;

[GlobalSetup]
public void Setup() =>
    data = new SortedSet<string>(Enumerable.Range(0, Iterations).Select(x => Guid.NewGuid().ToString()));

[Benchmark(Baseline = true)]
public void MaxMethod()
{
    for (var i = 0; i < Iterations; i++)
    {
        _ = data.Max();     // Max() extension method
    }
}

[Benchmark]
public void MaxProperty()
{
    for (var i = 0; i < Iterations; i++)
    {
        _ = data.Max;       // Max property
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6609.json ================================================ { "title": "\"Min\/Max\" properties of \"Set\" types should be used instead of the \"Enumerable\" extension methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6609", "sqKey": "S6609", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6610.html ================================================

Why is this an issue?

With string.StartsWith(char) and string.EndsWith(char), only the first character of the string is compared to the provided character, whereas the string versions of those methods have to do checks about the current StringComparison and CultureInfo. Thus, the char overloads are significantly faster for default comparison scenarios.

These overloads were introduced in .NET Core 2.0.

What is the potential impact?

We measured at least 3.5x improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

If you are targeting a runtime version equal or greater than .NET Core 2.0, the string.StartsWith and string.EndsWith overloads are available, with the argument’s type being char instead of string. Thus, an argument of char type can be provided.

Code examples

Noncompliant code example

bool StartsWithSlash(string s) =>
    s.StartsWith("/");
bool EndsWithSlash(string s) =>
    s.EndsWith("/");

Compliant solution

bool StartsWithSlash(string s) =>
    s.StartsWith('/');
bool EndsWithSlash(string s) =>
    s.EndsWith('/');

Resources

Documentation

Benchmarks

Method Mean Standard Deviation

StartsWith_String

30.965 ms

3.2732 ms

StartsWith_Char

7.568 ms

0.3235 ms

EndsWith_String

30.421 ms

5.1136 ms

EndsWith_Char

8.067 ms

0.7092 ms

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private List<string> data;

[Params(1_000_000)]
public int N { get; set; }

[GlobalSetup]
public void Setup() =>
    data = Enumerable.Range(0, N).Select(_ => Guid.NewGuid().ToString()).ToList();

[Benchmark]
public void StartsWith_String()
{
    _ = data.Where(guid => guid.StartsWith("d")).ToList();
}

[Benchmark]
public void StartsWith_Char()
{
    _ = data.Where(guid => guid.StartsWith('d')).ToList();
}

[Benchmark]
public void EndsWith_String()
{
    _ = data.Where(guid => guid.EndsWith("d")).ToList();
}

[Benchmark]
public void EndsWith_Char()
{
    _ = data.Where(guid => guid.EndsWith('d')).ToList();
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]   : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S6610.json ================================================ { "title": "\"StartsWith\" and \"EndsWith\" overloads that take a \"char\" should be used instead of the ones that take a \"string\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6610", "sqKey": "S6610", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S6612.html ================================================

Why is this an issue?

When using the ConcurrentDictionary, there are many overloads of the GetOrAdd and AddOrUpdate methods that take both a TKey argument and a lambda that expects a TKey parameter. This means that the right side of the lambda can be written using either the lambda’s parameter or the method’s argument. However, using the method’s argument leads to the lambda capturing it, and the compiler will need to generate a class and instantiate it before the call. This means memory allocations, as well as more time spend during Garbage Collection.

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

When you are using the ConcurrentDictionary methods GetOrAdd or AddOrUpdate, reference the key by using the lambda’s parameter instead of the method’s one.

Code examples

Noncompliant code example

int UpdateValue(ConcurrentDictionary<int, int> dict, int key) =>
    dict.GetOrAdd(key, _ => key + 42);

Compliant solution

int UpdateValue(ConcurrentDictionary<int, int> dict, int key) =>
    dict.GetOrAdd(key, x => x + 42);

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

Capture

.NET 7.0

68.52 ms

4.450 ms

88000063 B

Lambda

.NET 7.0

39.29 ms

3.712 ms

50 B

Capture

.NET Framework 4.6.2

74.58 ms

5.199 ms

88259787 B

Lambda

.NET Framework 4.6.2

42.03 ms

2.752 ms

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private ConcurrentDictionary<int, string> dict;
private List<int> data;

[Params(1_000_000)]
public int N { get; set; }

[GlobalSetup]
public void Setup()
{
    dict = new ConcurrentDictionary<int, string>();
    data = Enumerable.Range(0, N).OrderBy(_ => Guid.NewGuid()).ToList();
}

[Benchmark(baseline=true)]
public void Capture()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, _ => $"{guid}"); // "guid" is captured
    }
}

[Benchmark]
public void Lambda()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, x => $"{x}"); // no capture
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6612.json ================================================ { "title": "The lambda parameter should be used instead of capturing arguments in \"ConcurrentDictionary\" methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6612", "sqKey": "S6612", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6613.html ================================================

Why is this an issue?

Both the Enumerable.First extension method and the LinkedList<T>.First property can be used to find the first value in a LinkedList<T>. However, LinkedList<T>.First is much faster than Enumerable.First. For small collections, the performance difference may be minor, but for large collections, it can be noticeable. The same applies for the Last property as well.

Applies to:

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

The First and Last properties are defined on the LinkedList class, and the extension method call can be replaced by calling the propery instead.

Code examples

Noncompliant code example

int GetFirst(LinkedList<int> data) =>
    data.First();
int GetLast(LinkedList<int> data) =>
    data.Last();

Compliant solution

int GetFirst(LinkedList<int> data) =>
    data.First.Value;
int GetLast(LinkedList<int> data) =>
    data.Last.Value;

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

LastMethod

.NET 7.0

919,577,629.0 ns

44,299,688.61 ns

48504 B

LastProperty

.NET 7.0

271.8 ns

15.63 ns

-

LastMethod

.NET Framework 4.6.2

810,316,427.1 ns

47,768,482.31 ns

57344 B

LastProperty

.NET Framework 4.6.2

372.0 ns

13.38 ns

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private LinkedList<int> data;
private Random random = new Random();

[Params(100_000)]
public int Size { get; set; }

[Params(1_000)]
public int Runs { get; set; }

[GlobalSetup]
public void Setup() =>
    data = new LinkedList<int>(Enumerable.Range(0, Size).Select(x => random.Next()));

[Benchmark(Baseline = true)]
public void LastMethod()
{
    for (var i = 0; i < Runs; i++)
    {
        _ = data.Last();                // Enumerable.Last()
    }
}

[Benchmark]
public void LastProperty()
{
    for (var i = 0; i < Runs; i++)
    {
        _ = data.Last;                  // Last property
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6613.json ================================================ { "title": "\"First\" and \"Last\" properties of \"LinkedList\" should be used instead of the \"First()\" and \"Last()\" extension methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6613", "sqKey": "S6613", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S6617.html ================================================

Why is this an issue?

When testing if a collection contains a specific item by simple equality, both ICollection.Contains(T item) and IEnumerable.Any(x ⇒ x == item) can be used. However, Any searches the data structure in a linear manner using a foreach loop, whereas Contains is considerably faster in some collection types, because of the underlying implementation. More specifically:

For small collections, the performance difference may be negligible, but for large collections, it can be noticeable.

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

Exceptions

Since LINQ to Entities relies a lot on System.Linq for query conversion, this rule won’t raise when used within LINQ to Entities syntaxes.

How to fix it

Contains is a method defined on the ICollection<T> interface and takes a T item argument. Any is an extension method defined on the IEnumerable<T> interface and takes a predicate argument. Therefore, calls with simple equality checks like Any(x ⇒ x == item) can be replaced by Contains(item).

This applies to the following collection types:

Code examples

Noncompliant code example

bool ValueExists(HashSet<int> data) =>
    data.Any(x => x == 42);
bool ValueExists(List<int> data) =>
    data.Any(x => x == 42);

Compliant solution

bool ValueExists(HashSet<int> data) =>
    data.Contains(42);
bool ValueExists(List<int> data) =>
    data.Contains(42);

Resources

Documentation

Articles & blog posts

Benchmarks

Method Runtime Mean Standard Deviation Allocated

HashSet_Any

.NET 7.0

35,388.333 us

620.1863 us

40132 B

HashSet_Contains

.NET 7.0

3.799 us

0.1489 us

-

List_Any

.NET 7.0

32,851.509 us

667.1658 us

40130 B

List_Contains

.NET 7.0

375.132 us

8.0764 us

-

HashSet_Any

.NET Framework 4.6.2

28,979.763 us

678.0093 us

40448 B

HashSet_Contains

.NET Framework 4.6.2

5.987 us

0.1090 us

-

List_Any

.NET Framework 4.6.2

25,830.221 us

487.2470 us

40448 B

List_Contains

.NET Framework 4.6.2

5,935.812 us

57.7569 us

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

[Params(10_000)]
public int SampleSize;

[Params(1_000)]
public int Iterations;

private static HashSet<int> hashSet;
private static List<int> list;

[GlobalSetup]
public void Setup()
{
    hashSet = new HashSet<int>(Enumerable.Range(0, SampleSize));
    list = Enumerable.Range(0, SampleSize).ToList();
}

[Benchmark]
public void HashSet_Any() =>
    CheckAny(hashSet, SampleSize / 2);

[Benchmark]
public void HashSet_Contains() =>
    CheckContains(hashSet, SampleSize / 2);

[Benchmark]
public void List_Any() =>
    CheckAny(list, SampleSize / 2);

[Benchmark]
public void List_Contains() =>
    CheckContains(list, SampleSize / 2);

void CheckAny(IEnumerable<int> values, int target)
{
    for (int i = 0; i < Iterations; i++)
    {
        _ = values.Any(x => x == target);  // Enumerable.Any
    }
}

void CheckContains(ICollection<int> values, int target)
{
    for (int i = 0; i < Iterations; i++)
    {
        _ = values.Contains(target); // ICollection<T>.Contains
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/cs/S6617.json ================================================ { "title": "\"Contains\" should be used instead of \"Any\" for simple equality checks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6617", "sqKey": "S6617", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6618.html ================================================

Why is this an issue?

In order to produce a formatted string, both string.Create and either FormattableString.Invariant or FormattableString.CurrentCulture can be used. However, string.Create rents array buffers from ArrayPool<char> making it more performant, as well as preventing unnecessary allocations and future stress on the Garbage Collector.

This applies to .NET versions after .NET 6, when these string.Create overloads were introduced.

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

Replace calls to FormattableString.CurrentCulture or FormattableString.Invariant with calls to string.Create(CultureInfo.CurrentCulture, …​) or string.Create(CultureInfo.InvariantCulture, …​) respectively.

Code examples

Noncompliant code example

string Interpolate(string value) =>
    FormattableString.Invariant($"Value: {value}");
string Interpolate(string value) =>
    FormattableString.CurrentCulture($"Value: {value}");

Compliant solution

string Interpolate(string value) =>
    string.Create(CultureInfo.InvariantCulture, $"Value: {value}");
string Interpolate(string value) =>
    string.Create(CultureInfo.CurrentCulture, $"Value: {value}");

Resources

Documentation

Articles & blog posts

Benchmarks

The results were generated by running the following snippet with BenchmarkDotNet:

Method Runtime Mean Standard Deviation Allocated

StringCreate

.NET 7.0

152.5 ms

3.09 ms

83.92 MB

FormattableString

.NET 7.0

191.8 ms

6.92 ms

198.36 MB

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

int Value = 42;
DateTime Now = DateTime.UtcNow;

[Params(1_000_000)]
public int N;

[Benchmark]
public void StringCreate()
{
    for (int i = 0; i < N; i++)
    {
        _ = string.Create(CultureInfo.InvariantCulture, $"{Now}: Value is {Value}");
    }
}

[Benchmark]
public void FormattableStringInvariant()
{
    for (int i = 0; i < N; i++)
    {
        _ = FormattableString.Invariant($"{Now}: Value is {Value}");
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2728/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]   : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S6618.json ================================================ { "title": "\"string.Create\" should be used instead of \"FormattableString\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6618", "sqKey": "S6618", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6640.html ================================================

Using unsafe code blocks can lead to unintended security or stability risks.

unsafe code blocks allow developers to use features such as pointers, fixed buffers, function calls through pointers and manual memory management. Such features may be necessary for interoperability with native libraries, as these often require pointers. It may also increase performance in some critical areas, as certain bounds checks are not executed in an unsafe context.

unsafe code blocks aren’t necessarily dangerous, however, the contents of such blocks are not verified by the Common Language Runtime. Therefore, it is up to the programmer to ensure that no bugs are introduced through manual memory management or casting. If not done correctly, then those bugs can lead to memory corruption vulnerabilities such as stack overflows. unsafe code blocks should be used with caution because of these security and stability risks.

Ask Yourself Whether

There is a risk if you answered yes to the question.

Recommended Secure Coding Practices

Unless absolutely necessary, do not use unsafe code blocks. If unsafe is used to increase performance, then the Span and Memory APIs may serve a similar purpose in a safe context.

If it is not possible to remove the code block, then it should be kept as short as possible. Doing so reduces risk, as there is less code that can potentially introduce new bugs. Within the unsafe code block, make sure that:

Sensitive Code Example

public unsafe int SubarraySum(int[] array, int start, int end)  // Sensitive
{
    var sum = 0;

    // Skip array bound checks for extra performance
    fixed (int* firstNumber = array)
    {
        for (int i = start; i < end; i++)
            sum += *(firstNumber + i);
    }

    return sum;
}

Compliant Solution

public int SubarraySum(int[] array, int start, int end)
{
    var sum = 0;

    Span<int> span = array.AsSpan();
    for (int i = start; i < end; i++)
        sum += span[i];

    return sum;
}

See

================================================ FILE: analyzers/rspec/cs/S6640.json ================================================ { "title": "Using unsafe code blocks is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "60min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6640", "sqKey": "S6640", "scope": "All", "quickfix": "infeasible", "securityStandards": { "CWE": [ 119, 787 ] } } ================================================ FILE: analyzers/rspec/cs/S6664.html ================================================

A code block should not contain too many logging statements of a specific level.

Why is this an issue?

Excessive logging within a code block can lead to several problems:

Only the logging statements that are directly within the code block will be counted, and any logging statements within nested blocks will count towards their own. For example consider the snippet below:

void MyMethod(List<MyObject> items)
{
    logger.Debug("The operation started");
    foreach(var item in items)
    {
        logger.Debug($"Evaluating {item.Name}");
        var result = Evaluate(item);
        logger.Debug($"Evaluating resulted in {result}");
    }
    logger.Debug("The operation ended");
}

The rule will count 2 logging statements that are within the method block (namely logger.Debug("The operation started") and logger.Debug("The operation ended")). Any statements within nested blocks, such as the foreach block will be counted separately. The rule considers the log level of the calls, as follows:

The most popular logging frameworks are supported:

How to fix it

Reduce the number of specific logging level calls within the code block by identifying and selecting essential log statements with relevant information, necessary for understanding the flow of execution or diagnosing issues.

Code examples

Noncompliant code example

With the default Information threshold parameter value 2:

void MyMethod(List<MyObject> items)
{
    logger.Debug("The operation started");
    foreach(var item in items)
    {
        logger.Information($"Evaluating {item.Name}"); // Noncompliant
        var result = Evaluate(item);
        logger.Information($"Evaluating resulted in {result}"); // Secondary 1
        if (item.Name is string.Empty)
        {
            logger.Error("Invalid item name");
        }
        logger.Information("End item evaluation"); // Secondary 2
    }
    logger.Debug("The operation ended");
}

Compliant solution

With the default Information threshold parameter value 2:

void MyMethod(List<MyObject> items)
{
    logger.Debug("The operation started");
    foreach(var item in items)
    {
        logger.Information($"Evaluating {item.Name}");
        var result = Evaluate(item);
        if (item.Name is string.Empty)
        {
            logger.Error("Invalid item name");
        }
        logger.Information($"End item evaluation with result: {result}");
    }
    logger.Debug("The operation ended");
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6664.json ================================================ { "title": "The code block contains too many logging calls", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6664", "sqKey": "S6664", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6667.html ================================================

This rule raises an issue on logging calls inside a catch clause that does not pass the raised Exception.

Why is this an issue?

A log entry should contain all the relevant information about the current execution context. The Exception raised in a catch block not only provides the message but also:

Logging methods provide overloads that accept an Exception as a parameter and logging providers persist the Exception in a structured way to facilitate the tracking of system failures. Therefore Exceptions should be passed to the logger.

The rule covers the following logging frameworks:

How to fix it

Code examples

Noncompliant code example

public bool Save()
{
    try
    {
        DoSave();
        return true;
    }
    catch(IOException)
    {
        logger.LogError("Saving failed.");             // Noncompliant: No specifics about the error are logged
        return false;
    }
}

Compliant solution

public bool Save()
{
    try
    {
        DoSave();
        return true;
    }
    catch(IOException exception)
    {
        logger.LogError(exception, "Saving failed.");  // Compliant: Exception details are logged
        return false;
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6667.json ================================================ { "title": "Logging in a catch clause should pass the caught exception as a parameter.", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "error-handling", "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6667", "sqKey": "S6667", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6668.html ================================================

Why is this an issue?

Most logging frameworks have methods that take a log level, an event ID or an exception as a separate input next to the log format and its arguments. There is a high chance that if the log level, the event ID or the exception are passed as the arguments to the message format, it was a mistake. This rule is going to raise in that scenario.

The rule covers the following logging frameworks:

How to fix it

Use the dedicated overload that takes the log level, event id, and/or exception as arguments.

Noncompliant code example

try { }
catch (Exception ex)
{
    logger.LogDebug("An exception occured {Exception} with {EventId}.", ex, eventId); // Noncompliant
}

Compliant solution

try { }
catch (Exception ex)
{
    logger.LogDebug(eventId, ex, "An exception occured.");
}

Exceptions

This rule will not raise an issue if one of the parameters mentioned above is passed twice, once as a separate argument to the invocation and once as an argument to the message format.

try { }
catch (Exception ex)
{
    logger.LogDebug(ex, "An exception occured {Exception}.", ex); // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6668.json ================================================ { "title": "Logging arguments should be passed to the correct parameter", "type": "CODE_SMELL", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6668", "sqKey": "S6668", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6669.html ================================================

Why is this an issue?

Sharing some naming conventions is a key point to make it possible for a team to efficiently collaborate. This rule checks that the logger field or property name matches a provided regular expression.

The rule supports the most popular logging frameworks:

How to fix it

Update the name of the field or property to follow the configured naming convention. By default, the following names are considered compliant:

Noncompliant code example

private readonly ILogger myLogger; // Noncompliant

public ILogger MyLogger { get; set; } // Noncompliant

Compliant solution

private readonly ILogger logger; // Compliant

public ILogger Logger { get; set; } // Compliant

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6669.json ================================================ { "title": "Logger field or property name should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6669", "sqKey": "S6669", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6670.html ================================================

Why is this an issue?

Trace.Write and Trace.WriteLine methods are writing to the underlying output stream directly, bypassing the trace formatting and filtering performed by TraceListener.TraceEvent implementations. It is preferred to use Trace.TraceError, Trace.TraceWarning, and Trace.TraceInformation methods instead because they call the TraceEvent method which filters the trace output according to the TraceEventType (Error, Warning or Information) and enhance the output with additional information.

How to fix it

Use the Trace.TraceError, Trace.TraceWarning, or Trace.TraceInformation methods.

Noncompliant code example

try
{
    var message = RetrieveMessage();
    Trace.Write($"Message received: {message}"); // Noncompliant
}
catch (Exception ex)
{
    Trace.WriteLine(ex); // Noncompliant
}

Compliant solution

try
{
    var message = RetrieveMessage();
    Trace.TraceInformation($"Message received: {message}");
}
catch (Exception ex)
{
    Trace.TraceError(ex);
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6670.json ================================================ { "title": "\"Trace.Write\" and \"Trace.WriteLine\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6670", "sqKey": "S6670", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6672.html ================================================

Why is this an issue?

In most logging frameworks, it’s good practice to set the logger name to match its enclosing type, as enforced by {rule:csharpsquid:S3416}.

Logging frameworks can define or use Generic interfaces for the logger, such as ILogger<TCategoryName>.

The use of a logger of a generic type parameter A (e.g. ILogger<A>) in a type different than A, say B, goes against the convention.

Because the instance of type A would log with a logger named after B, log items would appear as if they were logged by B instead, resulting in confusion and logging misconfiguration:

Further details and examples are provided in {rule:csharpsquid:S3416}.

This rule specifically targets the generic logging interface ILogger<TCategoryName> Interface defined by Microsoft Extensions Logging.

How to fix it

Change the generic type parameter of the ILogger interface to match the enclosing type.

Noncompliant code example

class EnclosingType
{
    public EnclosingType(ILogger<AnotherType> logger) // Noncompliant
    {
        // ...
    }
}

Compliant solution

class EnclosingType
{
    public EnclosingType(ILogger<EnclosingType> logger) // Compliant
    {
        // ...
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6672.json ================================================ { "title": "Generic logger injection should match enclosing type", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing", "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6672", "sqKey": "S6672", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6673.html ================================================

The positions of arguments in a logging call should match the positions of their message template placeholders.

Why is this an issue?

The placeholders of a message template are defined by their name and their position. Log methods specify the values for the placeholder at runtime by passing them in a params array:

logger.LogError("{First} placeholder and {Second} one.", first, second);

This rule raises an issue if the position of an argument does not match the position of the corresponding placeholder:

// 'first' and 'second' are swapped
logger.LogError("{First} placeholder and {Second} one.", second, first);
//                                                       ^^^^^^  ^^^^^

What is the potential impact?

Logging providers use placeholder names to create key/value pairs in the log entry. The key corresponds to the placeholder and the value is the argument passed in the log call.

If the positions of the placeholder and the argument do not match, the value is associated with the wrong key. This corrupts the logs entry and makes log analytics unreliable.

How to fix it

Make sure that the placeholder positions and the argument positions match. Use local variables, fields, or properties for the arguments and name the placeholders accordingly.

Code examples

Noncompliant code example

'path' and 'fileName' are swapped and therefore assigned to the wrong placeholders.

logger.LogError("File {FileName} not found in folder {Path}", path, fileName);
//                                                            ^^^^  ^^^^^^^^

Compliant solution

Swap the arguments.

logger.LogError("File {FileName} not found in folder {Path}", fileName, path);

Noncompliant code example

'Name' is detected but 'Folder' is not. The placeholder’s name should correspond to the name from the argument.

logger.LogError("File {Name} not found in folder {Folder}", file.DirectoryName, file.Name);
//                                                                                   ^^^^

Compliant solution

Swap the arguments and rename the placeholder to 'DirectoryName'.

logger.LogError("File {Name} not found in folder {DirectoryName}", file.Name, file.DirectoryName);

Noncompliant code example

Not detected: A name for the arguments can not be inferred. Use locals to support detection.

logger.LogError("Sum is {Sum} and product is {Product}", x * y, x + y); // Not detected

Compliant solution

Help detection by using arguments with a name.

var sum = x + y;
var product = x * y;
logger.LogError("Sum is {Sum} and product is {Product}", sum, product);

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6673.json ================================================ { "title": "Log message template placeholders should be in the right order", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6673", "sqKey": "S6673", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6674.html ================================================

A message template must conform to the specification. The rule raises an issue if the template string violates the template string grammar.

Why is this an issue?

A message template needs to comply with a set of rules. Logging provider parse the template and enrich log entries with the information found in the template. An unparsable message template leads to corrupted log entries and might result in a loss of information in the logs.

The rule covers the following logging frameworks:

How to fix it

Follow the syntax described on https://messagetemplates.org/.

Code examples

Noncompliant code example

logger.LogError("Login failed for {User", user);       // Noncompliant: Syntactically incorrect
logger.LogError("Login failed for {}", user);          // Noncompliant: Empty placeholder
logger.LogError("Login failed for {User-Name}", user); // Noncompliant: Only letters, numbers, and underscore are allowed for placeholders
logger.LogDebug("Retry attempt {Cnt,r}", cnt);         // Noncompliant: The alignment specifier must be numeric
logger.LogDebug("Retry attempt {Cnt:}", cnt);          // Noncompliant: Empty format specifier is not allowed

Compliant solution

logger.LogError("Login failed for {User}", user);
logger.LogError("Login failed for {User}", user);
logger.LogError("Login failed for {User_Name}", user);
logger.LogDebug("Retry attempt {Cnt,-5}", cnt);
logger.LogDebug("Retry attempt {Cnt:000}", cnt);

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6674.json ================================================ { "title": "Log message template should be syntactically correct", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-6674", "sqKey": "S6674", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6675.html ================================================

Why is this an issue?

The Trace.WriteLineIf Method from the System.Diagnostic.Trace facility writes a trace if the condition passed as the first parameter is true.

TraceSwitch allows trace control via bool properties for each relevant TraceLevel, such as TraceSwitch.TraceError.

Using Trace.WriteLineIf with such properties should be avoided since it can lead to misinterpretation and produce confusion.

In particular, Trace.WriteLineIf may appear as equivalent to the level-specific tracing methods provided by Trace, such as Trace.Error, but it is not.

The difference is that Trace.WriteLineIf(switch.TraceError, …​) conditionally writes the trace, based on the switch, whereas Trace.TraceError always writes the trace, no matter whether switch.TraceError is true or false.

Moreover, unlike Trace.TraceError, Trace.WriteLineIf(switch.TraceError, …​) would behave like Trace.WriteLine(…​) when switch.TraceError is true, writing unfiltered to the underlying trace listeners and not categorizing the log entry by level, as described more in detail in {rule:csharpsquid:S6670}.

How to fix it

The fix depends on the intent behind the use of TraceSwitch levels with Trace.WriteLineIf.

If it is trace categorization, level-specific tracing methods, such as Trace.TraceError or Trace.TraceWarning, should be used instead.

If it is trace filtering, TraceSource should be used instead.

If it is log filtering, Trace should be replaced by logging APIs, such as the ILogger API.

Modern logging APIs are also more suitable than Trace when high-performance logging is required.

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6675.json ================================================ { "title": "\"Trace.WriteLineIf\" should not be used with \"TraceSwitch\" levels", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing", "clumsy", "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6675", "sqKey": "S6675", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6677.html ================================================

Why is this an issue?

Named placeholders in message templates should be unique. The meaning of the named placeholders is to store the value of the provided argument under that name, enabling easier log querying. Since the named placeholder is used multiple times, it cannot store the different values uniquely with each name hence not serving its original purpose. There can be different behaviours when using the same named placeholder multiple times:

The rule covers the following logging frameworks:

How to fix it

Assign unique names to each template placeholder.

Code examples

Noncompliant code example

public void Checkout(ILogger logger, User user, Order order)
{
    logger.LogDebug("User {Id} purchased order {Id}", user.Id, order.Id);
}

Compliant solution

public void Checkout(ILogger logger, User user, Order order)
{
    logger.LogDebug("User {UserId} purchased order {OrderId}", user.Id, order.Id);
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6677.json ================================================ { "title": "Message template placeholders should be unique", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "logging" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6677", "sqKey": "S6677", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/cs/S6678.html ================================================

Within a message template each named placeholder should be in PascalCase.

Why is this an issue?

Using consistent naming conventions is important for the readability and maintainability of code. In the case of message templates, using PascalCase for named placeholders ensures consistency with structured logging conventions, where each named placeholder is used as a property name in the structured data.

The rule covers the following logging frameworks:

How to fix it

Use PascalCase for named placeholders.

Code examples

Noncompliant code example

logger.LogDebug("User {firstName} logged in", firstName); // Noncompliant

Compliant solution

logger.LogDebug("User {FirstName} logged in", firstName); // Compliant

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6678.json ================================================ { "title": "Use PascalCase for named placeholders", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "logging" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6678", "sqKey": "S6678", "scope": "Main", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/S6781.html ================================================

Secret leaks often occur when a sensitive piece of authentication data is stored with the source code of an application. Considering the source code is intended to be deployed across multiple assets, including source code repositories or application hosting servers, the secrets might get exposed to an unintended audience.

Why is this an issue?

In most cases, trust boundaries are violated when a secret is exposed in a source code repository or an uncontrolled deployment environment. Unintended people who don’t need to know the secret might get access to it. They might then be able to use it to gain unwanted access to associated services or resources.

The trust issue can be more or less severe depending on the people’s role and entitlement.

What is the potential impact?

If a JWT secret key leaks to an unintended audience, it can have serious security implications for the corresponding application. The secret key is used to encode and decode JWTs when using a symmetric signing algorithm, and an attacker could potentially use it to perform malicious actions.

For example, an attacker could use the secret key to create their own authentication tokens that appear to be legitimate, allowing them to bypass authentication and gain access to sensitive data or functionality.

In the worst-case scenario, an attacker could be able to execute arbitrary code on the application by abusing administrative features, and take over its hosting server.

How to fix it in ASP.NET Core

Code examples

Noncompliant code example

Secrets stored in appsettings.json can be read by anyone with access to the file.

[ApiController]
[Route("login-example")]
public class LoginExampleController : ControllerBase
{
    private readonly IConfiguration _config;
    public LoginExampleController(IConfiguration config)
    {
        _config = config;
    }

    [HttpPost]
    public IActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var key = _config["Jwt:Key"] ??
            throw new ApplicationException("JWT key is not configured.");
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var Sectoken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials);

        var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
        return Ok(token);
    }
}

Secrets that are hard-coded into the application can be read by anyone with access to the source code or can be decompiled from the application binaries.

[ApiController]
[Route("login-example")]
public class LoginExampleController : ControllerBase
{
    private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret";

    [HttpPost]
    public IActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)); // Noncompliant
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var Sectoken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials);

        var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
        return Ok(token);
    }
}

Compliant solution

[ApiController]
[Route("login-example")]
public class LoginExampleController : ControllerBase
{
    [HttpPost]
    public IActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var key = Environment.GetEnvironmentVariable("JWT_KEY") ??
            throw new ApplicationException("JWT key is not configured.");
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var Sectoken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials);

        var token = new JwtSecurityTokenHandler().WriteToken(Sectoken);
        return Ok(token);
    }
}

How does this work?

Here, the compliant solution uses an environment variable to hold the secret. Environment variables are easy to change and are not easily accessible outside of the application.

Going the extra mile

Use a secret vault

Secret vaults provide secure methods for storing and accessing secrets. They protect against the unexpected disclosure of the secrets they store.

Microsoft recommends using Azure Key Vault with .NET Core applications.

var builder = WebApplication.CreateBuilder(args);

// Get the name of the key vault
var keyVaultName = Environment.GetEnvironmentVariable("AZURE_KEYVAULT") ??
    throw new ApplicationException("Azure Key Vault location is not configured.");
// Add Azure Key Vault in the configuration
builder.Configuration.AddAzureKeyVault(new Uri($"https://{keyVaultName}.vault.azure.net/"), new EnvironmentCredential());
// Get the JWT secret from Azure Key Vault
var jwtKey = builder.Configuration.GetSection("JWT-KEY").Get<string>() ??
    throw new ApplicationException("JWT key is not configured.");

builder.Services
  .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options => {
      options.TokenValidationParameters = new TokenValidationParameters{
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey!)),
        ValidateIssuerSigningKey = true,
        ValidIssuer = "example.com",
        ValidateIssuer = true,
        ValidAudience = "example.com",
        ValidateAudience = true,
        ValidateLifetime = true,
      };
  });

How to fix it in ASP.NET

Code examples

Noncompliant code example

Secrets stored in web.config can be read by anyone with access to the file.

public class LoginExampleController : ApiController
{
    public IHttpActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var key = ConfigurationManager.AppSettings["key"] ??
            throw new ApplicationException("JWT key is not configured.");
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var secToken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials
        );

        var token = new JwtSecurityTokenHandler().WriteToken(secToken);
        return Ok(token);
    }
}

Secrets that are hard-coded into the application can be read by anyone with access to the source code or can be decompiled from the application binaries.

public class LoginExampleController : ApiController
{
    private const string key = "SecretSecretSecretSecretSecretSecretSecretSecret";

    public IHttpActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var secToken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials
        );

        var token = new JwtSecurityTokenHandler().WriteToken(secToken);
        return Ok(token);
    }
}

Compliant solution

public class LoginExampleController : ApiController
{
    public IHttpActionResult Post([FromBody] LoginModel login)
    {
        // Code to validate the login information is omitted

        var key = Environment.GetEnvironmentVariable("JWT_KEY") ??
            throw new ApplicationException("JWT key is not configured.");
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var secToken = new JwtSecurityToken(
            "example.com",
            "example.com",
            null,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: credentials
        );

        var token = new JwtSecurityTokenHandler().WriteToken(secToken);
        return Ok(token);
    }
}

How does this work?

Here, the compliant solution uses an environment variable to hold the secret. Environment variables are easy to change and are not easily accessible outside of the application.

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S6781.json ================================================ { "title": "JWT secret keys should not be disclosed", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-6781", "sqKey": "S6781", "scope": "All", "securityStandards": { "CWE": [ 798, 259 ], "OWASP": [ "A3" ], "CERT": [ "MSC03-J." ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.4", "6.4.1" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S6797.html ================================================

Why is this an issue?

The SupplyParameterFromQuery attribute can be used to specify that a component parameter, of a routable component, comes from the query string.

Component parameters supplied from the query string support the following types:

Query parameters should have one of the supported types. Otherwise, an unhandled exception will be raised at runtime.

Unhandled exception rendering component: Querystring values cannot be parsed as type '<type>'.
System.NotSupportedException: Querystring values cannot be parsed as type '<type>'
...

How to fix it

Change the parameter type to one of the following ones:

Code examples

Noncompliant code example

@page "/print"
<p> Parameter value is: @Value </p>
@code {
    [Parameter]
    [SupplyParameterFromQuery()]
    public TimeSpan Value { get; set; }     // Noncompliant
}

Compliant solution

@page "/print"
<p> Parameter value is: @Value </p>
@code {
    [Parameter]
    [SupplyParameterFromQuery()]
    public long Value { get; set; }         // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6797.json ================================================ { "title": "Blazor query parameter type should be supported", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "blazor" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6797", "sqKey": "S6797", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S6798.html ================================================

Why is this an issue?

In Blazor, the [JSInvokable] attribute is used to annotate a method, enabling it to be invoked from JavaScript code. The prerequisite for this functionality is that the method must be declared as public.
Otherwise, a runtime error will be triggered when an attempt is made to call the method from JavaScript.

How to fix it

To fix the issue, ensure the methods annotated with the [JSInvokable] attribute are public.

Code examples

Noncompliant code example

@code {
    [JSInvokable]
    private static void MyStaticMethod() { } // Noncompliant

    [JSInvokable]
    internal void MyMethod() { } // Noncompliant
}

Compliant solution

@code {
    [JSInvokable]
    public static void MyStaticMethod() { } // Compliant

    [JSInvokable]
    public void MyMethod() { } // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6798.json ================================================ { "title": "[JSInvokable] attribute should only be used on public methods", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "blazor" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6798", "sqKey": "S6798", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S6800.html ================================================

Why is this an issue?

In Blazor, when a route parameter constraint is applied, the value is automatically cast to the corresponding component parameter type. If the constraint type does not match the component parameter type, it can lead to confusion and potential runtime errors due to unsuccessful casting. Therefore, it is crucial to ensure that the types of route parameters and component parameters match to prevent such issues and maintain code clarity.

How to fix it

Ensure the component parameter type matches the route parameter constraint type.

Constraint Type .NET Type

bool

bool

datetime

DateTime

decimal

decimal

double

double

float

float

guid

Guid

int

int

long

long

string

string

Code examples

Noncompliant code example

@page "/my-route/{Param:datetime}"

@code {
    [Parameter]
    public string Param { get; set; } // Noncompliant
}

Compliant solution

@page "/my-route/{Param:datetime}"

@code {
    [Parameter]
    public DateTime Param { get; set; } // Compliant
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6800.json ================================================ { "title": "Component parameter type should match the route parameter type constraint", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "blazor" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6800", "sqKey": "S6800", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S6802.html ================================================

Why is this an issue?

In Blazor, using lambda expressions as event handlers when the UI elements are rendered in a loop can lead to negative user experiences and performance issues. This is particularly noticeable when rendering a large number of elements.

The reason behind this is that Blazor rebuilds all lambda expressions within the loop every time the UI elements are rendered.

How to fix it

Ensure to not use a delegate in elements rendered in loops, you can try:

Code examples

Noncompliant code example

@for (var i = 1; i < 100; i++)
{
    var buttonNumber = i;

    <button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@
        Button #@buttonNumber
    </button>
}

@code {
    private void DoAction(MouseEventArgs e, int button)
    {
        // Do something here
    }
}

Compliant solution

@foreach (var button in Buttons)
{
    <button @key="button.Id" @onclick="button.Action">  @* Compliant *@
        Button #@button.Id
    </button>
}

@code {
    private List<Button> Buttons { get; set; } = new();

    protected override void OnInitialized()
    {
        for (var i = 0; i < 100; i++)
        {
            var button = new Button();

            button.Action = (e) => DoAction(e, button);

            Buttons.Add(button);
        }
    }

    private void DoAction(MouseEventArgs e, Button button)
    {
        // Do something here
    }

    private class Button
    {
        public string? Id { get; } = Guid.NewGuid().ToString();
        public Action<MouseEventArgs> Action { get; set; } = e => { };
    }
}

Noncompliant code example

@* Component.razor *@

@for (var i = 1; i < 100; i++)
{
    var buttonNumber = i;

    <button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@
        Button #@buttonNumber
    </button>
}

@code {
    private void DoAction(MouseEventArgs e, int button)
    {
        // Do something here
    }
}

Compliant solution

@* MyButton.razor *@

<button @onclick="OnClickCallback">
    @ChildContent
</button>

@code {
    [Parameter]
    public int Id { get; set; }

    [Parameter]
    public EventCallback<int> OnClick { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private void OnClickCallback()
    {
        OnClick.InvokeAsync(Id);
    }
}

@* Component.razor *@

@for (var i = 1; i < 100; i++)
{
    var buttonNumber = i;
    <MyButton Id="buttonNumber" OnClick="DoAction">
        Button #@buttonNumber
    </MyButton>
}

@code {
    private void DoAction(int button)
    {
        // Do something here
    }
}

Resources

Documentation

Benchmarks

The results were generated with the help of BenchmarkDotNet and Benchmark.Blazor:

Method NbButtonRendered Mean StdDev Ratio

UseDelegate

10

6.603 us

0.0483 us

1.00

UseAction

10

1.994 us

0.0592 us

0.29

UseDelegate

100

50.666 us

0.5449 us

1.00

UseAction

100

2.016 us

0.0346 us

0.04

UseDelegate

1000

512.513 us

9.7561 us

1.000

UseAction

1000

2.005 us

0.0243 us

0.004

Hardware configuration:

BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3448/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.100-rc.1.23463.5
  [Host]   : .NET 7.0.11 (7.0.1123.42427), X64 RyuJIT AVX2
  .NET 7.0 : .NET 7.0.11 (7.0.1123.42427), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/cs/S6802.json ================================================ { "title": "Using lambda expressions in loops should be avoided in Blazor markup section", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "blazor" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6802", "sqKey": "S6802", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" } } ================================================ FILE: analyzers/rspec/cs/S6803.html ================================================

This rule is deprecated, and will eventually be removed.

Component parameters can only receive query parameter values in routable components with an @page directive.

Why is this an issue?

SupplyParameterFromQuery attribute is used to specify that a component parameter of a routable component comes from the query string.

In the case of non-routable components, the SupplyParameterFromQuery does not contribute to the functionality, and removing it will not affect the behavior.

How to fix it

Either make the component routable or remove the SupplyParameterFromQuery attribute.

Code examples

Noncompliant code example

<h3>Component</h3>

@code {
    [Parameter]
    [SupplyParameterFromQuery]  // Noncompliant
    public bool Param { get; set; }
}

Compliant solution

@page "/component"

<h3>Component</h3>

@code {
    [Parameter]
    [SupplyParameterFromQuery]  // Compliant
    public bool Param { get; set; }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6803.json ================================================ { "title": "Parameters with SupplyParameterFromQuery attribute should be used only in routable components", "type": "CODE_SMELL", "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "blazor" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6803", "sqKey": "S6803", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S6930.html ================================================

Backslash characters (\) should be avoided in route templates.

Why is this an issue?

Routing in ASP.NET MVC maps controllers and actions to paths in request URIs.

In the former syntax specification of URIs, backslash characters (\) were not allowed at all (see section "2.4.3. Excluded US-ASCII Characters" of RFC 2396). While the current specification (RFC 3986) doesn’t include anymore the "Excluded US-ASCII Characters" section, most URL processors still don’t support backslash properly.

For instance, a backslash in the "path" part of a URL is automatically converted to a forward slash (/) both by Chrome and Internet Explorer (see here).

As an example, \Calculator\Evaluate?expression=3\4 is converted on the fly into /Calculator/Evaluate?expression=3\4 before the HTTP request is made to the server.

While backslashes are allowed in the "query" part of a URL, and it’s common to have them as part of a complex query expression, the route of a controller is always part of the "path".

That is why the use of backslashes in controller templates should be avoided in general.

What is the potential impact?

A backslash in the route pattern of a controller would only make sense if the developer intended the backslash in the route to be explicitly escaped by the user, using %5C.

For example, the route Something\[controller] for the HomeController would need to be called as Something%5CHome.

The validity of such a scenario is unlikely and the resulting behavior is surprising.

How to fix it

Code examples

Noncompliant code example

[Route(@"Something\[controller]")] // Noncompliant: Replace '\' with '/'.
public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index() => View();
}

Compliant solution

[Route(@"Something/[controller]")] // '\' replaced with '/'
public class HomeController : Controller
{
    [HttpGet]
    public ActionResult Index() => View();
}

Noncompliant code example

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}\\{action=Index}"); // Noncompliant: Replace '\' with '/'.

Compliant solution

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}"); // '\' replaced with '/'

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/cs/S6930.json ================================================ { "title": "Backslash should be avoided in route templates", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6930", "sqKey": "S6930", "scope": "Main", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S6931.html ================================================

Route templates for ASP.NET controller actions, defined via a RouteAttribute or any derivation of HttpMethodAttribute, should not start with "/".

Why is this an issue?

Routing in ASP.NET Core MVC maps controllers and actions to paths in request URIs. Similar routing happens in ASP.NET Framework MVC.

In ASP.NET Core MVC, when an action defines a route template starting with a "/", the route is considered absolute and the action is registered at the root of the web application.

In such a scenario, any route defined at the controller level is disregarded, as shown in the following example:

[Route("[controller]")]  // This route is ignored for the routing of Index1 and Index2
public class HomeController : Controller
{
    [HttpGet("/Index1")] // This action is mapped to the root of the web application
    public ActionResult Index1() => View();

    [Route("/Index2")]   // The same applies here
    public ActionResult Index2() => View();
}

The behavior can be found confusing and surprising because any relative action route is relativized to the controller route.

Therefore, in the vast majority of scenarios, controllers group all related actions not only in the source code, but also at the routing level.

In ASP.NET Framework MVC with attribute routing enabled via MapMvcAttributeRoutes, the mere presence of an absolute route at the action level will produce an InvalidOperationException at runtime.

It is then a good practice to avoid absolute routing at the action level and move the "/" to the root level, changing the template defined in the RouteAttribute of the controller appropriately.

Exceptions

The rule only applies when all route templates of all actions of the controller start with "/". Sometimes some actions may have both relative and absolute route templates, for example for backward compatibility reasons (i.e. a former route needs to be preserved). In such scenarios, it may make sense to keep the absolute route template at the action level.

How to fix it

Code examples

Noncompliant code example

[Route("[controller]")]  // This route is ignored
public class ReviewsController : Controller // Noncompliant
{
    // Route is /reviews
    [HttpGet("/reviews")]
    public ActionResult Index() { /* ... */ }

    // Route is /reviews/{reviewId}
    [Route("/reviews/{reviewId}")]
    public ActionResult Show(int reviewId)() { /* ... */ }
}

Compliant solution

[Route("/")] // Turns on attribute routing
public class ReviewsController : Controller
{
    // Route is /reviews
    [HttpGet("reviews")]
    public ActionResult Index() { /* ... */ }

    // Route is /reviews/{reviewId}
    [Route("reviews/{reviewId}")]
    public ActionResult Show(int reviewId)() { /* ... */ }
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6931.json ================================================ { "title": "ASP.NET controller actions should not have a route template starting with \"\/\"", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6931", "sqKey": "S6931", "scope": "Main", "quickfix": "partial", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/cs/S6932.html ================================================

The HttpRequest class provides access to the raw request data through the QueryString, Headers, and Forms properties. However, whenever possible it is recommended to use model binding instead of directly accessing the input data.

Why is this an issue?

Both ASP.Net MVC implementations - Core and Framework - support model binding in a comparable fashion. Model binding streamlines the process by automatically aligning data from HTTP requests with action method parameters, providing numerous benefits compared to manually parsing raw incoming request data:

Simplicity

Model binding simplifies the code by automatically mapping data from HTTP requests to action method parameters. You don’t need to write any code to manually extract values from the request.

Type Safety

Model binding provides type safety by automatically converting the incoming data into the appropriate .NET types. If the conversion fails, the model state becomes invalid, which you can easily check using ModelState.IsValid.

Validation

With model binding, you can easily apply validation rules to your models using data annotations. If the incoming data doesn’t comply with these rules, the model state becomes invalid.

Security

Model binding helps protect against over-posting attacks by only including properties in the model that you explicitly bind using the [Bind] attribute or by using view models that only contain the properties you want to update.

Maintainability

By using model binding, your code becomes cleaner, easier to read, and maintain. It promotes the use of strongly typed views, which can provide compile-time checking of your views.

How to fix it in ASP.NET Core

Request.Form, Request.Form.Files, Request.Headers, Request.Query and Request.RouteValues are keyed collections that expose data from the incoming HTTP request:

Model binding can bind these keyed collections to

To replace the keyed collection access, you can:

Replace with parameter binding or complex type binding or route binding

Request.Form["id"]

optional [FromForm] attribute on the parameter or a FormCollection parameter

optional [FromForm] attribute on the property

Request.Form.Files

IFormFile, IFormFileCollection, or IEnumerable<IFormFile> parameter

Request.Headers["id"]

[FromHeader] attribute on the parameter

[FromHeader] attribute on the property

Request.Query["id"]

optional [FromQuery] attribute on the parameter

optional [FromQuery] attribute on the property

Request.RouteValues["id"]

optional [FromRoute] attribute on the parameter

optional [Route("{id}")]attribute on the action method/controller or via conventional routing

The Model Binding in ASP.NET Core article describes the mechanisms, conventions, and customization options for model binding in more detail. Route-based binding is described in the Routing to controller actions in ASP.NET Core document.

Code examples

Noncompliant code example

public IActionResult Post()
{
    var name = Request.Form["name"];                           // Noncompliant: Request.Form
    var birthdate = DateTime.Parse(Request.Form["Birthdate"]); // Noncompliant: Request.Form

    var locale = Request.Query.TryGetValue("locale", out var locales)
        ? locales.ToString()
        : "en-US";                                             // Noncompliant: Request.Query
    // ..
}

Compliant solution

public record User
{
    [Required, StringLength(100)]
    public required string Name { get; init; }
    [DataType(DataType.Date)]
    public DateTime? Birthdate { get; init; }
}

public IActionResult Post(User user, [FromHeader] string origin, [FromQuery] string locale = "en-US")
{
    if (ModelState.IsValid)
    {
        // ...
    }
}

How does this work?

Model binding in ASP.NET Core MVC and ASP.NET MVC 4.x works by automatically mapping data from HTTP requests to action method parameters. Here’s a step-by-step breakdown of how it works:

  1. Request Data When a user submits a form or sends a request to an ASP.NET application, the request data might include form data, query string parameters, request body, and HTTP headers.
  2. Model Binder The model binder’s job is to create .NET objects from the request data. It looks at each parameter in the action method and attempts to populate it with the incoming data.
  3. Value Providers The model binder uses Value Providers to get data from various parts of the request, such as the query string, form data, or route data. Each value provider tells the model binder where to find values in the request.
  4. Binding The model binder tries to match the keys from the incoming data with the properties of the action method’s parameters. If a match is found, it attempts to convert the incoming data into the appropriate .NET type and assigns it to the parameter.
  5. Validation If the model binder can’t convert the value or if the converted value doesn’t pass any specified validation rules, it adds an error to the ModelState.Errors collection. You can check ModelState.IsValid in your action method to see if any errors occurred during model binding.
  6. Action Method Execution The action method is executed with the bound parameters. If ModelState.IsValid is false, you can handle the errors in your action method and return an appropriate response.

See the links in the Resources section for more information.

How to fix it in ASP.NET MVC 4.x

Request.Form and Request.QueryString are keyed collections that expose data from the incoming HTTP request:

Model binding can bind these keyed collections to

To replace the keyed collection access, you can:

Replace with parameter binding or complex type binding

Request.Form["id"]

optional [Bind] attribute on the parameter or a FormCollection parameter

optional [Bind] attribute on the parameter or type

Request.QueryString["id"]

optional [Bind] attribute on the parameter

property name must match query parameter key

Code examples

Noncompliant code example

public ActionResult Post()
{
    var name = Request.Form["name"];                            // Noncompliant: Request.Form
    Debug.WriteLine(Request.Form[0]);                           // Compliant: Binding by index is not supported.
    var birthdate = DateTime.Parse(Request.Form["Birthdate"]);  // Noncompliant: Request.Form

    var cultureName = Request.QueryString["locale"] ?? "en-US"; // Noncompliant: Request.QueryString
    // ..
}

Compliant solution

public class User
{
    [Required, StringLength(100)]
    public string Name { get; set; }
    [DataType(DataType.Date)]
    public DateTime? Birthdate { get; set; }
}

public ActionResult Post(User user, [Bind(Prefix = "locale")] string cultureName = "en-US")
{
    if (ModelState.IsValid)
    {
        // ...
    }
}

public IActionResult Post()
{
    var origin = Request.Headers[HeaderNames.Origin];          // Compliant: Access via non-constant field
    var nameField = "name";
    var name = Request.Form[nameField];                        // Compliant: Access via local
    var birthdate = DateTime.Parse(Request.Form["Birthdate"]); // Compliant: Access via constant and variable keys is mixed.
                                                               // Model binding would only work partially in the method, so we do not raise here.
    return Ok();
    // ..
}

How does this work?

Model binding in ASP.NET Core MVC and ASP.NET MVC 4.x works by automatically mapping data from HTTP requests to action method parameters. Here’s a step-by-step breakdown of how it works:

  1. Request Data When a user submits a form or sends a request to an ASP.NET application, the request data might include form data, query string parameters, request body, and HTTP headers.
  2. Model Binder The model binder’s job is to create .NET objects from the request data. It looks at each parameter in the action method and attempts to populate it with the incoming data.
  3. Value Providers The model binder uses Value Providers to get data from various parts of the request, such as the query string, form data, or route data. Each value provider tells the model binder where to find values in the request.
  4. Binding The model binder tries to match the keys from the incoming data with the properties of the action method’s parameters. If a match is found, it attempts to convert the incoming data into the appropriate .NET type and assigns it to the parameter.
  5. Validation If the model binder can’t convert the value or if the converted value doesn’t pass any specified validation rules, it adds an error to the ModelState.Errors collection. You can check ModelState.IsValid in your action method to see if any errors occurred during model binding.
  6. Action Method Execution The action method is executed with the bound parameters. If ModelState.IsValid is false, you can handle the errors in your action method and return an appropriate response.

See the links in the Resources section for more information.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6932.json ================================================ { "title": "Use model binding instead of reading raw request data", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6932", "sqKey": "S6932", "scope": "Main", "quickfix": "infeasible", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM", "RELIABILITY": "MEDIUM", "SECURITY": "MEDIUM" }, "attribute": "FOCUSED" } } ================================================ FILE: analyzers/rspec/cs/S6934.html ================================================

When a route template is defined through an attribute on an action method, conventional routing for that action is disabled. To maintain good practice, it’s recommended not to combine conventional and attribute-based routing within a single controller to avoid unpredicted behavior. As such, the controller should exclude itself from conventional routing by applying a [Route] attribute.

Why is this an issue?

In ASP.NET Core MVC, the routing middleware utilizes a series of rules and conventions to identify the appropriate controller and action method to handle a specific HTTP request. This process, known as conventional routing, is generally established using the MapControllerRoute method. This method is typically configured in one central location for all controllers during the application setup.

Conversely, attribute routing allows routes to be defined at the controller or action method level. It is possible to mix both mechanisms. Although it’s permissible to employ diverse routing strategies across multiple controllers, combining both mechanisms within one controller can result in confusion and increased complexity, as illustrated below.

// Conventional mapping definition
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

public class PersonController
{
    // Conventional routing:
    // Matches e.g. /Person/Index/123
    public IActionResult Index(int? id) => View();

    // Attribute routing:
    // Matches e.g. /Age/Ascending (and model binds "Age" to sortBy and "Ascending" to direction)
    // but does not match /Person/List/Age/Ascending
    [HttpGet(template: "{sortBy}/{direction}")]
    public IActionResult List(string sortBy, SortOrder direction) => View();
}

How to fix it in ASP.NET Core

When any of the controller actions are annotated with a HttpMethodAttribute with a route template defined, you should specify a route template on the controller with the RouteAttribute as well.

Code examples

Noncompliant code example

public class PersonController : Controller
{
    // Matches /Person/Index/123
    public IActionResult Index(int? id) => View();

    // Matches /Age/Ascending
    [HttpGet(template: "{sortBy}/{direction}")] // Noncompliant: The "Index" and the "List" actions are
                                                // reachable via different routing mechanisms and routes
    public IActionResult List(string sortBy, SortOrder direction) => View();
}

Compliant solution

[Route("[controller]/{action=Index}")]
public class PersonController : Controller
{
    // Matches /Person/Index/123
    [Route("{id?}")]
    public IActionResult Index(int? id) => View();

    // Matches Person/List/Age/Ascending
    [HttpGet("{sortBy}/{direction}")] // Compliant: The route is relative to the controller
    public IActionResult List(string sortBy, SortOrder direction) => View();
}

There are also alternative options to prevent the mixing of conventional and attribute-based routing:

// Option 1. Replace the attribute-based routing with a conventional route
app.MapControllerRoute(
    name: "Lists",
    pattern: "{controller}/List/{sortBy}/{direction}",
    defaults: new { action = "List" } ); // Matches Person/List/Age/Ascending

// Option 2. Use a binding, that does not depend on route templates
public class PersonController : Controller
{
    // Matches Person/List?sortBy=Age&direction=Ascending
    [HttpGet] // Compliant: Parameters are bound from the query string
    public IActionResult List(string sortBy, SortOrder direction) => View();
}

// Option 3. Use an absolute route
public class PersonController : Controller
{
    // Matches Person/List/Age/Ascending
    [HttpGet("/[controller]/[action]/{sortBy}/{direction}")] // Illustrate the expected route by starting with "/"
    public IActionResult List(string sortBy, SortOrder direction) => View();
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6934.json ================================================ { "title": "A Route attribute should be added to the controller when a route template is specified at the action level", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6934", "sqKey": "S6934", "scope": "Main", "quickfix": "partial", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/cs/S6960.html ================================================

ASP.NET controllers should not have mixed responsibilities. Following the Single Responsibility Principle (SRP), they should be kept lean and focused on a single, separate concern. In short, they should have a single reason to change.

The rule identifies different responsibilities by looking at groups of actions that use different sets of services defined in the controller.

Basic services that are typically required by most controllers are not considered:

The rule currently applies to ASP.NET Core only, and doesn’t cover ASP.NET MVC 4.x.

It also only takes into account web APIs controllers, i.e. the ones marked with the ApiController attribute. MVC controllers are not in scope.

Why is this an issue?

Multiple issues can appear when the Single Responsibility Principle (SRP) is violated.

Harder to read and understand

A controller violating SRP is harder to read and understand since its Cognitive Complexity is generally above average (see {rule:csharpsquid:S3776}).

For example, a controller MediaController that is in charge of both the "movies" and "photos" APIs would need to define all the actions dealing with movies, alongside the ones dealing with photos, all defined in the same controller class.

The alternative is to define two controllers: a MovieController and a PhotoController, each in charge of a smaller number of actions.

Harder to maintain and modify

Such complexity makes the controller harder to maintain and modify, slowing down new development and increasing the likelihood of bugs.

For example, a change in MediaController made for the movies APIs may inadvertently have an impact on the photos APIs as well. Because the change was made in the context of movies, tests on photos may be overlooked, resulting in bugs in production.

That would not be likely to happen when two distinct controllers, MovieController and a PhotoController, are defined.

Harder to test

The controller also becomes harder to test since the test suite would need to define a set of tests for each of the responsibilities of the controller, resulting in a large and complex test suite.

For example, the MediaController introduced above would need to be tested on all movies-related actions, as well as on all photos-related actions.

All those tests would be defined in the same test suite for MediaController, which would be affected by the same issues of cognitive complexity as the controller under test by the suite.

Harder to reuse

A controller that has multiple responsibilities is less likely to be reusable. Lack of reuse can result in code duplication.

For example, when a new controller wants to derive from an existing one, it’s less probable that the new controller requires all the behaviors exposed by the reused controller.

Rather, it’s much more common that the new controller only needs to reuse a fraction of the existing one. When reuse is not possible, the only valid alternative is to duplicate part of the logic in the new controller.

Higher likelihood of performance issues

A controller that has multiple responsibilities may end up doing more than strictly necessary, resulting in a higher likelihood of performance issues.

To understand why, it’s important to consider the difference between ASP.NET application vs non-ASP.NET applications.

In a non-ASP.NET application, controllers may be defined as Singletons via a Dependency Injection library.

In such a scenario, they would typically be instantiated only once, lazily or eagerly, at application startup.

In ASP.NET applications, however, the default is that controllers are instantiated as many times as the number of requests that are served by the web server. Each instance of the controller would need to resolve services independently.

While service instantiation is typically handled at application startup, service resolution happens every time an instance of controller needs to be built, for each service declared in the controller.

Whether the resolution is done via Dependency Injection, direct static access (in the case of a Singleton), or a Service Locator, the cost of resolution needs to be paid at every single instantiation.

For example, the movies-related APIs of the MediaController mentioned above may require the instantiation of an IStreamingService, typically done via dependency injection. Such a service may not be relevant for photos-related APIs.

Similarly, some of the photos-related APIs may require the instantiation of an IRedEyeRemovalService, which may not work at all with movies.

Having a single controller would force the developer to deal with both instantiations, even though a given instance of the controller may be used only for photos, or only for movies.

More complex routing

A controller that deals with multiple concerns often has unnecessarily complex routing: the route template at controller level cannot factorize the route identifying the concern, so the full route template needs to be defined at the action level.

For example, the MediaController would have an empty route (or equivalent, e.g. / or ~/) and the actions would need to define themselves the movie or photo prefix, depending on the type of media they deal with.

On the other hand, MovieController and PhotoController can factorize the movie and photo route respectively, so that the route on the action would only contain action-specific information.

What is the potential impact?

As the size and the responsibilities of the controller increase, the issues that come with such an increase will have a further impact on the code.

Why MVC controllers are not in scope

Alongside attribute routing, which is typical of web APIs, MVC controllers also come with [conventional routing].

In MVC, the file structure of controllers is important, since it drives conventional routing, which is specific to MVC, as well as default view mapping.

For those reasons, splitting an MVC controller into smaller pieces may break core behaviors of the web application such as routing and views, triggering a large refactor of the whole project.

How to fix it in ASP.NET Core

Split the controller into multiple controllers, each dealing with a single responsibility.

Code examples

Noncompliant code example

[Route("media")]
public class MediaController( // Noncompliant: This controller has multiple responsibilities and could be split into 2 smaller units.
    // Used by all actions
    ILogger<MediaController> logger,
    // Movie-specific dependencies
    IStreamingService streamingService, ISubtitlesService subtitlesService,
    // Photo-specific dependencies
    IRedEyeRemovalService redEyeRemovalService, IPhotoEnhancementService photoEnhancementService) : Controller
{
    [Route("movie/stream")]
    public IActionResult MovieStream([FromQuery] StreamRequest request) // Belongs to responsibility #1.
    {
        logger.LogInformation("Requesting movie stream for {MovieId}", request.MovieId);
        return File(streamingService.GetStream(request.MovieId), "video/mp4");
    }

    [Route("movie/subtitles")]
    public IActionResult MovieSubtitles([FromQuery] SubtitlesRequest request) // Belongs to responsibility #1.
    {
        logger.LogInformation("Requesting movie subtitles for {MovieId}", request.MovieId);
        return File(subtitlesService.GetSubtitles(request.MovieId, request.Language), "text/vtt");
    }

    [Route("photo/remove-red-eye")]
    public IActionResult RemoveRedEye([FromQuery] RedEyeRemovalRequest request) // Belongs to responsibility #2.
    {
        logger.LogInformation("Removing red-eye from photo {PhotoId}", request.PhotoId);
        return File(redEyeRemovalService.RemoveRedEye(request.PhotoId, request.Sensitivity), "image/jpeg");
    }

    [Route("photo/enhance")]
    public IActionResult EnhancePhoto([FromQuery] PhotoEnhancementRequest request) // Belongs to responsibility #2.
    {
        logger.LogInformation("Enhancing photo {PhotoId}", request.PhotoId);
        return File(photoEnhancementService.EnhancePhoto(request.PhotoId, request.ColorGrading), "image/jpeg");
    }
}

Compliant solution

[Route("media/[controller]")]
public class MovieController(
    ILogger<MovieController> logger,
    IStreamingService streamingService, ISubtitlesService subtitlesService) : Controller
{
    [Route("stream")]
    public IActionResult MovieStream([FromQuery] StreamRequest request)
    {
        logger.LogInformation("Requesting movie stream for {MovieId}", request.MovieId);
        return File(streamingService.GetStream(request.MovieId), "video/mp4");
    }

    [Route("subtitles")]
    public IActionResult MovieSubtitles([FromQuery] SubtitlesRequest request)
    {
        logger.LogInformation("Requesting movie subtitles for {MovieId}", request.MovieId);
        return File(subtitlesService.GetSubtitles(request.MovieId, request.Language), "text/vtt");
    }
}

[Route("media/[controller]")]
public class PhotoController(
    ILogger<PhotoController> logger,
    IRedEyeRemovalService redEyeRemovalService, IPhotoEnhancementService photoEnhancementService) : Controller
{
    [Route("remove-red-eye")]
    public IActionResult RemoveRedEye([FromQuery] RedEyeRemovalRequest request)
    {
        logger.LogInformation("Removing red-eye from photo {PhotoId}", request.PhotoId);
        return File(redEyeRemovalService.RemoveRedEye(request.PhotoId, request.Sensitivity), "image/jpeg");
    }

    [Route("enhance")]
    public IActionResult EnhancePhoto([FromQuery] PhotoEnhancementRequest request)
    {
        logger.LogInformation("Enhancing photo {PhotoId}", request.PhotoId);
        return File(photoEnhancementService.EnhancePhoto(request.PhotoId, request.ColorGrading), "image/jpeg");
    }
}

Resources

Documentation

Articles & blog posts

Conference presentations

Related rules

================================================ FILE: analyzers/rspec/cs/S6960.json ================================================ { "title": "Controllers should not have mixed responsibilities", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Linear", "linearDesc": "responsibilities", "linearFactor": "15min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6960", "sqKey": "S6960", "scope": "Main", "quickfix": "partial", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" } } ================================================ FILE: analyzers/rspec/cs/S6961.html ================================================

In ASP.NET Core, controllers usually inherit either from ControllerBase or Controller. If a controller does not use any View-specific functionality, it is recommended to inherit from ControllerBase.

Why is this an issue?

The ControllerBase class contains all the necessary functionality to handle API requests and responses. The Controller class inherits from ControllerBase and adds support for Views, PartialViews and ViewComponents.

Inheriting from Controller when not using any View-specific functionality exposes unnecessary methods and can lead to confusion about the intention of the class.

Furthermore, inheriting from Controller may come with a performance cost. Even though the controller might only deal with API operations, the support for Views might introduce some overhead from the MVC framework during the request processing pipeline.

An issue is raised when:

Exceptions

How to fix it

Change the base type of the controller from Controller to ControllerBase.

Code examples

Noncompliant code example

[ApiController]
public class MyController : Controller // Noncompliant: Inherit from ControllerBase instead of Controller.
//                          ^^^^^^^^^^
{
    // ..
}

Compliant solution

[ApiController]
public class MyController : ControllerBase
{
    // ..
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6961.json ================================================ { "title": "API Controllers should derive from ControllerBase instead of Controller", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net", "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6961", "sqKey": "S6961", "scope": "Main", "quickfix": "targeted", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" } } ================================================ FILE: analyzers/rspec/cs/S6962.html ================================================

In frequently used code paths, such as controller actions, you should avoid using the HttpClient directly and opt for one of the IHttpClientFactory-based mechanisms instead. This way, you avoid wasting resources and creating performance overhead.

Why is this an issue?

If a code path that creates and disposes of HttpClient objects is frequently used, then the following issues can occur:

How to fix it

The IHttpClientFactory was introduced in ASP.NET Core 2.1 to solve these problems. It handles pooling HTTP connections to optimize performance and reliability.

There are several ways that you can use IHttpClientFactory in your application:

Alternatively, you may cache the HttpClient in a singleton or a static field. You should be aware that by default, the HttpClient doesn’t respect the DNS’s Time To Live (TTL) settings. If the IP address associated with a domain name changes, HttpClient might still use the old, cached IP address, leading to failed requests.

Code examples

Noncompliant code example

[ApiController]
[Route("controller")]
public class FooController : Controller
{
    [HttpGet]
    public async Task<string> Foo()
    {
        using var client = new HttpClient();  // Noncompliant
        return await client.GetStringAsync(_url);
    }
}

Compliant solution

// File: Startup.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // ...
    }
}

[ApiController]
[Route("controller")]
public class FooController : Controller
{
    private readonly IHttpClientFactory _clientFactory;

    public FooController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    [HttpGet]
    public async Task<string> Foo()
    {
        using var client = _clientFactory.CreateClient(); // Compliant (Basic usage)
        return await client.GetStringAsync(_url);
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6962.json ================================================ { "title": "You should pool HTTP connections with HttpClientFactory", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6962", "sqKey": "S6962", "scope": "Main", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "COMPLETE" } } ================================================ FILE: analyzers/rspec/cs/S6964.html ================================================

"Under-posting" refers to a situation where a client sends less data than expected to the server during an HTTP request, for example when the client omits some properties from the request body that the server expects to receive.

Why is this an issue?

One of the main issues that under-posting can cause is data inconsistency. If the client sends less data than expected, the application might fill any value type properties with their default values, leading to inaccurate or inconsistent data in your database. Additionally, there might be unexpected behavior if there are certain data expected that are not provided and even security issues; for example, if a user omits a role or permission field from a POST request, and the server fills in a default value, it could inadvertently grant more access than intended.

A model class (in this case the Product class) can be an input of an HTTP handler method:

public class ProductsController : Controller
{
    [HttpPost]
    public IActionResult Create([FromBody]Product product)
    {
        // Process product data...
    }
}

Exceptions

This rule does not raise an issue when properties are decorated with the following attributes:

Additionally, this rule does not raise for properties in model classes that are not in the same project as the Controller class that references them. This is due to a limitation of Roslyn (see here).

How to fix it

You should mark any model value-type property as nullable, required or JsonRequired. Thus when a client underposts, you ensure that the missing properties can be detected on the server side rather than being auto-filled, and therefore, incoming data meets the application’s expectations.

Code examples

Noncompliant code example

public class Product
{
    public int Id { get; set; }             // Noncompliant
    public string Name { get; set; }
    public int NumberOfItems { get; set; }  // Noncompliant
    public decimal Price { get; set; }      // Noncompliant
}

If the client sends a request without setting the NumberOfItems or Price properties, they will default to 0. In the request handler method, there’s no way to determine whether they were intentionally set to 0 or omitted by mistake.

Compliant solution

public class Product
{
    public required int Id { get; set; }
    public string Name { get; set; }
    public int? NumberOfItems { get; set; }            // Compliant - property is optional
    [JsonRequired] public decimal Price { get; set; }  // Compliant - property must have a value
}

In this example, the request handler method can

public class ProductsController : Controller
{
    [HttpPost]
    public IActionResult Create(Product product)
    {
        if (!ModelState.IsValid)    // if product.Price is missing then the model state will not be valid
        {
            return View(product);
        }

        if (product.NumberOfItems.HasValue)
        {
            // ...
        }
        // Process input...
    }
}

Recommended Secure Coding Practices

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6964.json ================================================ { "title": "Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting.", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6964", "sqKey": "S6964", "scope": "Main", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" } } ================================================ FILE: analyzers/rspec/cs/S6965.html ================================================

When building a REST API, it’s recommended to annotate the controller actions with the available HTTP attributes to be precise about what your API supports.

Why is this an issue?

How to fix it

You should annotate the controller actions with the available HttpMethod attributes. You can still use them in conjunction with the Route attribute, in case there are multiple templates for one action and you need to set the order. This allows you to clearly define the HTTP methods each action method should respond to, while still being able to customize your routes.

Exceptions

This rule does not raise if the controller or the action is annotated with [ApiExplorerSettings(IgnoreApi = true)] or AcceptsVerbs attribute.

Code examples

Noncompliant code example

[Route("Customer")]                                                        // This route conflicts with GetCustomers action route
public async Task<IResult> ChangeCustomer([FromBody] CustomerData data)   // Noncompliant
{
    // ...
    return Results.Ok();
}

[Route("Customer")]                         // This route conflicts with ChangeCustomer action route
public async Task<string> GetCustomers()    // Noncompliant
{
    return _customerRepository.GetAll();
}

Compliant solution

[Route("Customer")]
[HttpPost]
public async Task<IResult> ChangeCustomer([FromBody] CustomerData data)    // Compliant
{
    // ...
    return Results.Ok();
}

[HttpGet("Customer")]
public async Task<string> GetCustomers()    // Compliant
{
    return _customerRepository.GetAll();
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6965.json ================================================ { "title": "REST API actions should be annotated with an HTTP verb attribute", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6965", "sqKey": "S6965", "scope": "Main", "quickfix": "infeasible", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S6966.html ================================================

In an async method, any blocking operations should be avoided.

Why is this an issue?

Using a synchronous method instead of its asynchronous counterpart in an async method blocks the execution and is considered bad practice for several reasons:

Resource Utilization

Each thread consumes system resources, such as memory. When a thread is blocked, it’s not doing any useful work, but it’s still consuming these resources. This can lead to inefficient use of system resources.

Scalability

Blocking threads can limit the scalability of your application. In a high-load scenario where many operations are happening concurrently, each blocked thread represents a missed opportunity to do useful work. This can prevent your application from effectively handling increased load.

Performance

Blocking threads can degrade the performance of your application. If all threads in the thread pool become blocked, new tasks can’t start executing until an existing task completes and frees up a thread. This can lead to delays and poor responsiveness.

Instead of blocking, it’s recommended to use the async operator with async methods. This allows the system to release the current thread back to the thread pool until the awaited task is complete, improving scalability and responsiveness.

How to fix it

Code examples

Noncompliant code example

public async Task Examples(Stream stream, DbSet<Person> dbSet)
{
    stream.Read(array, 0, 1024);            // Noncompliant
    File.ReadAllLines("path");              // Noncompliant
    dbSet.ToList();                         // Noncompliant in Entity Framework Core queries
    dbSet.FirstOrDefault(x => x.Age >= 18); // Noncompliant in Entity Framework Core queries
}

Compliant solution

public async Task Examples(Stream stream, DbSet<Person> dbSet)
{
    await stream.ReadAsync(array, 0, 1024);
    await File.ReadAllLinesAsync("path");
    await dbSet.ToListAsync();
    await dbSet.FirstOrDefaultAsync(x => x.Age >= 18);
}

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S6966.json ================================================ { "title": "Awaitable method should be used", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "async-await" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6966", "sqKey": "S6966", "scope": "All", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "COMPLETE" } } ================================================ FILE: analyzers/rspec/cs/S6967.html ================================================

In the context of ASP.NET Core MVC web applications, both model binding and model validation are processes that take place prior to the execution of a controller action. It is imperative for the application to examine the ModelState.IsValid and respond accordingly.

This rule enforces the developer to validate the model within a controller action, ensuring the application’s robustness and reliability.

Why is this an issue?

Querying the ModelState.IsValid property is necessary because it checks if the submitted data in the HTTP request is valid or not. This property evaluates all the validation attributes applied on your model properties and determines whether the data provided satisfies those validation rules.

What is the potential impact?

Skipping model validation can lead to:

Therefore, it’s highly recommended to always validate models in your application to ensure data integrity, application stability, and a good user experience.

While client-side validation enhances user experience by providing immediate feedback, it’s not sufficient due to potential manipulation of client-side code, browser compatibility issues, and dependence on JavaScript. Users can bypass or disable it, leading to invalid or malicious data being submitted. Therefore, server-side validation is essential to ensure data integrity and security, making it a best practice to use both client-side and server-side validation in your application.

Exceptions

How to fix it

If ModelState.IsValid returns true, it means that the data is valid and the process can continue. If it returns false, it means that the validation failed, indicating that the data is not in the expected format or is missing required information.

In such cases, the controller action should handle this by returning an appropriate response, such as re-displaying the form with error messages. This helps maintain the integrity of the data and provides feedback to the user, enhancing the overall user experience and security of your application.

Code examples

Noncompliant code example

public async Task<IActionResult> Create(Movie movie) // Noncompliant: model validity check is missing
{
    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Compliant solution

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6967.json ================================================ { "title": "ModelState.IsValid should be called in controller actions", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-6967", "sqKey": "S6967", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM", "RELIABILITY": "HIGH", "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" } } ================================================ FILE: analyzers/rspec/cs/S6968.html ================================================

In an ASP.NET Core Web API, controller actions can optionally return a result value. If a controller action returns a value in the happy path, for example ControllerBase.Ok(Object), annotating the action with one of the [ProducesResponseType] overloads that describe the type is recommended.

Why is this an issue?

If an ASP.NET Core Web API uses Swagger, the API documentation will be generated based on the input/output types of the controller actions, as well as the attributes annotating the actions. If an action returns IActionResult or IResult, Swagger cannot infer the type of the response. From the consumer’s perspective, this can be confusing and lead to unexpected results and bugs in the long run without the API provider’s awareness.

This rule raises an issue on a controller action when:

How to fix it

There are multiple ways to fix this issue:

Code examples

Noncompliant code example

[HttpGet("foo")]
// Noncompliant: Annotate this method with ProducesResponseType containing the return type for succesful responses.
public IActionResult MagicNumber() => Ok(42);
[HttpGet("foo")]
// Noncompliant: Use the ProducesResponseType overload containing the return type for succesful responses.
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult MagicNumber() => Ok(42);

Compliant solution

[HttpGet("foo")]
[ProducesResponseType<int>(StatusCodes.Status200OK)]
public IActionResult MagicNumber() => Ok(42);
[HttpGet("foo")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public IActionResult MagicNumber() => Ok(42);

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S6968.json ================================================ { "title": "Actions that return a value should be annotated with ProducesResponseTypeAttribute containing the return type", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6968", "sqKey": "S6968", "scope": "Main", "quickfix": "partial", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/cs/S7039.html ================================================

Why is this an issue?

The Content Security Policy (CSP) is a computer security standard that serves as an additional layer of protection against various types of attacks, including Cross-Site Scripting (XSS) and clickjacking. It provides a set of standard procedures for loading resources by user agents, which can help to mitigate the risk of content injection vulnerabilities.

However, it is important to note that CSP is not a primary line of defense, but rather a safety net that catches attempts to exploit vulnerabilities that exist in the system despite other protective measures. An insecure CSP does not automatically imply that the website is vulnerable, but it does mean that this additional layer of protection is weakened.

A CSP can be considered insecure if it allows potentially harmful practices, such as inline scripts or loading resources from arbitrary domains. These practices can increase the risk of content injection attacks.

What is the potential impact?

An insecure Content Security Policy (CSP) can increase the potential severity of other vulnerabilities in the system. For instance, if an attacker manages to exploit a Cross-Site Scripting (XSS) vulnerability, an insecure CSP might not provide the intended additional protection.

The impact of a successful XSS attack can be severe. XSS allows an attacker to inject malicious scripts into web pages viewed by other users. These scripts can then be used to steal sensitive information like session cookies, personal data, or credit card details, leading to identity theft or financial fraud.

Moreover, XSS can be used to perform actions on behalf of the user without their consent, such as changing their email address or password, or making transactions. This can lead to unauthorized access and potential loss of control over user accounts.

In addition, an insecure CSP that allows loading resources from arbitrary domains could potentially expose sensitive user data to untrusted sources. This could lead to data breaches, which can have serious legal and reputational consequences.

How to fix it

Code examples

Noncompliant code example

using System.Web;

public async Task InvokeAsync(HttpContext context)
{
    context.Response.Headers.ContentSecurityPolicy = "script-src 'self' 'unsafe-inline';"; // Noncompliant
}

Compliant solution

using System.Web;

public async Task InvokeAsync(HttpContext context)
{
    context.Response.Headers.ContentSecurityPolicy = "script-src 'self' 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=';";
}

How does this work?

To fix an insecure Content Security Policy (CSP), you should adhere to the principle of least privilege. This principle states that a user should be given the minimum levels of access necessary to complete their tasks. In the context of CSP, this means restricting the sources from which content can be loaded to the minimum necessary.

Here are some steps to secure your CSP:

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/cs/S7039.json ================================================ { "title": "Content Security Policies should be restrictive", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-7039", "sqKey": "S7039", "scope": "All", "securityStandards": { "CWE": [ 693 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "6.5.7" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.3.3" ], "STIG ASD_V5R3": [ "V-222602" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S7130.html ================================================

When working with collections that are known to be non-empty, using First or Single is generally preferred over FirstOrDefault or SingleOrDefault.

Why is this an issue?

Using FirstOrDefault or SingleOrDefault on collections that are known to be non-empty is an issue due to:

Code examples

Noncompliant code example

var items = new List<int> { 1, 2, 3 };

int firstItem = items.FirstOrDefault(); // Noncompliant, this implies the collection might be empty, when we know it is not

Compliant solution

var items = new List<int> { 1, 2, 3 };

int firstItem = items.First(); // Compliant

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/cs/S7130.json ================================================ { "title": "First\/Single should be used instead of FirstOrDefault\/SingleOrDefault on collections that are known to be non-empty", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-7130", "sqKey": "S7130", "scope": "All", "quickfix": "targeted", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/cs/S7131.html ================================================

When using ReaderWriterLock and ReaderWriterLockSlim for managing read and write locks, you should not release a read lock while holding a write lock and vice versa, otherwise you might have runtime exceptions. The locks should be always correctly paired so that the shared resource is accessed safely.

This rule raises if:

Why is this an issue?

If you use the ReaderWriterLockSlim class, you will get a LockRecursionException. In the case of ReaderWriterLock, you’ll get a runtime exception for trying to release a lock that is not owned by the calling thread.

Code examples

Noncompliant code example

public class Example
{
    private static ReaderWriterLock rwLock = new();

    public void Writer()
    {
        rwLock.AcquireWriterLock(2000);
        try
        {
            // ...
        }
        finally
        {
            rwLock.ReleaseReaderLock(); // Noncompliant, will throw runtime exception
        }
    }

    public void Reader()
    {
        rwLock.AcquireReaderLock(2000);
        try
        {
            // ...
        }
        finally
        {
            rwLock.ReleaseWriterLock(); // Noncompliant, will throw runtime exception
        }
    }
}

Compliant solution

public class Example
{
    private static ReaderWriterLock rwLock = new();

    public static void Writer()
    {
        rwLock.AcquireWriterLock(2000);
        try
        {
            // ...
        }
        finally
        {
            rwLock.ReleaseWriterLock();
        }
    }

    public static void Reader()
    {
        rwLock.AcquireReaderLock(2000);
        try
        {
            // ...
        }
        finally
        {
            rwLock.ReleaseReaderLock();
        }
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S7131.json ================================================ { "title": "A write lock should not be released when a read lock has been acquired and vice versa", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-7131", "sqKey": "S7131", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/cs/S7133.html ================================================

This rule raises if you acquire a lock with one of the following methods, and do not release it within the same method.

This rule will raise an issue when the code uses the disposable pattern. This pattern makes locking easy to use and delegates the responsibility to the caller. Users should accept issues in such cases, as they should appear only once for each synchronization type.

Why is this an issue?

Not releasing a lock in the same method where you acquire it, and releasing in another one, makes the code less clear and harder to maintain. You are also introducing the risk of not releasing a lock at all which can lead to deadlocks or exceptions.

Code examples

Noncompliant code example

public class Example
{
    private static ReaderWriterLock rwLock = new();

    public void AcquireWriterLock() =>
        rwLock.AcquireWriterLock(2000);  // Noncompliant, as the lock release is on the callers responsibility

    public void DoSomething()
    {
        // ...
    }

    public void ReleaseWriterLock() =>
        rwLock.ReleaseWriterLock();
}

Compliant solution

public class Example
{
    private static ReaderWriterLock rwLock = new();

    public void DoSomething()
    {
        rwLock.AcquireWriterLock(2000); // Compliant, locks are released in the same method
        try
        {
            // ...
        }
        finally
        {
            rwLock.ReleaseWriterLock();
        }
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S7133.json ================================================ { "title": "Locks should be released within the same method", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-7133", "sqKey": "S7133", "scope": "All", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S818.html ================================================

Why is this an issue?

Using upper case literal suffixes removes the potential ambiguity between "1" (digit 1) and "l" (letter el) for declaring literals.

Noncompliant code example

const long b = 0l;      // Noncompliant

Compliant solution

const long b = 0L;
================================================ FILE: analyzers/rspec/cs/S818.json ================================================ { "title": "Literal suffixes should be upper case", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention", "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-818", "sqKey": "S818", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/cs/S8367.html ================================================

This rule raises an issue when code uses 'field' as an identifier in contexts where it conflicts with the new field contextual keyword introduced in C# 14.

Why is this an issue?

C# 14 introduces the field contextual keyword for field-backed properties. This keyword allows access to the synthesized backing fields directly within property accessors, simplifying property implementations that need custom logic.

By using 'field' as an identifier, several problems can occur:

What is the potential impact?

Using 'field' as an identifier can prevent successful compilation when upgrading to C# 14. In property accessors, local variables or parameters named 'field' will cause compilation errors, while class members named 'field' may be unexpectedly overshadowed by synthesized backing fields, leading to logic errors and unexpected runtime behavior.

How to fix it

Rename the identifier, escape it by prefixing with @, or qualify member access with this. or base. to avoid conflicts with the contextual keyword. This rule applies only inside property get, set, and init accessors; indexer and event accessors are not affected.

Code examples

Noncompliant code example

public class ClassFieldExample
{
    private string field;

    public string Message
    {
        get => field; // Noncompliant
        set => field = value; // Noncompliant
    }
}
public class LocalFunctionExample
{
    public int Value
    {
        get
        {
            return LocalFunction(42);

            int LocalFunction(int field) // Noncompliant
                => field;
        }
    }
}

Compliant solution

public class ClassFieldExample
{
    private string field;

    public string Message
    {
        get => this.field;          // or @field
        set => this.field = value;  // or @field = value;
    }
}
public class LocalFunctionExample
{
    public int Value
    {
        get
        {
            return LocalFunction(42);

            int LocalFunction(int value) // Compliant - renamed
                => value;
        }
    }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S8367.json ================================================ { "title": "Identifiers should not conflict with the C# 14 \"field\" contextual keyword", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [ "csharp-14", "compatibility", "upgrade" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-8367", "sqKey": "S8367", "scope": "All", "quickfix": "unknown", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S8368.html ================================================

Identifiers named extension should be renamed or use the @ escape prefix to avoid breaking changes in C# 14 or later.

Why is this an issue?

Starting with C# 14, extension is a contextual keyword for declaring extension members and extension containers. This change can cause compilation errors in existing code that uses the word extension as an identifier. For example, a class named 'extension' will be interpreted as an extension container declaration, leading to parsing failures.

This rule flags any unescaped extension identifiers used in the following cases:

Note that method names, parameter names, and local variable names are not affected by this breaking change. For example, void extension() { } remains valid in C# 14.

What is the potential impact?

Code that currently compiles will fail to compile when the project is upgraded to C# 14.

How to fix it

Rename the identifier, or add the @ prefix to it.

Code examples

Noncompliant code example

class extension { }
class MyClass
{
    extension field;
    extension Property { get; }
}

Compliant solution

Escape the identifier with the @ verbatim prefix:

class @extension { }
class MyClass
{
    @extension field;
    @extension Property { get; }
}

Noncompliant code example

class extension { }
class MyClass
{
    extension ReturnType() { return default; }
}

Compliant solution

Rename the identifier:

class MyExtension { }
class MyClass
{
    MyExtension ReturnType() { return default; }
}

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S8368.json ================================================ { "title": "Identifiers should not conflict with the C# 14 \"extension\" contextual keyword", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1 min" }, "tags": [ "csharp14", "breaking-change", "contextual-keyword" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-8368", "sqKey": "S8368", "scope": "Main", "quickfix": "unknown", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S8380.html ================================================

Using partial as a return type identifier will cause a compilation error when upgrading to C# 14.

Why is this an issue?

In C# 14, partial in a return type position is always treated as a modifier, even when a type named partial is in scope. This causes compilation errors in code that previously compiled successfully.

The issue specifically affects method declarations where a custom type named partial is used as the return type. Other uses of the partial type (such as in variable declarations or constructor calls) are not affected by this change.

What is the potential impact?

This breaking change will cause compilation failures when upgrading existing codebases to C# 14. The compilation error prevents the application from building, requiring immediate attention to fix the syntax.

While this is not a runtime issue, it can block development and deployment processes until resolved. The fix is straightforward but must be applied to all affected method declarations.

How to fix it

Add the @ prefix before partial when used as a return type. This tells the compiler to treat partial as an identifier rather than a modifier. Alternatively, rename the type to avoid the conflict.

Code examples

Noncompliant code example

class C
{
    partial F() => new partial(); // Noncompliant
}

class partial { }

Compliant solution

class C
{
    @partial F() => new partial();
}

class partial { }

Noncompliant code example

class C
{
    partial F() => new partial(); // Noncompliant
}

class partial { }

Compliant solution

class C
{
    PartialItem F() => new PartialItem();
}

class PartialItem { }

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S8380.json ================================================ { "title": "Return types named \"partial\" should be escaped with \"@\"", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [ "breaking-change", "csharp14" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-8380", "sqKey": "S8380", "scope": "All", "quickfix": "unknown", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S8381.html ================================================

Using scoped as a type name in lambda parameter lists will cause a compilation error when upgrading to C# 14 or later.

Why is this an issue?

In C# 14, the language introduced the ability to write lambdas with parameter modifiers without specifying parameter types. As part of this enhancement, a breaking change was implemented where scoped is always treated as a modifier in lambda parameter lists.

As a result, parenthesized lambda parameters that use scoped as an identifier or type name will fail to compile after upgrading to C# 14. In that context, the compiler interprets scoped as a modifier unless it is escaped with @.

This breaking change affects existing code that was valid in earlier C# versions, particularly when upgrading projects to C# 14. The issue is specific to lambda expressions and doesn’t affect other contexts where the scoped type name might be used.

What is the potential impact?

This issue prevents code compilation when upgrading to C# 14. Projects that previously compiled successfully will fail to build, blocking development and deployment processes until the code is updated.

How to fix it

Rename the type, or escape the type name scoped with the @ prefix. This tells the compiler to treat it as an identifier rather than a contextual keyword.

Code examples

Noncompliant code example

Action<int> lambda1 = (scoped) => { }; // Noncompliant
var lambda2 = (scoped scoped) => { }; // Noncompliant
var lambda3 = (scoped scoped parameter) => { }; // Noncompliant

ref struct @scoped { }

Compliant solution

Action<int> lambda1 = (@scoped) => { };
var lambda2 = (@scoped scoped) => { };
var lambda3 = (scoped @scoped parameter) => { };

ref struct @scoped { }

Noncompliant code example

var lambda = (scoped scoped parameter) => { }; // Noncompliant

ref struct @scoped { }

Compliant solution

var lambda = (scoped ScopedItem parameter) => { };

ref struct ScopedItem { }

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S8381.json ================================================ { "title": "\"scoped\" should be escaped when used as an identifier or type name in parenthesized lambda parameter lists", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [ "csharp14", "breaking-change", "lambda" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-8381", "sqKey": "S8381", "scope": "Main", "quickfix": "unknown", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/cs/S881.html ================================================

Why is this an issue?

The use of increment and decrement operators in method calls or in combination with other arithmetic operators is not recommended, because:

Noncompliant code example

u8a = ++u8b + u8c--;
foo = bar++ / 4;

Compliant solution

The following sequence is clearer and therefore safer:

++u8b;
u8a = u8b + u8c;
u8c--;
foo = bar / 4;
bar++;
================================================ FILE: analyzers/rspec/cs/S881.json ================================================ { "title": "Increment (++) and decrement (--) operators should not be used in a method call or mixed with other operators in an expression", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-881", "sqKey": "S881", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S907.html ================================================

Why is this an issue?

goto is an unstructured control flow statement. It makes code less readable and maintainable. Structured control flow statements such as if, for, while, continue or break should be used instead.

================================================ FILE: analyzers/rspec/cs/S907.json ================================================ { "title": "\"goto\" statement should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-907", "sqKey": "S907", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/cs/S927.html ================================================

Why is this an issue?

Parameters are part of the method signature and its identity.

Implementing a method from an interface, a base class, or a partial method and changing one of its parameters' names will confuse and impact the method’s readability.

interface IBankAccount
{
  void AddMoney(int money);
}

class BankAccount : IBankAccount
{
  void AddMoney(int amount) // Noncompliant: parameter's name differs from base
  {
    // ...
  }
}

To avoid any ambiguity in the code, a parameter’s name should match the initial declaration, whether its initial declaration is from an interface, a base class, or a partial method.

interface IBankAccount
{
  void AddMoney(int money);
}

class BankAccount : IBankAccount
{
  void AddMoney(int money) // Compliant: parameter's name match base name
  {
    // ...
  }
}

Exceptions

The rule is ignored if both the parameter defined in the initial decalaration is a generic type and the implementing member’s declaration is a non-generic type.

This allows the implementing member to be more specific and provide more information.

Resources

Documentation

================================================ FILE: analyzers/rspec/cs/S927.json ================================================ { "title": "Parameter names should match base declaration and other partial definitions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-927", "sqKey": "S927", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/cs/Sonar_way_profile.json ================================================ { "name": "Sonar way", "ruleKeys": [ "S101", "S107", "S108", "S110", "S112", "S125", "S127", "S818", "S907", "S927", "S1006", "S1048", "S1066", "S1075", "S1104", "S1110", "S1116", "S1117", "S1118", "S1121", "S1123", "S1125", "S1133", "S1134", "S1135", "S1144", "S1155", "S1163", "S1168", "S1172", "S1185", "S1186", "S1192", "S1199", "S1206", "S1210", "S1215", "S1244", "S1264", "S1313", "S1450", "S1479", "S1481", "S1607", "S1643", "S1656", "S1694", "S1696", "S1699", "S1751", "S1764", "S1848", "S1854", "S1862", "S1871", "S1905", "S1939", "S1940", "S1944", "S1994", "S2053", "S2068", "S2077", "S2092", "S2094", "S2114", "S2115", "S2123", "S2139", "S2166", "S2178", "S2183", "S2184", "S2187", "S2190", "S2198", "S2201", "S2219", "S2222", "S2223", "S2225", "S2234", "S2245", "S2251", "S2252", "S2257", "S2259", "S2275", "S2290", "S2291", "S2292", "S2306", "S2325", "S2326", "S2328", "S2342", "S2344", "S2345", "S2346", "S2365", "S2368", "S2372", "S2376", "S2386", "S2436", "S2437", "S2445", "S2479", "S2486", "S2551", "S2583", "S2589", "S2612", "S2629", "S2674", "S2681", "S2688", "S2692", "S2696", "S2699", "S2701", "S2737", "S2743", "S2755", "S2757", "S2761", "S2857", "S2925", "S2930", "S2933", "S2934", "S2953", "S2955", "S2970", "S2971", "S2995", "S2996", "S2997", "S3005", "S3010", "S3011", "S3060", "S3063", "S3168", "S3169", "S3172", "S3217", "S3218", "S3220", "S3236", "S3237", "S3241", "S3244", "S3246", "S3247", "S3249", "S3251", "S3260", "S3261", "S3262", "S3263", "S3264", "S3265", "S3267", "S3329", "S3330", "S3343", "S3346", "S3358", "S3363", "S3376", "S3397", "S3398", "S3400", "S3415", "S3427", "S3431", "S3433", "S3440", "S3442", "S3443", "S3444", "S3445", "S3447", "S3449", "S3450", "S3451", "S3453", "S3456", "S3457", "S3458", "S3459", "S3464", "S3466", "S3597", "S3598", "S3600", "S3603", "S3604", "S3610", "S3626", "S3655", "S3776", "S3869", "S3871", "S3875", "S3877", "S3878", "S3881", "S3885", "S3887", "S3889", "S3897", "S3903", "S3904", "S3923", "S3925", "S3926", "S3927", "S3928", "S3949", "S3963", "S3966", "S3971", "S3972", "S3973", "S3981", "S3984", "S3993", "S3998", "S4015", "S4019", "S4035", "S4036", "S4050", "S4052", "S4061", "S4070", "S4136", "S4143", "S4144", "S4158", "S4159", "S4200", "S4201", "S4210", "S4211", "S4220", "S4260", "S4275", "S4277", "S4347", "S4423", "S4426", "S4428", "S4433", "S4456", "S4487", "S4502", "S4507", "S4524", "S4545", "S4581", "S4583", "S4586", "S4635", "S4663", "S4790", "S4830", "S5034", "S5042", "S5122", "S5332", "S5344", "S5443", "S5445", "S5542", "S5547", "S5659", "S5693", "S5753", "S5766", "S5773", "S5856", "S6377", "S6418", "S6419", "S6420", "S6422", "S6424", "S6444", "S6561", "S6562", "S6575", "S6580", "S6588", "S6607", "S6608", "S6609", "S6610", "S6612", "S6613", "S6617", "S6618", "S6640", "S6664", "S6667", "S6668", "S6669", "S6670", "S6672", "S6673", "S6674", "S6675", "S6677", "S6678", "S6781", "S6797", "S6798", "S6800", "S6930", "S6931", "S6932", "S6934", "S6960", "S6961", "S6962", "S6964", "S6965", "S6966", "S6967", "S6968", "S7039", "S7130", "S7131", "S7133", "S8367", "S8368", "S8380", "S8381" ] } ================================================ FILE: analyzers/rspec/vbnet/S101.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently.

This rule raises an issue when a class name does not match a provided regular expression.

The default configuration is the one recommended by Microsoft:

For example, with the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$, the class:

Class foo ' Noncompliant
End Class

should be renamed to

Class Foo
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S101.json ================================================ { "title": "Class names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-101", "sqKey": "S101", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S103.html ================================================

Why is this an issue?

Scrolling horizontally to see a full line of code lowers the code readability.

================================================ FILE: analyzers/rspec/vbnet/S103.json ================================================ { "title": "Lines should not be too long", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-103", "sqKey": "S103", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S104.html ================================================

Why is this an issue?

When a source file grows too much, it can accumulate numerous responsibilities and become challenging to understand and maintain.

Above a specific threshold, refactor the file into smaller files whose code focuses on well-defined tasks. Those smaller files will be easier to understand and test.

================================================ FILE: analyzers/rspec/vbnet/S104.json ================================================ { "title": "Files should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-104", "sqKey": "S104", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1048.html ================================================

Why is this an issue?

The Finalize methods are used to perform any necessary final clean-up when the garbage collector is collecting a class instance. The programmer has no control over when the Finalize method is called; the garbage collector decides when to call it.

When creating a Finalize method, it should never throw an exception, as there is a high risk of having the application terminated leaving unmanaged resources without a graceful cleanup.

The rule raises an issue on throw statements used in a Finalize method.

How to fix it

Code examples

Noncompliant code example

Class Sample
    Protected Overrides Sub Finalize()
        Throw New NotImplementedException() ' Noncompliant: Finalize method throws an exception
    End Sub
End Class

Compliant solution

Class Sample
    Protected Overrides Sub Finalize()
        ' Noncompliant: Finalize method does not throw an exception
    End Sub
End Class

Going the extra mile

In general object finalization can be a complex and error-prone operation and should not be implemented except within the dispose pattern.

When cleaning up unmanaged resources, it is recommended to implement the dispose pattern or, to cover uncalled Dispose method by the consumer, implement SafeHandle.

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1048.json ================================================ { "title": "Finalize method should not throw exceptions", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1048", "sqKey": "S1048", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S105.html ================================================

Why is this an issue?

The tab width can differ from one development environment to another. Using tabs may require other developers to configure their environment (text editor, preferences, etc.) to read source code.

That is why using spaces is preferable.

================================================ FILE: analyzers/rspec/vbnet/S105.json ================================================ { "title": "Tabulation characters should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-105", "sqKey": "S105", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1066.html ================================================

Why is this an issue?

Nested code - blocks of code inside blocks of code - is eventually necessary, but increases complexity. This is why keeping the code as flat as possible, by avoiding unnecessary nesting, is considered a good practice.

Merging if statements when possible will decrease the nesting of the code and improve its readability.

Code like

If condition1 Then
    If condition2 Then ' Noncompliant
        ' ...
    End If
End If

Will be more readable as

If condition1 AndAlso condition2 Then
    ' ...
End If
================================================ FILE: analyzers/rspec/vbnet/S1066.json ================================================ { "title": "Mergeable \"if\" statements should be combined", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1066", "sqKey": "S1066", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1067.html ================================================

Why is this an issue?

Complex boolean expressions are hard to read and so to maintain.

Noncompliant code example

With the default threshold value of 3

If ((condition1 AndAlso condition2) OrElse (condition3 AndAlso condition4)) AndAlso condition5) Then  'Noncompliant
  ...
End If

Compliant solution

If ((MyFirstCondition() OrElse MySecondCondition()) AndAlso MyLastCondition()) Then
  ...
End If
================================================ FILE: analyzers/rspec/vbnet/S1067.json ================================================ { "title": "Expressions should not be too complex", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "3min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1067", "sqKey": "S1067", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S107.html ================================================

Why is this an issue?

Procedures with a long parameter list are difficult to use because maintainers must figure out the role of each parameter and keep track of their position.

Sub SetCoordinates(x1 As Integer, y1 As Integer, z1 As Integer, x2 As Integer, y2 As Integer, z2 As Integer) ' Noncompliant
    ' ...
End Sub

The solution can be to:

' Each function does a part of what the original setCoordinates function was doing, so confusion risks are lower
Sub SetOrigin(x As Integer, y As Integer, z As Integer)
   ' ...
End Sub

Sub SetSize(width As Integer, height As Integer, depth As Integer)
   ' ...
End Sub
Class Point ' In geometry, Point is a logical structure to group data

    Public x As Integer
    Public y As Integer
    Public z As Integer

End Class

Sub SetCoordinates(p1 As Point, p2 As Point)
    ' ...
End Sub

This rule raises an issue when a procedure has more parameters than the provided threshold.

Exceptions

The rule does not count the parameters intended for a base class constructor.

With a maximum number of 4 parameters:

Class BaseClass

    Sub New(Param1 As Integer)
        ' ...
    End Sub

End Class

Class DerivedClass
    Inherits BaseClass

    Public Sub New(Param1 As Integer, Param2 As Integer, Param3 As Integer, Param4 As Integer, Param5 As Long)
    ' Compliant by exception: Param1 is used in the base class constructor, so it does not count in the sum of the parameter list.
        MyBase.New(Param1)
        ' ...
    End Sub

End Class
================================================ FILE: analyzers/rspec/vbnet/S107.json ================================================ { "title": "Procedures should not have too many parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-107", "sqKey": "S107", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1075.html ================================================

Why is this an issue?

Hard-coding a URI makes it difficult to test a program for a variety of reasons:

In addition, hard-coded URIs can contain sensitive information, like IP addresses, and they should not be stored in the code.

For all those reasons, a URI should never be hard coded. Instead, it should be replaced by a customizable parameter.

Further, even if the elements of a URI are obtained dynamically, portability can still be limited if the path delimiters are hard-coded.

This rule raises an issue when URIs or path delimiters are hard-coded.

================================================ FILE: analyzers/rspec/vbnet/S1075.json ================================================ { "title": "URIs should not be hardcoded", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1075", "sqKey": "S1075", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S108.html ================================================

Why is this an issue?

An empty code block is confusing. It will require some effort from maintainers to determine if it is intentional or indicates the implementation is incomplete.

For Index As Integer = 1 To 42 ' Noncompliant: is the block empty on purpose, or is code missing?
Next

Removing or filling the empty code blocks takes away ambiguity and generally results in a more straightforward and less surprising code.

Exceptions

The rule ignores code blocks that contain comments.

================================================ FILE: analyzers/rspec/vbnet/S108.json ================================================ { "title": "Nested blocks of code should not be left empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-108", "sqKey": "S108", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1110.html ================================================

Why is this an issue?

The use of parentheses, even those not required to enforce a desired order of operations, can clarify the intent behind a piece of code. However, redundant pairs of parentheses could be misleading and should be removed.

Noncompliant code example

If a AndAlso ((x + y > 0)) Then ' Noncompliant
    ' ...
End If

Return ((x + 1))  ' Noncompliant

Compliant solution

If a AndAlso x + y > 0 Then
    ' ...
End If

Return x + 1
================================================ FILE: analyzers/rspec/vbnet/S1110.json ================================================ { "title": "Redundant pairs of parentheses should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1110", "sqKey": "S1110", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S112.html ================================================

This rule raises an issue when a generic exception (such as Exception, SystemException, ApplicationException, IndexOutOfRangeException, NullReferenceException, OutOfMemoryException or ExecutionEngineException) is thrown.

Why is this an issue?

Throwing general exceptions such as Exception, SystemException and ApplicationException will have a negative impact on any code trying to catch these exceptions.

From a consumer perspective, it is generally a best practice to only catch exceptions you intend to handle. Other exceptions should ideally not be caught and let to propagate up the stack trace so that they can be dealt with appropriately. When a generic exception is thrown, it forces consumers to catch exceptions they do not intend to handle, which they then have to re-throw.

Besides, when working with a generic type of exception, the only way to distinguish between multiple exceptions is to check their message, which is error-prone and difficult to maintain. Legitimate exceptions may be unintentionally silenced and errors may be hidden.

For instance, if an exception such as StackOverflowException is caught and not re-thrown, it may prevent the program from terminating gracefully.

When throwing an exception, it is therefore recommended to throw the most specific exception possible so that it can be handled intentionally by consumers.

Additionally, some reserved exceptions should not be thrown manually. Exceptions such as IndexOutOfRangeException, NullReferenceException, OutOfMemoryException or ExecutionEngineException will be thrown automatically by the runtime when the corresponding error occurs. Many of them indicate serious errors, which the application may not be able to recover from. It is therefore recommended to avoid throwing them as well as using them as base classes.

How to fix it

To fix this issue, make sure to throw specific exceptions that are relevant to the context in which they arise. It is recommended to either:

Code examples

Noncompliant code example

Public Sub DoSomething(obj As Object)
  If obj Is Nothing Then
    ' Noncompliant
    Throw New NullReferenceException("obj") ' Noncompliant: This reserved exception should not be thrown manually
  End If
  ' ...
End Sub

Compliant solution

Public Sub DoSomething(obj As Object)
  If obj Is Nothing Then
    Throw New ArgumentNullException("obj") ' Compliant: this is a specific and non-reserved exception type
  End If
  ' ...
End Sub

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S112.json ================================================ { "title": "General or reserved exceptions should never be thrown", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-112", "sqKey": "S112", "scope": "Main", "securityStandards": { "CWE": [ 397 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1123.html ================================================

Why is this an issue?

The Obsolete attribute can be applied with or without a message argument. Marking something Obsolete without including advice on why it’s obsolete or what to use instead will lead maintainers to waste time trying to figure those things out.

Noncompliant code example

Public Class Car

    <Obsolete>  ' Noncompliant
    Public Sub CrankEngine(Turns As Integer)
        ' ...
    End Sub

End Class

Compliant solution

Public Class Car

    <Obsolete("Replaced by the automatic starter")>
    Public Sub CrankEngine(Turns As Integer)
        ' ...
    End Sub

End Class
================================================ FILE: analyzers/rspec/vbnet/S1123.json ================================================ { "title": "\"Obsolete\" attributes should include explanations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "obsolete", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1123", "sqKey": "S1123", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1125.html ================================================

Why is this an issue?

A boolean literal can be represented in two different ways: True or False. They can be combined with logical operators (Not, And, Or, =) to produce logical expressions that represent truth values. However, comparing a boolean literal to a variable or expression that evaluates to a boolean value is unnecessary and can make the code harder to read and understand. The more complex a boolean expression is, the harder it will be for developers to understand its meaning and expected behavior, and it will favour the introduction of new bugs.

How to fix it

Remove redundant boolean literals from expressions to improve readability and make the code more maintainable.

Code examples

Noncompliant code example

If BooleanMethod() = True Then ' Noncompliant
  ' ...
End If
If BooleanMethod() = False Then ' Noncompliant
  ' ...
End If
If BooleanMethod() OrElse False Then ' Noncompliant
  ' ...
End If
DoSomething(Not False) ' Noncompliant
DoSomething(BooleanMethod() = True) ' Noncompliant

Dim booleanVariable = If(BooleanMethod(), True, False) ' Noncompliant
booleanVariable = If(BooleanMethod(), True, exp) ' Noncompliant
booleanVariable = If(BooleanMethod(), False, exp) ' Noncompliant
booleanVariable = If(BooleanMethod(), exp, True) ' Noncompliant
booleanVariable = If(BooleanMethod(), exp, False) ' Noncompliant

Compliant solution

If BooleanMethod() Then
  ' ...
End If
If Not BooleanMethod() Then
  ' ...
End If
If BooleanMethod() Then
  ' ...
End If
DoSomething(True)
DoSomething(BooleanMethod())

Dim booleanVariable = BooleanMethod()
booleanVariable = BooleanMethod() OrElse exp
booleanVariable = Not BooleanMethod() AndAlso exp
booleanVariable = Not BooleanMethod() OrElse exp
booleanVariable = BooleanMethod() AndAlso exp
================================================ FILE: analyzers/rspec/vbnet/S1125.json ================================================ { "title": "Boolean literals should not be redundant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1125", "sqKey": "S1125", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1133.html ================================================

Why is this an issue?

This rule is meant to be used as a way to track code which is marked as being deprecated. Deprecated code should eventually be removed.

Noncompliant code example

<Obsolete> ' Noncompliant
Sub Procedure()
End Sub
================================================ FILE: analyzers/rspec/vbnet/S1133.json ================================================ { "title": "Deprecated code should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "INFO" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "obsolete" ], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1133", "sqKey": "S1133", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1134.html ================================================

Why is this an issue?

FIXME tags are commonly used to mark places where a bug is suspected, but which the developer wants to deal with later.

Sometimes the developer will not have the time or will simply forget to get back to that tag.

This rule is meant to track those tags and to ensure that they do not go unnoticed.

Function Divide(numerator As Integer, denominator As Integer) As Integer
    Return numerator / denominator  ' FIXME denominator value might be  0
End Function

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1134.json ================================================ { "title": "Track uses of \"FIXME\" tags", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1134", "sqKey": "S1134", "scope": "All", "securityStandards": { "CWE": [ 546 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1135.html ================================================

Why is this an issue?

Developers often use TODO tags to mark areas in the code where additional work or improvements are needed but are not implemented immediately. However, these TODO tags sometimes get overlooked or forgotten, leading to incomplete or unfinished code. This rule aims to identify and address unattended TODO tags to ensure a clean and maintainable codebase. This description explores why this is a problem and how it can be fixed to improve the overall code quality.

What is the potential impact?

Unattended TODO tags in code can have significant implications for the development process and the overall codebase.

Incomplete Functionality: When developers leave TODO tags without implementing the corresponding code, it results in incomplete functionality within the software. This can lead to unexpected behavior or missing features, adversely affecting the end-user experience.

Missed Bug Fixes: If developers do not promptly address TODO tags, they might overlook critical bug fixes and security updates. Delayed bug fixes can result in more severe issues and increase the effort required to resolve them later.

Impact on Collaboration: In team-based development environments, unattended TODO tags can hinder collaboration. Other team members might not be aware of the intended changes, leading to conflicts or redundant efforts in the codebase.

Codebase Bloat: The accumulation of unattended TODO tags over time can clutter the codebase and make it difficult to distinguish between work in progress and completed code. This bloat can make it challenging to maintain an organized and efficient codebase.

Addressing this code smell is essential to ensure a maintainable, readable, reliable codebase and promote effective collaboration among developers.

Noncompliant code example

Sub DoSomething()
    ' TODO
End Sub

Resources

================================================ FILE: analyzers/rspec/vbnet/S1135.json ================================================ { "title": "Track uses of \"TODO\" tags", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "INFO" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" }, "tags": [ "cwe" ], "defaultSeverity": "Info", "ruleSpecification": "RSPEC-1135", "sqKey": "S1135", "scope": "All", "securityStandards": { "CWE": [ 546 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S114.html ================================================

Why is this an issue?

Sharing some naming conventions is a key point to make it possible for a team to efficiently collaborate.

This rule allows to check that all interface names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^I([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Interface Foo  ' Noncompliant
End Interface

Compliant solution

Interface IFoo ' Compliant
End Interface
================================================ FILE: analyzers/rspec/vbnet/S114.json ================================================ { "title": "Interface names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-114", "sqKey": "S114", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1147.html ================================================

Why is this an issue?

End statements exit the control flow of the program in an unstructured way. This statement stops code execution immediately without executing Dispose or Finalize methods, or executing Finally blocks. Therefore, it should be avoided.

Noncompliant code example

Module Module1
    Sub Print(ByVal str As String)
       Try
            ...
            End       ' Noncompliant
        Finally
            ' do something important here
            ...
        End Try
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1147.json ================================================ { "title": "\"End\" statements should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1147", "sqKey": "S1147", "scope": "Main", "securityStandards": { "CWE": [ 382 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1151.html ================================================

Why is this an issue?

The Select...Case statement should be used only to clearly define some new branches in the control flow. As soon as a case clause contains too many statements this highly decreases the readability of the overall control flow statement. In such case, the content of the case clause should be extracted into a dedicated procedure.

Noncompliant code example

With the default threshold of 3:

Select Case number
    Case 1 To 5 ' Noncompliant: 4 statements in the case
        MethodCall1("")
        MethodCall2("")
        MethodCall3("")
        MethodCall4("")
    Case Else
        ' ...
End Select

Compliant solution

Select Case number
    Case 1 To 5
        DoSomething()
    Case Else
        ' ...
End Select
...
Sub DoSomething()
    MethodCall1("")
    MethodCall2("")
    MethodCall3("")
    MethodCall4("")
End Sub
================================================ FILE: analyzers/rspec/vbnet/S1151.json ================================================ { "title": "\"Select...Case\" clauses should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1151", "sqKey": "S1151", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1155.html ================================================

Why is this an issue?

Using .Count() to test for emptiness works, but using .Any() makes the intent clearer, and the code more readable. However, there are some cases where special attention should be paid:

Noncompliant code example

Private Function HasContent(Strings As IEnumerable(Of String)) As Boolean
    Return Strings.Count() > 0      ' Noncompliant
End Function

Private Function HasContent2(Strings As IEnumerable(Of String)) As Boolean
    Return Strings.Count() >= 1     ' Noncompliant
End Function

Private Function IsEmpty(Strings As IEnumerable(Of String)) As Boolean
    Return Strings.Count() = 0      ' Noncompliant
End Function

Compliant solution

Private Function HasContent(Strings As IEnumerable(Of String)) As Boolean
    Return Strings.Any
End Function

Private Function HasContent2(Strings As IEnumerable(Of String)) As Boolean
    Return Strings.Any
End Function

Private Function IsEmpty(Strings As IEnumerable(Of String)) As Boolean
    Return Not Strings.Any
End Function
================================================ FILE: analyzers/rspec/vbnet/S1155.json ================================================ { "title": "\"Any()\" should be used to test for emptiness", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1155", "sqKey": "S1155", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1163.html ================================================

Why is this an issue?

If an exception is already being thrown within the try block or caught in a catch block, throwing another exception in the finally block will override the original exception. This means that the original exception’s message and stack trace will be lost, potentially making it challenging to diagnose and troubleshoot the root cause of the problem.

Try
    ' Some work which end up throwing an exception
    Throw New ArgumentException()
Finally
    ' Cleanup
    Throw New InvalidOperationException() ' Noncompliant: will mask the ArgumentException
End Try
Try
    ' Some work which end up throwing an exception
    Throw New ArgumentException()
Finally
    ' Cleanup without throwing
End Try

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1163.json ================================================ { "title": "Exceptions should not be thrown in finally blocks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "error-handling", "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1163", "sqKey": "S1163", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S117.html ================================================

Local variables should be named consistently to communicate intent and improve maintainability. Rename your local variable to follow your project’s naming convention to address this issue.

Why is this an issue?

A naming convention in software development is a set of guidelines for naming code elements like variables, functions, and classes.
Local variables hold the meaning of the written code. Their names should be meaningful and follow a consistent and easily recognizable pattern.
Adhering to a consistent naming convention helps to make the code more readable and understandable, which makes it easier to maintain and debug. It also ensures consistency in the code, especially when multiple developers are working on the same project.

This rule checks that local variable names match a provided regular expression.

What is the potential impact?

Inconsistent naming of local variables can lead to several issues in your code:

In summary, not adhering to a naming convention for local variables can lead to confusion, errors, and inefficiencies, making the code harder to read, understand, and maintain.

How to fix it

First, familiarize yourself with the particular naming convention of the project in question. Then, update the name to match the convention, as well as all usages of the name. For many IDEs, you can use built-in renaming and refactoring features to update all usages at once.

Code examples

Noncompliant code example

With the default regular expression ^[a-z][a-z0-9]*([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$, bringing the following constraints:

Module Module1
    Sub Main()
        Dim Foo = 0 ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim foo = 0 ' Compliant
    End Sub
End Module

Resources

Documentation

Related rules

================================================ FILE: analyzers/rspec/vbnet/S117.json ================================================ { "title": "Local variable names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-117", "sqKey": "S117", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1172.html ================================================

Why is this an issue?

A typical code smell known as unused function parameters refers to parameters declared in a function but not used anywhere within the function’s body. While this might seem harmless at first glance, it can lead to confusion and potential errors in your code. Disregarding the values passed to such parameters, the function’s behavior will be the same, but the programmer’s intention won’t be clearly expressed anymore. Therefore, removing function parameters that are not being utilized is considered best practice.

This rule raises an issue when a private procedure of a Class, Module or Structure takes a parameter without using it.

Exceptions

This rule doesn’t raise any issue in the following contexts:

How to fix it

Having unused function parameters in your code can lead to confusion and misunderstanding of a developer’s intention. They reduce code readability and introduce the potential for errors. To avoid these problems, developers should remove unused parameters from function declarations.

Code examples

Noncompliant code example

Private Sub DoSomething(ByVal a As Integer, ByVal b as Integer) ' "b" is unused
    Compute(a)
End Sub

Private Function DoSomething2(ByVal a As Integer, ByVal b As Integer) As Integer ' "a" is unused
    Compute(b)
    Return b
End Function

Compliant solution

Private Sub DoSomething(ByVal a As Integer)
    Compute(a)
End Sub

Private Function DoSomething2(ByVal b As Integer) As Integer
    Compute(b)
    Return b
End Function
================================================ FILE: analyzers/rspec/vbnet/S1172.json ================================================ { "title": "Unused procedure parameters should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1172", "sqKey": "S1172", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1186.html ================================================

Why is this an issue?

An empty method is generally considered bad practice and can lead to confusion, readability, and maintenance issues. Empty methods bring no functionality and are misleading to others as they might think the method implementation fulfills a specific and identified requirement.

There are several reasons for a method not to have a body:

Exceptions

The following empty methods are considered compliant:

How to fix it

Code examples

Noncompliant code example

Sub ShouldNotBeEmpty()  ' Noncompliant - method is empty
End Sub

Sub NotImplementedYet()  ' Noncompliant - method is empty
End Sub

Sub WillNeverBeImplemented()  ' Noncompliant - method is empty
End Sub

Sub EmptyOnPurpose()  ' Noncompliant - method is empty
End Sub

Compliant solution

Sub ShouldNotBeEmpty()
    DoSomething()
End Sub

Sub NotImplementedYet()
    Throw New NotImplementedException
End Sub

Sub WillNeverBeImplemented()
    Throw New NotSupportedException
End Sub

Sub EmptyOnPurpose()
    ' Do nothing because of X
End Sub
================================================ FILE: analyzers/rspec/vbnet/S1186.json ================================================ { "title": "Methods should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1186", "sqKey": "S1186", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S119.html ================================================

Why is this an issue?

Inconsistent naming conventions can lead to confusion and errors when working in a team. This rule ensures that all generic type parameter names follow a consistent naming convention by checking them against a provided regular expression.

The default configuration follows Microsoft’s recommended convention:

How to fix it

To fix this issue, ensure that all generic type parameter names in your code follow the naming convention specified in the regular expression.

Code examples

Noncompliant code example

With the default parameter value ^T(([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?)?$:

Public Class Foo(Of tkey) ' Noncompliant
End Class

Compliant solution

Public Class Foo(Of TKey) ' Compliant
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S119.json ================================================ { "title": "Generic type parameter names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-119", "sqKey": "S119", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1192.html ================================================

Why is this an issue?

Duplicated string literals make the process of refactoring complex and error-prone, as any change would need to be propagated on all occurrences.

Exceptions

The following are ignored:

How to fix it

Use constants to replace the duplicated string literals. Constants can be referenced from many places, but only need to be updated in a single place.

Code examples

Noncompliant code example

Public Class Foo

    Private Name As String = "foobar" ' Noncompliant

    Public ReadOnly Property DefaultName As String = "foobar" ' Noncompliant

    Public Sub New(Optional Value As String = "foobar") ' Noncompliant

        Dim Something = If(Value, "foobar") ' Noncompliant

    End Sub

End Class

Compliant solution

Public Class Foo

    Private Const Foobar As String = "foobar"

    Private Name As String = Foobar

    Public ReadOnly Property DefaultName As String = Foobar

    Public Sub New(Optional Value As String = Foobar)

        Dim Something = If(Value, Foobar)

    End Sub

End Class
================================================ FILE: analyzers/rspec/vbnet/S1192.json ================================================ { "title": "String literals should not be duplicated", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Linear with offset", "linearDesc": "per duplicate instance", "linearOffset": "2min", "linearFactor": "2min" }, "tags": [ "design" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1192", "sqKey": "S1192", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1197.html ================================================

Why is this an issue?

Array designators should always be located on the type for better code readability. Otherwise, developers must look both at the type and the variable name to know whether or not a variable is an array.

Noncompliant code example

Module Module1
    Sub Main()
        Dim foo() As String ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim foo As String() ' Compliant
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1197.json ================================================ { "title": "Array designators \"()\" should be on the type, not the variable", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1197", "sqKey": "S1197", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S122.html ================================================

Why is this an issue?

Putting multiple statements on a single line lowers the code readability and makes debugging the code more complex.

Dim a = 0 : Dim b = 0  ' Noncompliant

Write one statement per line to improve readability.

Dim a = 0
Dim b = 0
================================================ FILE: analyzers/rspec/vbnet/S122.json ================================================ { "title": "Statements should be on separate lines", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-122", "sqKey": "S122", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1226.html ================================================

Why is this an issue?

While it is technically correct to assign to parameters from within method bodies, doing so before the parameter value is read is likely a bug. Instead, initial values of parameters should be, if not treated as readonly then at least read before reassignment.

Noncompliant code example

Module Module1
    Sub Foo(ByVal a As Integer)
        a = 42                  ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Foo(ByVal a As Integer)
        Dim tmp = a
        tmp = 42
    End Sub
End Module

Exceptions

ByRef parameters are ignored.

Module Module1
    Sub Foo(ByRef a As Integer)
        a = 42                  ' Ignored; it is a ByRef parameter
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1226.json ================================================ { "title": "Method parameters and caught exceptions should not be reassigned", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1226", "sqKey": "S1226", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S126.html ================================================

Why is this an issue?

This rule applies whenever an If statement is followed by one or more ElseIf statements; the final ElseIf should be followed by an Else statement.

The requirement for a final Else statement is defensive programming.

The Else statement should either take appropriate action or contain a suitable comment as to why no action is taken. This is consistent with the requirement to have a final Case Else clause in a Select Case statement.

Noncompliant code example

If x = 0 Then
    DoSomething()
ElseIf x = 1 Then
    DoSomethingElse()
End If

Compliant solution

If x = 0 Then
    DoSomething()
ElseIf x = 1 Then
    DoSomethingElse()
Else
    Throw New ArgumentException("...")
End If

Exceptions

None

================================================ FILE: analyzers/rspec/vbnet/S126.json ================================================ { "title": "\"If ... ElseIf\" constructs should end with \"Else\" clauses", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-126", "sqKey": "S126", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1301.html ================================================

Why is this an issue?

switch statements are useful when there are many different cases depending on the value of the same expression.

For just one or two cases, however, the code will be more readable with if statements.

Noncompliant code example

Select Case variable
    Case 0
        doSomething()
    Case Else
        doSomethingElse()
End Select

Compliant solution

If variable = 0 Then
    doSomething()
Else
    doSomethingElse()
End If
================================================ FILE: analyzers/rspec/vbnet/S1301.json ================================================ { "title": "\"Select\" statements should have at least 3 \"Case\" clauses", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1301", "sqKey": "S1301", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S131.html ================================================

Why is this an issue?

The requirement for a final Case Else clause is defensive programming.

This clause should either take appropriate action or contain a suitable comment as to why no action is taken.

Noncompliant code example

Select Case param ' Noncompliant - Case Else clause is missing
  Case 0
    DoSomething()
  Case 1
    DoSomethingElse()
End Select

Compliant solution

Select Case param
  Case 0
    DoSomething()
  Case 1
    DoSomethingElse()
  Case Else ' Compliant
    DoSomethingElse()
End Select

Resources

================================================ FILE: analyzers/rspec/vbnet/S131.json ================================================ { "title": "\"Select\" statements should end with a \"Case Else\" clause", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-131", "sqKey": "S131", "scope": "All", "securityStandards": { "CWE": [ 478 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1313.html ================================================

Hardcoding IP addresses is security-sensitive. It has led in the past to the following vulnerabilities:

Today’s services have an ever-changing architecture due to their scaling and redundancy needs. It is a mistake to think that a service will always have the same IP address. When it does change, the hardcoded IP will have to be modified too. This will have an impact on the product development, delivery, and deployment:

Last but not least it has an effect on application security. Attackers might be able to decompile the code and thereby discover a potentially sensitive address. They can perform a Denial of Service attack on the service, try to get access to the system, or try to spoof the IP address to bypass security checks. Such attacks can always be possible, but in the case of a hardcoded IP address solving the issue will take more time, which will increase an attack’s impact.

Ask Yourself Whether

The disclosed IP address is sensitive, e.g.:

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

Recommended Secure Coding Practices

Don’t hard-code the IP address in the source code, instead make it configurable with environment variables, configuration files, or a similar approach. Alternatively, if confidentially is not required a domain name can be used since it allows to change the destination quickly without having to rebuild the software.

Sensitive Code Example

Dim ip = "192.168.12.42" ' Sensitive
Dim address = IPAddress.Parse(ip)

Compliant Solution

Dim ip = ConfigurationManager.AppSettings("myapplication.ip") ' Compliant
Dim address = IPAddress.Parse(ip)

Exceptions

No issue is reported for the following cases because they are not considered sensitive:

See

================================================ FILE: analyzers/rspec/vbnet/S1313.json ================================================ { "title": "Using hardcoded IP addresses is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1313", "sqKey": "S1313", "scope": "Main", "securityStandards": { "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A1" ], "CWE": [ 547 ] } } ================================================ FILE: analyzers/rspec/vbnet/S134.html ================================================

Why is this an issue?

Nested control flow statements If, Select, For, For Each, While, Do, and Try are often key ingredients in creating what’s known as "Spaghetti code". This code smell can make your program difficult to understand and maintain.

When numerous control structures are placed inside one another, the code becomes a tangled, complex web. This significantly reduces the code’s readability and maintainability, and it also complicates the testing process.

How to fix it

Code examples

The following example demonstrates the behavior of the rule with the default threshold of 3 levels of nesting:

Noncompliant code example

If condition1 ' Compliant - depth = 1
  ' ...
  If condition2 ' Compliant - depth = 2
    ' ...
    For i = 0 to 10 ' Compliant - depth = 3, not exceeding the limit
      ' ...
      If condition4 ' Noncompliant - depth = 4
        If condition5 ' Depth = 5, exceeding the limit, but issues are only reported on depth = 4
          ' ...
        End If
        Return
      End If
    Next
  End If
End If
================================================ FILE: analyzers/rspec/vbnet/S134.json ================================================ { "title": "Control flow statements \"If\", \"For\", \"For Each\", \"Do\", \"While\", \"Select\" and \"Try\" should not be nested too deeply", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-134", "sqKey": "S134", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S138.html ================================================

Why is this an issue?

A procedure that grows too large tends to aggregate too many responsibilities.

Such procedures inevitably become harder to understand and therefore harder to maintain.

Above a specific threshold, it is strongly advised to refactor into smaller procedures which focus on well-defined tasks.

Those smaller procedures will not only be easier to understand but also probably easier to test.

================================================ FILE: analyzers/rspec/vbnet/S138.json ================================================ { "title": "Procedures should not have too many lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-138", "sqKey": "S138", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S139.html ================================================

Why is this an issue?

This rule verifies that single-line comments are not located at the ends of lines of code. The main idea behind this rule is that in order to be really readable, trailing comments would have to be properly written and formatted (correct alignment, no interference with the visual structure of the code, not too long to be visible) but most often, automatic code formatters would not handle this correctly: the code would end up less readable. Comments are far better placed on the previous empty line of code, where they will always be visible and properly formatted.

Noncompliant code example

With the default comment pattern ^'\s*\S+\s*$, which ignores single word comments:

Module Module1
  Sub Main()
    Console.WriteLine("Hello, world!") ' Noncompliant - My first program!
    Console.WriteLine("Hello, world!") ' CompliantOneWord
  End Sub
End Module

Compliant solution

Module Module1
  Sub Main()
    ' Compliant - My first program!
    Console.WriteLine("Hello, world!")
    Console.WriteLine("Hello, world!") ' CompliantOneWord
  End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S139.json ================================================ { "title": "Comments should not be located at the end of lines of code", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-139", "sqKey": "S139", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1451.html ================================================

Why is this an issue?

Each source file should start with a header stating file ownership and the license which must be used to distribute the application.

This rule must be fed with the header text that is expected at the beginning of every file.

The headerFormat must end with an empty line if you want to have an empty line between the file header and the first line for your source file (using, namespace…​).

For example, if you want the source file to look like this

' Copyright (c) SonarSource. All Rights Reserved. Licensed under the LGPL License.  See License.txt in the project root for license information.

namespace Foo
{
}

then the headerFormat parameter should end with an empty line like this

' Copyright (c) SonarSource. All Rights Reserved. Licensed under the LGPL License.  See License.txt in the project root for license information.

Compliant solution

/*
 * SonarQube, open source software quality management tool.
 * Copyright (C) 2008-2013 SonarSource
 * mailto:contact AT sonarsource DOT com
 *
 * SonarQube 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.
 *
 * SonarQube 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.
 */
================================================ FILE: analyzers/rspec/vbnet/S1451.json ================================================ { "title": "Track lack of copyright and license headers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LAWFUL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-1451", "sqKey": "S1451", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1479.html ================================================

Why is this an issue?

When Select Case statements have large sets of multi-line Case clauses, the code becomes hard to read and maintain.

For example, the Cognitive Complexity is going to be particularly high.

In such scenarios, it’s better to refactor the Select Case to only have single-line case clauses.

When all the Case clauses of a Select Case statement are single-line, the readability of the code is not affected.

Exceptions

This rule ignores:

How to fix it

Extract the logic of multi-line Case clauses into separate methods.

Code examples

The examples below use the "Maximum number of case" property set to 4.

Noncompliant code example

Public Function MapChar(ch As Char, value As Integer) As Integer ' Noncompliant
    Select Case ch
        Case "a"c
            Return 1
        Case "b"c
            Return 2
        Case "c"c
            Return 3
        ' ...
        Case "-"c
            If value > 10 Then
                Return 42
            ElseIf value < 5 AndAlso value > 1 Then
                Return 21
            End If
            Return 99
        Case Else
            Return 1000
    End Select
End Function

Compliant solution

Public Function MapChar(ch As Char, value As Integer) As Integer
    Select Case ch
        Case "a"c
            Return 1
        Case "b"c
            Return 2
        Case "c"c
            Return 3
        ' ...
        Case "-"c
            Return HandleDash(value)
        Case Else
            Return 1000
    End Select
End Function

Private Function HandleDash(value As Integer) As Integer
    If value > 10 Then
        Return 42
    ElseIf value < 5 AndAlso value > 1 Then
        Return 21
    End If
    Return 99
End Function

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1479.json ================================================ { "title": "\"Select Case\" statement with many \"Case\" clauses should have only one statement", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1479", "sqKey": "S1479", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1481.html ================================================

Why is this an issue?

An unused local variable is a variable that has been declared but is not used anywhere in the block of code where it is defined. It is dead code, contributing to unnecessary complexity and leading to confusion when reading the code. Therefore, it should be removed from your code to maintain clarity and efficiency.

What is the potential impact?

Having unused local variables in your code can lead to several issues:

In summary, unused local variables can make your code less readable, more confusing, and harder to maintain, and they can potentially lead to bugs or inefficient memory use. Therefore, it is best to remove them.

Exceptions

Unused locally created resources in a Using statement are not reported.

Using t = New TestTimer()
End Using

How to fix it

The fix for this issue is straightforward. Once you ensure the unused variable is not part of an incomplete implementation leading to bugs, you just need to remove it.

Code examples

Noncompliant code example

Public Function NumberOfMinutes(ByVal hours As Integer) As Integer
    Dim seconds As Integer = 0 ' Noncompliant - seconds is unused
    Return hours * 60
End Function

Compliant solution

Public Function NumberOfMinutes(ByVal hours As Integer) As Integer
    Return hours * 60
End Function
================================================ FILE: analyzers/rspec/vbnet/S1481.json ================================================ { "title": "Unused local variables should be removed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "unused" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1481", "sqKey": "S1481", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1541.html ================================================

Why is this an issue?

The cyclomatic complexity of a function, procedure or property should not exceed a defined threshold. Complex code can perform poorly and will in any case be difficult to understand and therefore to maintain.

================================================ FILE: analyzers/rspec/vbnet/S1541.json ================================================ { "title": "Functions, procedures and properties should not be too complex", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1541", "sqKey": "S1541", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1542.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently. This rule checks that all subroutine and function names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$

Module Module1
  Sub bad_subroutine()                      ' Noncompliant
  End Sub

  Public Function Bad_Function() As Integer ' Noncompliant
    Return 42
  End Function
End Module

Compliant solution

Module Module1
  Sub GoodSubroutine()                      ' Compliant
  End Sub

  Public Function GoodFunction() As Integer ' Compliant
    Return 42
  End Function
End Module
================================================ FILE: analyzers/rspec/vbnet/S1542.json ================================================ { "title": "Functions and procedures should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1542", "sqKey": "S1542", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1643.html ================================================

Why is this an issue?

StringBuilder is more efficient than string concatenation, especially when the operator is repeated over and over as in loops.

Noncompliant code example

Module Module1
    Sub Main()
        Dim foo = ""
        foo &= "Result: "       ' Compliant - outside of loop

        For i = 1 To 9
            foo &= i            ' Noncompliant
        Next
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim foo = New System.Text.StringBuilder
        foo.Append("Result: ")  ' Compliant

        For i = 1 To 9
            foo.Append(i)       ' Compliant
        Next
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1643.json ================================================ { "title": "Strings should not be concatenated using \"+\" or \"\u0026\" in a loop", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1643", "sqKey": "S1643", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1645.html ================================================

Why is this an issue?

Consistently using the & operator for string concatenation make the developer intentions clear.

&, unlike +, will convert its operands to strings and perform an actual concatenation.

+ on the other hand can be an addition, or a concatenation, depending on the operand types.

Noncompliant code example

Module Module1
    Sub Main()
        Console.WriteLine("1" + 2) ' Noncompliant - will display "3"
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Console.WriteLine(1 & 2)   ' Compliant - will display "12"
        Console.WriteLine(1 + 2)   ' Compliant - but will display "3"
        Console.WriteLine("1" & 2) ' Compliant - will display "12"
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1645.json ================================================ { "title": "The \"\u0026\" operator should be used to concatenate strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1645", "sqKey": "S1645", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S1654.html ================================================

Why is this an issue?

Sharing some naming conventions is a key point to make it possible for a team to efficiently collaborate.

This rule allows to check that all parameter names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^[a-z][a-z0-9]*([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$

Module Module1
    Sub GetSomething(ByVal ID As Integer) ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub GetSomething(ByVal id As Integer) ' Compliant
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S1654.json ================================================ { "title": "Method parameters should follow a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1654", "sqKey": "S1654", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1656.html ================================================

Why is this an issue?

There is no reason to re-assign a variable to itself. Either this statement is redundant and should be removed, or the re-assignment is a mistake and some other value or variable was intended for the assignment instead.

Code examples

Noncompliant code example

Public Sub SetName(name As String)
  name = name ' Noncompliant
End Sub

Compliant solution

Public Sub SetName(name As String)
  Me.name = name ' Compliant
End Sub
================================================ FILE: analyzers/rspec/vbnet/S1656.json ================================================ { "title": "Variables should not be self-assigned", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "3min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1656", "sqKey": "S1656", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1659.html ================================================

Why is this an issue?

Declaring multiple variable on one line is difficult to read.

Noncompliant code example

Module Module1
  Public Const AAA As Integer = 5, BBB = 42, CCC As String = "foo"  ' Noncompliant
End Module

Compliant solution

Module Module1
  Public Const AAA As Integer = 5
  Public Const BBB = 42
  Public Const CCC as String = "foo"
End Module
================================================ FILE: analyzers/rspec/vbnet/S1659.json ================================================ { "title": "Multiple variables should not be declared on the same line", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1659", "sqKey": "S1659", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S1751.html ================================================

Why is this an issue?

A loop statement with at most one iteration is equivalent to an If statement; the following block is executed only once.

If the initial intention was to conditionally execute the block only once, an If statement should be used instead. If that was not the initial intention, the block of the loop should be fixed so the block is executed multiple times.

A loop statement with at most one iteration can happen when a statement unconditionally transfers control, such as a jump statement or a throw statement, is misplaced inside the loop block.

This rule raises when the following statements are misplaced:

How to fix it

Code examples

Noncompliant code example

Public Function Method(items As IEnumerable(Of Object)) As Object
    For i As Integer = 0 To 9
        Console.WriteLine(i)
        Exit For ' Noncompliant: loop only executes once
    Next

    For Each item As Object In items
        Return item ' Noncompliant: loop only executes once
    Next
    Return Nothing
End Function

Compliant solution

Public Function Method(items As IEnumerable(Of Object)) As Object
    For i As Integer = 0 To 9
        Console.WriteLine(i)
    Next

    Dim item = items.FirstOrDefault()
    If item IsNot Nothing Then
        Return item
    End If
    Return Nothing
End Function

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1751.json ================================================ { "title": "Loops with at most one iteration should be refactored", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1751", "sqKey": "S1751", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1764.html ================================================

Why is this an issue?

Using the same value on either side of a binary operator is almost always a mistake. In the case of logical operators, it is either a copy/paste error and therefore a bug, or it is simply wasted code, and should be simplified. In the case of most binary mathematical operators, having the same value on both sides of an operator yields predictable results, and should be simplified.

This rule ignores *, +, &, <<, and >>.

Noncompliant code example

If (a = a) Then
  doZ()
End If

If a = b OrElse a = b Then
  doW()
End If

Dim j = 5 / 5
j = 5 \ 5
j = 5 Mod 5
Dim k = 5 - 5

Dim i = 42
i /= i
i -= i

Exceptions

This rule ignores *, +, and =.

Resources

================================================ FILE: analyzers/rspec/vbnet/S1764.json ================================================ { "title": "Identical expressions should not be used on both sides of a binary operator", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1764", "sqKey": "S1764", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1821.html ================================================

Why is this an issue?

Nested Select Case structures are difficult to understand because you can easily confuse the cases of an inner Select Case as belonging to an outer statement. Therefore nested Select Case statements should be avoided.

Specifically, you should structure your code to avoid the need for nested Select Case statements, but if you cannot, then consider moving the inner Select Case to another function.

Noncompliant code example

Public Sub Foo(A As Integer, B As Integer)
    Select Case A
        Case 0
            ' ...
        Case 1
            Select Case B   ' Noncompliant; nested Select Case
                Case 2
                    ' ...
                Case 3
                    ' ...
                Case 4
                    ' ...
                Case Else
                    ' ...
            End Select
        Case 2
            ' ...
        Case Else
            ' ...
    End Select
End Sub

Compliant solution

Public Sub Foo(A As Integer, B As Integer)
    Select Case A
        Case 0
            ' ...
        Case 1
            HandleB(B)
        Case 2
            ' ...
        Case Else
            ' ...
    End Select
End Sub
================================================ FILE: analyzers/rspec/vbnet/S1821.json ================================================ { "title": "\"Select Case\" statements should not be nested", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-1821", "sqKey": "S1821", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1862.html ================================================

Why is this an issue?

A chain of If/ElseIf statements is evaluated from top to bottom. At most, only one branch will be executed: the first statement with a condition that evaluates to True. Therefore, duplicating a condition leads to unreachable code inside the duplicated condition block. Usually, this is due to a copy/paste error.

The result of such duplication can lead to unreachable code or even to unexpected behavior.

How to fix it

Code examples

Noncompliant code example

If param = 1 Then
  OpenWindow()
ElseIf param = 2 Then
  CloseWindow()
ElseIf param = 1 Then ' Noncompliant: condition has already been checked
  MoveWindowToTheBackground() ' unreachable code
End If

Compliant solution

If param = 1 Then
  OpenWindow()
ElseIf param = 2 Then
  CloseWindow()
ElseIf param = 3 Then
  MoveWindowToTheBackground()
End If

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1862.json ================================================ { "title": "Related \"If\/ElseIf\" statements should not have the same condition", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "unused", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1862", "sqKey": "S1862", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1871.html ================================================

Why is this an issue?

When the same code is duplicated in two or more separate branches of a conditional, it can make the code harder to understand, maintain, and can potentially introduce bugs if one instance of the code is changed but others are not.

Having two Cases in the same Select statement or branches in the same If structure with the same implementation is at best duplicate code, and at worst a coding error.

If a >= 0 AndAlso a < 10 Then
  DoFirst()
  DoTheThing()
ElseIf a >= 10 AndAlso a < 20 Then
  DoTheOtherThing()
ElseIf a >= 20 AndAlso a < 50   ' Noncompliant; duplicates first condition
  DoFirst()
  DoTheThing()
Else
  DoTheRest();
End If
Select i
  Case 1
    DoFirst()
    DoSomething()
  Case 2
    DoSomethingDifferent()
  Case 3  ' Noncompliant; duplicates case 1's implementation
    DoFirst()
    DoSomething()
  Case Else:
    DoTheRest()
End Select

If the same logic is needed for both instances, then:

If (a >= 0 AndAlso a < 10) OrElse (a >= 20 AndAlso a < 50) Then
  DoFirst()
  DoTheThing()
ElseIf a >= 10 AndAlso a < 20 Then
  DoTheOtherThing()
Else
  DoTheRest();
End If
Select i
  Case 1, 3
    DoFirst()
    DoSomething()
  Case 2
    DoSomethingDifferent()
  Case Else:
    DoTheRest()
End Select

Exceptions

Blocks in an If chain or Case clause that contain a single line of code are ignored.

If a >= 0 AndAlso a < 10 Then
  DoTheThing()
ElseIf a >= 10 AndAlso a < 20 Then
  DoTheOtherThing()
ElseIf a >= 20 AndAlso a < 50   ' no issue, usually this is done on purpose to increase the readability
  DoTheThing()
End If

But this exception does not apply to If chains without Else-s, or to Select-s without Case Else clauses when all branches have the same single line of code. In the case of If chains with Else-s, or of Select-es with Case Else clauses, rule {rule:vbnet:S3923} raises a bug.

If a == 1 Then
  DoTheThing() ' Noncompliant, this might have been done on purpose but probably not
ElseIf a == 2 Then
  DoTheThing()
End If

Resources

Related rules

================================================ FILE: analyzers/rspec/vbnet/S1871.json ================================================ { "title": "Two branches in a conditional structure should not have exactly the same implementation", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "design", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1871", "sqKey": "S1871", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S1940.html ================================================

Why is this an issue?

It is needlessly complex to invert the result of a boolean comparison. The opposite comparison should be made instead.

Noncompliant code example

If Not (a = 2) Then  // Noncompliant
Dim b as Boolean = Not (i < 10)  // Noncompliant

Compliant solution

If a <> 2 Then
Dim b as Boolean = i >= 10
================================================ FILE: analyzers/rspec/vbnet/S1940.json ================================================ { "title": "Boolean checks should not be inverted", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1940", "sqKey": "S1940", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S1944.html ================================================

Why is this an issue?

A cast is an explicit conversion, which is a way to tell the compiler the intent to convert from one type to another.

In Visual Basic, there are two explicit conversion operators:

Public Sub Method(Value As Object)
    Dim i As Integer
    i = DirectCast(Value, Integer)  ' Direct casting from object holding an integer type to Integer
    i = CType(Value, Integer)       ' Conversion from the underlying type to Integer
End Sub

In most cases, the compiler will be able to catch invalid casts between incompatible value types or reference types.

However, the compiler will not be able to detect invalid casts to interfaces.

What is the potential impact?

Invalid casts will lead to unexpected behaviors or runtime errors such as InvalidCastException.

Exceptions

No issue is reported if the interface has no implementing class in the assembly.

How to fix it

To prevent an InvalidCastException from raising during an explicit conversion, it is recommended to use the TryCast operator. When the conversion is not possible, the TryCast operator returns Nothing and will never raise an exception.

Code examples

Noncompliant code example

Public Interface IMyInterface
End Interface

Public Class Implementer
    Implements IMyInterface
End Class

Public Class AnotherClass
End Class

Module Program
    Sub Main()
        Dim Another As New AnotherClass
        Dim x As IMyInterface = DirectCast(Another, IMyInterface)   ' Noncompliant: InvalidCastException is being thrown
    End Sub
End Module

Compliant solution

Public Interface IMyInterface
End Interface

Public Class Implementer
    Implements IMyInterface
End Class

Public Class AnotherClass
End Class

Module Program
    Sub Main()
        Dim Another As New AnotherClass
        Dim x = TryCast(Another, IMyInterface)                      ' Compliant: but will always be Nothing
    End Sub
End Module

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S1944.json ================================================ { "title": "Invalid casts should be avoided", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-1944", "sqKey": "S1944", "scope": "All", "securityStandards": { "CWE": [ 588, 704 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2053.html ================================================

This vulnerability increases the likelihood that attackers are able to compute the cleartext of password hashes.

Why is this an issue?

During the process of password hashing, an additional component, known as a "salt," is often integrated to bolster the overall security. This salt, acting as a defensive measure, primarily wards off certain types of attacks that leverage pre-computed tables to crack passwords.

However, potential risks emerge when the salt is deemed insecure. This can occur when the salt is consistently the same across all users or when it is too short or predictable. In scenarios where users share the same password and salt, their password hashes will inevitably mirror each other. Similarly, a short salt heightens the probability of multiple users unintentionally having identical salts, which can potentially lead to identical password hashes. These identical hashes streamline the process for potential attackers to recover clear-text passwords. Thus, the emphasis on implementing secure, unique, and sufficiently lengthy salts in password-hashing functions is vital.

What is the potential impact?

Despite best efforts, even well-guarded systems might have vulnerabilities that could allow an attacker to gain access to the hashed passwords. This could be due to software vulnerabilities, insider threats, or even successful phishing attempts that give attackers the access they need.

Once the attacker has these hashes, they will likely attempt to crack them using a couple of methods. One is brute force, which entails trying every possible combination until the correct password is found. While this can be time-consuming, having the same salt for all users or a short salt can make the task significantly easier and faster.

If multiple users have the same password and the same salt, their password hashes would be identical. This means that if an attacker successfully cracks one hash, they have effectively cracked all identical ones, granting them access to multiple accounts at once.

A short salt, while less critical than a shared one, still increases the odds of different users having the same salt. This might create clusters of password hashes with identical salt that can then be attacked as explained before.

With short salts, the probability of a collision between two users' passwords and salts couple might be low depending on the salt size. The shorter the salt, the higher the collision probability. In any case, using longer, cryptographically secure salt should be preferred.

Exceptions

To securely store password hashes, it is a recommended to rely on key derivation functions that are computationally intensive. Examples of such functions are:

When they are used for password storage, using a secure, random salt is required.

However, those functions can also be used for other purposes such as master key derivation or password-based pre-shared key generation. In those cases, the implemented cryptographic protocol might require using a fixed salt to derive keys in a deterministic way. In such cases, using a fixed salt is safe and accepted.

How to fix it in .NET

Code examples

The following code contains examples of hard-coded salts.

Noncompliant code example

Imports System.Security.Cryptography

Public Sub Hash(Password As String)
    Dim Salt As Byte() = Encoding.UTF8.GetBytes("salty")
    Dim Hashed As New Rfc2898DeriveBytes(Password, Salt) ' Noncompliant
End Sub

Compliant solution

Imports System.Security.Cryptography

Public Sub Hash(Password As String)
    Dim Hashed As New Rfc2898DeriveBytes(Password, 16, 10000, HashAlgorithmName.SHA256)
End Sub

How does this work?

This code ensures that each user’s password has a unique salt value associated with it. It generates a salt randomly and with a length that provides the required security level. It uses a salt length of at least 16 bytes (128 bits), as recommended by industry standards.

In the case of the code sample, the class automatically takes care of generating a secure salt if none is specified.

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S2053.json ================================================ { "title": "Password hashing functions should use an unpredictable salt", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2053", "sqKey": "S2053", "scope": "Main", "securityStandards": { "CWE": [ 759, 760 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222542" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2068.html ================================================

Why is this an issue?

Hard-coding credentials in source code or binaries makes it easy for attackers to extract sensitive information, especially in distributed or open-source applications. This practice exposes your application to significant security risks.

This rule flags instances of hard-coded credentials used in database and LDAP connections. It looks for hard-coded credentials in connection strings, and for variable names that match any of the patterns from the provided list.

In the past, it has led to the following vulnerabilities:

How to fix it

Credentials should be stored in a configuration file that is not committed to the code repository, in a database, or managed by your cloud provider’s secrets management service. If a password is exposed in the source code, it must be changed immediately.

Code Examples

Noncompliant code example

Dim username As String = "admin"
Dim password As String = "Password123" ' Noncompliant
Dim usernamePassword As String = "user=admin&password=Password123" ' Noncompliant
Dim url As String = "scheme://user:Admin123@domain.com" ' Noncompliant

Compliant solution

Dim username As String = "admin"
Dim password As String = GetEncryptedPassword()
Dim usernamePassword As String = String.Format("user={0}&password={1}", GetEncryptedUsername(), GetEncryptedPassword())
Dim url As String = $"scheme://{username}:{password}@domain.com"

Dim url2 As String= "http://guest:guest@domain.com" ' Compliant
Const Password_Property As String = "custom.password" ' Compliant

Exceptions

Resources

================================================ FILE: analyzers/rspec/vbnet/S2068.json ================================================ { "title": "Credentials should not be hard-coded", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "quickfix": "infeasible", "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2068", "sqKey": "S2068", "scope": "Main", "securityStandards": { "CWE": [ 798, 259 ], "OWASP": [ "A2" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.4", "3.5.2", "6.4.1" ] } } ================================================ FILE: analyzers/rspec/vbnet/S2077.html ================================================

Formatted SQL queries can be difficult to maintain, debug and can increase the risk of SQL injection when concatenating untrusted values into the query. However, this rule doesn’t detect SQL injections, the goal is only to highlight complex/formatted queries.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

Public Sub SqlCommands(ByVal connection As SqlConnection, ByVal query As String, ByVal param As String)
    Dim sensitiveQuery As String = String.Concat(query, param)
    command = New SqlCommand(sensitiveQuery) ' Sensitive

    command.CommandText = sensitiveQuery ' Sensitive

    Dim adapter As SqlDataAdapter
    adapter = New SqlDataAdapter(sensitiveQuery, connection) ' Sensitive
End Sub

Public Sub Foo(ByVal context As DbContext, ByVal query As String, ByVal param As String)
    Dim sensitiveQuery As String = String.Concat(query, param)
    context.Database.ExecuteSqlCommand(sensitiveQuery) ' Sensitive

    context.Query(Of User)().FromSql(sensitiveQuery) ' Sensitive
End Sub

Compliant Solution

Public Sub Foo(ByVal context As DbContext, ByVal value As String)
    context.Database.ExecuteSqlCommand("SELECT * FROM mytable WHERE mycol=@p0", value) ' Compliant, the query is parameterized
End Sub

See

================================================ FILE: analyzers/rspec/vbnet/S2077.json ================================================ { "title": "Formatting SQL queries is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "MAINTAINABILITY": "LOW", "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "bad-practice", "sql" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2077", "sqKey": "S2077", "scope": "Main", "securityStandards": { "CWE": [ 20, 89 ], "OWASP": [ "A1" ], "OWASP Top 10 2021": [ "A3" ], "PCI DSS 3.2": [ "6.5.1" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.1.3", "5.1.4", "5.3.4", "5.3.5" ] } } ================================================ FILE: analyzers/rspec/vbnet/S2094.html ================================================

Why is this an issue?

There is no good excuse for an empty class. If it’s being used simply as a common extension point, it should be replaced with an interface. If it was stubbed in as a placeholder for future development it should be fleshed-out. In any other case, it should be eliminated.

Noncompliant code example

Public Class Empty ' Noncompliant

End Class

Compliant solution

Public Interface IEmpty

End Interface

Exceptions

Imports Microsoft.AspNetCore.Mvc.RazorPages

Public Class EmptyPageModel ' Compliant - an empty PageModel can be fully functional, the VB code can be in the vbhtml file
    Inherits PageModel
End Class
================================================ FILE: analyzers/rspec/vbnet/S2094.json ================================================ { "title": "Classes should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2094", "sqKey": "S2094", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2166.html ================================================

Why is this an issue?

Clear, communicative naming is important in code. It helps maintainers and API users understand the intentions for and uses of a unit of code. Using "exception" in the name of a class that does not extend Exception or one of its subclasses is a clear violation of the expectation that a class' name will indicate what it is and/or does.

Noncompliant code example

Public Class FruitException ' Noncompliant - this has nothing to do with Exception
    Private expected As Fruit
    Private unusualCharacteristics As String
    Private appropriateForCommercialExploitation As Boolean
    ' ...
End Class

Public Class CarException ' Noncompliant - does not derive from any Exception-based class
    Public Sub New(message As String, inner As Exception)
        ' ...
    End Sub
End Class

Compliant solution

Public Class FruitSport ' Compliant - class name does not end with 'Exception'
    Private expected As Fruit
    Private unusualCharacteristics As String
    Private appropriateForCommercialExploitation As Boolean
    ' ...
End Class

Public Class CarException Inherits Exception ' Compliant - correctly extends System.Exception
    Public Sub New(message As String, inner As Exception)
        MyBase.New(message, inner)
        ' ...
    End Sub
End Class
================================================ FILE: analyzers/rspec/vbnet/S2166.json ================================================ { "title": "Classes named like \"Exception\" should extend \"Exception\" or a subclass", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "error-handling", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2166", "sqKey": "S2166", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2178.html ================================================

Why is this an issue?

Short-circuit evaluation is an evaluation strategy for Boolean operators, that doesn’t evaluate the second argument of the operator if it is not needed to determine the result of the operation.

VB.NET provides logical operators that implement short-circuiting evaluations AndAlso and OrElse, as well as the non-short-circuiting versions And and Or. Unlike short-circuiting operators, the non-short-circuiting operators evaluate both operands and afterward perform the logical operation.

For example False AndAlso FunctionCall always results in False even when the FunctionCall invocation would raise an exception. In contrast, False And FunctionCall also evaluates FunctionCall, and results in an exception if FunctionCall raises an exception.

Similarly, True OrElse FunctionCall always results in True, no matter what the return value of FunctionCall would be.

The use of non-short-circuit logic in a boolean context is likely a mistake, one that could cause serious program errors as conditions are evaluated under the wrong circumstances.

How to fix it

Code examples

Noncompliant code example

If GetTrue() Or GetFalse() Then ' Noncompliant: both sides evaluated
End If

Compliant solution

If GetTrue() OrElse GetFalse() Then ' Compliant: short-circuit logic used
End If

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S2178.json ================================================ { "title": "Short-circuit logic should be used in boolean contexts", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2178", "sqKey": "S2178", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S2222.html ================================================

Why is this an issue?

To prevent potential deadlocks in an application, it is crucial to release any locks that are acquired within a method along all possible execution paths.

Failing to release locks properly can lead to potential deadlocks, where the lock might not be released, causing issues in the application.

This rule specifically focuses on tracking the following types from the System.Threading namespace:

An issue is reported when a lock is acquired within a method but not released on all paths.

Exceptions

If the lock is never released within the method, no issue is raised, assuming that the callers will handle the release.

How to fix it

To make sure that a lock is always released correctly, you can follow one of these two methods:

Code examples

Noncompliant code example

Class Example
    Private obj As Object = New Object()

    Public Sub DoSomethingWithMonitor()
        Monitor.Enter(obj) ' Noncompliant: not all paths release the lock

        If IsInitialized() Then
            ' ...
            Monitor.Exit(obj)
        End If
    End Sub
End Class
Class Example
    Private lockObj As ReaderWriterLockSlim = New ReaderWriterLockSlim()

    Public Sub DoSomethingWithReaderWriteLockSlim()
        lockObj.EnterReadLock() ' Noncompliant: not all paths release the lock
        If IsInitialized() Then
            ' ...
            lockObj.ExitReadLock()
        End If
    End Sub
End Class

Compliant solution

Class Example
    Private obj As Object = New Object()

    Public Sub DoSomethingWithMonitor()
        SyncLock obj ' Compliant: the lock will be released at the end of the SyncLock block
            If IsInitialized() Then
                ' ...
            End If
        End SyncLock
    End Sub
End Class
Class Example
    Private lockObj As ReaderWriterLockSlim = New ReaderWriterLockSlim()

    Public Sub DoSomethingWithReaderWriteLockSlim()
        lockObj.EnterReadLock() ' Compliant: the lock will be released in the finally block

        Try
            If IsInitialized() Then
                ' ...
            End If
        Finally
            lockObj.ExitReadLock()
        End Try
    End Sub
End Class

Resources

================================================ FILE: analyzers/rspec/vbnet/S2222.json ================================================ { "title": "Locks should be released on all paths", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "multi-threading", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2222", "sqKey": "S2222", "scope": "Main", "securityStandards": { "CWE": [ 459 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2225.html ================================================

Why is this an issue?

Calling ToString() on an object should always return a string. Thus, overriding the ToString method should never return Nothing because it breaks the method’s implicit contract, and as a result the consumer’s expectations.

Public Overrides Function ToString() As String
    Return Nothing ' Noncompliant
End Function
Public Overrides Function ToString() As String
    Return ""
End Function

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2225.json ================================================ { "title": "\"ToString()\" method should not return Nothing", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2225", "sqKey": "S2225", "scope": "Main", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2234.html ================================================

Why is this an issue?

Calling a procedure with argument variables whose names match the procedure parameter names but in a different order can cause confusion. It could indicate a mistake in the arguments' order, leading to unexpected results.

Public Function Divide(divisor As Integer, dividend As Integer) As Double
    Return divisor / dividend
End Function

Public Sub DoTheThing()
    Dim divisor = 15
    Dim dividend = 5

    Dim result = Divide(dividend, divisor)  ' Noncompliant: arguments' order doesn't match their respective parameter names
    '...
End Sub

However, matching the procedure parameters' order contributes to clearer and more readable code:

Public Function Divide(divisor As Integer, dividend As Integer) As Double
    Return divisor / dividend
End Function

Public Sub DoTheThing()
    Dim divisor = 15
    Dim dividend = 5

    Dim result = Divide(divisor, dividend) ' Compliant
    '...
End Sub
================================================ FILE: analyzers/rspec/vbnet/S2234.json ================================================ { "title": "Arguments should be passed in the same order as the procedure parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2234", "sqKey": "S2234", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2257.html ================================================

The use of a non-standard algorithm is dangerous because a determined attacker may be able to break the algorithm and compromise whatever data has been protected. Standard algorithms like AES, RSA, SHA, …​ should be used instead.

This rule tracks custom implementation of these types from System.Security.Cryptography namespace:

Recommended Secure Coding Practices

Sensitive Code Example

Public Class CustomHash     ' Noncompliant
    Inherits HashAlgorithm

    Private fResult() As Byte

    Public Overrides Sub Initialize()
        fResult = Nothing
    End Sub

    Protected Overrides Function HashFinal() As Byte()
        Return fResult
    End Function

    Protected Overrides Sub HashCore(array() As Byte, ibStart As Integer, cbSize As Integer)
        fResult = If(fResult, array.Take(8).ToArray)
    End Sub

End Class

Compliant Solution

Dim mySHA256 As SHA256 = SHA256.Create()

See

================================================ FILE: analyzers/rspec/vbnet/S2257.json ================================================ { "title": "Using non-standard cryptographic algorithms is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1d" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2257", "sqKey": "S2257", "scope": "Main", "securityStandards": { "CWE": [ 327 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] } } ================================================ FILE: analyzers/rspec/vbnet/S2259.html ================================================

Why is this an issue?

Accessing a Nothing value will always throw a NullReferenceException most likely causing an abrupt program termination.

Such termination might expose sensitive information that a malicious third party could exploit to, for instance, bypass security measures.

Exceptions

In the following cases, the rule does not raise:

Extensions Methods

Calls to extension methods can still operate on Nothing values.

Imports System.Diagnostics.CodeAnalysis
Imports System.Runtime.CompilerServices
Imports System.Text.RegularExpressions

Module Program
    <Extension>
    Function RemoveVowels(Value As String) As String
        If Value Is Nothing Then
            Return Nothing
        End If
        Return Regex.Replace(Value, "[aeoui]*", "", RegexOptions.IgnoreCase)
    End Function

    Sub Main()
        Dim StrValue As String = Nothing
        Console.WriteLine(StrValue.RemoveVowels()) ' Compliant: 'RemoveVowels' is an extension method
    End Sub
End Module

Unreachable code

Unreachable code is not executed, thus Nothing values will never be accessed.

Public Sub Method()
    Dim o As Object = Nothing
    If False Then
        o.ToString() ' Compliant: code is unreachable
    End If
End Sub

Validated value by analysis attributes

Nullable analysis attributes enable the developer to annotate methods with information about the null-state of its arguments. Thus, potential Nothing values validated by one of the following attributes will not raise:

It is important to note those attributes are only available starting .NET Core 3. As a workaround, it is possible to define those attributes manually in a custom class:

Public NotInheritable Class NotNullAttribute ' The alternative name 'ValidatedNotNullAttribute' is also supported
    Inherits Attribute
End Class

Public Module Guard
    Public Sub CheckNotNull(Of T)(<NotNull> Value As T, Name As String)
        If Value Is Nothing Then Throw New ArgumentNullException(Name)
    End Sub
End Module

Public Module Utils
    Public Function Normalize(Value As String) As String
        CheckNotNull(Value, nameof(Value)) ' Will throw if 'Value' is Nothing
        Return Value.ToUpper() ' Compliant: value is known to be not Nothing here
    End Function
End Module

Validated value by Debug.Assert

A value validated with Debug.Assert to not be Nothing is safe to access.

Imports System.Diagnostics

Public Sub Method(MyObject As Object)
    Debug.Assert(MyObject IsNot Nothing)
    MyObject.ToString() ' Compliant: 'MyObject' is known to be not Nothing here.
End Sub

Validated value by IDE-specific attributes

Like with null-analysis-attribute, potential Nothing values validated by one of the following IDE-specific attributes will not raise

Visual Studio
JetBrains Rider

How to fix it

To fix the issue, the access of the Nothing value needs to be prevented by either:

Code examples

Noncompliant code example

The variable MyObject is equal to Nothing, meaning it has no value:

Public Sub Method()
    Dim MyObject As Object = Nothing
    Console.WriteLine(MyObject.ToString)   ' Noncompliant: 'MyObject' is always Nothing
End Sub

The parameter Input might be Nothing as suggested by the if condition:

Public Sub Method(Input As Object)
    If Input Is Nothing Then
        ' ...
    End If
    Console.WriteLine(Input.ToString) ' Noncompliant: 'Input' might be Nothing
End Sub

Compliant solution

Ensuring the variable MyObject has a value resolves the issue:

Public Sub Method()
    Dim MyObject As New Object
    Console.WriteLine(MyObject.ToString) ' Compliant: 'MyObject' is not Nothing
End Sub

Preventing the non-compliant code to be executed by returning early:

Public Sub Method(Input As Object)
    If Input Is Nothing Then
        Return
    End If
    Console.WriteLine(Input.ToString) ' Compliant: if 'Input' is Nothing, this part is unreachable
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2259.json ================================================ { "title": "Null pointers should not be dereferenced", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2259", "sqKey": "S2259", "scope": "Main", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2302.html ================================================

Why is this an issue?

Because parameter names could be changed during refactoring, they should not be spelled out literally in strings. Instead, use NameOf(), and the string that’s output will always be correct.

This rule raises an issue when any string in the Throw statement is an exact match for the name of one of the method parameters.

Noncompliant code example

Public Sub DoSomething(param As Integer, secondParam As String)
    If (param < 0)
        Throw New Exception("param") ' Noncompliant
    End If
    If secondParam is Nothing
      Throw New Exception("secondParam should be valid") ' Noncompliant
    End If
End Sub

Compliant solution

Public Sub DoSomething(param As Integer, secondParam As String)
    If (param < 0)
        Throw New Exception(NameOf(param))
    End If
    If secondParam is Nothing
      Throw New Exception($"{NameOf(secondParam)} should be valid")
    End If
End Sub

Exceptions

================================================ FILE: analyzers/rspec/vbnet/S2302.json ================================================ { "title": "\"NameOf\" should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2302", "sqKey": "S2302", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2304.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all namespace names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression: ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?(\.([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2}))*$

Namespace foo  ' Noncompliant
End Namespace

Compliant solution

Namespace Foo  ' Compliant
End Namespace
================================================ FILE: analyzers/rspec/vbnet/S2304.json ================================================ { "title": "Namespace names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2304", "sqKey": "S2304", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2339.html ================================================

Why is this an issue?

Constant members are copied at compile time to the call sites, instead of being fetched at runtime.

As an example, say you have a library with a constant Version member set to 1.0, and a client application linked to it. This library is then updated and Version is set to 2.0. Unfortunately, even after the old DLL is replaced by the new one, Version will still be 1.0 for the client application. In order to see 2.0, the client application would need to be rebuilt against the new version of the library.

This means that you should use constants to hold values that by definition will never change, such as Zero. In practice, those cases are uncommon, and therefore it is generally better to avoid constant members.

This rule only reports issues on public constant fields, which can be reached from outside the defining assembly.

Noncompliant code example

Public Class Foo
    Public Const Version = 1.0           ' Noncompliant
End Class

Compliant solution

Public Class Foo
    Public Shared ReadOnly Property Version = 1.0 ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2339.json ================================================ { "title": "Public constant members should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2339", "sqKey": "S2339", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2340.html ================================================

Why is this an issue?

A Do ... Loop without a While or Until condition must be terminated by an unstructured Exit Do statement. It is safer and more readable to use structured loops instead.

Noncompliant code example

Module Module1
    Sub Main()
        Dim i = 1

        Do                        ' Non-Compliant
            If i = 10 Then
                Exit Do
            End If

            Console.WriteLine(i)

            i = i + 1
        Loop
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        For i = 1 To 9            ' Compliant
            Console.WriteLine(i)
        Next
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2340.json ================================================ { "title": "\"Do\" loops should not be used without a \"While\" or \"Until\" condition", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2340", "sqKey": "S2340", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2342.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently. This rule checks that all enum names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression for non-flags enums: ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$

Public Enum foo ' Noncompliant
    FooValue = 0
End Enum

With the default regular expression for flags enums: ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$

<Flags()>
Public Enum Option ' Noncompliant
    None = 0,
    Option1 = 1,
    Option2 = 2
End Enum

Compliant solution

Public Enum Foo
    FooValue = 0
End Enum
<Flags()>
Public Enum Options
    None = 0,
    Option1 = 1,
    Option2 = 2
End Enum
================================================ FILE: analyzers/rspec/vbnet/S2342.json ================================================ { "title": "Enumeration types should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2342", "sqKey": "S2342", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2343.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all enumeration value names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Enum Foo
    fooValue   ' Noncompliant
End Enum

Compliant solution

Enum Foo
    FooValue   ' Compliant
End Enum
================================================ FILE: analyzers/rspec/vbnet/S2343.json ================================================ { "title": "Enumeration values should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2343", "sqKey": "S2343", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2344.html ================================================

Why is this an issue?

The information that an enumeration type is actually an enumeration or a set of flags should not be duplicated in its name.

Noncompliant code example

Enum FooFlags ' Noncompliant
    Foo = 1
    Bar = 2
    Baz = 4
End Enum

Compliant solution

Enum Foo      ' Compliant
    Foo = 1
    Bar = 2
    Baz = 4
End Enum
================================================ FILE: analyzers/rspec/vbnet/S2344.json ================================================ { "title": "Enumeration type names should not have \"Flags\" or \"Enum\" suffixes", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2344", "sqKey": "S2344", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2345.html ================================================

Why is this an issue?

When you annotate an Enum with the Flags attribute, you must not rely on the values that are automatically set by the language to the Enum members, but you should define the enumeration constants in powers of two (1, 2, 4, 8, and so on). Automatic value initialization will set the first member to zero and increment the value by one for each subsequent member. As a result, you won’t be able to use the enum members with bitwise operators.

Exceptions

The default initialization of 0, 1, 2, 3, 4, …​ matches 0, 1, 2, 4, 8 …​ in the first three values, so no issue is reported if the first three members of the enumeration are not initialized.

How to fix it

Define enumeration constants in powers of two, that is, 1, 2, 4, 8, and so on.

Code examples

Noncompliant code example

<Flags()>
Enum FruitType    ' Non-Compliant
  None
  Banana
  Orange
  Strawberry
End Enum

Module Module1
  Sub Main()
    Dim bananaAndStrawberry = FruitType.Banana Or FruitType.Strawberry
    Console.WriteLine(bananaAndStrawberry.ToString()) ' Will display only "Strawberry"
  End Sub
End Module

Compliant solution

<Flags()>
Enum FruitType    ' Compliant
  None = 0
  Banana = 1
  Orange = 2
  Strawberry = 4
End Enum

Module Module1
  Sub Main()
    Dim bananaAndStrawberry = FruitType.Banana Or FruitType.Strawberry
    Console.WriteLine(bananaAndStrawberry.ToString()) ' Will display "Banana, Strawberry"
  End Sub
End Module

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2345.json ================================================ { "title": "Flags enumerations should explicitly initialize all their members", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2345", "sqKey": "S2345", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2346.html ================================================

Why is this an issue?

An enumeration can be decorated with the FlagsAttribute to indicate that it can be used as a bit field: a set of flags, that can be independently set and reset.

For example, the following definition of the day of the week:

<Flags()>
Enum Days
    Monday = 1    ' 0b00000001
    Tuesday = 2   ' 0b00000010
    Wednesday = 4 ' 0b00000100
    Thursday = 8  ' 0b00001000
    Friday = 16   ' 0b00010000
    Saturday = 32 ' 0b00100000
    Sunday = 64   ' 0b01000000
End Enum

allows to define special set of days, such as WeekDays and Weekend using the Or operator:

<Flags()>
Enum Days
    ' ...
    None = 0                                                        ' 0b00000000
    Weekdays = Monday Or Tuesday Or Wednesday Or Thursday Or Friday ' 0b00011111
    Weekend = Saturday Or Sunday                                    ' 0b01100000
    All = Weekdays Or Weekend                                       ' 0b01111111
End Enum

These can be used to write more expressive conditions, taking advantage of bitwise operators and Enum.HasFlag:

Dim someDays = Days.Wednesday | Days.Weekend ' 0b01100100
someDays.HasFlag(Days.Wednesday)             ' someDays contains Wednesday

Dim mondayAndWednesday = Days.Monday Or Days.Wednesday
someDays.HasFlag(mondayAndWednesday)         ' someDays contains Monday and Wednesday
someDays.HasFlag(Days.Monday) OrElse someDays.HasFlag(Days.Wednesday) ' someDays contains Monday or Wednesday
someDays And Days.Weekend <> Days.None       ' someDays overlaps with the weekend
someDays And Days.Weekdays = Days.Weekdays   ' someDays is only made of weekdays

Consistent use of None in flag enumerations indicates that all flag values are cleared. The value 0 should not be used to indicate any other state since there is no way to check that the bit 0 is set.

<Flags()>
Enum Days
    Monday = 0    ' 0 is used to indicate Monday
    Tuesday = 1
    Wednesday = 2
    Thursday = 4
    Friday = 8
    Saturday = 16
    Sunday = 32
    Weekdays = Monday Or Tuesday Or Wednesday Or Thursday Or Friday
    Weekend = Saturday Or Sunday
    All = Weekdays Or Weekend
End Enum

Dim someDays = Days.Wednesday Or Days.Thursday
someDays & Days.Tuesday = Days.Tuesday ' False, because someDays doesn't contains Tuesday
someDays & Days.Monday = Days.Monday   ' True, even though someDays doesn't contains Monday!
someDays.HasFlag(Days.Monday)          ' Same issue as above

How to fix it

Code examples

Noncompliant code example

<Flags()>
Enum FruitType
    Void = 0        ' Non-Compliant
    Banana = 1
    Orange = 2
    Strawberry = 4
End Enum

Compliant solution

<Flags()>
Enum FruitType
    None = 0        ' Compliant
    Banana = 1
    Orange = 2
    Strawberry = 4
End Enum

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S2346.json ================================================ { "title": "Flags enumerations zero-value members should be named \"None\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2346", "sqKey": "S2346", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2347.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all even handler names match a provided regular expression.

The default configuration is:

Event handlers with a handles clause and two-parameter methods with EventArgs second parameter are covered by this rule.

Noncompliant code example

With the default regular expression ^(([a-z][a-z0-9]*)?([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?_)?([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Module Module1
    Sub subject__SomeEvent() Handles X.SomeEvent   ' Noncompliant - two underscores
    End Sub
End Module

Compliant solution

Module Module1
    Sub subject_SomeEvent() Handles X.SomeEvent    ' Compliant
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2347.json ================================================ { "title": "Event handlers should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2347", "sqKey": "S2347", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2348.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all even names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Class Foo
    Event fooEvent() ' Noncompliant
End Class

Compliant solution

Class Foo
    Event FooEvent() ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2348.json ================================================ { "title": "Events should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2348", "sqKey": "S2348", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2349.html ================================================

Why is this an issue?

"After" and "Before" prefixes or suffixes should not be used to indicate pre and post events. The concepts of before and after should be given to events using the present and past tense.

Noncompliant code example

Class Foo
    Event BeforeClose() ' Noncompliant
    Event AfterClose()  ' Noncompliant
End Class

Compliant solution

Class Foo
    Event Closing()     ' Compliant
    Event Closed()      ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2349.json ================================================ { "title": "Event names should not have \"Before\" or \"After\" as a prefix or suffix", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2349", "sqKey": "S2349", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2352.html ================================================

Why is this an issue?

Indexed properties are meant to represent access to a logical collection. When multiple parameters are required, this design guideline may be violated, and refactoring the property into a method is preferable.

Noncompliant code example

Module Module1
    ReadOnly Property Sum(ByVal a As Integer, ByVal b As Integer) ' Noncompliant
        Get
            Return a + b
        End Get
    End Property
End Module

Compliant solution

Module Module1
    Function Sum(ByVal a As Integer, ByVal b As Integer)          ' Compliant
        Return a + b
    End Function
End Module
================================================ FILE: analyzers/rspec/vbnet/S2352.json ================================================ { "title": "Indexed properties with more than one parameter should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2352", "sqKey": "S2352", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2354.html ================================================

Why is this an issue?

To improve the code readability, the explicit line continuation character, _, should not be used. Instead, it is better to break lines after an operator.

Noncompliant code example

Module Module1
    Sub Main()
        ' Noncompliant
        Console.WriteLine("Hello" _
                          & "world")
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()

        Console.WriteLine("Hello" &
                          "world")
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2354.json ================================================ { "title": "Line continuations should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2354", "sqKey": "S2354", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2355.html ================================================

Why is this an issue?

Array literals are more compact than array creation expressions.

Noncompliant code example

Module Module1
    Sub Main()
        Dim foo = New String() {"a", "b", "c"} ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim foo = {"a", "b", "c"}              ' Compliant
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2355.json ================================================ { "title": "Array literals should be used instead of array creation expressions", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2355", "sqKey": "S2355", "scope": "Main", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S2357.html ================================================

Why is this an issue?

Fields should not be part of an API, and therefore should always be private. Indeed, they cannot be added to an interface for instance, and validation cannot be added later on without breaking backward compatibility. Instead, developers should encapsulate their fields into properties. Explicit property getters and setters can be introduced for validation purposes or to smooth the transition to a newer system.

Noncompliant code example

Class Foo
    Public Foo = 42          ' Noncompliant
End Class

Compliant solution

Class Foo
    Public Property Foo = 42 ' Compliant
End Class

Exceptions

Shared and Const fields are ignored.

================================================ FILE: analyzers/rspec/vbnet/S2357.json ================================================ { "title": "Fields should be private", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2357", "sqKey": "S2357", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2358.html ================================================

Why is this an issue?

The ... IsNot ... syntax is more compact and more readable than the Not ... Is ... syntax.

Noncompliant code example

Module Module1
    Sub Main()
        Dim a = Not "a" Is Nothing ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim a = "a" IsNot Nothing  ' Compliant
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2358.json ================================================ { "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2358", "sqKey": "S2358", "scope": "Main", "quickfix": "covered", "title": "\"IsNot\" should be used instead of \"Not ... Is ...\"" } ================================================ FILE: analyzers/rspec/vbnet/S2359.html ================================================

Why is this an issue?

Prefer the use of Try ... Catch blocks instead of On Error statements.

Visual Basic .NET and Visual Basic 2005 offer structured exception handling that provides a powerful, more readable alternative to the On Error Goto error handling from previous versions of Microsoft Visual Basic. Structured exception handling is more powerful because it allows you to nest error handlers inside other error handlers within the same procedure. Furthermore, structured exception handling uses a block syntax similar to the If...Else...End If statement. This makes Visual Basic .NET and Visual Basic 2005 code more readable and easier to maintain.

Noncompliant code example

Sub DivideByZero()
  On Error GoTo nextstep
  Dim result As Integer
  Dim num As Integer
  num = 100
  result = num / 0
nextstep:
  System.Console.WriteLine("Error")
End Sub

Compliant solution

Sub DivideByZero()
  Try
    Dim result As Integer
    Dim num As Integer
    num = 100
    result = num / 0
  Catch
    System.Console.WriteLine("Error")
  End Try
End Sub
================================================ FILE: analyzers/rspec/vbnet/S2359.json ================================================ { "title": "\"On Error\" statements should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2359", "sqKey": "S2359", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2360.html ================================================

Why is this an issue?

The overloading mechanism should be used in place of optional parameters for several reasons:

Noncompliant code example

Sub Notify(ByVal Company As String, Optional ByVal Office As String = "QJZ") ' Noncompliant

End Sub

Compliant solution

Sub Notify(ByVal Company As String)
  Notify(Company, "QJZ")
End Sub

Sub Notify(ByVal Company As String, ByVal Office As String)

End Sub

Exceptions

The rule ignores non externally visible methods.

================================================ FILE: analyzers/rspec/vbnet/S2360.json ================================================ { "title": "Optional parameters should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2360", "sqKey": "S2360", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2362.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all Private Const field names comply with the provided regular expression.

The default configuration is:

Noncompliant code example

With the default regular expression ^(s_|_)?[a-z][a-z0-9]*([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Module Module1
    Private Const Foo = 0  ' Noncompliant
End Module

Compliant solution

Module Module1
    Private Const foo = 0  ' Compliant
End Module
================================================ FILE: analyzers/rspec/vbnet/S2362.json ================================================ { "title": "Private constants should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2362", "sqKey": "S2362", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2363.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all Private Shared ReadOnly field names comply with the provided regular expression.

The default configuration is:

Noncompliant code example

With the default regular expression ^(s_|_)?[a-z][a-z0-9]*([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Class Foo
    Private Shared ReadOnly Foo As Integer  ' Noncompliant
End Class

Compliant solution

Class Foo
    Private Shared ReadOnly foo As Integer  ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2363.json ================================================ { "title": "\"Private Shared ReadOnly\" fields should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2363", "sqKey": "S2363", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2364.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all Private field names match the provided regular expression.

Note that this rule does not apply to Private Shared ReadOnly fields, which are checked by another rule.

The default configuration is:

Noncompliant code example

With the default regular expression ^(s_|_)?[a-z][a-z0-9]*([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Class Foo
    Private Foo As Integer  ' Noncompliant
End Class

Compliant solution

Class Foo
    Private foo As Integer  ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2364.json ================================================ { "title": "\"Private\" fields should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2364", "sqKey": "S2364", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2365.html ================================================

Why is this an issue?

Most developers expect property access to be as efficient as field access. However, if a property returns a copy of an array or collection, it will be much slower than a simple field access, contrary to the caller’s likely expectations. Therefore, such properties should be refactored into methods so that callers are not surprised by the unexpectedly poor performance.

This rule tracks calls to the following methods inside properties:

How to fix it

Code examples

Noncompliant code example

Private fFoo As New List(Of String) From {"a", "b", "c"}
Private fBar As String() = {"a", "b", "c"}

Public ReadOnly Property Foo() As IEnumerable(Of String) ' Noncompliant: collection fFoo is copied
    Get
        Return fFoo.ToList()
    End Get
End Property

Public ReadOnly Property Bar() As IEnumerable(Of String) ' Noncompliant: array fBar is copied
    Get
        Return DirectCast(fBar.Clone(), String())
    End Get
End Property

Compliant solution

Private fFoo As New List(Of String) From {"a", "b", "c"}
Private fBar As String() = {"a", "b", "c"}

Public Function GetFoo() As IEnumerable(Of String)
    Return fFoo.ToList()
End Function

Public Function GetBar() As IEnumerable(Of String)
    Return DirectCast(fBar.Clone(), String())
End Function

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2365.json ================================================ { "title": "Properties should not make collection or array copies", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "api-design", "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2365", "sqKey": "S2365", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2366.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that property names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Module Module1
    Public Property foo As Integer   ' Noncompliant
End Module

Compliant solution

Module Module1
    Public Property Foo As Integer   ' Compliant
End Module
================================================ FILE: analyzers/rspec/vbnet/S2366.json ================================================ { "title": "Properties should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2366", "sqKey": "S2366", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2367.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all non-private Const field names comply with the provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Module Module1
    Public Const foo = 0  ' Noncompliant
End Module

Compliant solution

Module Module1
    Public Const Foo = 0  ' Compliant
End Module
================================================ FILE: analyzers/rspec/vbnet/S2367.json ================================================ { "title": "Non-private constants should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2367", "sqKey": "S2367", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2368.html ================================================

Why is this an issue?

Using multidimensional and jagged arrays as method parameters in C# can be challenging for developers.

When these methods are exposed to external users, it requires advanced language knowledge for effective usage.

Determining the appropriate data to pass to these parameters may not be intuitive.

Public Class Program
    Public Sub WriteMatrix(matrix As Integer()()) ' Noncompliant: data type is not intuitive
        ' ...
    End Sub
End Class

In this example, it cannot be inferred easily what the matrix should look like. Is it a 2x2 Matrix or even a triangular Matrix?

Using a collection, data structure, or class that provides a more suitable representation of the required data is recommended instead of a multidimensional array or jagged array to enhance code readability.

Public Class Matrix2x2
    ' ...
End Class

Public Class Program
    Public Sub WriteMatrix(matrix As Matrix2x2) ' Compliant: data type is intuitive
        ' ...
    End Sub
End Class

As a result, avoiding exposing such methods to external users is recommended.

Exceptions

However, using multidimensional and jagged array method parameters internally, such as in private or internal methods or within internal classes, is compliant since they are not publicly exposed.

Public Class FirstClass
    Private Sub UpdateMatrix(matrix As Integer()()) ' Compliant: method is private
        ' ...
    End Sub
End Class

Friend Class SecondClass
    Public Sub UpdateMatrix(matrix As Integer()()) ' Compliant: class is internal
        ' ...
    End Sub
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2368.json ================================================ { "title": "Public methods should not have multidimensional array parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [ "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2368", "sqKey": "S2368", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2369.html ================================================

Why is this an issue?

Shared coding conventions allow teams to collaborate efficiently. This rule checks that all non-private fields names match a provided regular expression.

Note that this rule does not apply to non-private Shared ReadOnly fields, for which there is another rule.

The default configuration is:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Class Foo
    Public foo As Integer  ' Noncompliant
End Class

Compliant solution

Class Foo
    Public Foo As Integer  ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2369.json ================================================ { "title": "Non-private fields should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2369", "sqKey": "S2369", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2370.html ================================================

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently. This rule checks that all non-private Shared ReadOnly fields names match a provided regular expression.

The default configuration is:

Noncompliant code example

With the default regular expression ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$:

Class Foo
    Public Shared ReadOnly foo As Integer  ' Noncompliant
End Class

Compliant solution

Class Foo
    Public Shared ReadOnly Foo As Integer  ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2370.json ================================================ { "title": "Non-private \"Shared ReadOnly\" fields should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2370", "sqKey": "S2370", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2372.html ================================================

Why is this an issue?

Property getters should be simple operations that are always safe to call. If exceptions need to be thrown, it is best to convert the property to a method.

It is valid to throw exceptions from indexed property getters and from property setters, which are not detected by this rule.

Noncompliant code example

Module Module1
    Public Property Foo() As Integer
        Get
            Throw New Exception  ' Non-Compliant
        End Get
        Set(ByVal value As Integer)
            ' ... some code ...
        End Set
    End Property
End Module

Compliant solution

Module Module1
    Sub SetFoo(ByVal value As Integer)         ' Compliant
        ' ... some code ...
    End Sub
End Module

Exceptions

No issue is raised when the thrown exception derives from or is of type NotImplementedException, NotSupportedException or InvalidOperationException.

================================================ FILE: analyzers/rspec/vbnet/S2372.json ================================================ { "title": "Exceptions should not be thrown from property getters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "error-handling" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2372", "sqKey": "S2372", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2373.html ================================================

This rule is deprecated; use {rule:vbnet:S119} instead.

Why is this an issue?

Shared naming conventions allow teams to collaborate efficiently. This rule checks that all generic type parameter names match a provided regular expression.

The default configuration is the one recommended by Microsoft:

Noncompliant code example

With the default parameter value ^T(([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?)?$:

Public Class Foo(Of t) ' Noncompliant
End Class

Compliant solution

Public Class Foo(Of T) ' Compliant
End Class
================================================ FILE: analyzers/rspec/vbnet/S2373.json ================================================ { "title": "Generic type parameter names should comply with a naming convention", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "IDENTIFIABLE" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2373", "sqKey": "S2373", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2374.html ================================================

Why is this an issue?

Unsigned integers have different arithmetic operators than signed ones - operators that few developers understand. Therefore, signed types should be preferred where possible.

Noncompliant code example

Module Module1
    Sub Main()
        Dim foo1 As UShort   ' Noncompliant
        Dim foo2 As UInteger ' Noncompliant
        Dim foo3 As ULong    ' Noncompliant
    End Sub
End Module

Compliant solution

Module Module1
    Sub Main()
        Dim foo1 As Short
        Dim foo2 As Integer
        Dim foo3 As Long
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2374.json ================================================ { "title": "Signed types should be preferred to unsigned ones", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2374", "sqKey": "S2374", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2375.html ================================================

Why is this an issue?

Using the With statement for a series of calls to the same object makes the code more readable.

Noncompliant code example

With the default value of 6:

Module Module1
    Dim product = New With {.Name = "paperclips", .RetailPrice = 1.2, .WholesalePrice = 0.6, .A = 0, .B = 0, .C = 0}

    Sub Main()
        product.Name = ""           ' Noncompliant
        product.RetailPrice = 0
        product.WholesalePrice = 0
        product.A = 0
        product.B = 0
        product.C = 0
    End Sub
End Module

Compliant solution

Module Module1
    Dim product = New With {.Name = "paperclips", .RetailPrice = 1.2, .WholesalePrice = 0.6, .A = 0, .B = 0, .C = 0}

    Sub Main()
        With product
            .Name = ""
            .RetailPrice = 0
            .WholesalePrice = 0
            .A = 0
            .B = 0
            .C = 0
        End With
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2375.json ================================================ { "title": "\"With\" statements should be used for a series of calls to the same object", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2375", "sqKey": "S2375", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2376.html ================================================

Why is this an issue?

Properties with only setters are confusing and counterintuitive. Instead, a property getter should be added if possible, or the property should be replaced with a setter method.

Noncompliant code example

Module Module1
    WriteOnly Property Foo() As Integer ' Non-Compliant
        Set(ByVal value As Integer)
            ' ... some code ...
        End Set
    End Property
End Module

Compliant solution

Module Module1
    Sub SetFoo(ByVal value As Integer)  ' Compliant
        ' ... some code ...
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2376.json ================================================ { "title": "Write-only properties should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2376", "sqKey": "S2376", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2387.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

Having a variable with the same name in two unrelated classes is fine, but do the same thing within a class hierarchy and you’ll get confusion at best, chaos at worst.

Noncompliant code example

Public Class Fruit

    Protected Ripe As Season
    Protected Flesh As Color

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    Private Ripe As Boolean         ' Noncompliant
    Private Shared FLESH As Color   ' Noncompliant

    ' ...

End Class

Compliant solution

Public Class Fruit

    Protected Ripe As Season
    Protected Flesh As Color

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    Private Riped As Boolean
    Private Shared FLESH_COLOR As Color   ' Noncompliant

    ' ...

End Class

Exceptions

This rule ignores same-name fields that are Shared in both the parent and child classes. It also ignores Private parent class fields and fields explicitly declared as Shadows, but in all other such cases, the child class field should be renamed.

Public Class Fruit

    Private Ripe As Season
    Protected Flesh As Color

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    Private Ripe As Season      ' Compliant as parent field 'Ripe' is not visible from Raspberry anyway
    Protected Shadows Flesh As Color    ' Compliant as the intention is explicitly declared

    ' ...

End Class
================================================ FILE: analyzers/rspec/vbnet/S2387.json ================================================ { "title": "Child class fields should not shadow parent class fields", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "CLEAR" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2387", "sqKey": "S2387", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2429.html ================================================

Why is this an issue?

The ... = {} syntax is more compact, more readable and less error-prone.

Noncompliant code example

Module Module1
  Sub Main()
    Dim foo(1) As String   ' Noncompliant
    foo(0) = "foo"
    foo(1) = "bar"
  End Sub
End Module

Compliant solution

Module Module1
  Sub Main()
    Dim foo = {"foo", "bar"}  ' Compliant
  End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2429.json ================================================ { "title": "Arrays should be initialized using the \"... \u003d {}\" syntax", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2429", "sqKey": "S2429", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2437.html ================================================

Why is this an issue?

Certain bitwise operations are not needed and should not be performed because their results are predictable.

Specifically, using And -1 with any value always results in the original value.

That is because the binary representation of -1 on a numeric data type supporting negative numbers, such as Integer or Long, is based on two’s complement and made of all 1s: &B111…​111.

Performing And between a value and &B111…​111 means applying the And operator to each bit of the value and the bit 1, resulting in a value equal to the provided one, bit by bit.

anyValue And -1 ' Noncompliant
anyValue        ' Compliant

Similarly, anyValue Or 0 always results in anyValue, because the binary representation of 0 is always &B000…​000 and the Or operator returns its first input when the second is 0.

anyValue Or 0 ' Noncompliant
anyValue      ' Compliant

The same applies to anyValue Xor 0: the Xor operator returns 1 when its two input bits are different (1 and 0 or 0 and 1) and returns 0 when its two input bits are the same (both 0 or both 1). When Xor is applied with 0, the result would be 1 if the other input is 1, because the two input bits are different, and 0 if the other input bit is 0, because the two input are the same. That results in returning anyValue.

anyValue Xor 0 ' Noncompliant
anyValue       ' Compliant

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S2437.json ================================================ { "title": "Unnecessary bit operations should not be performed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-2437", "sqKey": "S2437", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S2551.html ================================================

The instance passed to the SyncLock statement should be a dedicated private field.

Why is this an issue?

If the instance representing an exclusively acquired lock is publicly accessible, another thread in another part of the program could accidentally attempt to acquire the same lock. This increases the likelihood of deadlocks.

For example, a string should never be used for locking. When a string is interned by the runtime, it can be shared by multiple threads, breaking the locking mechanism.

Instead, a dedicated private Lock object instance (or object instance, for frameworks before .Net 9) should be used for locking. This minimizes access to the lock instance and therefore prevents accidential lock sharing.

The following objects are considered potentially prone to accidental lock sharing:

How to fix it

Code examples

Noncompliant code example

Public Sub MyLockingMethod()
    SyncLock Me 'Noncompliant
        ' ...
    End SyncLock
End Sub

Compliant solution

Private lockObj As New Object()
Public Sub MyLockingMethod()
    SyncLock lockObj
        ' ...
    End SyncLock
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2551.json ================================================ { "title": "Shared resources should not be used for locking", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "multi-threading" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2551", "sqKey": "S2551", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2583.html ================================================

Why is this an issue?

Conditional expressions which are always true or false can lead to unreachable code.

In the case below, the call of Dispose() never happens.

Dim a = False

If a Then
    Dispose() ' Never reached
End If

Exceptions

This rule will not raise an issue in either of these cases:

Const debug = False
'...
If debug Then
    ' Print something
End If

In these cases, it is obvious the code is as intended.

How to fix it

The conditions should be reviewed to decide whether:

Code examples

Noncompliant code example

Public Sub Sample(ByVal b As Boolean)
    Dim a = False
    If a Then                   ' Noncompliant: The true branch is never reached
        DoSomething()           ' Never reached
    End If

    If Not a OrElse b Then      ' Noncompliant: "not a" is always "True" and the false branch is never reached
        DoSomething()
    Else
        DoSomethingElse()       ' Never reached
    End If

    Dim c = "xxx"
    Dim res = If(c, "value")    ' Noncompliant: d is always not Nothing, "value" is never used
End Sub

Compliant solution

Public Sub Sample(ByVal b As Boolean)
    Dim a = False
    If Foo(a) Then             ' Condition was updated
        DoSomething()
    End If

    If b Then                  ' Parts of the condition were removed.
        DoSomething()
    Else
        DoSomethingElse()
    End If

    Dim c = "xxx"
    Dim res = c                ' "value" was removed
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2583.json ================================================ { "title": "Conditionally executed code should be reachable", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "unused", "suspicious", "pitfall", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2583", "sqKey": "S2583", "scope": "All", "securityStandards": { "CWE": [ 489, 571, 570 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2589.html ================================================

Gratuitous boolean expressions are conditions that do not change the evaluation of a program. This issue can indicate logical errors and affect the correctness of an application, as well as its maintainability.

Why is this an issue?

Control flow constructs like if-statements allow the programmer to direct the flow of a program depending on a boolean expression. However, if the condition is always true or always false, only one of the branches will ever be executed. In that case, the control flow construct and the condition no longer serve a purpose; they become gratuitous.

What is the potential impact?

The presence of gratuitous conditions can indicate a logical error. For example, the programmer intended to have the program branch into different paths but made a mistake when formulating the branching condition. In this case, this issue might result in a bug and thus affect the reliability of the application. For instance, it might lead to the computation of incorrect results.

Additionally, gratuitous conditions and control flow constructs introduce unnecessary complexity. The source code becomes harder to understand, and thus, the application becomes more difficult to maintain.

This rule looks for operands of a boolean expression never changing the result of the expression. It also applies to the null conditional operator when one of the operands always evaluates to Nothing.

Dim d As String = Nothing
Dim v1 = If(d, "value")

Exceptions

This rule will not raise an issue in either of these cases:

Const debug = False
'...
If debug Then
    ' Print something
End If

In these cases, it is obvious the code is as intended.

How to fix it

Gratuitous boolean expressions are suspicious and should be carefully removed from the code.

First, the boolean expression in question should be closely inspected for logical errors. If a mistake was made, it can be corrected so the condition is no longer gratuitous.

If it becomes apparent that the condition is actually unnecessary, it can be removed. The associated control flow construct (e.g., the if-statement containing the condition) will be adapted or even removed, leaving only the necessary branches.

Code examples

Noncompliant code example

Public Sub Sample(ByVal b As Boolean, ByVal c As Boolean)
    Dim a = True
    If a Then                  ' Noncompliant: "a" is always "true"
        DoSomething()
    End If

    If b AndAlso a Then        ' Noncompliant: "a" is always "true"
        DoSomething()
    End If

    If c OrElse Not a Then     ' Noncompliant: "Not a" is always "false"
        DoSomething()
    End If

    Dim d As String = Nothing
    Dim v1 = If(d, "value")    ' Noncompliant: "d" is always Nothing and v1 is always "value".
    Dim v2 = If(s, d)          ' Noncompliant: "d" is always Nothing and v2 is always equal to s.
End Sub

Compliant solution

Public Sub Sample(ByVal b As Boolean, ByVal c As Boolean, ByVal s As String)
    Dim a = IsAllowed()
    If a Then                   ' Compliant
        DoSomething()
    End If

    If b AndAlso a Then         ' Compliant
        DoSomething()
    End If

    If c OrElse Not a Then      ' Compliant
        DoSomething()
    End If

    Dim d As String = GetStringData()
    Dim v1 = If(d, "value")     ' Compliant
    Dim v2 = If(s, d)           ' Compliant
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2589.json ================================================ { "title": "Boolean expressions should not be gratuitous", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "suspicious", "redundant", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2589", "sqKey": "S2589", "scope": "All", "securityStandards": { "CWE": [ 489, 571, 570 ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2612.html ================================================

Why is this an issue?

In Windows, "Everyone" group is similar and includes all members of the Authenticated Users group as well as the built-in Guest account, and several other built-in security accounts.

Granting permissions to this category can lead to unintended access to files or directories that could allow attackers to obtain sensitive information, disrupt services or elevate privileges.

What is the potential impact?

Unauthorized access to sensitive information

When file or directory permissions grant access to all users on a system (often represented as "others" or "everyone" in permission models), attackers who gain access to any user account can read sensitive files containing credentials, configuration data, API keys, database passwords, personal information, or proprietary business data. This exposure can lead to data breaches, identity theft, compliance violations, and competitive disadvantage.

Service disruption and data corruption

Granting write permissions to broad user categories allows any user on the system to modify or delete critical files and directories. Attackers or compromised low-privileged accounts can corrupt application data, modify configuration files to alter system behavior or disrupt services, or delete important resources, leading to service outages, system instability, data loss, and denial of service.

Privilege escalation

When executable files or scripts have overly permissive permissions, especially when combined with special permission bits that allow programs to execute with the permissions of the file owner or group rather than the executing user, attackers can replace legitimate executables with malicious code. When these modified files are executed by privileged users or processes, the attacker’s code runs with elevated privileges, potentially enabling them to escalate from a low-privileged account to root or administrator access, install backdoors, or pivot to other systems in the network.

How to fix it in .NET Framework

Replace the AccessControlType.Allow with AccessControlType.Deny when creating file system access rules for the "Everyone" group. This explicitly denies permissions rather than granting them, ensuring that broad user groups cannot access the file.

Code examples

Noncompliant code example

Dim unsafeAccessRule = new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Allow)

Dim fileSecurity = File.GetAccessControl("path")
fileSecurity.AddAccessRule(unsafeAccessRule) ' Noncompliant
File.SetAccessControl("fileName", fileSecurity)

Compliant solution

Dim safeAccessRule = new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Deny)

Dim fileSecurity = File.GetAccessControl("path")
fileSecurity.AddAccessRule(safeAccessRule)
File.SetAccessControl("path", fileSecurity)

How to fix it in .NET

Use AccessControlType.Deny instead of AccessControlType.Allow when setting access rules for the "Everyone" group. This prevents the broad group from having write or full control access to files.

Code examples

Noncompliant code example

Dim accessRule   = new FileSystemAccessRule("Everyone", FileSystemRights.Write, AccessControlType.Allow)
Dim fileInfo     = new FileInfo("path")
Dim fileSecurity = fileInfo.GetAccessControl()

fileSecurity.SetAccessRule(accessRule) ' Noncompliant
fileInfo.SetAccessControl(fileSecurity)

Compliant solution

Dim accessRule   = new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Deny)
Dim fileInfo     = new FileInfo("path")
Dim fileSecurity = fileInfo.GetAccessControl()

fileSecurity.SetAccessRule(accessRule)
fileInfo.SetAccessControl(fileSecurity)

How to fix it in Mono

Avoid setting permissions that grant read, write, or execute access to "others" (all users). Instead, restrict permissions to the file owner or specific groups. Use FileAccessPermissions.UserExecute or other restrictive permission flags that limit access to the owner only.

Code examples

Noncompliant code example

Dim fsEntry = UnixFileSystemInfo.GetFileSystemEntry("path")
fsEntry.FileAccessPermissions = FileAccessPermissions.OtherReadWriteExecute ' Noncompliant

Compliant solution

Dim fsEntry = UnixFileSystemInfo.GetFileSystemEntry("path")
fsEntry.FileAccessPermissions = FileAccessPermissions.UserExecute

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/vbnet/S2612.json ================================================ { "title": "File permissions should not be set to world-accessible values", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "former-hotspot" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2612", "sqKey": "S2612", "scope": "Main", "securityStandards": { "CWE": [ 732, 266 ], "OWASP": [ "A5" ], "OWASP Top 10 2021": [ "A1", "A4" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "4.3.3" ], "STIG ASD_V5R3": [ "V-222430" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2692.html ================================================

Why is this an issue?

Most checks against an IndexOf value compare it with -1 because 0 is a valid index.

strings.IndexOf(someString) = -1  ' Test for "index not found"
strings.IndexOf(someString) < 0   ' Test for "index not found"
strings.IndexOf(someString) >= 0  ' Test for "index found"

Any checks which look for values > 0 ignore the first element, which is likely a bug. If the intent is merely to check the inclusion of a value in a String, List, or array, consider using the Contains method instead.

strings.Contains(someString) ' Boolean result

This rule raises an issue when the output value of any of the following methods is tested against > 0:

someArray.IndexOf(someItem) > 0        ' Noncompliant: index 0 missing
someString.IndexOfAny(charsArray) > 0  ' Noncompliant: index 0 missing
someList.LastIndexOf(someItem) > 0     ' Noncompliant: index 0 missing
someString.LastIndexOf(charsArray) > 0 ' Noncompliant: index 0 missing

How to fix it

Code examples

Noncompliant code example

Dim Color As String = "blue"
Dim Name As String = "ishmael"

Dim Strings As New List(Of String)
Strings.Add(Color)
Strings.Add(Name)
Dim StringArray As String() = Strings.ToArray()

If Strings.IndexOf(Color) > 0 Then ' Noncompliant
  ' ...
End If

If Name.IndexOf("ish") > 0 Then ' Noncompliant
  ' ...
End If

If Name.IndexOf("ae") > 0 Then ' Noncompliant
  ' ...
End If

If Array.IndexOf(StringArray, Color) > 0 Then ' Noncompliant
  ' ...
End If

Compliant solution

Dim Color As String = "blue"
Dim Name As String = "ishmael"

Dim Strings As New List(Of String)
Strings.Add(Color)
Strings.Add(Name)
Dim StringArray As String() = Strings.ToArray()

If Strings.IndexOf(Color) > -1 Then
  ' ...
End If

If Name.IndexOf("ish") >= 0 Then
  ' ...
End If

If Name.Contains("ae") Then
  ' ...
End If

If Array.IndexOf(StringArray, Color) >= 0 Then
  ' ...
End If

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2692.json ================================================ { "title": "\"IndexOf\" checks should not be for positive numbers", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-2692", "sqKey": "S2692", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S2737.html ================================================

Why is this an issue?

A Catch clause that only rethrows the caught exception has the same effect as omitting the Catch altogether and letting it bubble up automatically.

Dim s As String = ""
Try
    s = File.ReadAllText(fileName)
Catch e As Exception
    Throw
End Try

Such clauses should either be removed or populated with the appropriate logic.

Dim s As String = File.ReadAllText(fileName)

or

Dim s As String = ""
Try
    s = File.ReadAllText(fileName)
Catch e As Exception
    logger.LogError(e)
    Throw
End Try

Exceptions

This rule will not generate issues for Catch blocks if they are followed by a Catch block for a more general exception type that does more than just rethrowing the exception.

Dim s As String = ""
Try
    s = File.ReadAllText(fileName)
Catch e As IOException 'Compliant by exception: removing it would change the logic
    Throw
Catch e As Exception 'Compliant: does more than just rethrow
    logger.LogError(e)
    Throw
End Try
================================================ FILE: analyzers/rspec/vbnet/S2737.json ================================================ { "title": "\"catch\" clauses should do more than rethrow", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "error-handling", "unused", "finding", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2737", "sqKey": "S2737", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2757.html ================================================

Why is this an issue?

Using operator pairs (=+ or =-) that look like reversed single operators (+= or -=) is confusing. They compile and run but do not produce the same result as their mirrored counterpart.

Dim target As Integer = -5
Dim num As Integer = 3

target =- num ' Noncompliant: target = -3. Is that the intended behavior?
target =+ num ' Noncompliant: target = 3

This rule raises an issue when =+ or =- are used without any space between the operators and when there is at least one whitespace after.

Replace the operators with a single one if that is the intention

Dim num As Integer = 3

target -= num  ' target = -8

Or fix the spacing to avoid confusion

Dim num As Integer = 3

target = -num  // target = -3
================================================ FILE: analyzers/rspec/vbnet/S2757.json ================================================ { "title": "Non-existent operators like \"\u003d+\" should not be used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2757", "sqKey": "S2757", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2761.html ================================================

Why is this an issue?

The repetition of the Not operator is usually a typo. The second operator invalidates the first one:

Dim b As Boolean = False
Dim c As Boolean = Not Not b 'Noncompliant: equivalent to "b"
================================================ FILE: analyzers/rspec/vbnet/S2761.json ================================================ { "title": "\u0027Not\u0027 boolean operator should not be repeated", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2761", "sqKey": "S2761", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S2925.html ================================================

Why is this an issue?

Using Thread.Sleep in a test might introduce unpredictable and inconsistent results depending on the environment. Furthermore, it will block the thread, which means the system resources are not being fully used.

<TestMethod>
Public Sub SomeTest()
    Threading.Thread.Sleep(500) ' Noncompliant
    ' assertions...
End Sub

An alternative is a task-based asynchronous approach, using async and await.

More specifically the Task.Delay method should be used, because of the following advantages:

<TestMethod>
Public Async Function SomeTest() As Task
    Await Task.Delay(500)
    ' assertions...
End Function

Another scenario is when some data might need to be mocked using Moq, and a delay needs to be introduced:

<TestMethod>
Public Sub UserService_Test()
    Dim UserService As New Mock(Of UserService)
    Dim Expected As New User
    UserService.Setup(Function(X) X.GetUserById(42)).Returns(
        Function()
            Threading.Thread.Sleep(500) ' Noncompliant
            Return Task.FromResult(Expected)
        End Function)
    ' assertions...
End Sub

An alternative to Thread.Sleep while mocking with Moq is to use ReturnsAsync and pass the amount of time to delay there:

<TestMethod>
Public Sub UserService_Test()
    Dim UserService As New Mock(Of UserService)
    Dim Expected As New User
    UserService.Setup(Function(X) X.GetUserById(42)).ReturnsAsync(Expected, TimeSpan.FromMilliseconds(500))
    ' assertions...
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S2925.json ================================================ { "title": "\"Thread.Sleep\" should not be used in tests", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "tests", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-2925", "sqKey": "S2925", "scope": "Tests", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S2951.html ================================================

Why is this an issue?

Visual Basic .NET, unlike many other programming languages, has no "fall-through" for its Select cases. Each case already has an implicit Exit Select as its last instruction. It therefore is redundant to explicitly add one.

Noncompliant code example

Module Module1
  Sub Main()
    Dim x = 0
    Select Case x
      Case 0
        Console.WriteLine("0")
        Exit Select                ' Noncompliant
      Case Else
        Console.WriteLine("Not 0")
        Exit Select                ' Noncompliant
    End Select
  End Sub
End Module

Compliant solution

Module Module1
  Sub Main()
    Dim x = 0
    Select Case x
      Case 0                         ' Compliant
        Console.WriteLine("0")
      Case Else                      ' Compliant
        Console.WriteLine("Not 0")
    End Select
  End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S2951.json ================================================ { "title": "\"Exit Select\" statements should not be used redundantly", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "unused", "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-2951", "sqKey": "S2951", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3011.html ================================================

Why is this an issue?

Altering or bypassing the accessibility of classes, methods, or fields through reflection violates the encapsulation principle. This can break the internal contracts of the accessed target and lead to maintainability issues and runtime errors.

This rule raises an issue when reflection is used to change the visibility of a class, method or field, and when it is used to directly update a field value.

Imports System.Reflection

Dim dynClass = Type.GetType("MyInternalClass")
' Sensitive. Using BindingFlags.NonPublic will return non-public members
Dim bindingAttr As BindingFlags = BindingFlags.NonPublic Or BindingFlags.Static
Dim dynMethod As MethodInfo = dynClass.GetMethod("mymethod", bindingAttr)
Dim result = dynMethod.Invoke(dynClass, Nothing)

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3011.json ================================================ { "title": "Reflection should not be used to increase accessibility of classes, methods, or fields", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3011", "sqKey": "S3011", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3063.html ================================================

Why is this an issue?

StringBuilder instances that never build a string clutter the code and worse are a drag on performance. Either they should be removed, or the missing ToString() call should be added.

Noncompliant code example

Public Sub DoSomething(ByVal strings As List(Of String))
    Dim sb As StringBuilder = New StringBuilder() ' Noncompliant
    sb.Append("Got: ")

    For Each str As String In strings
        sb.Append(str).Append(", ")
    Next
End Sub

Compliant solution

Public Sub DoSomething(ByVal strings As List(Of String))
    For Each str As String In strings
    Next
End Sub

or

Public Sub DoSomething(ByVal strings As List(Of String))
    Dim sb As StringBuilder = New StringBuilder()
    sb.Append("Got: ")

    For Each str As String In strings
        sb.Append(str).Append(", ")
    Next

    My.Application.Log.WriteEntry(sb.ToString())
End Sub

Exceptions

No issue is reported when StringBuilder is:

================================================ FILE: analyzers/rspec/vbnet/S3063.json ================================================ { "title": "\"StringBuilder\" data should be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3063", "sqKey": "S3063", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3329.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

In the mode Cipher Block Chaining (CBC), each block is used as cryptographic input for the next block. For this reason, the first block requires an initialization vector (IV), also called a "starting variable" (SV).

If the same IV is used for multiple encryption sessions or messages, each new encryption of the same plaintext input would always produce the same ciphertext output. This may allow an attacker to detect patterns in the ciphertext.

What is the potential impact?

After retrieving encrypted data and performing cryptographic attacks on it on a given timeframe, attackers can recover the plaintext that encryption was supposed to protect.

Depending on the recovered data, the impact may vary.

Below are some real-world scenarios that illustrate the potential impact of an attacker exploiting the vulnerability.

Additional attack surface

By modifying the plaintext of the encrypted message, an attacker may be able to trigger additional vulnerabilities in the code. An attacker can further exploit a system to obtain more information.
Encrypted values are often considered trustworthy because it would not be possible for a third party to modify them under normal circumstances.

Breach of confidentiality and privacy

When encrypted data contains personal or sensitive information, its retrieval by an attacker can lead to privacy violations, identity theft, financial loss, reputational damage, or unauthorized access to confidential systems.

In this scenario, a company, its employees, users, and partners could be seriously affected.

The impact is twofold, as data breaches and exposure of encrypted data can undermine trust in the organization, as customers, clients and stakeholders may lose confidence in the organization’s ability to protect their sensitive data.

Legal and compliance issues

In many industries and locations, there are legal and compliance requirements to protect sensitive data. If encrypted data is compromised and the plaintext can be recovered, companies face legal consequences, penalties, or violations of privacy laws.

How to fix it in .NET

Code examples

Noncompliant code example

Imports System.IO
Imports System.Security.Cryptography

Public Sub Encrypt(key As Byte(), dataToEncrypt As Byte(), target As MemoryStream)
    Dim aes = New AesCryptoServiceProvider()

    Dim iv = New Byte() {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    Dim encryptor = aes.CreateEncryptor(key, iv) ' Noncompliant

    Dim cryptoStream = New CryptoStream(target, encryptor, CryptoStreamMode.Write)
    Dim swEncrypt = New StreamWriter(cryptoStream)

    swEncrypt.Write(dataToEncrypt)
End Sub

Compliant solution

In this example, the code implicitly uses a number generator that is considered strong, thanks to aes.IV.

Imports System.IO
Imports System.Security.Cryptography

Public Sub Encrypt(key As Byte(), dataToEncrypt As Byte(), target As MemoryStream)
    Dim aes = New AesCryptoServiceProvider()

    Dim encryptor = aes.CreateEncryptor(key, aes.IV)

    Dim cryptoStream = New CryptoStream(target, encryptor, CryptoStreamMode.Write)
    Dim swEncrypt = New StreamWriter(cryptoStream)

    swEncrypt.Write(dataToEncrypt)
End Sub

How does this work?

Use unique IVs

To ensure high security, initialization vectors must meet two important criteria:

The IV does not need be secret, so the IV or information sufficient to determine the IV may be transmitted along with the ciphertext.

In the previous non-compliant example, the problem is not that the IV is hard-coded.
It is that the same IV is used for multiple encryption attempts.

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S3329.json ================================================ { "title": "Cipher Block Chaining IVs should be unpredictable", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3329", "sqKey": "S3329", "scope": "Main", "securityStandards": { "CWE": [ 327, 780 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3358.html ================================================

Why is this an issue?

Nested ternaries are hard to read and can make the order of operations complex to understand.

Public Function GetReadableStatus(job As Job) As String
    Return If(job.IsRunning, "Running", If(job.HasErrors, "Failed", "Succeeded")) ' Noncompliant
End Function

Instead, use another line to express the nested operation in a separate statement.

Public Function GetReadableStatus(job As Job) As String
    If job.IsRunning Then Return "Running"
    Return If(job.HasErrors, "Failed", "Succeeded")
End Function
================================================ FILE: analyzers/rspec/vbnet/S3358.json ================================================ { "title": "If operators should not be nested", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3358", "sqKey": "S3358", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3363.html ================================================

You should only set a property of a temporal type (like DateTime or DateTimeOffset) as the primary key of a table if the values are guaranteed to be unique.

Why is this an issue?

Using temporal types as the primary key of a table is risky. When these types are used as primary keys, it usually means that each new key is created with the use of .Now or .UtcNow properties from DateTime and DateTimeOffset classes. In those cases, duplicate keys exceptions may occur in many ways:

The rule raises an issue if:

How to fix it

Either use a GUID or the auto generated ID as a primary key.

Code examples

Noncompliant code example

Public Class Account
    Public Property Id As DateTime

    Public Property Name As String
    Public Property Surname As String
End Class

Compliant solution

Public Class Account
    Public Property Id As Guid

    Public Property Name As String
    Public Property Surname As String
End Class

or

Noncompliant code example

Public Class Person
    <Key>
    Public Property PersonIdentifier As DateTime

    Public Property Name As String
    Public Property Surname As String
End Class

Compliant solution

Public Class Person
    <Key>
    Public Property PersonIdentifier As Guid

    Public Property Name As String
    Public Property Surname As String
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3363.json ================================================ { "title": "Date and time should not be used as a type for primary keys", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3363", "sqKey": "S3363", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3385.html ================================================

Why is this an issue?

Exit Function, Exit Property, and Exit Sub are all poor, less-readable substitutes for a simple Return, and if used with code that should return a value (Exit Function and in some cases Exit Property) they could result in a NullReferenceException.

This rule raises an issue for all their usages.

Noncompliant code example

Public Class Sample

  Public Sub MySub(Condition As Boolean)
    If Condition Then Exit Sub                  ' Noncompliant
    ' ...
  End Sub

  Public Function MyFunction(Condition As Boolean) As Integer
    If Condition Then
        MyFunction = 42
        Exit Function              ' Noncompliant
    End If
    ' ...
  End Function

End Class

Compliant solution

Public Class Sample

  Public Sub MySub(Condition As Boolean)
    If Condition Then Return                  ' Noncompliant
    ' ...
  End Sub

  Public Function MyFunction(Condition As Boolean) As Integer
    If Condition Then Return 42
    ' ...
  End Function

End Class
================================================ FILE: analyzers/rspec/vbnet/S3385.json ================================================ { "title": "\"Exit\" statements should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "brain-overload", "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3385", "sqKey": "S3385", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3431.html ================================================

Why is this an issue?

It should be clear to a casual reader what code a test is testing and what results are expected. Unfortunately, that’s not usually the case with the ExpectedException attribute since an exception could be thrown from almost any line in the method.

This rule detects MSTest and NUnit ExpectedException attribute.

Exceptions

This rule ignores:

<TestMethod>
<ExpectedException(GetType(InvalidOperationException))>
Public Sub UsingTest()
    Console.ForegroundColor = ConsoleColor.Black
    Try
        Using alert As New ConsoleAlert()
            Assert.AreEqual(ConsoleColor.Red, Console.ForegroundColor)
            Throw New InvalidOperationException()
        End Using
    Finally
        Assert.AreEqual(ConsoleColor.Black, Console.ForegroundColor) ' The exception itself is not relevant for the test.
    End Try
End Sub

Public NotInheritable Class ConsoleAlert
    Implements IDisposable

    Private ReadOnly previous As ConsoleColor

    Public Sub New()
        previous = Console.ForegroundColor
        Console.ForegroundColor = ConsoleColor.Red
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Console.ForegroundColor = previous
    End Sub
End Class

How to fix it in MSTest

Remove the ExpectedException attribute in favor of using the Assert.ThrowsException assertion.

Code examples

Noncompliant code example

<TestMethod>
<ExpectedException(GetType(ArgumentNullException))>  ' Noncompliant
Public Sub Method_NullParam()
    Dim sut As New MyService()
    sut.Method(Nothing)
End Sub

Compliant solution

<TestMethod>
Public Sub Method_NullParam()
    Dim sut As New MyService()
    Assert.ThrowsException(Of ArgumentNullException)(Sub() sut.Method(Nothing))
End Sub

How to fix it in NUnit

Remove the ExpectedException attribute in favor of using the Assert.Throws assertion.

Code examples

Noncompliant code example

<Test>
<ExpectedException(GetType(ArgumentNullException))>  ' Noncompliant
Public Sub Method_NullParam()
    Dim sut As New MyService()
    sut.Method(Nothing)
End Sub

Compliant solution

<Test>
Public Sub Method_NullParam()
    Dim sut As New MyService()
    Assert.Throws(Of ArgumentNullException)(Sub() sut.Method(Nothing))
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3431.json ================================================ { "title": "\"[ExpectedException]\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "tests" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3431", "sqKey": "S3431", "scope": "Tests", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3449.html ================================================

Why is this an issue?

Numbers can be shifted with the << and >> operators, but the right operand of the operation needs to be an int or a type that has an implicit conversion to int. However, when the left operand is an object, the compiler’s type checking is turned off, therfore you can pass anything to the right of a shift operator and have it compile. If the argument can’t be implicitly converted to int at runtime, a RuntimeBinderException will be raised.

Dim o As Object = 5
Dim x As Integer = 5

x = o >> 5 ' Noncompliant
x = x << o ' Noncompliant

Exceptions

This rule does not raise when the left or the right expression is Nothing.

x = Nothing >> 5
x = 5 << Nothing

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3449.json ================================================ { "title": "Right operands of shift operators should be integers", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3449", "sqKey": "S3449", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3453.html ================================================

Why is this an issue?

When a class has only a private constructor, it can’t be instantiated except within the class itself. Such classes can be considered dead code and should be fixed

Exceptions

How to fix it

Code examples

Noncompliant code example

Public Class [MyClass] ' Noncompliant: the class contains only private constructors
    Private Sub New()
        ' ...
    End Sub
End Class

Compliant solution

Public Class [MyClass] ' Compliant: the class contains at least one non-private constructor
    Public Sub New()
        ' ...
    End Sub
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3453.json ================================================ { "title": "Classes should not have only \"private\" constructors", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3453", "sqKey": "S3453", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3464.html ================================================

Why is this an issue?

Recursion is a technique used to define a problem in terms of the problem itself, usually in terms of a simpler version of the problem itself.

For example, the implementation of the generator for the n-th value of the Fibonacci sequence comes naturally from its mathematical definition, when recursion is used:

Function NthFibonacciNumber(ByVal n As Integer) As Integer
    If n <= 1 Then
        Return 1
    Else
        Return NthFibonacciNumber(n - 1) + NthFibonacciNumber(n - 2)
    End If
End Function

As opposed to:

Function NthFibonacciNumber(ByVal n As Integer) As Integer
    Dim previous As Integer = 0
    Dim last As Integer = 1

    For i = 0 To n - 1
        Dim temp = previous
        previous = last
        last = last + temp
    Next

    Return last
End Function

The use of recursion is acceptable in methods, like the one above, where you can break out of it.

Function NthFibonacciNumber(ByVal n As Integer) As Integer
    If n <= 1 Then
        Return 1 ' Base case: stop the recursion
    End If
    ' ...
End Function

It is also acceptable and makes sense in some type definitions:

Class Box
    Inherits IComparable(Of Box)

    Public Function CompareTo(ByVal other As Box?) As Integer
        ' Compare the two Box instances...
    End Function
End Class

With types, some invalid recursive definitions are caught by the compiler:

Class C2(Of T)               ' Error BC31447 C2(Of T) cannot reference itself in Inherits clause
    Inherits C2(Of T)
End Class

Class C2(Of T)
    Inherits C2(Of C2(Of T)) ' Error BC31447 C2(Of T) cannot reference itself in Inherits clause
End Class

In more complex scenarios, however, the code will compile but execution will result in a TypeLoadException if you try to instantiate the class.

Class C1(Of T)
End Class

Class C2(Of T)              ' Noncompliant
    Inherits C1(Of C2(Of C2(Of T)))
End Class

Dim c2 = New C2(Of Integer) ' This would result into a TypeLoadException

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S3464.json ================================================ { "title": "Type inheritance should not be recursive", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1h" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3464", "sqKey": "S3464", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3466.html ================================================

Why is this an issue?

When optional parameter values are not passed to base method calls, the value passed in by the caller is ignored. This can cause the function to behave differently than expected, leading to errors and making the code difficult to debug.

How to fix it

Code examples

Noncompliant code example

Public Class BaseClass
    Public Overridable Sub MyMethod(ByVal Optional i As Integer = 1)
        Console.WriteLine(i)
    End Sub
End Class

Public Class DerivedClass
    Inherits BaseClass

    Public Overrides Sub MyMethod(ByVal Optional i As Integer = 1)
        ' ...
        MyBase.MyMethod() ' Noncompliant: caller's value is ignored
    End Sub

    Private Shared Function Main(ByVal args As String()) As Integer
        Dim dc As DerivedClass = New DerivedClass()
        dc.MyMethod(12) ' prints 1
    End Function
End Class

Compliant solution

Public Class BaseClass
    Public Overridable Sub MyMethod(ByVal Optional i As Integer = 1)
        Console.WriteLine(i)
    End Sub
End Class

Public Class DerivedClass
    Inherits BaseClass

    Public Overrides Sub MyMethod(ByVal Optional i As Integer = 1)
        ' ...
        MyBase.MyMethod(i)
    End Sub

    Private Shared Function Main(ByVal args As String()) As Integer
        Dim dc As DerivedClass = New DerivedClass()
        dc.MyMethod(12) ' prints 12
    End Function
End Class

Resources

Documentation

Microsoft Learn - Optional Arguments (Visual Basic)

================================================ FILE: analyzers/rspec/vbnet/S3466.json ================================================ { "title": "Optional parameters should be passed to \"base\" calls", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3466", "sqKey": "S3466", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3598.html ================================================

Why is this an issue?

When declaring a Windows Communication Foundation (WCF) OperationContract method as one-way, that service method won’t return any result, not even an underlying empty confirmation message. These are fire-and-forget methods that are useful in event-like communication. Therefore, specifying a return type has no effect and can confuse readers.

Exceptions

The rule doesn’t report if OperationContractAttribute.AsyncPattern is set to true.

How to fix it

Code examples

Noncompliant code example

<ServiceContract>
Interface IMyService
    <OperationContract(IsOneWay:=True)>
    Function SomethingHappened(ByVal parameter As Integer) As Integer ' Noncompliant
End Interface

Compliant solution

<ServiceContract>
Interface IMyService
    <OperationContract(IsOneWay:=True)>
    Sub SomethingHappened(ByVal parameter As Integer)
End Interface

Resources

Documentation

Microsoft Learn - OperationContractAttribute

================================================ FILE: analyzers/rspec/vbnet/S3598.json ================================================ { "title": "One-way \"OperationContract\" methods should have \"void\" return type", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3598", "sqKey": "S3598", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3603.html ================================================

Why is this an issue?

Marking a method with the Pure attribute indicates that the method doesn’t make any visible state changes. Therefore, a Pure method should return a result. Otherwise, it indicates a no-operation call.

Using Pure on a void method is either by mistake or the method is not doing a meaningful task.

How to fix it

Code examples

Noncompliant code example

Class Person
    Private age As Integer

    <Pure> ' Noncompliant: The method makes a state change
    Private Sub ConfigureAge(ByVal age As Integer)
        Me.age = age
    End Sub

    <Pure>
    Private Sub WriteAge() ' Noncompliant
        Console.WriteLine(Me.age)
    End Sub

End Class

Compliant solution

Class Person
    Private age As Integer

    Private Sub ConfigureAge(ByVal age As Integer)
        Me.age = age
    End Sub

    <Pure>
    Private Function Age() As Integer
        Return Me.age
    End Function

    ' or remove Pure attribute from the method

    Private Sub WriteAge()
        Console.WriteLine(Me.age)
    End Sub

End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3603.json ================================================ { "title": "Methods with \"Pure\" attribute should return a value ", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3603", "sqKey": "S3603", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3655.html ================================================

Why is this an issue?

Nullable value types can hold either a value or Nothing.

The value stored in the nullable type can be accessed with the Value property or by casting it to the underlying type. Still, both operations throw an InvalidOperationException when the value is Nothing. A nullable type should always be tested before accessing the value to avoid raising exceptions.

How to fix it

Code examples

Noncompliant code example

Sub Sample(condition As Boolean)
    Dim nullableValue As Integer? = If(condition, 42, Nothing)
    Console.WriteLine(nullableValue.Value)             ' Noncompliant: InvalidOperationException is raised

    Dim nullableCast As Integer? = If(condition, 42, Nothing)
    Console.WriteLine(CType(nullableCast, Integer))    ' Noncompliant: InvalidOperationException is raised
End Sub

Compliant solution

Sub Sample(condition As Boolean)
    Dim nullableValue As Integer? = If(condition, 42, Nothing)
    If nullableValue.HasValue Then
        Console.WriteLine(nullableValue.Value)
    End If

    Dim nullableCast As Integer? = If(condition, 42, Nothing)
    If nullableCast.HasValue Then
        Console.WriteLine(CType(nullableCast, Integer))
    End If
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3655.json ================================================ { "title": "Empty nullable value should not be accessed", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3655", "sqKey": "S3655", "scope": "All", "securityStandards": { "CWE": [ 476 ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3776.html ================================================

This rule raises an issue when the code cognitive complexity of a function is above a certain threshold.

Why is this an issue?

Cognitive Complexity is a measure of how hard it is to understand the control flow of a unit of code. Code with high cognitive complexity is hard to read, understand, test, and modify.

As a rule of thumb, high cognitive complexity is a sign that the code should be refactored into smaller, easier-to-manage pieces.

Which syntax in code does impact cognitive complexity score?

Here are the core concepts:

The method of computation is fully detailed in the pdf linked in the resources.

What is the potential impact?

Developers spend more time reading and understanding code than writing it. High cognitive complexity slows down changes and increases the cost of maintenance.

Function Abs(ByVal n As Integer) As Integer ' Noncompliant: cognitive complexity = 5
    If n >= 0 Then    ' +1
        Return n
    Else              ' +2, due to nesting
        If n = Integer.MinValue Then      ' +1
            Throw New ArgumentException("The absolute value of int.MinValue is outside of int boundaries")
        Else                              ' +1
            Return -n
        End If
    End If
End Function

They should be refactored to have lower complexity:

Function Abs(ByVal n As Integer) As Integer  ' Compliant: cognitive complexity = 3
    If n = Integer.MinValue Then    ' +1
        Throw New ArgumentException("The absolute value of int.MinValue is outside of int boundaries")
    Else If n >= 0 Then             ' +1
        Return n
    Else                            ' +1
        Return -n
    End If
End Function

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S3776.json ================================================ { "title": "Cognitive Complexity of methods should not be too high", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Linear with offset", "linearDesc": "per complexity point over the threshold", "linearOffset": "5min", "linearFactor": "1min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3776", "sqKey": "S3776", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3860.html ================================================

Why is this an issue?

Since Visual Studio 2010 SP1, the ByVal parameter modifier is implicitly applied, and therefore not required anymore. Removing it from your source code will improve readability.

Noncompliant code example

Sub Foo(ByVal bar As String)
  ' ...
End Sub

Compliant solution

Sub Foo(bar As String)
  ' ...
End Sub
================================================ FILE: analyzers/rspec/vbnet/S3860.json ================================================ { "title": "\"ByVal\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3860", "sqKey": "S3860", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S3866.html ================================================

Why is this an issue?

Visual Basic .NET offers a non-short-circuit conditional function, IIf(), which returns either its second or third parameter based on the expression in the first parameter. Using it is slower than using If() because each parameter is unconditionally evaluated. Further, its use can lead to runtime exceptions because IIf always evaluates all three of its arguments.

The newer version, If(), should be used instead because it short-circuits the evaluation of its parameters.

Noncompliant code example

Public Class Foo
    Public Sub Bar()
        Dim var As Object = IIf(Date.Now.Year = 1999, "Lets party!", "Lets party like it is 1999!") ' Noncompliant
    End Sub
End Class

Compliant solution

Public Class Foo
    Public Sub Bar()
        Dim var As String = If(Date.Now.Year = 1999, "Lets party!", "Lets party like it is 1999!")
    End Sub
End Class

Resources

================================================ FILE: analyzers/rspec/vbnet/S3866.json ================================================ { "title": "\"IIf\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "performance" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3866", "sqKey": "S3866", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S3869.html ================================================

Why is this an issue?

The SafeHandle.DangerousGetHandle method poses significant risks and should be used carefully. This method carries the inherent danger of potentially returning an invalid handle, which can result in resource leaks and security vulnerabilities. Although it is technically possible to utilize this method without encountering issues, doing so correctly requires a high level of expertise. Therefore, it is recommended to avoid using this method altogether.

What is the potential impact?

The SafeHandle.DangerousGetHandle method is potentially prone to leaks and vulnerabilities due to its nature and usage. Here are a few reasons why:

Sub Dangerous(fieldInfo As System.Reflection.FieldInfo)
  Dim handle As SafeHandle = CType(fieldInfo.GetValue(fieldInfo), SafeHandle)
  Dim dangerousHandle As IntPtr = handle.DangerousGetHandle ' Noncompliant
End Sub

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S3869.json ================================================ { "title": "\"SafeHandle.DangerousGetHandle\" should not be called", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "leak", "unpredictable" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3869", "sqKey": "S3869", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3871.html ================================================

Why is this an issue?

The point of having custom exception types is to convey more information than is available in standard types. But custom exception types must be public for that to work.

If a method throws a non-public exception, the best you can do on the caller’s side is to catch the closest public base of the class. However, you lose all the information that the new exception type carries.

This rule will raise an issue if you directly inherit one of the following exception types in a non-public class:

How to fix it

Code examples

Noncompliant code example

Friend Class MyException    ' Noncompliant
    Inherits Exception
    ' ...
End Class

Compliant solution

Public Class MyException
    Inherits Exception
    ' ...
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3871.json ================================================ { "title": "Exception types should be \"Public\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "error-handling", "api-design" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3871", "sqKey": "S3871", "scope": "All", "securityStandards": { "OWASP": [ "A10" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3878.html ================================================

Why is this an issue?

There’s no point in creating an array solely for the purpose of passing it to a ParamArray parameter. Simply pass the elements directly. They will be consolidated into an array automatically.

Noncompliant code example

Class SurroundingClass
    Public Sub Base()
        Method(New String() { "s1", "s2" }) ' Noncompliant: unnecessary
        Method(New String(12) {}) ' Compliant
    End Sub

    Public Sub Method(ParamArray args As String())
        ' Do something
    End Sub
End Class

Compliant solution

Class SurroundingClass
    Public Sub Base()
        Method("s1", "s2")
        Method(New String(12) {})
    End Sub

    Public Sub Method(ParamArray args As String())
        ' Do something
    End Sub
End Class
================================================ FILE: analyzers/rspec/vbnet/S3878.json ================================================ { "title": "Arrays should not be created for ParamArray parameters", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "clumsy" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-3878", "sqKey": "S3878", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3884.html ================================================

This rule is deprecated, and will eventually be removed.

Why is this an issue?

CoSetProxyBlanket and CoInitializeSecurity both work to set the permissions context in which the process invoked immediately after is executed. Calling them from within that process is useless because it’s too late at that point; the permissions context has already been set.

Specifically, these methods are meant to be called from non-managed code such as a C++ wrapper that then invokes the managed, i.e. C# or VB.NET, code.

Noncompliant code example

Public Class Noncompliant

    <DllImport("ole32.dll")>
    Public Shared Function CoSetProxyBlanket(<MarshalAs(UnmanagedType.IUnknown)>pProxy As Object, dwAuthnSvc as UInt32, dwAuthzSvc As UInt32, <MarshalAs(UnmanagedType.LPWStr)> pServerPrincName As String, dwAuthnLevel As UInt32, dwImpLevel As UInt32, pAuthInfo As IntPtr, dwCapabilities As UInt32) As Integer
    End Function

    Public Enum RpcAuthnLevel
        [Default] = 0
        None = 1
        Connect = 2
        [Call] = 3
        Pkt = 4
        PktIntegrity = 5
        PktPrivacy = 6
    End Enum

    Public Enum RpcImpLevel
        [Default] = 0
        Anonymous = 1
        Identify = 2
        Impersonate = 3
        [Delegate] = 4
    End Enum

    Public Enum EoAuthnCap
        None = &H00
        MutualAuth = &H01
        StaticCloaking = &H20
        DynamicCloaking = &H40
        AnyAuthority = &H80
        MakeFullSIC = &H100
        [Default] = &H800
        SecureRefs = &H02
        AccessControl = &H04
        AppID = &H08
        Dynamic = &H10
        RequireFullSIC = &H200
        AutoImpersonate = &H400
        NoCustomMarshal = &H2000
        DisableAAA = &H1000
    End Enum

    <DllImport("ole32.dll")>
    Public Shared Function CoInitializeSecurity(pVoid As IntPtr, cAuthSvc As Integer, asAuthSvc As IntPtr, pReserved1 As IntPtr, level As RpcAuthnLevel, impers As RpcImpLevel, pAuthList As IntPtr, dwCapabilities As EoAuthnCap, pReserved3 As IntPtr) As Integer
    End Function

    Public Sub DoSomething()
        Dim Hres1 As Integer = CoSetProxyBlanket(Nothing, 0, 0, Nothing, 0, 0, IntPtr.Zero, 0) ' Noncompliant
        Dim Hres2 As Integer = CoInitializeSecurity(IntPtr.Zero, -1, IntPtr.Zero, IntPtr.Zero, RpcAuthnLevel.None, RpcImpLevel.Impersonate, IntPtr.Zero, EoAuthnCap.None, IntPtr.Zero) ' Noncompliant
    End Sub

End Class

Resources

================================================ FILE: analyzers/rspec/vbnet/S3884.json ================================================ { "title": "\"CoSetProxyBlanket\" and \"CoInitializeSecurity\" should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3884", "sqKey": "S3884", "scope": "All", "securityStandards": { "CWE": [ 648 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3889.html ================================================

Why is this an issue?

Thread.Suspend and Thread.Resume can give unpredictable results, and both methods have been deprecated. Indeed, if Thread.Suspend is not used very carefully, a thread can be suspended while holding a lock, thus leading to a deadlock.

There are other synchronization mechanisms that are safer and should be used instead, such as:

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3889.json ================================================ { "title": "\"Thread.Resume\" and \"Thread.Suspend\" should not be used", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "multi-threading", "unpredictable" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-3889", "sqKey": "S3889", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3898.html ================================================

Why is this an issue?

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

Resources

================================================ FILE: analyzers/rspec/vbnet/S3898.json ================================================ { "title": "Value types should implement \"IEquatable\u003cT\u003e\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "performance" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3898", "sqKey": "S3898", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3900.html ================================================

Why is this an issue?

Methods declared as Public, Protected, or Protected Friend can be accessed from other assemblies, which means you should validate parameters to be within the expected constraints. In general, checking against Nothing is recommended in defensive programming.

This rule raises an issue when a parameter of a publicly accessible method is not validated against Nothing before being dereferenced.

Noncompliant code example

Public Class Sample

    Public Property Message As String

    Public Sub PublicMethod(Arg As Exception)
        Message = Arg.Message   ' Noncompliant
    End Sub

    Protected Sub ProtectedMethod(Arg As Exception)
        Message = Arg.Message   ' Noncompliant
    End Sub

End Class

Compliant solution

Public Class Sample

    Public Property Message As String

    Public Sub PublicMethod(Arg As Exception)
        If Arg IsNot Nothing Then Message = Arg.Message   ' Noncompliant
    End Sub

    Protected Sub ProtectedMethod(Arg As Exception)
        ArgumentNullException.ThrowIfNull(Arg)
        Message = Arg.Message   ' Noncompliant
    End Sub

    Private Sub PrivateMethod(Arg As Exception)
        Message = Arg.Message   ' Compliant: method is not publicly accessible
    End Sub

End Class

Exceptions

Imports System.Runtime.CompilerServices

<AttributeUsage(AttributeTargets.Parameter, Inherited:=False)>
Public NotInheritable Class ValidatedNotNullAttribute
    Inherits Attribute

End Class

Public Module Guard

    Public Sub NotNull(Of T As Class)(<ValidatedNotNullAttribute> Value As T, <CallerArgumentExpression("Value")> Optional Name As String = "")
        If Value Is Nothing Then Throw New ArgumentNullException(Name)
    End Sub

End Module

Public Module SampleUsage

    Public Function CustomToUpper(Value As String) As String
        Guard.NotNull(Value)
        Return Value.ToUpper
    End Function

End Module
================================================ FILE: analyzers/rspec/vbnet/S3900.json ================================================ { "title": "Arguments of public methods should be validated against Nothing", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "convention", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3900", "sqKey": "S3900", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3903.html ================================================

Why is this an issue?

Types are declared in namespaces in order to prevent name collisions and as a way to organize them into the object hierarchy. Types that are defined outside any named namespace are in a global namespace that cannot be referenced in code.

Noncompliant code example

Public Class Foo
End Class

Public Structure Bar
End Structure

Compliant solution

Namespace SomeSpace
    Public Class Foo
    End Class

    Public Structure Bar
    End Structure
End Namespace
================================================ FILE: analyzers/rspec/vbnet/S3903.json ================================================ { "title": "Types should be defined in named namespaces", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "MODULAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3903", "sqKey": "S3903", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3904.html ================================================

Why is this an issue?

The AssemblyVersion attribute is used to specify the version number of an assembly. An assembly is a compiled unit of code, which can be marked with a version number by applying the attribute to an assembly’s source code file.

The AssemblyVersion attribute is useful for many reasons:

If no AssemblyVersion is provided, the same default version will be used for every build. Since the version number is used by .NET Framework to uniquely identify an assembly, this can lead to broken dependencies.

How to fix it

Code examples

Noncompliant code example

Imports System.Reflection
<Assembly: AssemblyTitle("MyAssembly")> ' Noncompliant
Namespace MyLibrary
' ...
End Namespace

Compliant solution

Imports System.Reflection
<Assembly: AssemblyTitle("MyAssembly")>
<Assembly: AssemblyVersion("42.1.125.0")>
Namespace MyLibrary
' ...
End Namespace

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3904.json ================================================ { "title": "Assemblies should have version information", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3904", "sqKey": "S3904", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3923.html ================================================

Why is this an issue?

Having all branches of a Select Case or If chain with the same implementation indicates a problem.

In the following code:

Dim b As Integer = If(a > 12, 4, 4)  // Noncompliant

If b = 0 Then  // Noncompliant
    DoTheThing()
Else
    DoTheThing()
End If

Select Case i  // Noncompliant
    Case 1
        DoSomething()
    Case 2
        DoSomething()
    Case 3
        DoSomething()
    Case Else
        DoSomething()
End Select

Either there is a copy-paste error that needs fixing or an unnecessary Select Case or If chain that needs removing.

Exceptions

This rule does not apply to If chains without Else, nor to Select Case without a Case Else clause.

If b = 0 Then ' No issue, this could have been done on purpose to make the code more readable
    DoTheThing()
ElseIf
    DoTheThing()
End If
================================================ FILE: analyzers/rspec/vbnet/S3923.json ================================================ { "title": "All branches in a conditional structure should not have exactly the same implementation", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3923", "sqKey": "S3923", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3926.html ================================================

Why is this an issue?

Fields marked with System.Runtime.Serialization.OptionalFieldAttribute are serialized just like any other field. But such fields are ignored on deserialization, and retain the default values associated with their types. Therefore, deserialization event handlers should be declared to set such fields during the deserialization process.

This rule raises when at least one field with the System.Runtime.Serialization.OptionalFieldAttribute attribute is declared but one (or both) of the following event handlers System.Runtime.Serialization.OnDeserializingAttribute or System.Runtime.Serialization.OnDeserializedAttribute are not present.

Noncompliant code example

<Serializable>
Public Class Foo ' Noncompliant
    <OptionalField(VersionAdded:=2)>
    Private optionalField As Integer = 5
End Class

Compliant solution

<Serializable>
Public Class Foo
    <OptionalField(VersionAdded:=2)>
    Private optionalField As Integer = 5

    <OnDeserializing>
    Private Sub OnDeserializing(ByVal context As StreamingContext)
        optionalField = 5
    End Sub

    <OnDeserialized>
    Private Sub OnDeserialized(ByVal context As StreamingContext)
    End Sub
End Class
================================================ FILE: analyzers/rspec/vbnet/S3926.json ================================================ { "title": "Deserialization methods should be provided for \"OptionalField\" members", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "serialization" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3926", "sqKey": "S3926", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3927.html ================================================

Why is this an issue?

Serialization event handlers that don’t have the correct signature will not be called, bypassing augmentations to automated serialization and deserialization events.

A method is designated a serialization event handler by applying one of the following serialization event attributes:

Serialization event handlers take a single parameter of type StreamingContext, return void, and have private visibility.

This rule raises an issue when any of these constraints are not respected.

How to fix it

Code examples

Noncompliant code example

<Serializable>
Public Class Foo
    <OnSerializing>
    Public Sub OnSerializing(ByVal context As StreamingContext) ' Noncompliant: should be private
    End Sub

    <OnSerialized>
    Private Function OnSerialized(ByVal context As StreamingContext) As Integer '  Noncompliant: should return void
    End Function

    <OnDeserializing>
    Private Sub OnDeserializing() ' Noncompliant: should have a single parameter of type StreamingContext
    End Sub

    <OnSerializing>
    Public Sub OnSerializing2(Of T)(ByVal context As StreamingContext) ' Noncompliant: should have no type parameters
    End Sub

    <OnDeserialized>
    Private Sub OnDeserialized(ByVal context As StreamingContext, ByVal str As String) ' Noncompliant: should have a single parameter of type StreamingContext
    End Sub
End Class

Compliant solution

<Serializable>
Public Class Foo
    <OnSerializing>
    Private Sub OnSerializing(ByVal context As StreamingContext)
    End Sub

    <OnSerialized>
    Private Sub OnSerialized(ByVal context As StreamingContext)
    End Sub

    <OnDeserializing>
    Private Sub OnDeserializing(ByVal context As StreamingContext)
    End Sub

    <OnDeserialized>
    Private Sub OnDeserialized(ByVal context As StreamingContext)
    End Sub
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3927.json ================================================ { "title": "Serialization event handlers should be implemented correctly", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3927", "sqKey": "S3927", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3949.html ================================================

Why is this an issue?

Numbers are infinite, but the types that hold them are not. Each numeric type has hard upper and lower bounds. Try to calculate numbers beyond those bounds, and the result will be an OverflowException. When the compilation is configured to remove integer overflow checking, the value will be silently wrapped around from the expected positive value to a negative one, or vice versa.

Noncompliant code example

Public Function Transform(Value As Integer) As Integer
    If Value <= 0 Then Return Value
    Dim Number As Integer = Integer.MaxValue
    Return Number + Value       ' Noncompliant
End Function

Compliant solution

Public Function Transform(Value As Integer) As Long
    If Value <= 0 Then Return Value
    Dim Number As Long = Integer.MaxValue
    Return Number + Value
End Function

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S3949.json ================================================ { "title": "Calculations should not overflow", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "overflow", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3949", "sqKey": "S3949", "scope": "All", "securityStandards": { "STIG ASD_V5R3": [ "V-222612" ] }, "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S3966.html ================================================

Why is this an issue?

Disposing an object twice in the same method, either with the Using statement or by calling Dispose directly, is confusing and error-prone. For example, another developer might try to use an already-disposed object, or there can be runtime errors for specific paths in the code.

In addition, even if the documentation explicitly states that calling the Dispose method multiple times should not throw an exception, some implementations still do it. Thus it is safer to not dispose of an object twice when possible.

How to fix it

Code examples

Noncompliant code example

Dim foo As New Disposable()
foo.Dispose()
foo.Dispose() ' Noncompliant
Using bar As New Disposable()  ' Noncompliant
    bar.Dispose()
End Using

Compliant solution

Dim foo As New Disposable()
foo.Dispose()
Using bar As New Disposable()  ' Compliant

End Using

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S3966.json ================================================ { "title": "Objects should not be disposed more than once", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "confusing", "pitfall", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3966", "sqKey": "S3966", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S3981.html ================================================

Why is this an issue?

The size of a collection and the length of an array are always greater than or equal to zero. Testing it doesn’t make sense, since the result is always true.

If Collection.Count >= 0 Then ... 'Noncompliant always true

If array.Length >= 0 Then ... 'Noncompliant always true

Similarly testing that it is less than zero will always return false.

If Enumerable.Count < 0 Then ... 'Noncompliant always false

Dim result As Boolean = Array.Length >= 0 'Noncompliant always true

Fix the code to properly check for emptiness if it was the intent, or remove the redundant code to keep the current behavior.

================================================ FILE: analyzers/rspec/vbnet/S3981.json ================================================ { "title": "Collection sizes and array length comparisons should make sense", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3981", "sqKey": "S3981", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3990.html ================================================

Why is this an issue?

Assemblies should conform with the Common Language Specification (CLS) in order to be usable across programming languages. To be compliant an assembly has to indicate it with System.CLSCompliantAttribute.

Compliant solution

<Assembly: CLSCompliant(True)>

Namespace MyLibrary

End Namespace
================================================ FILE: analyzers/rspec/vbnet/S3990.json ================================================ { "title": "Assemblies should be marked as CLS compliant", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3990", "sqKey": "S3990", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3992.html ================================================

Why is this an issue?

Assemblies should explicitly indicate whether they are meant to be COM visible or not. If the ComVisibleAttribute is not present, the default is to make the content of the assembly visible to COM clients.

Note that COM visibility can be overridden for individual types and members.

Noncompliant code example

Namespace MyLibrary  ' Noncompliant

End Namespace

Compliant solution

<Assembly: Runtime.InteropServices.ComVisible(False)>

Namespace MyLibrary

End Namespace
================================================ FILE: analyzers/rspec/vbnet/S3992.json ================================================ { "title": "Assemblies should explicitly specify COM visibility", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "api-design" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-3992", "sqKey": "S3992", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S3998.html ================================================

Why is this an issue?

Objects that can be accessed across application domain boundaries are said to have weak identity. This means that these objects can be considered shared resources outside of the domain, which can be lead to them being accessed or modified by multiple threads or concurrent parts of a program, outside of the domain.

A thread acquiring a lock on such an object runs the risk of being blocked by another thread in a different application domain, leading to poor performance and potentially thread starvation and deadlocks.

Types with weak identity are:

How to fix it

Code examples

Noncompliant code example

Public Class Sample
    Private ReadOnly myLock As New StackOverflowException

    Public Sub Go()
        SyncLock myLock ' Noncompliant
        ' ...
        End SyncLock
    End Sub
End Class

Compliant solution

Public Class Sample
    Private ReadOnly myLock As New Object

    Public Sub Go()
        SyncLock myLock
        ' ...
        End SyncLock
    End Sub
End Class

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S3998.json ================================================ { "title": "Threads should not lock on objects with weak identity", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "multi-threading", "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-3998", "sqKey": "S3998", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S4025.html ================================================

Why is this an issue?

Having a field in a child class with a name that differs from a parent class' field only by capitalization is sure to cause confusion. Such child class fields should be renamed.

Noncompliant code example

Public Class Fruit

    Protected PlantingSeason As String

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    Protected Plantingseason As String  ' Noncompliant

    ' ...

End Class

Compliant solution

Public Class Fruit

    Protected PlantingSeason As String

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    Protected WhenToPlant As String

    ' ...

End Class

Or

Public Class Fruit

    Protected PlantingSeason As String

    ' ...

End Class

Public Class Raspberry
    Inherits Fruit

    ' Field removed, parent field will be used instead

End Class

Exceptions

This rule ignores same-name fields that are Shared in both the parent and child classes. It also ignores Private parent class fields and fields explicitly declared as Shadows, but in all other such cases, the child class field should be renamed.

================================================ FILE: analyzers/rspec/vbnet/S4025.json ================================================ { "title": "Child class fields should not differ from parent class fields only by capitalization", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4025", "sqKey": "S4025", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4036.html ================================================

When you run an OS command, it is always important to protect yourself against the risk of accidental or malicious replacement of the executables in the production system.

To do so, it is important to point to the specific executable that should be used.

For example, if you call git (without specifying a path), the operating system will search for the executable in the directories specified in the PATH environment variable.
An attacker could have added, in a permissive directory covered by PATH , another executable called git, but with a completely different behavior, for example exfiltrating data or exploiting a vulnerability in your own code.

However, by calling /usr/bin/git or ../git (relative path) directly, the operating system will always use the intended executable.
Note that you still need to make sure that the executable is not world-writeable and potentially overwritten. This is not the scope of this rule.

Ask Yourself Whether

There is a risk if you answered no to this question.

Recommended Secure Coding Practices

If you wish to rely on the PATH environment variable to locate the OS command, make sure that each of its listed directories is fixed, not susceptible to change, and not writable by unprivileged users.

If you determine that these folders cannot be altered, and that you are sure that the program you intended to use will be used, then you can determine that these risks are under your control.

A good practice you can use is to also hardcode the PATH variable you want to use, if you can do so in the framework you use.

If the previous recommendations cannot be followed due to their complexity or other requirements, then consider using the absolute path of the command instead.

$ whereis git
git: /usr/bin/git /usr/share/man/man1/git.1.gz
$ ls -l /usr/bin/git
-rwxr-xr-x 1 root root 3376112 Jan 28 10:13 /usr/bin/git

Sensitive Code Example

Dim p As New Process()
p.StartInfo.FileName = "binary" ' Sensitive

Compliant Solution

Dim p As New Process()
p.StartInfo.FileName = "C:\Apps\binary.exe"

See

================================================ FILE: analyzers/rspec/vbnet/S4036.json ================================================ { "title": "Searching OS commands in PATH is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4036", "sqKey": "S4036", "scope": "Main", "securityStandards": { "CWE": [ 427, 426 ], "OWASP": [ "A1" ], "OWASP Top 10 2021": [ "A8" ] } } ================================================ FILE: analyzers/rspec/vbnet/S4060.html ================================================

Why is this an issue?

The .NET framework class library provides methods for retrieving custom attributes. Sealing the attribute eliminates the search through the inheritance hierarchy, and can improve performance.

This rule raises an issue when a public type inherits from System.Attribute, is not abstract, and is not sealed.

Noncompliant code example

Public Class MyAttribute    ' Noncompliant
    Inherits Attribute

    Public ReadOnly Property Name As String

    Public Sub New(Name As String)
        Me.Name = Name
    End Sub

End Class

Compliant solution

Public NotInheritable Class MyAttribute
    Inherits Attribute

    Public ReadOnly Property Name As String

    Public Sub New(Name As String)
        Me.Name = Name
    End Sub

End Class
================================================ FILE: analyzers/rspec/vbnet/S4060.json ================================================ { "title": "Non-abstract attributes should be sealed", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4060", "sqKey": "S4060", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4136.html ================================================

Why is this an issue?

For clarity, all overloads of the same method should be grouped together. That lets both users and maintainers quickly understand all the current available options.

Noncompliant code example

Interface IMyInterface
    Function DoTheThing() As Integer
    Function DoTheOtherThing() As String // Noncompliant
    Function DoTheThing(ByVal Path As String) As Integer
End Interface

Compliant solution

Interface IMyInterface
    Function DoTheThing() As Integer
    Function DoTheThing(ByVal Path As String) As Integer
    Function DoTheOtherThing() As String
End Interface

Exceptions

As it is common practice to group method declarations by implemented interface, no issue will be raised for interface implementations if grouped together with other members of that interface.

As it is also a common practice to group method declarations by accessibility level, no issue will be raised for method overloads having different access modifiers.

Example:

Class MyClass

    Private Sub DoTheThing(s As String) ' Ok - this method is declared as Private while the other one is Public
        ' ...
    End Sub

    Private Sub DoTheOtherThing(s As String)
        ' ...
    End Sub

    Public Sub DoTheThing()
        ' ...
    End Sub

End Class
================================================ FILE: analyzers/rspec/vbnet/S4136.json ================================================ { "title": "Method overloads should be grouped together", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FORMATTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "convention" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4136", "sqKey": "S4136", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4143.html ================================================

Why is this an issue?

Storing a value inside a collection at a given key or index and then unconditionally overwriting it without reading the initial value is a case of a "dead store".

towns.Item(x) = "London"
towns.Item(x) = "Chicago";  // Noncompliant

This practice is redundant and will cause confusion for the reader. More importantly, it is often an error and not what the developer intended to do.

================================================ FILE: analyzers/rspec/vbnet/S4143.json ================================================ { "title": "Map values should not be replaced unconditionally", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4143", "sqKey": "S4143", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4144.html ================================================

Why is this an issue?

Two methods having the same implementation are suspicious. It might be that something else was intended. Or the duplication is intentional, which becomes a maintenance burden.

Private Const CODE As String = "bounteous"
Private callCount As Integer = 0

Public Function GetCode() As String
  callCount = callCount + 1
  Return CODE
End Function

Public Function GetName() As String ' Noncompliant: duplicates GetCode
  callCount = callCount + 1
  Return CODE
End Function

If the identical logic is intentional, the code should be refactored to avoid duplication. For example, by having both methods call the same method or by having one implementation invoke the other.

Private Const CODE As String = "bounteous"
Private callCount As Integer = 0

Public Function GetCode() As String
  callCount = callCount + 1
  Return CODE
End Function

Public Function GetName() As String ' Intent is clear
  Return GetCode()
End Function

Exceptions

Empty methods, methods with only one line of code and methods with the same name (overload) are ignored.

================================================ FILE: analyzers/rspec/vbnet/S4144.json ================================================ { "title": "Methods should not have identical implementations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "DISTINCT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "confusing", "duplicate", "suspicious" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4144", "sqKey": "S4144", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S4158.html ================================================

Why is this an issue?

When a collection is empty, iterating it has no effect. Doing so anyway is likely a bug; either population was accidentally omitted, or the iteration needs to be revised.

How to fix it

Code examples

Noncompliant code example

Public Sub Method()
    Dim Values As New List(Of String)
    Values.Remove("bar")                ' Noncompliant
    If Values.Contains("foo") Then      ' Noncompliant
    End If
    For Each Value As String In Values  ' Noncompliant
    Next
End Sub

Compliant solution

Public Sub Method()
    Dim Values As List(Of String) = LoadValues()
    Values.Remove("bar")
    If Values.Contains("foo") Then
    End If
    For Each Value As String In Values
    Next
End Sub
================================================ FILE: analyzers/rspec/vbnet/S4158.json ================================================ { "title": "Empty collections should not be accessed or iterated", "type": "BUG", "code": { "impacts": { "RELIABILITY": "LOW" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4158", "sqKey": "S4158", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S4159.html ================================================

Why is this an issue?

The Attributed Programming Model, also known as Attribute-oriented programming (@OP), is a programming model used to embed attributes within codes.

In this model, objects are required to conform to a specific structure so that they can be used by the Managed Extensibility Framework (MEF).

MEF provides a way to discover available components implicitly, via composition. A MEF component, called a part, declaratively specifies:

The ExportAttribute declares that a part "exports", or provides to the composition container, an object that fulfills a particular contract.

During composition, parts with imports that have matching contracts will have those dependencies filled by the exported object.

If the type doesn’t implement the interface it is exporting there will be an issue at runtime (either a cast exception or just a container not filled with the exported type) leading to unexpected behaviors/crashes.

The rule raises an issue when a class doesn’t implement or inherit the type declared in the ExportAttribute.

How to fix it

Code examples

Noncompliant code example

<Export(GetType(ISomeType))>
Public Class SomeType  ' Noncompliant: doesn't implement 'ISomeType'.
End Class

Compliant solution

<Export(GetType(ISomeType))>
Public Class SomeType
    Inherits ISomeType
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S4159.json ================================================ { "title": "Classes should implement their \"ExportAttribute\" interfaces", "type": "BUG", "code": { "impacts": { "RELIABILITY": "BLOCKER" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-4159", "sqKey": "S4159", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S4201.html ================================================

Why is this an issue?

There’s no need to null test in conjunction with an TypeOf ... Is test. Nothing is not an instance of anything, so a null check is redundant.

Noncompliant code example

If (x IsNot Nothing And TypeOf x Is MyClass)
    ' ...
End If

If (x Is Nothing Or TypeOf x IsNot MyClass)
    ' ...
End If

Compliant solution

If (TypeOf x Is MyClass)
    ' ...
End If

If (TypeOf x IsNot MyClass)
    ' ...
End If
================================================ FILE: analyzers/rspec/vbnet/S4201.json ================================================ { "title": "Null checks should not be combined with \"TypeOf Is\" operator checks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "redundant" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4201", "sqKey": "S4201", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4210.html ================================================

Why is this an issue?

When an assembly uses Windows Forms (classes and interfaces from the System.Windows.Forms namespace) its entry point should be marked with the STAThreadAttribute to indicate that the threading model should be "Single-Threaded Apartment" (STA) which is the only one supported by Windows Forms.

This rule raises an issue when the entry point (Shared Sub Main method) of an assembly using Windows Forms is not marked as STA.

Noncompliant code example

Imports System.Windows.Forms

Public Class Foo
  Shared Sub Main()
    Dim winForm As Form = New Form
    Application.Run(winForm)
  End Sub
End Class

Compliant solution

Imports System.Windows.Forms

Public Class Foo
  <STAThread()> Shared Sub Main()
    Dim winForm As Form = New Form
    Application.Run(winForm)
  End Sub
End Class
================================================ FILE: analyzers/rspec/vbnet/S4210.json ================================================ { "title": "Windows Forms entry points should be marked with STAThread", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "winforms", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4210", "sqKey": "S4210", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4225.html ================================================

Why is this an issue?

Creating an extension method that extends Object is not recommended because it makes the method available on every type. Extensions should be applied at the most specialized level possible, and that is very unlikely to be Object.

Noncompliant code example

Imports System.Runtime.CompilerServices

Module MyExtensions
    <Extension>
    Sub SomeExtension(obj As Object) ' Noncompliant
        ' ...
    End Sub
End Module
================================================ FILE: analyzers/rspec/vbnet/S4225.json ================================================ { "title": "Extension methods should not extend \"Object\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "FOCUSED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4225", "sqKey": "S4225", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4260.html ================================================

Why is this an issue?

When creating a custom Markup Extension that accepts parameters in WPF, the ConstructorArgument markup must be used to identify the discrete properties that match these parameters. However since this is done via a string, the compiler won’t give you any warning in case there are typos.

This rule raises an issue when the string argument to ConstructorArgumentAttribute doesn’t match any parameter of any constructor.

How to fix it

Code examples

Noncompliant code example

Imports System

Namespace MyLibrary
    Public Class MyExtension
        Inherits MarkupExtension

        Public Sub New()
        End Sub

        Public Sub New(ByVal value1 As Object)
            Value1 = value1
        End Sub

        <ConstructorArgument("value2")> ' Noncompliant
        Public Property Value1 As Object
    End Class
End Namespace

Compliant solution

Imports System

Namespace MyLibrary
    Public Class MyExtension
        Inherits MarkupExtension

        Public Sub New()
        End Sub

        Public Sub New(ByVal value1 As Object)
            Value1 = value1
        End Sub

        <ConstructorArgument("value1")>
        Public Property Value1 As Object
    End Class
End Namespace

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S4260.json ================================================ { "title": "\"ConstructorArgument\" parameters should exist in constructors", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "xaml", "wpf" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4260", "sqKey": "S4260", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4275.html ================================================

Why is this an issue?

Properties provide a way to enforce encapsulation by providing property procedures that give controlled access to Private fields. However, in classes with multiple fields, it is not unusual that copy-and-paste is used to quickly create the needed properties, which can result in the wrong field being accessed by the property procedures.

Class C
    Private _x As Integer
    Private _Y As Integer

    Public ReadOnly Property Y As Integer
        Get
            Return _x ' Noncompliant: The returned field should be '_y'
        End Get
    End Property
End Class

This rule raises an issue in any of these cases:

For simple properties, it is better to use auto-implemented properties (VB.NET 10.0 or later).

Field and property names are compared as case-insensitive. All underscore characters are ignored.

How to fix it

Code examples

Noncompliant code example

Public Class Sample

    Private _x As Integer
    Private _y As Integer

    Public Property Y As Integer
        Get
            Return _x   ' Noncompliant: field '_y' is not used in the return value
        End Get
        Set(value As Integer)
            _x = value  ' Noncompliant: field '_y' is not updated
        End Set
    End Property

End Class

Compliant solution

Public Class Sample

    Private _x As Integer
    Private _y As Integer

    Public Property Y As Integer
        Get
            Return _y
        End Get
        Set(value As Integer)
            _y = value
        End Set
    End Property

End Class

Resources

================================================ FILE: analyzers/rspec/vbnet/S4275.json ================================================ { "title": "Property procedures should access the expected fields", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4275", "sqKey": "S4275", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S4277.html ================================================

Why is this an issue?

Marking a class with PartCreationPolicy(CreationPolicy.Shared), which is part of Managed Extensibility Framework (MEF), means that a single, shared instance of the exported object will be created. Therefore it doesn’t make sense to create new instances using the constructor and it will most likely result in unexpected behaviours.

This rule raises an issue when a constructor of a class marked shared with a PartCreationPolicyAttribute is invoked.

How to fix it

Code examples

Noncompliant code example

<Export(GetType(IFooBar))>
<PartCreationPolicy(CreationPolicy.[Shared])>
Public Class FooBar
    Inherits IFooBar
End Class

Public Class Program
    Public Shared Sub Main()
        Dim fooBar = New FooBar() ' Noncompliant
    End Sub
End Class

Compliant solution

<Export(GetType(IFooBar))>
<PartCreationPolicy(CreationPolicy.[Shared])>
Public Class FooBar
    Inherits IFooBar
End Class

Public Class Program
    Public Shared Sub Main()
        Dim fooBar = serviceProvider.GetService(Of IFooBar)()
    End Sub
End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S4277.json ================================================ { "title": "\"Shared\" parts should not be created with \"new\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4277", "sqKey": "S4277", "scope": "Main", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S4423.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

To provide communication security over a network, SSL and TLS are generally used. However, it is important to note that the following protocols are all considered weak by the cryptographic community, and are officially deprecated:

When these unsecured protocols are used, it is best practice to expect a breach: that a user or organization with malicious intent will perform mathematical attacks on this data after obtaining it by other means.

What is the potential impact?

After retrieving encrypted data and performing cryptographic attacks on it on a given timeframe, attackers can recover the plaintext that encryption was supposed to protect.

Depending on the recovered data, the impact may vary.

Below are some real-world scenarios that illustrate the potential impact of an attacker exploiting the vulnerability.

Additional attack surface

By modifying the plaintext of the encrypted message, an attacker may be able to trigger additional vulnerabilities in the code. An attacker can further exploit a system to obtain more information.
Encrypted values are often considered trustworthy because it would not be possible for a third party to modify them under normal circumstances.

Breach of confidentiality and privacy

When encrypted data contains personal or sensitive information, its retrieval by an attacker can lead to privacy violations, identity theft, financial loss, reputational damage, or unauthorized access to confidential systems.

In this scenario, the company, its employees, users, and partners could be seriously affected.

The impact is twofold, as data breaches and exposure of encrypted data can undermine trust in the organization, as customers, clients and stakeholders may lose confidence in the organization’s ability to protect their sensitive data.

Legal and compliance issues

In many industries and locations, there are legal and compliance requirements to protect sensitive data. If encrypted data is compromised and the plaintext can be recovered, companies face legal consequences, penalties, or violations of privacy laws.

How to fix it in .NET

Code examples

Noncompliant code example

These samples use a default TLS algorithm, which is a weak cryptographical algorithm: TLSv1.0.

Imports System.Net
Imports System.Security.Authentication

Public Sub Encrypt()
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls ' Noncompliant
End Sub
Imports System.Net.Http
Imports System.Security.Authentication

Public Sub Encrypt()
    Dim Handler As New HttpClientHandler With {
        .SslProtocols = SslProtocols.Tls ' Noncompliant
    }
End Sub

Compliant solution

Imports System.Net
Imports System.Security.Authentication

Public Sub Encrypt()
    ServicePointManager.SecurityProtocol = _
        SecurityProtocolType.Tls12 _
        Or SecurityProtocolType.Tls13
End Sub
Imports System.Net.Http
Imports System.Security.Authentication

Public Sub Encrypt()
    Dim Handler As New HttpClientHandler With {
        .SslProtocols = SslProtocols.Tls12
    }
End Sub

How does this work?

As a rule of thumb, by default you should use the cryptographic algorithms and mechanisms that are considered strong by the cryptographic community.

The best choices at the moment are the following.

Use TLS v1.2 or TLS v1.3

Even though TLS V1.3 is available, using TLS v1.2 is still considered good and secure practice by the cryptography community.

The use of TLS v1.2 ensures compatibility with a wide range of platforms and enables seamless communication between different systems that do not yet have TLS v1.3 support.

The only drawback depends on whether the framework used is outdated: its TLS v1.2 settings may enable older and insecure cipher suites that are deprecated as insecure.

On the other hand, TLS v1.3 removes support for older and weaker cryptographic algorithms, eliminates known vulnerabilities from previous TLS versions, and improves performance.

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/vbnet/S4423.json ================================================ { "title": "Weak SSL\/TLS protocols should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "2min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4423", "sqKey": "S4423", "scope": "Main", "securityStandards": { "CWE": [ 327, 326, 295 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2", "A7" ], "PCI DSS 3.2": [ "4.1", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "8.3.7", "9.1.2", "9.1.3" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4428.html ================================================

Why is this an issue?

To customize the default behavior for an export in the Managed Extensibility Framework (MEF), applying the PartCreationPolicyAttribute is necessary. For the PartCreationPolicyAttribute to be meaningful in the context of an export, the class must also be annotated with the ExportAttribute.

This rule raises an issue when a class is annotated with the PartCreationPolicyAttribute but not with the ExportAttribute.

How to fix it

Code examples

Noncompliant code example

Imports System.ComponentModel.Composition

<PartCreationPolicy(CreationPolicy.Any)> ' Noncompliant
Public Class FooBar
    Inherits IFooBar
End Class

Compliant solution

Imports System.ComponentModel.Composition

<Export(GetType(IFooBar))>
<PartCreationPolicy(CreationPolicy.Any)>
Public Class FooBar
    Inherits IFooBar
End Class

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S4428.json ================================================ { "title": "\"PartCreationPolicyAttribute\" should be used with \"ExportAttribute\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "mef", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4428", "sqKey": "S4428", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S4507.html ================================================

Development tools and frameworks usually have options to make debugging easier for developers. Although these features are useful during development, they should never be enabled for applications deployed in production. Debug instructions or error messages can leak detailed information about the system, like the application’s path or file names.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Do not enable debugging features on production servers.

The .Net Core framework offers multiple features which help during debug. Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage and Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage are two of them. Make sure that those features are disabled in production.

Use If env.IsDevelopment() to disable debug code.

Sensitive Code Example

This rule raises issues when the following .Net Core methods are called: Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDeveloperExceptionPage, Microsoft.AspNetCore.Builder.IApplicationBuilder.UseDatabaseErrorPage.

Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting

Namespace MyMvcApp
    Public Class Startup
        Public Sub Configure(ByVal app As IApplicationBuilder, ByVal env As IHostingEnvironment)
            ' Those calls are Sensitive because it seems that they will run in production
            app.UseDeveloperExceptionPage() 'Sensitive
            app.UseDatabaseErrorPage() 'Sensitive
        End Sub
    End Class
End Namespace

Compliant Solution

Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting

Namespace MyMvcApp
    Public Class Startup
        Public Sub Configure(ByVal app As IApplicationBuilder, ByVal env As IHostingEnvironment)
            If env.IsDevelopment() Then ' Compliant
                ' The following calls are ok because they are disabled in production
                app.UseDeveloperExceptionPage()
                app.UseDatabaseErrorPage()
            End If
        End Sub
    End Class
End Namespace

See

================================================ FILE: analyzers/rspec/vbnet/S4507.json ================================================ { "title": "Delivering code in production with debug features activated is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "LOW" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "cwe", "error-handling", "debug", "user-experience" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4507", "sqKey": "S4507", "scope": "Main", "securityStandards": { "CWE": [ 489, 215 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A5" ], "ASVS 4.0": [ "14.3.2" ] } } ================================================ FILE: analyzers/rspec/vbnet/S4545.html ================================================

Why is this an issue?

The DebuggerDisplayAttribute is used to determine how an object is displayed in the debugger window.

The DebuggerDisplayAttribute constructor takes a single mandatory argument: the string to be displayed in the value column for instances of the type. Any text within curly braces is evaluated as the name of a field or property, or any complex expression containing method calls and operators.

Naming a non-existent member between curly braces will result in a BC30451 error in the debug window when debugging objects. Although there is no impact on the production code, providing a wrong value can lead to difficulties when debugging the application.

This rule raises an issue when text specified between curly braces refers to members that don’t exist in the current context.

Noncompliant code example

<DebuggerDisplay("Name: {Name}")> ' Noncompliant - Name doesn't exist in this context
Public Class Person

    Public Property FullName As String

End Class

Compliant solution

<DebuggerDisplay("Name: {FullName}")>
Public Class Person

    Public Property FullName As String

End Class
================================================ FILE: analyzers/rspec/vbnet/S4545.json ================================================ { "title": "\"DebuggerDisplayAttribute\" strings should reference existing members", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4545", "sqKey": "S4545", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4581.html ================================================

Why is this an issue?

When the syntax new Guid() (i.e. parameterless instantiation) is used, it must be that one of three things is wanted:

  1. An empty GUID, in which case Guid.Empty is clearer.
  2. A randomly-generated GUID, in which case Guid.NewGuid() should be used.
  3. A new GUID with a specific initialization, in which case the initialization parameter is missing.

This rule raises an issue when a parameterless instantiation of the Guid struct is found.

Noncompliant code example

Public Sub Foo()
    Dim G1 As New Guid        ' Noncompliant - what's the intent?
    Dim G2 As Guid = Nothing  ' Noncompliant
End Sub

Compliant solution

public void Foo(byte[] bytes)
Public Sub Foo(Bytes As Byte())
    Dim G1 As Guid = Guid.Empty
    Dim G2 As Guid = Guid.NewGuid()
    Dim G3 As Guid = New Guid(Bytes)
End Sub
================================================ FILE: analyzers/rspec/vbnet/S4581.json ================================================ { "title": "\"new Guid()\" should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-4581", "sqKey": "S4581", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4583.html ================================================

Why is this an issue?

When calling the BeginInvoke method of a delegate, resources are allocated that are only freed up when EndInvoke is called. Failing to pair BeginInvoke with EndInvoke can lead to resource leaks and incomplete asynchronous calls.

This rule raises an issue in the following scenarios:

How to fix it

Code examples

Noncompliant code example

BeginInvoke without callback:

Public Delegate Function AsyncMethodCaller() As String

Public Class Sample

    Public Sub DoSomething()
        Dim Example As New AsyncExample()
        Dim Caller As New AsyncMethodCaller(Example.SomeMethod)
        ' Initiate the asynchronous call.
        Dim Result As IAsyncResult = Caller.BeginInvoke(Nothing, Nothing) ' Noncompliant: Not paired With EndInvoke
    End Sub

End Class

BeginInvoke with callback:

Public Delegate Function AsyncMethodCaller() As String

Public Class Sample

    Public Sub DoSomething()
        Dim Example As New AsyncExample()
        Dim Caller As New AsyncMethodCaller(Example.SomeMethod)
        ' Initiate the asynchronous call.
        Dim Result As IAsyncResult = Caller.BeginInvoke(New AsyncCallback(Sub(ar)
                                                                          End Sub), Nothing) ' Noncompliant: Not paired With EndInvoke
    End Sub

End Class

Compliant solution

BeginInvoke without callback:

Public Delegate Function AsyncMethodCaller() As String

Public Class Sample

    Public Function DoSomething() As String
        Dim Example As New AsyncExample()
        Dim Caller As New AsyncMethodCaller(Example.SomeMethod)
        ' Initiate the asynchronous call.
        Dim Result As IAsyncResult = Caller.BeginInvoke(Nothing, Nothing)
        ' ...
        Return Caller.EndInvoke(Result)
    End Function

End Class

BeginInvoke with callback:

Public Delegate Function AsyncMethodCaller() As String

Public Class Sample

    Public Sub DoSomething()
        Dim Example As New AsyncExample()
        Dim Caller As New AsyncMethodCaller(Example.SomeMethod)
        ' Initiate the asynchronous call.
        Dim Result As IAsyncResult = Caller.BeginInvoke(New AsyncCallback(Sub(ar)
                                                                              ' Call EndInvoke to retrieve the results.
                                                                              Dim Ret As String = Caller.EndInvoke(ar)
                                                                              ' ...
                                                                          End Sub), Nothing)
    End Sub

End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S4583.json ================================================ { "title": "Calls to delegate\u0027s method \"BeginInvoke\" should be paired with calls to \"EndInvoke\"", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4583", "sqKey": "S4583", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S4586.html ================================================

Why is this an issue?

Returning Nothing from a non-Async Task/Task(Of TResult) procedure will cause a NullReferenceException at runtime if the procedure is awaited. This problem can be avoided by returning Task.CompletedTask or Task.FromResult(Of TResult)(Nothing) respectively.

Public Function DoFooAsync() As Task
    Return Nothing            ' Noncompliant: Causes a NullReferenceException if awaited.
End Function

Public Async Function Main() As Task
    Await DoFooAsync()        ' NullReferenceException
End Function

How to fix it

Instead of Nothing Task.CompletedTask or Task.FromResult(Of TResult)(Nothing) should be returned.

Code examples

A Task returning procedure can be fixed like so:

Noncompliant code example

Public Function DoFooAsync() As Task
    Return Nothing            ' Noncompliant: Causes a NullReferenceException if awaited.
End Function

Compliant solution

Public Function DoFooAsync() As Task
    Return Task.CompletedTask ' Compliant: Method can be awaited.
End Function

A Task(Of TResult) returning procedure can be fixed like so:

Noncompliant code example

Public Function GetFooAsync() As Task(Of Object)
    Return Nothing                             ' Noncompliant: Causes a NullReferenceException if awaited.
End Function

Compliant solution

Public Function GetFooAsync() As Task(Of Object)
    Return Task.FromResult(Of Object)(Nothing) ' Compliant: Method can be awaited.
End Function

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S4586.json ================================================ { "title": "Non-async \"Task\/Task\u003cT\u003e\" methods should not return null", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5 min" }, "tags": [ "async-await" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4586", "sqKey": "S4586", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S4663.html ================================================

Why is this an issue?

Empty comments, as shown in the example, hurt readability and might indicate an oversight.

'

'''

Some meaningful text should be added to the comment, or the comment markers should be removed.

================================================ FILE: analyzers/rspec/vbnet/S4663.json ================================================ { "title": "Comments should not be empty", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-4663", "sqKey": "S4663", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S4790.html ================================================

Cryptographic hash algorithms such as MD2, MD4, MD5, MD6, HAVAL-128, DSA (which uses SHA-1), RIPEMD, RIPEMD-128, RIPEMD-160and SHA-1 are no longer considered secure, because it is possible to have collisions (little computational effort is enough to find two or more different inputs that produce the same hash).

Message authentication code (MAC) algorithms such as HMAC-MD5 or HMAC-SHA1 use weak hash functions as building blocks. Although they are not all proven to be weak, they are considered legacy algorithms and should be avoided.

Ask Yourself Whether

The hashed value is used in a security context like:

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

Recommended Secure Coding Practices

Safer alternatives, such as SHA-256, SHA-512, SHA-3 are recommended, and for password hashing, it’s even better to use algorithms that do not compute too "quickly", like bcrypt, scrypt, argon2 or pbkdf2 because it slows down brute force attacks.

Sensitive Code Example

Imports System.Security.Cryptography

Sub ComputeHash()

    ' Review all instantiations of classes that inherit from HashAlgorithm, for example:
    Dim hashAlgo As HashAlgorithm = HashAlgorithm.Create() ' Sensitive
    Dim hashAlgo2 As HashAlgorithm = HashAlgorithm.Create("SHA1") ' Sensitive
    Dim sha As SHA1 = New SHA1CryptoServiceProvider() ' Sensitive
    Dim md5 As MD5 = New MD5CryptoServiceProvider() ' Sensitive

    ' ...
End Sub

Class MyHashAlgorithm
    Inherits HashAlgorithm ' Sensitive

    ' ...
End Class

Compliant Solution

Imports System.Security.Cryptography

Sub ComputeHash()
    Dim sha256 = New SHA256CryptoServiceProvider() ' Compliant
    Dim sha384 = New SHA384CryptoServiceProvider() ' Compliant
    Dim sha512 = New SHA512CryptoServiceProvider() ' Compliant

    ' ...
End Sub

See

================================================ FILE: analyzers/rspec/vbnet/S4790.json ================================================ { "title": "Using weak hashing algorithms is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4790", "sqKey": "S4790", "scope": "Main", "securityStandards": { "CWE": [ 1240 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "3.4", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "6.2.4" ] } } ================================================ FILE: analyzers/rspec/vbnet/S4792.html ================================================

This rule is deprecated, and will eventually be removed.

Configuring loggers is security-sensitive. It has led in the past to the following vulnerabilities:

Logs are useful before, during and after a security incident.

Logs are also a target for attackers because they might contain sensitive information. Configuring loggers has an impact on the type of information logged and how they are logged.

This rule flags for review code that initiates loggers configuration. The goal is to guide security code reviews.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Remember that configuring loggers properly doesn’t make them bullet-proof. Here is a list of recommendations explaining on how to use your logs:

Sensitive Code Example

.Net Core: configure programmatically

Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports Microsoft.AspNetCore
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Logging
Imports Microsoft.Extensions.Options

Namespace MvcApp

    Public Class ProgramLogging

        Public Shared Function CreateWebHostBuilder(args As String()) As IWebHostBuilder

            WebHost.CreateDefaultBuilder(args) _
                .ConfigureLogging(Function(hostingContext, Logging) ' Sensitive
                                      ' ...
                                  End Function) _
            .UseStartup(Of StartupLogging)()

            '...
        End Function
    End Class


    Public Class StartupLogging

        Public Sub ConfigureServices(services As IServiceCollection)

            services.AddLogging(Function(logging) ' Sensitive
                                    '...
                                End Function)
        End Sub

        Public Sub Configure(app As IApplicationBuilder, env As IHostingEnvironment, loggerFactory As ILoggerFactory)

            Dim config As IConfiguration = Nothing
            Dim level As LogLevel = LogLevel.Critical
            Dim includeScopes As Boolean = False
            Dim filter As Func(Of String, Microsoft.Extensions.Logging.LogLevel, Boolean) = Nothing
            Dim consoleSettings As Microsoft.Extensions.Logging.Console.IConsoleLoggerSettings = Nothing
            Dim azureSettings As Microsoft.Extensions.Logging.AzureAppServices.AzureAppServicesDiagnosticsSettings = Nothing
            Dim eventLogSettings As Microsoft.Extensions.Logging.EventLog.EventLogSettings = Nothing

            ' An issue will be raised for each call to an ILoggerFactory extension methods adding loggers.
            loggerFactory.AddAzureWebAppDiagnostics() ' Sensitive
            loggerFactory.AddAzureWebAppDiagnostics(azureSettings) ' Sensitive
            loggerFactory.AddConsole() ' Sensitive
            loggerFactory.AddConsole(level) ' Sensitive
            loggerFactory.AddConsole(level, includeScopes) ' Sensitive
            loggerFactory.AddConsole(filter) ' Sensitive
            loggerFactory.AddConsole(filter, includeScopes) ' Sensitive
            loggerFactory.AddConsole(config) ' Sensitive
            loggerFactory.AddConsole(consoleSettings) ' Sensitive
            loggerFactory.AddDebug() ' Sensitive
            loggerFactory.AddDebug(level) ' Sensitive
            loggerFactory.AddDebug(filter) ' Sensitive
            loggerFactory.AddEventLog() ' Sensitive
            loggerFactory.AddEventLog(eventLogSettings) ' Sensitive
            loggerFactory.AddEventLog(level) ' Sensitive
            ' Only available for NET Standard 2.0 and above
            'loggerFactory.AddEventSourceLogger() ' Sensitive

            Dim providers As IEnumerable(Of ILoggerProvider) = Nothing
            Dim filterOptions1 As LoggerFilterOptions = Nothing
            Dim filterOptions2 As IOptionsMonitor(Of LoggerFilterOptions) = Nothing

            Dim factory As LoggerFactory = New LoggerFactory() ' Sensitive
            factory = New LoggerFactory(providers) ' Sensitive
            factory = New LoggerFactory(providers, filterOptions1) ' Sensitive
            factory = New LoggerFactory(providers, filterOptions2) ' Sensitive
        End Sub
    End Class
End Namespace

Log4Net

Imports System
Imports System.IO
Imports System.Xml
Imports log4net.Appender
Imports log4net.Config
Imports log4net.Repository

Namespace Logging
    Class Log4netLogging
        Private Sub Foo(ByVal repository As ILoggerRepository, ByVal element As XmlElement, ByVal configFile As FileInfo, ByVal configUri As Uri, ByVal configStream As Stream, ByVal appender As IAppender, ParamArray appenders As IAppender())
            log4net.Config.XmlConfigurator.Configure(repository) ' Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, element) ' Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configFile) ' Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configUri) ' Sensitive
            log4net.Config.XmlConfigurator.Configure(repository, configStream) ' Sensitive
            log4net.Config.XmlConfigurator.ConfigureAndWatch(repository, configFile) ' Sensitive

            log4net.Config.DOMConfigurator.Configure() ' Sensitive
            log4net.Config.DOMConfigurator.Configure(repository) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(element) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, element) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(configFile) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, configFile) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(configStream) ' Sensitive
            log4net.Config.DOMConfigurator.Configure(repository, configStream) ' Sensitive
            log4net.Config.DOMConfigurator.ConfigureAndWatch(configFile) ' Sensitive
            log4net.Config.DOMConfigurator.ConfigureAndWatch(repository, configFile) ' Sensitive

            log4net.Config.BasicConfigurator.Configure() ' Sensitive
            log4net.Config.BasicConfigurator.Configure(appender) ' Sensitive
            log4net.Config.BasicConfigurator.Configure(appenders) ' Sensitive
            log4net.Config.BasicConfigurator.Configure(repository) ' Sensitive
            log4net.Config.BasicConfigurator.Configure(repository, appender) ' Sensitive
            log4net.Config.BasicConfigurator.Configure(repository, appenders) ' Sensitive
        End Sub
    End Class
End Namespace

NLog: configure programmatically

Namespace Logging
    Class NLogLogging
        Private Sub Foo(ByVal config As NLog.Config.LoggingConfiguration)
            NLog.LogManager.Configuration = config ' Sensitive
        End Sub
    End Class
End Namespace

Serilog

Namespace Logging
    Class SerilogLogging
        Private Sub Foo()
            Dim config As Serilog.LoggerConfiguration = New Serilog.LoggerConfiguration() ' Sensitive
        End Sub
    End Class
End Namespace

See

================================================ FILE: analyzers/rspec/vbnet/S4792.json ================================================ { "title": "Configuring loggers is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "CONVENTIONAL" }, "status": "deprecated", "tags": [], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4792", "sqKey": "S4792", "scope": "Main", "securityStandards": { "CWE": [ 117, 532 ], "OWASP": [ "A3", "A10" ], "OWASP Top 10 2021": [ "A9" ], "PCI DSS 3.2": [ "10.1", "10.2", "10.3" ], "PCI DSS 4.0": [ "10.2" ], "ASVS 4.0": [ "7.1.1", "7.1.2" ] } } ================================================ FILE: analyzers/rspec/vbnet/S4830.html ================================================

This vulnerability makes it possible that an encrypted communication is intercepted.

Why is this an issue?

Transport Layer Security (TLS) provides secure communication between systems over the internet by encrypting the data sent between them. Certificate validation adds an extra layer of trust and security to this process to ensure that a system is indeed the one it claims to be.

When certificate validation is disabled, the client skips a critical security check. This creates an opportunity for attackers to pose as a trusted entity and intercept, manipulate, or steal the data being transmitted.

What is the potential impact?

Establishing trust in a secure way is a non-trivial task. When you disable certificate validation, you are removing a key mechanism designed to build this trust in internet communication, opening your system up to a number of potential threats.

Identity spoofing

If a system does not validate certificates, it cannot confirm the identity of the other party involved in the communication. An attacker can exploit this by creating a fake server and masquerading as a legitimate one. For example, they might set up a server that looks like your bank’s server, tricking your system into thinking it is communicating with the bank. This scenario, called identity spoofing, allows the attacker to collect any data your system sends to them, potentially leading to significant data breaches.

Loss of data integrity

When TLS certificate validation is disabled, the integrity of the data you send and receive cannot be guaranteed. An attacker could modify the data in transit, and you would have no way of knowing. This could range from subtle manipulations of the data you receive to the injection of malicious code or malware into your system. The consequences of such breaches of data integrity can be severe, depending on the nature of the data and the system.

How to fix it in .NET

Code examples

In the following example, the callback change impacts the entirety of HTTP requests made by the application.

The certificate validation gets disabled by overriding ServerCertificateValidationCallback with an empty implementation. It is highly recommended to use the original implementation.

Noncompliant code example

Imports System.Net

Public Sub Send()
    ServicePointManager.ServerCertificateValidationCallback =
        Function(sender, certificate, chain, errors) True ' Noncompliant

    Dim request As System.Net.HttpWebRequest = System.Net.HttpWebRequest.Create(New System.Uri("https://example.com"))
    request.Method = System.Net.WebRequestMethods.Http.Get
    Dim response As System.Net.HttpWebResponse = request.GetResponse()
    response.Close()
End Sub

How does this work?

Addressing the vulnerability of disabled TLS certificate validation primarily involves re-enabling the default validation.

To avoid running into problems with invalid certificates, consider the following sections.

Using trusted certificates

If possible, always use a certificate issued by a well-known, trusted CA for your server. Most programming environments come with a predefined list of trusted root CAs, and certificates issued by these authorities are validated automatically. This is the best practice, and it requires no additional code or configuration.

Working with self-signed certificates or non-standard CAs

In some cases, you might need to work with a server using a self-signed certificate, or a certificate issued by a CA not included in your trusted roots. Rather than disabling certificate validation in your code, you can add the necessary certificates to your trust store.

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S4830.json ================================================ { "title": "Server certificates should be verified during SSL\/TLS connections", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "privacy", "ssl" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-4830", "sqKey": "S4830", "scope": "Main", "securityStandards": { "CWE": [ 295 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2", "A5", "A7" ], "PCI DSS 3.2": [ "4.1", "6.5.4", "6.5.10" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "1.9.2", "9.2.1" ], "STIG ASD_V5R3": [ "V-222550" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S5042.html ================================================

Successful Zip Bomb attacks occur when an application expands untrusted archive files without controlling the size of the expanded data, which can lead to denial of service. A Zip bomb is usually a malicious archive file of a few kilobytes of compressed data but turned into gigabytes of uncompressed data. To achieve this extreme compression ratio, attackers will compress irrelevant data (eg: a long string of repeated bytes).

Ask Yourself Whether

Archives to expand are untrusted and:

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

Recommended Secure Coding Practices

Sensitive Code Example

For Each entry As ZipArchiveEntry in archive.Entries
    ' entry.FullName could contain parent directory references ".." and the destinationPath variable could become outside of the desired path
    string destinationPath = Path.GetFullPath(Path.Combine(path, entry.FullName))
    entry.ExtractToFile(destinationPath) ' Sensitive, extracts the entry to a file

    Dim stream As Stream
    stream = entry.Open() ' Sensitive, the entry is about to be extracted
Next

Compliant Solution

Const ThresholdRatio As Double = 10
Const ThresholdSize As Integer = 1024 * 1024 * 1024 ' 1 GB
Const ThresholdEntries As Integer = 10000
Dim TotalSizeArchive, TotalEntryArchive, TotalEntrySize, Cnt As Integer
Dim Buffer(1023) As Byte
Using ZipToOpen As New FileStream("ZipBomb.zip", FileMode.Open), Archive As New ZipArchive(ZipToOpen, ZipArchiveMode.Read)
    For Each Entry As ZipArchiveEntry In Archive.Entries
        Using s As Stream = Entry.Open
            TotalEntryArchive += 1
            TotalEntrySize = 0
            Do
                Cnt = s.Read(Buffer, 0, Buffer.Length)
                TotalEntrySize += Cnt
                TotalSizeArchive += Cnt
                If TotalEntrySize / Entry.CompressedLength > ThresholdRatio Then Exit Do    ' Ratio between compressed And uncompressed data Is highly suspicious, looks Like a Zip Bomb Attack
            Loop While Cnt > 0
        End Using
        If TotalSizeArchive > ThresholdSize Then Exit For       ' The uncompressed data size Is too much for the application resource capacity
        If TotalEntryArchive > ThresholdEntries Then Exit For   ' Too much entries in this archive, can lead to inodes exhaustion of the system
    Next
End Using

See

================================================ FILE: analyzers/rspec/vbnet/S5042.json ================================================ { "title": "Expanding archive files without controlling resource consumption is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5042", "sqKey": "S5042", "scope": "Main", "securityStandards": { "CWE": [ 409 ], "OWASP": [ "A5", "A6" ], "OWASP Top 10 2021": [ "A1", "A5" ], "ASVS 4.0": [ "12.1.2" ] } } ================================================ FILE: analyzers/rspec/vbnet/S5443.html ================================================

Operating systems have global directories where any user has write access. Those folders are mostly used as temporary storage areas like /tmp in Linux based systems. An application manipulating files from these folders is exposed to race conditions on filenames: a malicious user can try to create a file with a predictable name before the application does. A successful attack can result in other files being accessed, modified, corrupted or deleted. This risk is even higher if the application runs with elevated permissions.

In the past, it has led to the following vulnerabilities:

This rule raises an issue whenever it detects a hard-coded path to a publicly writable directory like /tmp (see examples bellow). It also detects access to environment variables that point to publicly writable directories, e.g., TMP, TMPDIR and TEMP.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Out of the box, .NET is missing secure-by-design APIs to create temporary files. To overcome this, one of the following options can be used:

Sensitive Code Example

Using Writer As New StreamWriter("/tmp/f") ' Sensitive
' ...
End Using
Dim Tmp As String = Environment.GetEnvironmentVariable("TMP") ' Sensitive

Compliant Solution

Dim RandomPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())

' Creates a new file with write, non inheritable permissions which is deleted on close.
Using FileStream As New FileStream(RandomPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.DeleteOnClose)
    Using Writer As New StreamWriter(FileStream) ' Sensitive
    ' ...
    End Using
End Using

See

================================================ FILE: analyzers/rspec/vbnet/S5443.json ================================================ { "title": "Using publicly writable directories is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5443", "sqKey": "S5443", "scope": "Main", "securityStandards": { "CWE": [ 377, 379 ], "OWASP": [ "A5", "A3" ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222567" ] } } ================================================ FILE: analyzers/rspec/vbnet/S5445.html ================================================

Temporary files are considered insecurely created when the file existence check is performed separately from the actual file creation. Such a situation can occur when creating temporary files using normal file handling functions or when using dedicated temporary file handling functions that are not atomic.

Why is this an issue?

Creating temporary files in a non-atomic way introduces race condition issues in the application’s behavior. Indeed, a third party can create a given file between when the application chooses its name and when it creates it.

In such a situation, the application might use a temporary file that it does not entirely control. In particular, this file’s permissions might be different than expected. This can lead to trust boundary issues.

What is the potential impact?

Attackers with control over a temporary file used by a vulnerable application will be able to modify it in a way that will affect the application’s logic. By changing this file’s Access Control List or other operating system-level properties, they could prevent the file from being deleted or emptied. They may also alter the file’s content before or while the application uses it.

Depending on why and how the affected temporary files are used, the exploitation of a race condition in an application can have various consequences. They can range from sensitive information disclosure to more serious application or hosting infrastructure compromise.

Information disclosure

Because attackers can control the permissions set on temporary files and prevent their removal, they can read what the application stores in them. This might be especially critical if this information is sensitive.

For example, an application might use temporary files to store users' session-related information. In such a case, attackers controlling those files can access session-stored information. This might allow them to take over authenticated users' identities and entitlements.

Attack surface extension

An application might use temporary files to store technical data for further reuse or as a communication channel between multiple components. In that case, it might consider those files part of the trust boundaries and use their content without additional security validation or sanitation. In such a case, an attacker controlling the file content might use it as an attack vector for further compromise.

For example, an application might store serialized data in temporary files for later use. In such a case, attackers controlling those files' content can change it in a way that will lead to an insecure deserialization exploitation. It might allow them to execute arbitrary code on the application hosting server and take it over.

How to fix it

Code examples

The following code example is vulnerable to a race condition attack because it creates a temporary file using an unsafe API function.

Noncompliant code example

Imports System.IO

Sub Example()
    Dim TempPath = Path.GetTempFileName() 'Noncompliant

    Using Writer As New StreamWriter(TempPath)
        Writer.WriteLine("content")
    End Using
End Sub

Compliant solution

Imports System.IO

Sub Example()
    Dim RandomPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())

    Using FileStream As New FileStream(RandomPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.DeleteOnClose)
        Using Writer As New StreamWriter(FileStream)
            Writer.WriteLine("content")
        End Using
    End Using
End Sub

How does this work?

Applications should create temporary files so that no third party can read or modify their content. It requires that the files' name, location, and permissions are carefully chosen and set. This can be achieved in multiple ways depending on the applications' technology stacks.

Strong security controls

Temporary files can be created using unsafe functions and API as long as strong security controls are applied. Non-temporary file-handling functions and APIs can also be used for that purpose.

In general, applications should ensure that attackers can not create a file before them. This turns into the following requirements when creating the files:

Moreover, when possible, it is recommended that applications destroy temporary files after they have finished using them.

Here the example compliant code uses the Path.GetTempPath and Path.GetRandomFileName functions to generate a unique random file name. The file is then open with the FileMode.CreateNew option that will ensure the creation fails if the file already exists. The FileShare.None option will additionally prevent the file from being opened again by any process. To finish, this code ensures the file will get destroyed once the application has finished using it with the FileOptions.DeleteOnClose option.

Resources

Documentation

Standards

================================================ FILE: analyzers/rspec/vbnet/S5445.json ================================================ { "title": "Insecure temporary file creation methods should not be used", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "cwe" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5445", "sqKey": "S5445", "scope": "Main", "securityStandards": { "CWE": [ 377, 379 ], "OWASP Top 10 2021": [ "A1" ], "PCI DSS 3.2": [ "6.5.8" ], "PCI DSS 4.0": [ "6.2.4" ], "STIG ASD_V5R3": [ "V-222567" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S5542.html ================================================

This vulnerability exposes encrypted data to a number of attacks whose goal is to recover the plaintext.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communications in a variety of domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and reducing the difficulty of recovering the original cleartext.

Unauthenticated modes such as CBC (Cipher Block Chaining) may be used but are prone to attacks that manipulate the ciphertext. They must be used with caution.

For RSA, the weakest algorithms are either using it without padding or using the PKCS1v1.5 padding scheme.

What is the potential impact?

The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.

Below are some real-world scenarios that illustrate possible impacts of an attacker exploiting the vulnerability.

Theft of sensitive data

The encrypted message might contain data that is considered sensitive and should not be known to third parties.

By using a weak algorithm the likelihood that an attacker might be able to recover the cleartext drastically increases.

Additional attack surface

By modifying the cleartext of the encrypted message it might be possible for an attacker to trigger other vulnerabilities in the code. Encrypted values are often considered trusted, since under normal circumstances it would not be possible for a third party to modify them.

How to fix it in .NET

Code examples

Noncompliant code example

Example with a symmetric cipher, AES:

Imports System.Security.Cryptography

Public Module Example

    Public Sub Encrypt()
        Dim Algorithm As New AesManaged() With {
            .KeySize = 128,
            .BlockSize = 128,
            .Mode = CipherMode.ECB, ' Noncompliant
            .Padding = PaddingMode.PKCS7
            }
    End Sub
End Module

Example with an asymmetric cipher, RSA:

Imports System.Security.Cryptography

Public Module Example

    Public Sub Encrypt()
        Dim data(10) As Byte
        Dim RsaCsp = New RSACryptoServiceProvider()
        RsaCsp.Encrypt(data, False) ' Noncompliant
    End Sub
End Module

Compliant solution

For the AES symmetric cipher, use the GCM mode:

Imports System.Security.Cryptography

Public Module Example

    Public Sub Encrypt()
        Dim data(10) As Byte
        Dim Algorithm As New AesGcm(data)
    End Sub
End Module

For the RSA asymmetric cipher, use the Optimal Asymmetric Encryption Padding (OAEP):

Imports System.Security.Cryptography

Public Module Example

    Public Sub Encrypt()
        Dim data(10) As Byte
        Dim RsaCsp = New RSACryptoServiceProvider()
        RsaCsp.Encrypt(data, True) ' Noncompliant
    End Sub
End Module

How does this work?

As a rule of thumb, use the cryptographic algorithms and mechanisms that are considered strong by the cryptographic community.

Appropriate choices are currently the following.

For AES: use authenticated encryption modes

The best-known authenticated encryption mode for AES is Galois/Counter mode (GCM).

GCM mode combines encryption with authentication and integrity checks using a cryptographic hash function and provides both confidentiality and authenticity of data.

Other similar modes are:

It is also possible to use AES-CBC with HMAC for integrity checks. However, it is considered more straightforward to use AES-GCM directly instead.

For RSA: use the OAEP scheme

The Optimal Asymmetric Encryption Padding scheme (OAEP) adds randomness and a secure hash function that strengthens the regular inner workings of RSA.

Resources

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/vbnet/S5542.json ================================================ { "title": "Encryption algorithms should be used with secure mode and padding scheme", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5542", "sqKey": "S5542", "scope": "Main", "securityStandards": { "CWE": [ 327, 780 ], "OWASP": [ "A6", "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "2.9.3", "6.2.2", "8.3.7" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S5547.html ================================================

This vulnerability makes it possible that the cleartext of the encrypted message might be recoverable without prior knowledge of the key.

Why is this an issue?

Encryption algorithms are essential for protecting sensitive information and ensuring secure communication in various domains. They are used for several important reasons:

When selecting encryption algorithms, tools, or combinations, you should also consider two things:

  1. No encryption is unbreakable.
  2. The strength of an encryption algorithm is usually measured by the effort required to crack it within a reasonable time frame.

For these reasons, as soon as cryptography is included in a project, it is important to choose encryption algorithms that are considered strong and secure by the cryptography community.

What is the potential impact?

The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.

Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.

Theft of sensitive data

The encrypted message might contain data that is considered sensitive and should not be known to third parties.

By using a weak algorithm the likelihood that an attacker might be able to recover the cleartext drastically increases.

Additional attack surface

By modifying the cleartext of the encrypted message it might be possible for an attacker to trigger other vulnerabilities in the code. Encrypted values are often considered trusted, since under normal circumstances it would not be possible for a third party to modify them.

How to fix it in .NET

Code examples

The following code contains examples of algorithms that are not considered highly resistant to cryptanalysis and thus should be avoided.

Noncompliant code example

Imports System.Security.Cryptography

Public Sub Encrypt()
    Dim SimpleDES As New DESCryptoServiceProvider() ' Noncompliant
End Sub

Compliant solution

Imports System.Security.Cryptography

Public Sub Encrypt()
    Dim AES128ECB = Aes.Create()
End Sub

How does this work?

Use a secure algorithm

It is highly recommended to use an algorithm that is currently considered secure by the cryptographic community. A common choice for such an algorithm is the Advanced Encryption Standard (AES).

For block ciphers, it is not recommended to use algorithms with a block size that is smaller than 128 bits.

How to fix it in BouncyCastle

Code examples

The following code contains examples of algorithms that are not considered highly resistant to cryptanalysis and thus should be avoided.

Noncompliant code example

```suggestion
Imports Org.BouncyCastle.Crypto.Engines
Imports Org.BouncyCastle.Crypto.Parameters

Public Sub Encrypt()
    Dim AesFast As new AesFastEngine() ' Noncompliant
End Sub

Compliant solution

Imports Org.BouncyCastle.Crypto.Engines
Imports Org.BouncyCastle.Crypto.Parameters

```suggestion
Public Sub Encrypt()
    Dim AES As new AESEngine()
End Sub

How does this work?

Use a secure algorithm

It is highly recommended to use an algorithm that is currently considered secure by the cryptographic community. A common choice for such an algorithm is the Advanced Encryption Standard (AES).

For block ciphers, it is not recommended to use algorithms with a block size that is smaller than 128 bits.

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S5547.json ================================================ { "title": "Cipher algorithms should be robust", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5547", "sqKey": "S5547", "scope": "Main", "securityStandards": { "CWE": [ 327, 326 ], "OWASP": [ "A3", "A6" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "4.1", "6.5.3", "6.5.4" ], "PCI DSS 4.0": [ "4.2.1", "6.2.4" ], "ASVS 4.0": [ "6.2.2", "6.2.3", "6.2.5", "8.3.7" ], "STIG ASD_V5R3": [ "V-222396" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S5659.html ================================================

This vulnerability allows forging of JSON Web Tokens to impersonate other users.

Why is this an issue?

JSON Web Tokens (JWTs), a popular method of securely transmitting information between parties as a JSON object, can become a significant security risk when they are not properly signed with a robust cipher algorithm, left unsigned altogether, or if the signature is not verified. This vulnerability class allows malicious actors to craft fraudulent tokens, effectively impersonating user identities. In essence, the integrity of a JWT hinges on the strength and presence of its signature.

What is the potential impact?

When a JSON Web Token is not appropriately signed with a strong cipher algorithm or if the signature is not verified, it becomes a significant threat to data security and the privacy of user identities.

Impersonation of users

JWTs are commonly used to represent user authorization claims. They contain information about the user’s identity, user roles, and access rights. When these tokens are not securely signed, it allows an attacker to forge them. In essence, a weak or missing signature gives an attacker the power to craft a token that could impersonate any user. For instance, they could create a token for an administrator account, gaining access to high-level permissions and sensitive data.

Unauthorized data access

When a JWT is not securely signed, it can be tampered with by an attacker, and the integrity of the data it carries cannot be trusted. An attacker can manipulate the content of the token and grant themselves permissions they should not have, leading to unauthorized data access.

How to fix it in Jwt.Net

Code examples

The following code contains examples of JWT encoding and decoding without a strong cipher algorithm.

Noncompliant code example

Imports JWT

Public Sub Decode(decoder AS IJwtDecoder)
    Dim decoded As String = decoder.Decode(token, secret, verify:= false) ' Noncompliant
End Sub
Imports JWT

Public Sub Decode()
    Dim decoded As String = new JwtBuilder()
        .WithSecret(secret)
        .Decode(token) ' Noncompliant
End Sub

Compliant solution

Imports JWT

Public Sub Decode(decoder AS IJwtDecoder)
    Dim decoded As String = decoder.Decode(token, secret, verify:= true)
End Sub

When using JwtBuilder, make sure to call MustVerifySignature().

Imports JWT

Public Sub Decode()
    Dim decoded As String = new JwtBuilder()
        .WithSecret(secret)
        .MustVerifySignature()
        .Decode(token)
End Sub

How does this work?

Verify the signature of your tokens

Resolving a vulnerability concerning the validation of JWT token signatures is mainly about incorporating a critical step into your process: validating the signature every time a token is decoded. Just having a signed token using a secure algorithm is not enough. If you are not validating signatures, they are not serving their purpose.

Every time your application receives a JWT, it needs to decode the token to extract the information contained within. It is during this decoding process that the signature of the JWT should also be checked.

To resolve the issue, follow these instructions:

  1. Use framework-specific functions for signature verification: Most programming frameworks that support JWTs provide specific functions to not only decode a token but also validate its signature simultaneously. Make sure to use these functions when handling incoming tokens.
  2. Handle invalid signatures appropriately: If a JWT’s signature does not validate correctly, it means the token is not trustworthy, indicating potential tampering. The action to take when encountering an invalid token should be denying the request carrying it and logging the event for further investigation.
  3. Incorporate signature validation in your tests: When you are writing tests for your application, include tests that check the signature validation functionality. This can help you catch any instances where signature verification might be unintentionally skipped or bypassed.

By following these practices, you can ensure the security of your application’s JWT handling process, making it resistant to attacks that rely on tampering with tokens. Validation of the signature needs to be an integral and non-negotiable part of your token handling process.

Going the extra mile

Securely store your secret keys

Ensure that your secret keys are stored securely. They should not be hard-coded into your application code or checked into your version control system. Instead, consider using environment variables, secure key management systems, or vault services.

Rotate your secret keys

Even with the strongest cipher algorithms, there is a risk that your secret keys may be compromised. Therefore, it is a good practice to periodically rotate your secret keys. By doing so, you limit the amount of time that an attacker can misuse a stolen key. When you rotate keys, be sure to allow a grace period where tokens signed with the old key are still accepted to prevent service disruptions.

Resources

Standards

================================================ FILE: analyzers/rspec/vbnet/S5659.json ================================================ { "title": "JWT should be signed and verified with strong cipher algorithms", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "HIGH" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "privacy" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5659", "sqKey": "S5659", "scope": "Main", "securityStandards": { "CWE": [ 347 ], "OWASP": [ "A3" ], "OWASP Top 10 2021": [ "A2" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ] }, "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S5693.html ================================================

Rejecting requests with significant content length is a good practice to control the network traffic intensity and thus resource consumption in order to prevent DoS attacks.

Ask Yourself Whether

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

Recommended Secure Coding Practices

It is recommended to customize the rule with the limit values that correspond to the web application.

Sensitive Code Example

Imports Microsoft.AspNetCore.Mvc

Public Class MyController
    Inherits Controller

    <HttpPost>
    <DisableRequestSizeLimit> ' Sensitive: No size  limit
    <RequestSizeLimit(10485760)> ' Sensitive: 10485760 B = 10240 KB = 10 MB is more than the recommended limit of 8MB
    Public Function PostRequest(Model model) As IActionResult
    ' ...
    End Function

    <HttpPost>
    <RequestFormLimits(MultipartBodyLengthLimit = 10485760)> ' Sensitive: 10485760 B = 10240 KB = 10 MB is more than the recommended limit of 8MB
    Public Function MultipartFormRequest(Model model) As IActionResult
    ' ...
    End Function

End Class

Compliant Solution

Imports Microsoft.AspNetCore.Mvc

Public Class MyController
    Inherits Controller

    <HttpPost>
    <RequestSizeLimit(8388608)> ' Compliant: 8388608 B = 8192 KB = 8 MB
    Public Function PostRequest(Model model) As IActionResult
    ' ...
    End Function

    <HttpPost>
    <RequestFormLimits(MultipartBodyLengthLimit = 8388608)> ' Compliant: 8388608 B = 8192 KB = 8 MB
    Public Function MultipartFormRequest(Model model) AS IActionResult
    ' ...
    End Function

End Class

See

================================================ FILE: analyzers/rspec/vbnet/S5693.json ================================================ { "title": "Allowing requests with excessive content length is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5693", "sqKey": "S5693", "scope": "All", "securityStandards": { "CWE": [ 400, 770 ], "OWASP": [ "A6" ], "OWASP Top 10 2021": [ "A5" ], "PCI DSS 3.2": [ "2.2" ], "PCI DSS 4.0": [ "2.2" ], "ASVS 4.0": [ "12.1.1", "12.1.3" ] } } ================================================ FILE: analyzers/rspec/vbnet/S5753.html ================================================

ASP.NET 1.1+ comes with a feature called Request Validation, preventing the server to accept content containing un-encoded HTML. This feature comes as a first protection layer against Cross-Site Scripting (XSS) attacks and act as a simple Web Application Firewall (WAF) rejecting requests potentially containing malicious content.

While this feature is not a silver bullet to prevent all XSS attacks, it helps to catch basic ones. It will for example prevent <script type="text/javascript" src="https://malicious.domain/payload.js"> to reach your Controller.

Note: Request Validation feature being only available for ASP.NET, no Security Hotspot is raised on ASP.NET Core applications.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

At Controller level:

<ValidateInput(False)>
Public Function Welcome(Name As String) As ActionResult
  ...
End Function

At application level, configured in the Web.config file:

<configuration>
   <system.web>
      <pages validateRequest="false" />
      ...
      <httpRuntime requestValidationMode="0.0" />
   </system.web>
</configuration>

Compliant Solution

At Controller level:

<ValidateInput(True)>
Public Function Welcome(Name As String) As ActionResult
  ...
End Function

or

Public Function Welcome(Name As String) As ActionResult
  ...
End Function

At application level, configured in the Web.config file:

<configuration>
   <system.web>
      <pages validateRequest="true" />
      ...
      <httpRuntime requestValidationMode="4.5" />
   </system.web>
</configuration>

See

================================================ FILE: analyzers/rspec/vbnet/S5753.json ================================================ { "title": "Disabling ASP.NET \"Request Validation\" feature is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "COMPLETE" }, "status": "ready", "tags": [ "cwe" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5753", "sqKey": "S5753", "scope": "Main", "securityStandards": { "CWE": [ 79 ], "OWASP": [ "A7" ], "OWASP Top 10 2021": [ "A3" ], "PCI DSS 3.2": [ "6.5.7" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "5.3.3" ] } } ================================================ FILE: analyzers/rspec/vbnet/S5773.html ================================================

Deserialization is the process of converting serialized data (such as objects or data structures) back into their original form. Types allowed to be unserialized should be strictly controlled.

Why is this an issue?

During the deserialization process, the state of an object will be reconstructed from the serialized data stream. By allowing unrestricted deserialization of types, the application makes it possible for attackers to use types with dangerous or otherwise sensitive behavior during the deserialization process.

What is the potential impact?

When an application deserializes untrusted data without proper restrictions, an attacker can craft malicious serialized objects. Depending on the affected objects and properties, the consequences can vary.

Remote Code Execution

If attackers can craft malicious serialized objects that contain executable code, this code will run within the application’s context, potentially gaining full control over the system. This can lead to unauthorized access, data breaches, or even complete system compromise.

For example, a well-known attack vector consists in serializing an object of type TempFileCollection with arbitrary files (defined by an attacker) which will be deleted on the application deserializing this object (when the finalize() method of the TempFileCollection object is called). These kinds of specially crafted serialized objects are called "gadgets".

Privilege escalation

Unrestricted deserialization can also enable attackers to escalate their privileges within the application. By manipulating the serialized data, an attacker can modify object properties or bypass security checks, granting them elevated privileges that they should not have. This can result in unauthorized access to sensitive data, unauthorized actions, or even administrative control over the application.

Denial of Service

In some cases, an attacker can abuse the deserialization process to cause a denial of service (DoS) condition. By providing specially crafted serialized data, the attacker can trigger excessive resource consumption, leading to system instability or unresponsiveness. This can disrupt the availability of the application, impacting its functionality and causing inconvenience to users.

How to fix it

Code examples

Noncompliant code example

With BinaryFormatter, NetDataContractSerializer or SoapFormatter:

Dim myBinaryFormatter = New BinaryFormatter()
myBinaryFormatter.Deserialize(stream) ' Noncompliant

With JavaScriptSerializer:

Dim serializer1 As JavaScriptSerializer = New JavaScriptSerializer(New SimpleTypeResolver()) ' Noncompliant: SimpleTypeResolver is insecure (every type is resolved)
serializer1.Deserialize(Of ExpectedType)(json)

Compliant solution

With BinaryFormatter, NetDataContractSerializer or SoapFormatter:

NotInheritable Class CustomBinder
    Inherits SerializationBinder
    Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
        If Not (Equals(typeName, "type1") OrElse Equals(typeName, "type2") OrElse Equals(typeName, "type3")) Then
            Throw New SerializationException("Only type1, type2 and type3 are allowed")
        End If
        Return Assembly.Load(assemblyName).[GetType](typeName)
    End Function
End Class

Dim myBinaryFormatter = New BinaryFormatter()
myBinaryFormatter.Binder = New CustomBinder()
myBinaryFormatter.Deserialize(stream)

With JavaScriptSerializer:

Public Class CustomSafeTypeResolver
    Inherits JavaScriptTypeResolver
    Public Overrides Function ResolveType(id As String) As Type
        If Not Equals(id, "ExpectedType") Then
            Throw New ArgumentNullException("Only ExpectedType is allowed during deserialization")
        End If
        Return Type.[GetType](id)
    End Function
End Class

Dim serializer As JavaScriptSerializer = New JavaScriptSerializer(New CustomSafeTypeResolver())
serializer.Deserialize(Of ExpectedType)(json)

Going the extra mile

Instead of using BinaryFormatter and similar serializers, it is recommended to use safer alternatives in most of the cases, such as XmlSerializer or DataContractSerializer.

If it’s not possible then try to mitigate the risk by restricting the types allowed to be deserialized:

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/vbnet/S5773.json ================================================ { "title": "Types allowed to be deserialized should be restricted", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "quickfix": "targeted", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "cwe", "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5773", "sqKey": "S5773", "scope": "Main", "securityStandards": { "CWE": [ 502 ], "OWASP": [ "A8" ], "OWASP Top 10 2021": [ "A8" ], "ASVS 4.0": [ "1.5.2", "5.5.1", "5.5.3" ] } } ================================================ FILE: analyzers/rspec/vbnet/S5856.html ================================================

Why is this an issue?

Regular expressions have their own syntax that is understood by regular expression engines. Those engines will throw an exception at runtime if they are given a regular expression that does not conform to that syntax.

To avoid syntax errors, special characters should be escaped with backslashes when they are intended to be matched literally and references to capturing groups should use the correctly spelled name or number of the group.

Negative lookaround groups cannot be combined with RegexOptions.NonBacktracking. Such combination would throw an exception during runtime.

How to fix it

Code examples

Noncompliant code example

Sub Regexes(Input As String)
    Dim Rx As New Regex("[A")                                                       ' Noncompliant: unmatched "["
    Dim Match = Regex.Match(Input, "[A")                                            ' Noncompliant
    Dim NegativeLookahead As New Regex("a(?!b)", RegexOptions.NonBacktracking)      ' Noncompliant: negative lookahead without backtracking
    Dim NegativeLookbehind As New Regex("(?<!a)b", RegexOptions.NonBacktracking)    ' Noncompliant: negative lookbehind without backtracking
End Sub

Compliant solution

Sub Regexes(Input As String)
    Dim Rx As New Regex("[A-Z]")
    Dim Match = Regex.Match(Input, "[A-Z]")
    Dim NegativeLookahead As New Regex("a(?!b)")
    Dim NegativeLookbehind As New Regex("(?<!a)b")
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S5856.json ================================================ { "title": "Regular expressions should be syntactically valid", "type": "BUG", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "15min" }, "tags": [ "regex" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-5856", "sqKey": "S5856", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S5944.html ================================================

Why is this an issue?

Functions can return values using two different syntaxes. The modern, and correct, way to do it is to use a Return statement. The VB6 way, i.e. old way, is to assign a return value to the function’s name .

The VB6 syntax is obsolete as it was introduced to simplify migration from VB6 projects. The compiler will create a local variable which is implicitly returned when execution exits the function’s scope.

Return statement should be used instead as they are easier to read and understand.

Noncompliant code example

Public Function FunctionName() As Integer
    FunctionName = 42 ' Noncompliant
End Function

Public Function FunctionNameFromVariable() As Integer
    Dim Value As Integer = 42
    FunctionNameFromVariable = Value ' Noncompliant
End Function

Compliant solution

Public Function FunctionName() As Integer
    Return 42
End Function

Public Function FunctionNameFromVariable() As Integer
    Dim Value As Integer = 42
    Return Value
End Function

Resources

================================================ FILE: analyzers/rspec/vbnet/S5944.json ================================================ { "title": "\"Return\" statements should be used instead of assigning values to function names", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice", "confusing" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-5944", "sqKey": "S5944", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6145.html ================================================

Why is this an issue?

There are several compilations options available for Visual Basic source code and Option Strict defines compiler behavior for implicit data type conversions. Specifying Option Strict Off will allow:

This behavior can lead to unexpected runtime errors due to type mismatch or missing members.

Option Strict can be set in project properties or overridden in individual source files.

Noncompliant code example

Option Strict Off    ' Noncompliant

Public Class KnownType

    Public ReadOnly Property Name As String

End Class

Public Module MainMod

    Public Function DoSomething(Arg) As String  ' Type for "Arg" argument is not defined.
        Dim Item As KnownType = Arg             ' Implicit narrowing conversion doesn't enforce "Arg" to be of type "KnownType"
        Return Arg.Title                        ' "Title" might not exist in "Arg"
    End Function

End Module

Compliant solution

Option Strict On

Public Class KnownType

    Public ReadOnly Property Name As String

End Class

Public Module MainMod

    Public Function DoSomething(Arg As KnownType) As String
        Dim Item As KnownType = Arg
        Return Arg.Name
    End Function

End Module

Resources

================================================ FILE: analyzers/rspec/vbnet/S6145.json ================================================ { "title": "\"Option Strict\" should be enabled", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6145", "sqKey": "S6145", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6146.html ================================================

Why is this an issue?

There are several compilations options available for Visual Basic source code and Option Explicit defines compiler behavior for implicit variable declarations. Specifying Option Explicit Off will allow creating a variable by it’s first usage. This behavior can lead to unexpected runtime errors due to typos in variable names.

Option Explicit can be set in project properties or overridden in individual source files.

Noncompliant code example

Option Explicit Off ' Noncompliant

Module MainMod

    Public Sub DoSomething(First As String, Second As String)
        Parameter = Fist        ' New local variable "Fist" is created and assigned to new local variable "Parameter" instead of "First" argument.
        DoSomething(Parameter)
        Parametr = Second       ' "Second" argument is assigned to newly created variable "Parametr" instead of intended "Parameter".
        DoSomething(Parameter)  ' Value of "Parameter" is always Nothing
    End Sub

    Private Sub DoSomething(Parameter As String)
        ' ...
    End Sub

End Module

Compliant solution

Option Explicit On

Module MainMod

    Public Sub DoSomething(First As String, Second As String)
        Dim Parameter As String = First
        DoSomething(Parameter)
        Parameter = Second
        DoSomething(Parameter)
    End Sub

    Private Sub DoSomething(Parameter As String)
        ' ...
    End Sub

End Module

Resources

================================================ FILE: analyzers/rspec/vbnet/S6146.json ================================================ { "title": "\"Option Explicit\" should be enabled", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "3h" }, "tags": [ "bad-practice" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-6146", "sqKey": "S6146", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6354.html ================================================

Why is this an issue?

One of the principles of a unit test is that it must have full control of the system under test. This is problematic when production code includes calls to static methods, which cannot be changed or controlled. Date/time functions are usually provided by system libraries as static methods.

This can be improved by wrapping the system calls in an object or service that can be controlled inside the unit test.

Noncompliant code example

Public Class Foo
    Public Function HelloTime() As String
        Return $"Hello at {DateTime.UtcNow}"
    End Function
End Class

Compliant solution

There are different approaches to solve this problem. One of them is suggested below. There are also open source libraries (such as NodaTime) which already implement an IClock interface and a FakeClock testing class.

Public Interface IClock
    Function UtcNow() As Date
End Interface

Public Class Foo
    Public Function HelloTime(clock As IClock) As String
        Return $"Hello at {clock.UtcNow()}"
    End Function
End Class

Public Class FooTest
    Public Class TestClock
        Implements IClock
        ' implement
    End Class

    <Fact>
    Public Sub HelloTime_Gives_CorrectTime()
        Dim dateTime = New DateTime(2017, 06, 11)
        Assert.Equal((New Foo()).HelloTime(New TestClock(dateTime)), $"Hello at {dateTime}")
    End Sub
End Class

Another possible solution is using an adaptable module, ideally supports an IDisposable method, that not only adjusts the time behaviour for the current thread only, but also for scope of the using.

Public Module Clock
    Public Function UtcNow() As Date
    End Function

    Public Function SetTimeForCurrentThread(time As Func(Of Date)) As IDisposable
    End Function
End Module

Public Class Foo
    Public Function HelloTime() As String
        Return $"Hello at {Clock.UtcNow()}"
    End Function
End Class

Public Class FooTest
    <Fact>
    Public Sub HelloTime_Gives_CorrectTime()
        Dim dateTime = New DateTime(2017, 06, 11)

        Using SetTimeForCurrentThread(Function() dateTime)
            Assert.Equal((New Foo()).HelloTime(), $"Hello at {dateTime}")
        End Using
    End Sub
End Class

Resources

NodaTime testing

================================================ FILE: analyzers/rspec/vbnet/S6354.json ================================================ { "title": "Use a testable date\/time provider", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "TESTED" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "20min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6354", "sqKey": "S6354", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6418.html ================================================

Why is this an issue?

Hard-coding secrets in source code or binaries makes it easy for attackers to extract sensitive information, especially in distributed or open-source applications. This practice exposes credentials and tokens, increasing the risk of unauthorized access and data breaches.

This rule detects variables/fields/properties having a name matching a list of words (secret, token, credential, auth, api[_.-]?key) being assigned a pseudorandom hard-coded value. The pseudorandomness of the hard-coded value is based on its entropy and the probability to be human-readable. The randomness sensibility can be adjusted if needed. Lower values will detect less random values, raising potentially more false positives.

How to fix it

Secrets should be stored in a configuration file that is not committed to the code repository, in a database, or managed by your cloud provider’s secrets management service. If a secret is exposed in the source code, it must be rotated immediately.

Code Examples

Noncompliant code example

Private MySecret As String = "47828a8dd77ee1eb9dde2d5e93cb221ce8c32b37"

Compliant solution

Private MySecret As String = Environment.GetEnvironmentVariable("MYSECRET")

Resources

================================================ FILE: analyzers/rspec/vbnet/S6418.json ================================================ { "title": "Secrets should not be hard-coded", "type": "VULNERABILITY", "code": { "impacts": { "SECURITY": "BLOCKER" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "quickfix": "infeasible", "tags": [ "cwe" ], "defaultSeverity": "Blocker", "ruleSpecification": "RSPEC-6418", "sqKey": "S6418", "scope": "Main", "securityStandards": { "CERT": [ "MSC03-J." ], "CWE": [ 798 ], "OWASP": [ "A2" ], "OWASP Top 10 2021": [ "A7" ], "PCI DSS 3.2": [ "6.5.10" ], "PCI DSS 4.0": [ "6.2.4" ], "ASVS 4.0": [ "2.10.4", "3.5.2", "6.4.1" ] } } ================================================ FILE: analyzers/rspec/vbnet/S6444.html ================================================

Not specifying a timeout for regular expressions can lead to a Denial-of-Service attack. Pass a timeout when using System.Text.RegularExpressions to process untrusted input because a malicious user might craft a value for which the evaluation lasts excessively long.

Ask Yourself Whether

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

Recommended Secure Coding Practices

Sensitive Code Example

Public Sub RegexPattern(Input As String)
    Dim EmailPattern As New Regex(".+@.+", RegexOptions.None)
    Dim IsNumber as Boolean = Regex.IsMatch(Input, "[0-9]+")
    Dim IsLetterA as Boolean = Regex.IsMatch(Input, "(a+)+")
End Sub

Compliant Solution

Public Sub RegexPattern(Input As String)
    Dim EmailPattern As New Regex(".+@.+", RegexOptions.None, TimeSpan.FromMilliseconds(100))
    Dim IsNumber as Boolean = Regex.IsMatch(Input, "[0-9]+", RegexOptions.None, TimeSpan.FromMilliseconds(100))
    Dim IsLetterA As Boolean = Regex.IsMatch(Input, "(a+)+", RegexOptions.NonBacktracking) '.Net 7 And above
    AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromMilliseconds(100)) 'process-wide setting
End Sub

See

================================================ FILE: analyzers/rspec/vbnet/S6444.json ================================================ { "title": "Not specifying a timeout for regular expressions is security-sensitive", "type": "SECURITY_HOTSPOT", "code": { "impacts": { "SECURITY": "MEDIUM" }, "attribute": "TRUSTWORTHY" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "cwe", "regex" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6444", "sqKey": "S6444", "scope": "Main", "securityStandards": { "CWE": [ 400, 1333 ], "OWASP": [ "A1" ] }, "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6513.html ================================================

Why is this an issue?

The ExcludeFromCodeCoverageAttribute is used to exclude portions of code from code coverage reporting. It is a bad practice to retain code that is not covered by unit tests. In .Net 5, the Justification property was added to the ExcludeFromCodeCoverageAttribute as an opportunity to document the rationale for the exclusion. This rule raises an issue when no such justification is given.

Noncompliant code example

Public Structure Coordinates

    Public ReadOnly Property X As Integer
    Public ReadOnly Property Y As Integer

    <ExcludeFromCodeCoverage> ' Noncompliant
    Public Overrides Function Equals(obj As Object) As Boolean
        If Not (TypeOf obj Is Coordinates) Then
            Return False
        End If

        Dim coordinates = DirectCast(obj, Coordinates)
        Return X = coordinates.X AndAlso
               Y = coordinates.Y
    End Function

    <ExcludeFromCodeCoverage> ' Noncompliant
    Public Overrides Function GetHashCode() As Integer
        Dim hashCode As Long = 1861411795
        hashCode = (hashCode * -1521134295 + X.GetHashCode()).GetHashCode()
        hashCode = (hashCode * -1521134295 + Y.GetHashCode()).GetHashCode()
        Return hashCode
    End Function
End Structure

Compliant solution

Public Structure Coordinates

    Public ReadOnly Property X As Integer
    Public ReadOnly Property Y As Integer

    <ExcludeFromCodeCoverage(Justification:="Code generated by Visual Studio refactoring")> ' Compliant
    Public Overrides Function Equals(obj As Object) As Boolean
        If Not (TypeOf obj Is Coordinates) Then
            Return False
        End If

        Dim coordinates = DirectCast(obj, Coordinates)
        Return X = coordinates.X AndAlso
               Y = coordinates.Y
    End Function

    <ExcludeFromCodeCoverage(Justification:="Code generated by Visual Studio refactoring")> ' Compliant
    Public Overrides Function GetHashCode() As Integer
        Dim hashCode As Long = 1861411795
        hashCode = (hashCode * -1521134295 + X.GetHashCode()).GetHashCode()
        hashCode = (hashCode * -1521134295 + Y.GetHashCode()).GetHashCode()
        Return hashCode
    End Function
End Structure

Resources

================================================ FILE: analyzers/rspec/vbnet/S6513.json ================================================ { "title": "\"ExcludeFromCodeCoverage\" attributes should include a justification", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "bad-practice" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6513", "sqKey": "S6513", "scope": "Main", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6561.html ================================================

The rule targets the use of DateTime.Now call followed by some arithmetic operation.

Why is this an issue?

Using DateTime.Now calls within a subtraction operation to measure elapsed time is not recommended. This property is subject to changes such as daylight savings transitions, which can invalidate the calculation if the change occurs during the benchmark session, or when updating a timer. Moreover, DateTime.Now is dependent on the system clock, which may have low resolution on older systems (as low as 15 milliseconds).

How to fix it

Code examples

If the purpose is to benchmark something then, instead of the DateTime.Now property, it’s recommended to use Stopwatch, which is not affected by changes in time such as daylight savings (DST) and automatically checks for the existence of high-precision timers. As a bonus, the StopWatch class is also lightweight and computationally faster than DateTime.

Noncompliant code example

Dim start = DateTime.Now ' First call, on March 26th 2:59 am
' Method to be benchmarked

Console.WriteLine($"{CInt((DateTime.Now - start).TotalMilliseconds)} ms") ' Second call happens 2 minutes later but `Now` is March 26th, 4:01 am as there's a shift to summer time

Compliant solution

Dim stopWatch = Stopwatch.StartNew() ' Compliant
' Method to be benchmarked
stopWatch.Stop()

Console.WriteLine($"{stopWatch.ElapsedMilliseconds} ms")

If, on the other hand, the goal is to refresh a timer prefer using the DateTime.UtcNow property, which guarantees reliable results when doing arithmetic operations during DST transitions.

Noncompliant code example

If (Date.Now - lastRefresh).TotalMilliseconds > MinRefreshInterval Then
    lastRefresh = Date.Now
    ' Refresh
End If

Compliant solution

If (Date.UtcNow - lastRefresh).TotalMilliseconds > MinRefreshInterval Then
    lastRefresh = Date.UtcNow
    ' Refresh
End If

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6561.json ================================================ { "title": "Avoid using \"DateTime.Now\" for benchmarking or timing operations", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6561", "sqKey": "S6561", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S6562.html ================================================

Why is this an issue?

Not knowing the Kind of the DateTime object that an application is using can lead to misunderstandings when displaying or comparing them. Explicitly setting the Kind property helps the application to stay consistent, and its maintainers understand what kind of date is being managed. To achieve this, when instantiating a new DateTime object you should always use a constructor overload that allows you to define the Kind property.

What is the potential impact?

Creating the DateTime object without specifying the property Kind will set it to the default value of DateTimeKind.Unspecified. In this case, calling the method ToUniversalTime will assume that Kind is DateTimeKind.Local and calling the method ToLocalTime will assume that it’s DateTimeKind.Utc. As a result, you might have mismatched DateTime objects in your application.

How to fix it

To resolve this issue, use a constructor overload that lets you specify the DateTimeKind when creating the DateTime object. From .Net 6 onwards, use the DateOnly type if the time portion of the date is not relevant.

Code examples

Noncompliant code example

Private Sub CreateNewTime()
    Dim birthDate = New DateTime(1994, 7, 5, 16, 23, 42)
End Sub

Compliant solution

Private Sub CreateNewTime()
    Dim birthDate = New DateTime(1994, 7, 5, 16, 23, 42, DateTimeKind.Utc)
    ' or from .Net 6 onwards, use DateOnly:
    Dim birthDate = New DateOnly(1994, 7, 5)
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6562.json ================================================ { "title": "Always set the \"DateTimeKind\" when creating new \"DateTime\" instances", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "localisation", "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6562", "sqKey": "S6562", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S6563.html ================================================

Why is this an issue?

You should avoid recording time instants with the use of property DateTime.Now. The property DateTime.Now returns the current date and time expressed in the machine’s local time without containing any timezone-related information (for example, the offset from Coordinated Universal Time). Not having this information means that if you need to display this DateTime object or use it for computations in another machine placed in a different time zone, you won’t be able to reconstruct it in the second machine’s local time without knowing the origin’s offset. This will likely lead to confusion and potential bugs.

Instead, you should record the DateTime instants in UTC, which gives you the date and time as it is in the Coordinated Universal Time. UTC is a time standard for all time zones and is not subjected to Daylight Saving Time (DST).

Similarly, the use of the DateTime.Today property should also be avoided, as it can return different date values depending on the time zone.

Generally, unless the purpose is to only display the Date and Time to a user on their local machine, you should always use UTC (for example, when storing dates in a datebase or using them for calculations).

What is the potential impact?

You can end up with DateTime instants that have no meaning for anyone except the machine they were recorded on. Using UTC gives an unambiguous representation of an instant, and this UTC instant can be transformed into any equivalent local time. This operation isn’t reversible as some local times are ambiguous and can be matched to more than one UTC instant (for example, due to daylight savings).

How to fix it

Instead of DateTime.Now use any of the following:

Instead of DateTime.Today use any of the following:

Code examples

Noncompliant code example

Private Sub LogDateTime()
    Using streamWriter = New StreamWriter("logs.txt", True)
    End Using

    streamWriter.WriteLine($"DateTime:{DateTime.Now.ToString("o")}") ' This log won't have any meaning if it's reconstructed in a machine in a different timezone.
End Sub

Compliant solution

Private Sub LogDateTime()
    Using streamWriter = New StreamWriter("logs.txt", True)
    End Using

    streamWriter.WriteLine($"DateTime:{DateTime.UtcNow.ToString("o")}")
End Sub

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S6563.json ================================================ { "title": "Use UTC when recording DateTime instants", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "pitfall" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6563", "sqKey": "S6563", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6566.html ================================================

This rule recommends using DateTimeOffset instead of DateTime for projects targeting .NET Framework 2.0 or later.

Why is this an issue?

You should use DateTimeOffset instead of DateTime as it provides all the information that the DateTime struct has, and additionally, the offset from Coordinated Universal Time (UTC). This way you can avoid potential problems created by the lack of timezone awareness (see the "Pitfalls" section below for more information).

However, it’s important to note that although DateTimeOffset contains more information than DateTime by storing the offset to UTC, it isn’t tied to a specific time zone. This information must be stored separately to have a full picture of the moment in time with the use of TimeZoneInfo.

How to fix it

In most cases, you can directly replace DateTime with DateTimeOffset. When hardcoding dates with local kind, remember that the offset is timezone dependent, so it should be set according to which timezone that data represents. For more information, refer to DateTime and DateTimeOffset documentation from Microsoft (see the "Resources" section below).

Code examples

Noncompliant code example

Dim myDate As DateTime = New DateTime(2008, 6, 19, 7, 0, 0, DateTimeKind.Local) ' Noncompliant

Dim now = DateTime.Now ' Noncompliant

Compliant solution

Dim myDate As DateTimeOffset = New DateTimeOffset(2008, 6, 19, 7, 0, 0, TimeSpan.FromHours(-7)) ' Compliant

Dim now = DateTimeOffset.Now ' Compliant

Pitfalls

Common DateTime pitfalls include:

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6566.json ================================================ { "title": "Use \"DateTimeOffset\" instead of \"DateTime\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6566", "sqKey": "S6566", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S6575.html ================================================

Since .NET 6 you don’t have to use the TimeZoneConverter library to manually do the conversion between IANA and Windows timezones. The .NET 6.0 introduced new Time Zone enhancements, one being the TimeZoneInfo.FindSystemTimeZoneById(string timezone) method now accepts as input both IANA and Windows time zone IDs on any operating system with installed time zone data. TimeZoneInfo.FindSystemTimeZoneById will automatically convert its input from IANA to Windows and vice versa if the requested time zone is not found on the system.

Why is this an issue?

The method TimeZoneInfo.FindSystemTimeZoneById(string timezone) can get both IANA and Windows timezones as input and automatically convert one to the other if the requested time zone is not found on the system. Because one does not need to handle the conversion, the code will be less complex and easier to maintain.

How to fix it

There’s no need to translate manually between time zones; it is enough to call TimeZoneInfo.FindSystemTimeZoneById(string timezone), where the timezone can be IANA or Windows format. Depending on the OS, the equivalent time zone will be returned (Windows Time Zones for Windows and IANA timezones for Linux, macOS).

Code examples

Noncompliant code example

// Assuming we are in Windows OS and we need to get the Tokyo Time Zone.
Dim ianaTimeZone = "Asia/Tokyo"
Dim windowsTimeZone = TZConvert.IanaToWindows(ianaTimeZone)
Dim tokyoWindowsTimeZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZone)

Compliant solution

// Assuming we are in Windows OS and we need to get the Tokyo Time Zone.
Dim ianaTimeZone = "Asia/Tokyo"
Dim tokyoWindowsTimeZone As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(ianaTimeZone)

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/vbnet/S6575.json ================================================ { "title": "Use \"TimeZoneInfo.FindSystemTimeZoneById\" without converting the timezones with \"TimezoneConverter\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6575", "sqKey": "S6575", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6580.html ================================================

When converting a string representation of a date and time to a DateTime object or any other temporal type with one of the available system parsing methods, you should always provide an IFormatProvider parameter.

Why is this an issue?

If you try to parse a string representation of a date or time without a format provider, the method will use the machine’s CultureInfo; if the given string does not follow it, you’ll have an object that does not match the string representation or an unexpected runtime error.

This rule raises an issue for the following date and time string representation parsing methods:

Of the following types:

How to fix it

Alway use an overload of the parse method, where you can provide an IFormatProvider parameter.

Code examples

Noncompliant code example

Dim dateTimeString = "4/12/2023 4:05:48 PM" ' This is an en-US format string - 12 of April 2023
Dim dateTimeObject = DateTime.Parse(dateTimeString) ' This is wrongly parsed as 4th of December, when it's read in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe)

Dim dateTimeString2 = "4/13/2023 4:05:48 PM" ' This is an en-US format string - 13 of April 2023
Dim dateTimeObject2 = DateTime.Parse(dateTimeString2) ' Runtime Error, when it's parsed in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe).

Dim timeInSaudiArabia = New TimeOnly(16, 23).ToString(New CultureInfo("ar-SA"))
Dim timeObject = TimeOnly.Parse(timeInSaudiArabia) ' Runtime Error, when it's parsed in a machine with "CultureInfo.CurrentCulture" en-150 (English Europe).

Compliant solution

Dim dateTimeString = "4/12/2023 4:05:48 PM" ' This is an en-US format string - 12 of April 2023
Dim dateTimeObject = DateTime.Parse(dateTimeString, New CultureInfo("en-US"))

Dim dateTimeString2 = "4/13/2023 4:05:48 PM" ' This is an en-US format string - 13 of April 2023
Dim dateTimeObject2 = DateTime.Parse(dateTimeString2, New CultureInfo("en-US"))

Dim timeInSaudiArabia = New TimeOnly(16, 23).ToString(New CultureInfo("ar-SA"))
Dim timeObject = TimeOnly.Parse(timeInSaudiArabia, New CultureInfo("ar-SA"))

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6580.json ================================================ { "title": "Use a format provider when parsing date and time", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "pitfall", "bug" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6580", "sqKey": "S6580", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S6585.html ================================================

Why is this an issue?

Hardcoding the date and time format strings can lead to formats that consumers misunderstand. Also, if the same format is meant to be used in multiple places, it is easier to make a mistake when it’s hardcoded instead of using a format provided by an IFormatProvider or using one of the standard format strings.

What is the potential impact?

If a non-conventional format is used, the formatted date and time can be misunderstood. Also, if a mistake is made in the format, the formatted date can be incomplete. For example, you might switch the place of the minutes and month parts of a date or simply forget to print the year.

How to fix it

Instead of hardcoding the format, provide one from the available formats through an IFormatProvider or use one of the standard format strings.

Code examples

Noncompliant code example

Private Sub PrintTime()
    Console.WriteLine(DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss"))

    Console.WriteLine(DateTime.UtcNow.ToString("dd/mm/yyyy HH:MM:ss")) ' Months and minutes have changed their places
End Sub

Compliant solution

Private Sub PrintTime()
    Console.WriteLine(DateTime.UtcNow.ToString(CultureInfo.GetCultureInfo("es-MX")))

    Console.WriteLine(DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)) ' Better provide a well known culture, so this kind of issues do not pop up
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6585.json ================================================ { "title": "Don\u0027t hardcode the format when turning dates and times to strings", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "COMPLETE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6585", "sqKey": "S6585", "scope": "All", "quickfix": "infeasible" } ================================================ FILE: analyzers/rspec/vbnet/S6588.html ================================================

Why is this an issue?

With .NET Core the UnixEpoch field was introduced to DateTime and DateTimeOffset types. Using this field clearly states that the intention is to use the beginning of the Unix epoch.

What is the potential impact?

You should not use the DateTime or DateTimeOffset constructors to set the time to the 1st of January 1970 to represent the beginning of the Unix epoch. Not everyone is familiar with what this particular date is representing and it can be misleading.

How to fix it

To fix this issue, use the UnixEpoch field of DateTime or DateTimeOffset instead of the constructor.

Code examples

Noncompliant code example

Private Sub GetEpochTime()
    Dim epochTime = New DateTime(1970, 1, 1)
End Sub

Compliant solution

Private Sub GetEpochTime()
    Dim epochTime = DateTime.UnixEpoch
End Sub

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S6588.json ================================================ { "title": "Use the \"UnixEpoch\" field instead of creating \"DateTime\" instances that point to the beginning of the Unix epoch", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6588", "sqKey": "S6588", "scope": "All", "quickfix": "covered" } ================================================ FILE: analyzers/rspec/vbnet/S6602.html ================================================

Why is this an issue?

Both the List.Find method and the Enumerable.FirstOrDefault method can be used to locate the first element that meets a specified condition within a collection. However, for List objects, List.Find may offer superior performance compared to Enumerable.FirstOrDefault. While the performance difference might be negligible for small collections, it can become significant for larger collections. This observation also holds true for ImmutableList and arrays.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought FirstOrDefault closer to the performance of collection-specific Find methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 2x improvement in the execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The Find method is defined on the collection class, and it has the same signature as FirstOrDefault extension method. The function can be replaced in place.

Code examples

Noncompliant code example

Function GetValue(data As List(Of Integer)) As Integer
    Return data.FirstOrDefault(Function(x) x Mod 2 = 0)
End Function
Function GetValue(data() As Integer) As Integer
    Return data.FirstOrDefault(Function(x) x Mod 2 = 0)
End Function

Compliant solution

Function GetValue(data As List(Of Integer)) As Integer
    Return data.Find(Function(x) x Mod 2 = 0)
End Function
Function GetValue(data() As Integer) As Integer
    Return Array.Find(data, Function(x) x Mod 2 = 0)
End Function

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayFirstOrDefault

.NET 8.0

Array

10.515 μs

0.1410 μs

32 B

ArrayFind

.NET 8.0

Array

4.417 μs

0.0729 μs

-

ArrayFirstOrDefault

.NET 9.0

Array

2.262 μs

0.0135 μs

-

ArrayFind

.NET 9.0

Array

3.428 μs

0.0206 μs

-

ArrayFirstOrDefault

.NET Framework 4.8.1

Array

45.074 μs

0.7517 μs

32 B

ArrayFind

.NET Framework 4.8.1

Array

13.948 μs

0.1496 μs

-

ImmutableListFirstOrDefault

.NET 8.0

ImmutableList<T>

83.796 μs

1.3199 μs

72 B

ImmutableListFind

.NET 8.0

ImmutableList<T>

59.720 μs

1.0723 μs

-

ImmutableListFirstOrDefault

.NET 9.0

ImmutableList<T>

81.984 μs

1.0886 μs

72 B

ImmutableListFind

.NET 9.0

ImmutableList<T>

58.288 μs

0.8079 μs

-

ImmutableListFirstOrDefault

.NET Framework 4.8.1

ImmutableList<T>

446.893 μs

9.8430 μs

76 B

ImmutableListFind

.NET Framework 4.8.1

ImmutableList<T>

427.476 μs

3.3371 μs

-

ListFirstOrDefault

.NET 8.0

List<T>

14.808 μs

0.1723 μs

40 B

ListFind

.NET 8.0

List<T>

6.040 μs

0.1104 μs

-

ListFirstOrDefault

.NET 9.0

List<T>

2.233 μs

0.0154 μs

-

ListFind

.NET 9.0

List<T>

4.458 μs

0.0745 μs

-

ListFirstOrDefault

.NET Framework 4.8.1

List<T>

57.290 μs

1.0494 μs

40 B

ListFind

.NET Framework 4.8.1

List<T>

18.476 μs

0.0504 μs

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == 1;
private readonly static Predicate<int> ConditionPredicate = static x => x == 1;
private List<int> list;
private ImmutableList<int> immutableList;
private int[] array;
public const int N = 10_000;

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public int ListFirstOrDefault() =>
    list.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark]
public int ListFind() =>
    list.Find(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public int ImmutableListFirstOrDefault() =>
    immutableList.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public int ImmutableListFind() =>
    immutableList.Find(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public int ArrayFirstOrDefault() =>
    array.FirstOrDefault(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public int ArrayFind() =>
    Array.Find(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6602.json ================================================ { "title": "\"Find\" method should be used instead of the \"FirstOrDefault\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6602", "sqKey": "S6602", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6603.html ================================================

Why is this an issue?

Both the List.TrueForAll method and the IEnumerable.All method can be used to check if all list elements satisfy a given condition in a collection. However, List.TrueForAll can be faster than IEnumerable.All for List objects. The performance difference may be minor for small collections, but for large collections, it can be noticeable.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought All closer to the performance of collection-specific TrueForAll methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 4x improvement both in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The TrueForAll method is defined on the collection class, and it has the same signature as the All extension method. The method can be replaced in place.

Code examples

Noncompliant code example

Public Function AreAllEven(data As List(Of Integer)) As Boolean
    Return data.All(Function(x) x Mod 2 = 0)
End Function
Public Function AreAllEven(data As Integer()) As Boolean
    Return data.All(Function(x) x Mod 2 = 0)
End Function

Compliant solution

Public Function AreAllEven(data As List(Of Integer)) As Boolean
    Return data.TrueForAll(Function(x) x Mod 2 = 0)
End Function
Public Function AreAllEven(data As Integer()) As Boolean
    Return Array.TrueForAll(data, Function(x) x Mod 2 = 0)
End Function

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayAll

.NET 8.0

Array

109.25 μs

1.767 μs

32 B

ArrayTrueForAll

.NET 8.0

Array

45.01 μs

0.547 μs

-

ArrayAll

.NET 9.0

Array

22.28 μs

0.254 μs

-

ArrayTrueForAll

.NET 9.0

Array

37.60 μs

0.382 μs

-

ArrayAll

.NET Framework 4.8.1

Array

495.90 μs

4.342 μs

40 B

ArrayTrueForAll

.NET Framework 4.8.1

Array

164.52 μs

2.030 μs

-

ImmutableListAll

.NET 8.0

ImmutableList<T>

940.29 μs

5.600 μs

72 B

ImmutableListTrueForAll

.NET 8.0

ImmutableList<T>

679.46 μs

2.371 μs

-

ImmutableListAll

.NET 9.0

ImmutableList<T>

922.43 μs

14.564 μs

72 B

ImmutableListTrueForAll

.NET 9.0

ImmutableList<T>

692.31 μs

8.897 μs

-

ImmutableListAll

.NET Framework 4.8.1

ImmutableList<T>

4,578.72 μs

77.920 μs

128 B

ImmutableListTrueForAll

.NET Framework 4.8.1

ImmutableList<T>

4,393.49 μs

122.061 μs

-

ImmutableListBuilderAll

.NET 8.0

ImmutableList<T>.Builder

970.45 μs

13.598 μs

73 B

ImmutableListBuilderTrueForAll

.NET 8.0

ImmutableList<T>.Builder

687.82 μs

6.142 μs

-

ImmutableListBuilderAll

.NET 9.0

ImmutableList<T>.Builder

981.17 μs

12.966 μs

72 B

ImmutableListBuilderTrueForAll

.NET 9.0

ImmutableList<T>.Builder

710.19 μs

16.195 μs

-

ImmutableListBuilderAll

.NET Framework 4.8.1

ImmutableList<T>.Builder

4,780.50 μs

43.282 μs

128 B

ImmutableListBuilderTrueForAll

.NET Framework 4.8.1

ImmutableList<T>.Builder

4,493.82 μs

76.530 μs

-

ListAll

.NET 8.0

List<T>

151.12 μs

2.028 μs

40 B

ListTrueForAll

.NET 8.0

List<T>

58.03 μs

0.493 μs

-

ListAll

.NET 9.0

List<T>

22.14 μs

0.327 μs

-

ListTrueForAll

.NET 9.0

List<T>

46.01 μs

0.327 μs

-

ListAll

.NET Framework 4.8.1

List<T>

619.86 μs

6.037 μs

48 B

ListTrueForAll

.NET Framework 4.8.1

List<T>

208.49 μs

2.340 μs

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == Math.Abs(x);
private readonly static Predicate<int> ConditionPredicate = static x => x == Math.Abs(x);

private List<int> list;
private ImmutableList<int> immutableList;
private ImmutableList<int>.Builder immutableListBuilder;
private int[] array;

[Params(100_000)]
public int N { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    immutableListBuilder = ImmutableList.CreateBuilder<int>();
    immutableListBuilder.AddRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark]
public bool ListAll() =>
    list.All(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public bool ListTrueForAll() =>
    list.TrueForAll(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public bool ImmutableListAll() =>
    immutableList.All(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public bool ImmutableListTrueForAll() =>
    immutableList.TrueForAll(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>.Builder"), Benchmark(Baseline = true)]
public bool ImmutableListBuilderAll() =>
    immutableListBuilder.All(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>.Builder"), Benchmark]
public bool ImmutableListBuilderTrueForAll() =>
    immutableListBuilder.TrueForAll(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public bool ArrayAll() =>
    array.All(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public bool ArrayTrueForAll() =>
    Array.TrueForAll(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6603.json ================================================ { "title": "The collection-specific \"TrueForAll\" method should be used instead of the \"All\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6603", "sqKey": "S6603", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6605.html ================================================

Why is this an issue?

Both the List.Exists method and IEnumerable.Any method can be used to find the first element that satisfies a predicate in a collection. However, List.Exists can be faster than IEnumerable.Any for List objects, as well as requires significantly less memory. For small collections, the performance difference may be negligible, but for large collections, it can be noticeable. The same applies to ImmutableList and arrays too.

It is important to enable this rule with caution, as performance outcomes can vary significantly across different runtimes. Notably, the performance improvements in .NET 9 have brought Any closer to the performance of collection-specific Exists methods in most scenarios.

Applies to

What is the potential impact?

We measured at least 3x improvement in execution time. For more details see the Benchmarks section from the More info tab.

Also, no memory allocations were needed for the Exists method, since the search is done in-place.

Exceptions

Since LINQ to Entities relies a lot on System.Linq for query conversion, this rule won’t raise when used within LINQ to Entities syntaxes.

How to fix it

The Exists method is defined on the collection class, and it has the same signature as Any extension method if a predicate is used. The method can be replaced in place.

Code examples

Noncompliant code example

Function ContainsEven(data As List(Of Integer)) As Boolean
    Return data.Any(Function(x) x Mod 2 = 0)
End Function
Function ContainsEven(data() As Integer) As Boolean
    Return data.Any(Function(x) x Mod 2 = 0)
End Function

Compliant solution

Function ContainsEven(data As List(Of Integer)) As Boolean
    Return data.Exists(Function(x) x Mod 2 = 0)
End Function
Function ContainsEven(data() As Integer) As Boolean
    Return Array.Exists(data, Function(x) x Mod 2 = 0)
End Function

Resources

Documentation

Benchmarks

Method Runtime Categories Mean Standard Deviation Allocated

ArrayAny

.NET 8.0

Array

1,174.0 ns

16.44 ns

32 B

ArrayExists

.NET 8.0

Array

570.6 ns

7.12 ns

-

ArrayAny

.NET 9.0

Array

358.5 ns

5.57 ns

-

ArrayExists

.NET 9.0

Array

581.6 ns

6.17 ns

-

ArrayAny

.NET Framework 4.8.1

Array

4,896.0 ns

102.83 ns

32 B

ArrayExists

.NET Framework 4.8.1

Array

1,649.4 ns

29.81 ns

-

ImmutableListAny

.NET 8.0

ImmutableList<T>

7,859.3 ns

91.45 ns

72 B

ImmutableListExists

.NET 8.0

ImmutableList<T>

5,898.1 ns

81.69 ns

-

ImmutableListAny

.NET 9.0

ImmutableList<T>

7,748.9 ns

119.10 ns

72 B

ImmutableListExists

.NET 9.0

ImmutableList<T>

5,705.0 ns

31.53 ns

-

ImmutableListAny

.NET Framework 4.8.1

ImmutableList<T>

45,118.5 ns

168.72 ns

72 B

ImmutableListExists

.NET Framework 4.8.1

ImmutableList<T>

41,966.0 ns

631.59 ns

-

ListAny

.NET 8.0

List<T>

1,643.5 ns

13.09 ns

40 B

ListExists

.NET 8.0

List<T>

726.2 ns

11.99 ns

-

ListAny

.NET 9.0

List<T>

398.6 ns

8.20 ns

-

ListExists

.NET 9.0

List<T>

612.4 ns

18.73 ns

-

ListAny

.NET Framework 4.8.1

List<T>

5,621.5 ns

35.80 ns

40 B

ListExists

.NET Framework 4.8.1

List<T>

1,748.0 ns

11.76 ns

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

// Explicitly cache the delegates to avoid allocations inside the benchmark.
private readonly static Func<int, bool> ConditionFunc = static x => x == -1 * Math.Abs(x);
private readonly static Predicate<int> ConditionPredicate = static x => x == -1 * Math.Abs(x);

private List<int> list;
private ImmutableList<int> immutableList;
private int[] array;

[Params(1_000)]
public int N { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
    list = Enumerable.Range(0, N).Select(x => N - x).ToList();
    immutableList = ImmutableList.CreateRange(list);
    array = list.ToArray();
}

[BenchmarkCategory("List<T>"), Benchmark]
public bool ListAny() =>
    list.Any(ConditionFunc);

[BenchmarkCategory("List<T>"), Benchmark(Baseline = true)]
public bool ListExists() =>
    list.Exists(ConditionPredicate);

[BenchmarkCategory("ImmutableList<T>"), Benchmark(Baseline = true)]
public bool ImmutableListAny() =>
    immutableList.Any(ConditionFunc);

[BenchmarkCategory("ImmutableList<T>"), Benchmark]
public bool ImmutableListExists() =>
    immutableList.Exists(ConditionPredicate);

[BenchmarkCategory("Array"), Benchmark(Baseline = true)]
public bool ArrayAny() =>
    array.Any(ConditionFunc);

[BenchmarkCategory("Array"), Benchmark]
public bool ArrayExists() =>
    Array.Exists(array, ConditionPredicate);

Hardware configuration:

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
  .NET 8.0             : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0             : .NET 9.0.0 (9.0.24.47305), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9277.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6605.json ================================================ { "title": "Collection-specific \"Exists\" method should be used instead of the \"Any\" extension", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6605", "sqKey": "S6605", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6607.html ================================================

Why is this an issue?

When working with LINQ in C#, it is recommended to pay attention to the order in which methods are chained, especially when using Where and OrderBy methods. It is advised to call the Where method before OrderBy because Where filters the elements of the sequence based on a given condition and returns a new sequence containing only the elements that satisfy that condition. Calling OrderBy before Where, may end up sorting elements that will be later discarded, which can lead to inefficiency. Conversely, calling Where before OrderBy, will first filter the sequence to include only the elements of interest, and then sort them based on the specified order.

What is the potential impact?

We measured at least 2x improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

The issue can be fixed by calling Where before OrderBy.

Code examples

Noncompliant code example

Public Function GetSortedFilteredList(data As IEnumerable(Of Integer)) As IEnumerable(Of Integer)
    Return data.OrderBy(Function(x) x).Where(Function(x) x Mod 2 = 0)
End Function

Compliant solution

Public Function GetSortedFilteredList(data As IEnumerable(Of Integer)) As IEnumerable(Of Integer)
    Return data.Where(Function(x) x Mod 2 = 0).OrderBy(Function(x) x)
End Function

Resources

Documentation

Articles & blog posts

Benchmarks

Method Runtime Mean Standard Deviation

OrderByThenWhere

.NET 7.0

175.36 ms

5.101 ms

WhereThenOrderBy

.NET 7.0

85.58 ms

1.697 ms

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private IList<int> data;
private static readonly Random Random = new Random();

[Params(1_000_000)]
public int NumberOfEntries;

[GlobalSetup]
public void Setup() =>
    data = Enumerable.Range(0, NumberOfEntries).Select(x => Random.Next(0, NumberOfEntries)).ToList();

[Benchmark(Baseline = true)]
public void OrderByThenWhere() =>
    _ = data.OrderBy(x => x).Where(x => x % 2 == 0 ).ToList();  // OrderBy followed by Where

[Benchmark]
public void WhereThenOrderBy() =>
    _ = data.Where(x => x % 2 == 0 ).OrderBy(x => x).ToList();  // Where followed by OrderBy

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/vbnet/S6607.json ================================================ { "title": "The collection should be filtered before sorting by using \"Where\" before \"OrderBy\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6607", "sqKey": "S6607", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6608.html ================================================

Why is this an issue?

Indexes in C# provide direct access to an element at a specific position within an array or collection. When compared to Enumerable methods, indexing can be more efficient for certain scenarios, such as iterating over a large collection, due to avoiding the overhead of checking the underlying collection type before accessing it.

This applies to types that implement one of these interfaces:

What is the potential impact?

We measured a significant improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

If the type you are using implements IList, IList<T> or IReadonlyList<T>, it implements this[int index]. This means calls to First, Last, or ElementAt(index) can be replaced with indexing at 0, Count-1 and index respectively.

Code examples

Noncompliant code example

Function GetAt(data As List(Of Integer), index As Integer) As Integer
    Return data.ElementAt(index)
End Function
Function GetFirst(data As List(Of Integer)) As Integer
    Return data.First()
End Function
Function GetLast(data As List(Of Integer)) As Integer
    Return data.Last()
End Function

Compliant solution

Function GetAt(data As List(Of Integer), index As Integer) As Integer
    Return data(index)
End Function
Function GetFirst(data As List(Of Integer)) As Integer
    Return data(0)
End Function
Function GetLast(data As List(Of Integer)) As Integer
    Return data(data.Count-1)
End Function

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation

ElementAt

3,403.4 ns

28.52 ns

26.67 ns

Index

478.0 ns

6.93 ns

6.48 ns

First

6,160.0 ns

57.66 ns

53.93 ns

First_Index

485.7 ns

5.81 ns

5.15 ns

Last

6,034.3 ns

20.34 ns

16.98 ns

Last_Index

408.3 ns

2.54 ns

2.38 ns

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private List<byte> data;
private Random random;

[Params(1_000_000)]
public int SampleSize;

[Params(1_000)]
public int LoopSize;

[GlobalSetup]
public void Setup()
{
    random = new Random(42);

    var bytes = new byte[SampleSize];
    random.NextBytes(bytes);
    data = bytes.ToList();
}

[Benchmark]
public int ElementAt()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.ElementAt(i);
    }

    return result;
}

[Benchmark]
public int Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[i];
    }

    return result;
}

[Benchmark]
public int First()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.First();
    }

    return result;
}

[Benchmark]
public int First_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[0];
    }

    return result;
}

[Benchmark]
public int Last()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data.Last();
    }

    return result;
}

[Benchmark]
public int Last_Index()
{
    int result = default;

    for (var i = 0; i < LoopSize; i++)
    {
        result = data[data.Count - 1];
    }

    return result;
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.4412/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=8.0.301
  [Host]   : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/vbnet/S6608.json ================================================ { "title": "Prefer indexing instead of \"Enumerable\" methods on types implementing \"IList\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6608", "sqKey": "S6608", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6609.html ================================================

Why is this an issue?

Both the Enumerable.Max extension method and the SortedSet<T>.Max property can be used to find the maximum value in a SortedSet<T>. However, SortedSet<T>.Max is much faster than Enumerable.Max. For small collections, the performance difference may be minor, but for large collections, it can be noticeable. The same applies for the Min property as well.

Max and Min in SortedSet<T> exploit the fact that the set is implemented via a Red-Black tree. The algorithm to find the Max/Min is "go left/right whenever possible". The operation has the time complexity of O(h) which becomes O(ln(n)) due to the fact that the tree is balanced. This is much better than the O(n) time complexity of extension methods.

Max and Min in ImmutableSortedSet<T> exploits a tree augmentation technique, storing the Min, Max and Count values on each node of the data structure. The time complexity in this case is O(1) that is significantly better than O(n) of extension methods.

Applies to:

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

The Min and Max properties are defined on the following classes, and the extension method call can be replaced by calling the propery instead:

Code examples

Noncompliant code example

Function GetMax(data As SortedSet(Of Integer)) As Integer
    Return Enumerable.Max(data)
End Function
Function GetMin(data As SortedSet(Of Integer)) As Integer
    Return Enumerable.Min(data)
End Function

Compliant solution

Function GetMax(data As SortedSet(Of Integer)) As Integer
    Return data.Max()
End Function
Function GetMin(data As SortedSet(Of Integer)) As Integer
    Return data.Min()
End Function

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

MaxMethod

.NET 7.0

68,961.483 us

499.6623 us

248063 B

MaxProperty

.NET 7.0

4.638 us

0.0634 us

-

MaxMethod

.NET Framework 4.6.2

85,827.359 us

1,531.1611 us

281259 B

MaxProperty

.NET Framework 4.6.2

67.682 us

0.3757 us

312919 B

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private SortedSet<string> data;

[Params(1_000)]
public int Iterations;

[GlobalSetup]
public void Setup() =>
    data = new SortedSet<string>(Enumerable.Range(0, Iterations).Select(x => Guid.NewGuid().ToString()));

[Benchmark(Baseline = true)]
public void MaxMethod()
{
    for (var i = 0; i < Iterations; i++)
    {
        _ = data.Max();     // Max() extension method
    }
}

[Benchmark]
public void MaxProperty()
{
    for (var i = 0; i < Iterations; i++)
    {
        _ = data.Max;       // Max property
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6609.json ================================================ { "title": "\"Min\/Max\" properties of \"Set\" types should be used instead of the \"Enumerable\" extension methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6609", "sqKey": "S6609", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6610.html ================================================

Why is this an issue?

With string.StartsWith(char) and string.EndsWith(char), only the first character of the string is compared to the provided character, whereas the string versions of those methods have to do checks about the current StringComparison and CultureInfo. Thus, the char overloads are significantly faster for default comparison scenarios.

These overloads were introduced in .NET Core 2.0.

What is the potential impact?

We measured at least 3.5x improvement in execution time. For more details see the Benchmarks section from the More info tab.

How to fix it

If you are targeting a runtime version equal or greater than .NET Core 2.0, the string.StartsWith and string.EndsWith overloads are available, with the argument’s type being char instead of string. Thus, an argument of char type can be provided.

Code examples

Noncompliant code example

Function StartsWithSlash(s As String) As Boolean
    Return s.StartsWith("/")
End Function
Function EndsWithSlash(s As String) As Boolean
    Return s.EndsWith("/")
End Function

Compliant solution

Function StartsWithSlash(s As String) As Boolean
    Return s.StartsWith("/"c)
End Function
Function EndsWithSlash(s As String) As Boolean
    Return s.EndsWith("/"c)
End Function

Resources

Documentation

Benchmarks

Method Mean Standard Deviation

StartsWith_String

30.965 ms

3.2732 ms

StartsWith_Char

7.568 ms

0.3235 ms

EndsWith_String

30.421 ms

5.1136 ms

EndsWith_Char

8.067 ms

0.7092 ms

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private List<string> data;

[Params(1_000_000)]
public int N { get; set; }

[GlobalSetup]
public void Setup() =>
    data = Enumerable.Range(0, N).Select(_ => Guid.NewGuid().ToString()).ToList();

[Benchmark]
public void StartsWith_String()
{
    _ = data.Where(guid => guid.StartsWith("d")).ToList();
}

[Benchmark]
public void StartsWith_Char()
{
    _ = data.Where(guid => guid.StartsWith('d')).ToList();
}

[Benchmark]
public void EndsWith_String()
{
    _ = data.Where(guid => guid.EndsWith("d")).ToList();
}

[Benchmark]
public void EndsWith_Char()
{
    _ = data.Where(guid => guid.EndsWith('d')).ToList();
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]   : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0 : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
================================================ FILE: analyzers/rspec/vbnet/S6610.json ================================================ { "title": "\"StartsWith\" and \"EndsWith\" overloads that take a \"char\" should be used instead of the ones that take a \"string\"", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6610", "sqKey": "S6610", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6612.html ================================================

Why is this an issue?

When using the ConcurrentDictionary, there are many overloads of the GetOrAdd and AddOrUpdate methods that take both a TKey argument and a lambda that expects a TKey parameter. This means that the right side of the lambda can be written using either the lambda’s parameter or the method’s argument. However, using the method’s argument leads to the lambda capturing it, and the compiler will need to generate a class and instantiate it before the call. This means memory allocations, as well as more time spend during Garbage Collection.

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

When you are using the ConcurrentDictionary methods GetOrAdd or AddOrUpdate, reference the key by using the lambda’s parameter instead of the method’s one.

Code examples

Noncompliant code example

Function UpdateValue(dict As ConcurrentDictionary(Of Integer, Integer), key As Integer) As Integer
    Return dict.GetOrAdd(key, Function(k)
                                   Return key + 42
                               End Function)
End Function

Compliant solution

Function UpdateValue(dict As ConcurrentDictionary(Of Integer, Integer), key As Integer) As Integer
    Return dict.GetOrAdd(key, Function(k)
                                   Return k + 42
                               End Function)
End Function

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

Capture

.NET 7.0

68.52 ms

4.450 ms

88000063 B

Lambda

.NET 7.0

39.29 ms

3.712 ms

50 B

Capture

.NET Framework 4.6.2

74.58 ms

5.199 ms

88259787 B

Lambda

.NET Framework 4.6.2

42.03 ms

2.752 ms

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private ConcurrentDictionary<int, string> dict;
private List<int> data;

[Params(1_000_000)]
public int N { get; set; }

[GlobalSetup]
public void Setup()
{
    dict = new ConcurrentDictionary<int, string>();
    data = Enumerable.Range(0, N).OrderBy(_ => Guid.NewGuid()).ToList();
}

[Benchmark(baseline=true)]
public void Capture()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, _ => $"{guid}"); // "guid" is captured
    }
}

[Benchmark]
public void Lambda()
{
    foreach (var guid in data)
    {
        dict.GetOrAdd(guid, x => $"{x}"); // no capture
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6612.json ================================================ { "title": "The lambda parameter should be used instead of capturing arguments in \"ConcurrentDictionary\" methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6612", "sqKey": "S6612", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6613.html ================================================

Why is this an issue?

Both the Enumerable.First extension method and the LinkedList<T>.First property can be used to find the first value in a LinkedList<T>. However, LinkedList<T>.First is much faster than Enumerable.First. For small collections, the performance difference may be minor, but for large collections, it can be noticeable. The same applies for the Last property as well.

Applies to:

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

How to fix it

The First and Last properties are defined on the LinkedList class, and the extension method call can be replaced by calling the propery instead.

Code examples

Noncompliant code example

Function GetFirst(data As LinkedList(Of Integer)) As Integer
    Return Enumerable.First(data)
End Function
Function GetLast(data As LinkedList(Of Integer)) As Integer
    Return Enumerable.Last(data)
End Function

Compliant solution

Function GetFirst(data As LinkedList(Of Integer)) As Integer
    Return data.First.Value
End Function
Function GetLast(data As LinkedList(Of Integer)) As Integer
    Return data.Last.Value
End Function

Resources

Documentation

Benchmarks

Method Runtime Mean Standard Deviation Allocated

LastMethod

.NET 7.0

919,577,629.0 ns

44,299,688.61 ns

48504 B

LastProperty

.NET 7.0

271.8 ns

15.63 ns

-

LastMethod

.NET Framework 4.6.2

810,316,427.1 ns

47,768,482.31 ns

57344 B

LastProperty

.NET Framework 4.6.2

372.0 ns

13.38 ns

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

private LinkedList<int> data;
private Random random = new Random();

[Params(100_000)]
public int Size { get; set; }

[Params(1_000)]
public int Runs { get; set; }

[GlobalSetup]
public void Setup() =>
    data = new LinkedList<int>(Enumerable.Range(0, Size).Select(x => random.Next()));

[Benchmark(Baseline = true)]
public void LastMethod()
{
    for (var i = 0; i < Runs; i++)
    {
        _ = data.Last();                // Enumerable.Last()
    }
}

[Benchmark]
public void LastProperty()
{
    for (var i = 0; i < Runs; i++)
    {
        _ = data.Last;                  // Last property
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
  [Host]               : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8 (4.8.4614.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6613.json ================================================ { "title": "\"First\" and \"Last\" properties of \"LinkedList\" should be used instead of the \"First()\" and \"Last()\" extension methods", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6613", "sqKey": "S6613", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S6617.html ================================================

Why is this an issue?

When testing if a collection contains a specific item by simple equality, both ICollection.Contains(T item) and IEnumerable.Any(x ⇒ x == item) can be used. However, Any searches the data structure in a linear manner using a foreach loop, whereas Contains is considerably faster in some collection types, because of the underlying implementation. More specifically:

For small collections, the performance difference may be negligible, but for large collections, it can be noticeable.

What is the potential impact?

We measured a significant improvement both in execution time and memory allocation. For more details see the Benchmarks section from the More info tab.

Exceptions

Since LINQ to Entities relies a lot on System.Linq for query conversion, this rule won’t raise when used within LINQ to Entities syntaxes.

How to fix it

Contains is a method defined on the ICollection<T> interface and takes a T item argument. Any is an extension method defined on the IEnumerable<T> interface and takes a predicate argument. Therefore, calls with simple equality checks like Any(x ⇒ x == item) can be replaced by Contains(item).

This applies to the following collection types:

Code examples

Noncompliant code example

Function ValueExists(data As HashSet(Of Integer)) As Boolean
    Return data.Any(Function(x) x = 42)
End Function
Function ValueExists(data As List(Of Integer)) As Boolean
    Return data.Any(Function(x) x = 42)
End Function

Compliant solution

Function ValueExists(data As HashSet(Of Integer)) As Boolean
    Return data.Contains(42)
End Function
Function ValueExists(data As List(Of Integer)) As Boolean
    Return data.Contains(42)
End Function

Resources

Documentation

Articles & blog posts

Benchmarks

Method Runtime Mean Standard Deviation Allocated

HashSet_Any

.NET 7.0

35,388.333 us

620.1863 us

40132 B

HashSet_Contains

.NET 7.0

3.799 us

0.1489 us

-

List_Any

.NET 7.0

32,851.509 us

667.1658 us

40130 B

List_Contains

.NET 7.0

375.132 us

8.0764 us

-

HashSet_Any

.NET Framework 4.6.2

28,979.763 us

678.0093 us

40448 B

HashSet_Contains

.NET Framework 4.6.2

5.987 us

0.1090 us

-

List_Any

.NET Framework 4.6.2

25,830.221 us

487.2470 us

40448 B

List_Contains

.NET Framework 4.6.2

5,935.812 us

57.7569 us

-

Glossary

The results were generated by running the following snippet with BenchmarkDotNet:

[Params(10_000)]
public int SampleSize;

[Params(1_000)]
public int Iterations;

private static HashSet<int> hashSet;
private static List<int> list;

[GlobalSetup]
public void Setup()
{
    hashSet = new HashSet<int>(Enumerable.Range(0, SampleSize));
    list = Enumerable.Range(0, SampleSize).ToList();
}

[Benchmark]
public void HashSet_Any() =>
    CheckAny(hashSet, SampleSize / 2);

[Benchmark]
public void HashSet_Contains() =>
    CheckContains(hashSet, SampleSize / 2);

[Benchmark]
public void List_Any() =>
    CheckAny(list, SampleSize / 2);

[Benchmark]
public void List_Contains() =>
    CheckContains(list, SampleSize / 2);

void CheckAny(IEnumerable<int> values, int target)
{
    for (int i = 0; i < Iterations; i++)
    {
        _ = values.Any(x => x == target);  // Enumerable.Any
    }
}

void CheckContains(ICollection<int> values, int target)
{
    for (int i = 0; i < Iterations; i++)
    {
        _ = values.Contains(target); // ICollection<T>.Contains
    }
}

Hardware configuration:

BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.2846/22H2/2022Update)
11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.203
  [Host]               : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET 7.0             : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2
  .NET Framework 4.6.2 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
================================================ FILE: analyzers/rspec/vbnet/S6617.json ================================================ { "title": "\"Contains\" should be used instead of \"Any\" for simple equality checks", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "LOW" }, "attribute": "EFFICIENT" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "performance" ], "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-6617", "sqKey": "S6617", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/S6930.html ================================================

Backslash characters (\) should be avoided in route templates.

Why is this an issue?

Routing in ASP.NET MVC maps controllers and actions to paths in request URIs.

In the former syntax specification of URIs, backslash characters (\) were not allowed at all (see section "2.4.3. Excluded US-ASCII Characters" of RFC 2396). While the current specification (RFC 3986) doesn’t include anymore the "Excluded US-ASCII Characters" section, most URL processors still don’t support backslash properly.

For instance, a backslash in the "path" part of a URL is automatically converted to a forward slash (/) both by Chrome and Internet Explorer (see here).

As an example, \Calculator\Evaluate?expression=3\4 is converted on the fly into /Calculator/Evaluate?expression=3\4 before the HTTP request is made to the server.

While backslashes are allowed in the "query" part of a URL, and it’s common to have them as part of a complex query expression, the route of a controller is always part of the "path".

That is why the use of backslashes in controller templates should be avoided in general.

What is the potential impact?

A backslash in the route pattern of a controller would only make sense if the developer intended the backslash in the route to be explicitly escaped by the user, using %5C.

For example, the route Something\[controller] for the HomeController would need to be called as Something%5CHome.

The validity of such a scenario is unlikely and the resulting behavior is surprising.

How to fix it

Code examples

Noncompliant code example

<Route("Something\[controller]")> ' Noncompliant: Replace '\' with '/'.
Public Class HomeController
    Inherits Controller

    <HttpGet>
    Public Function Index() As ActionResult
        Return View()
    End Function
End Class

Compliant solution

<Route("Something/[controller]")> ' '\' replaced with '/'
Public Class HomeController
    Inherits Controller

    <HttpGet>
    Public Function Index() As ActionResult
        Return View()
    End Function
End Class

Noncompliant code example

app.MapControllerRoute(
    name:="default",
    pattern:="{controller=Home}\{action=Index}") ' Noncompliant: Replace '\' with '/'.

Compliant solution

app.MapControllerRoute(
    name:="default",
    pattern:="{controller=Home}/{action=Index}") ' '\' replaced with '/'

Resources

Documentation

Articles & blog posts

Standards

================================================ FILE: analyzers/rspec/vbnet/S6930.json ================================================ { "title": "Backslash should be avoided in route templates", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6930", "sqKey": "S6930", "scope": "Main", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "MEDIUM" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/vbnet/S6931.html ================================================

Route templates for ASP.NET controller actions, defined via a RouteAttribute or any derivation of HttpMethodAttribute, should not start with "/".

Why is this an issue?

Routing in ASP.NET Core MVC maps controllers and actions to paths in request URIs. Similar routing happens in ASP.NET Framework MVC.

In ASP.NET Core MVC, when an action defines a route template starting with a "/", the route is considered absolute and the action is registered at the root of the web application.

In such a scenario, any route defined at the controller level is disregarded, as shown in the following example:

<Route("[controller]")> ' This route is ignored for the routing of Index1 and Index2
Public Class HomeController
    Inherits Controller

    <HttpGet("/Index1")> ' This action is mapped to the root of the web application
    Public Function Index1() As ActionResult
        Return View()
    End Function

    <Route("/Index2")>   ' The same applies here
    Public Function Index2() As ActionResult
        Return View()
    End Function
End Class

The behavior can be found confusing and surprising because any relative action route is relativized to the controller route.

Therefore, in the vast majority of scenarios, controllers group all related actions not only in the source code, but also at the routing level.

In ASP.NET Framework MVC with attribute routing enabled via MapMvcAttributeRoutes, the mere presence of an absolute route at the action level will produce an InvalidOperationException at runtime.

It is then a good practice to avoid absolute routing at the action level and move the "/" to the root level, changing the template defined in the RouteAttribute of the controller appropriately.

Exceptions

The rule only applies when all route templates of all actions of the controller start with "/". Sometimes some actions may have both relative and absolute route templates, for example for backward compatibility reasons (i.e. a former route needs to be preserved). In such scenarios, it may make sense to keep the absolute route template at the action level.

How to fix it

Code examples

Noncompliant code example

<Route("[controller]")> ' This route is ignored
Public Class ReviewsController
    Inherits Controller

    ' Route is /reviews
    <HttpGet("/reviews")>
    Public Function Index() As ActionResult
        ' ...
    End Function

    ' Route is /reviews/{reviewId}
    <Route("/reviews/{reviewId}")>
    Public Function Show(reviewId As Integer) As ActionResult
        ' ...
    End Function
End Class

Compliant solution

<Route("/")> ' Turns on attribute routing
Public Class ReviewsController
    Inherits Controller

    ' Route is /reviews
    <HttpGet("reviews")>
    Public Function Index() As ActionResult
        ' ...
    End Function

    ' Route is /reviews/{reviewId}
    <Route("reviews/{reviewId}")>
    Public Function Show(reviewId As Integer) As ActionResult
        ' ...
    End Function
End Class

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S6931.json ================================================ { "title": "ASP.NET controller actions should not have a route template starting with \"\/\"", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "asp.net" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-6931", "sqKey": "S6931", "scope": "Main", "quickfix": "partial", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/vbnet/S7130.html ================================================

When working with collections that are known to be non-empty, using First or Single is generally preferred over FirstOrDefault or SingleOrDefault.

Why is this an issue?

Using FirstOrDefault or SingleOrDefault on collections that are known to be non-empty is an issue due to:

Code examples

Noncompliant code example

Dim Items As New list(Of Integer) From {1, 2, 3}

Dim FirstItem As Integer = Items.FirstOrDefault() ' Noncompliant, this implies the collection might be empty, when we know it is not

Compliant solution

Dim Items As New list(Of Integer) From {1, 2, 3}

Dim FirstItem As Integer = Items.First() ' Compliant

Resources

Documentation

Articles & blog posts

================================================ FILE: analyzers/rspec/vbnet/S7130.json ================================================ { "title": "First\/Single should be used instead of FirstOrDefault\/SingleOrDefault on collections that are known to be non-empty", "type": "CODE_SMELL", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "1min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-7130", "sqKey": "S7130", "scope": "All", "quickfix": "targeted", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" } } ================================================ FILE: analyzers/rspec/vbnet/S7131.html ================================================

When using ReaderWriterLock and ReaderWriterLockSlim for managing read and write locks, you should not release a read lock while holding a write lock and vice versa, otherwise you might have runtime exceptions. The locks should be always correctly paired so that the shared resource is accessed safely.

This rule raises if:

Why is this an issue?

If you use the ReaderWriterLockSlim class, you will get a LockRecursionException. In the case of ReaderWriterLock, you’ll get a runtime exception for trying to release a lock that is not owned by the calling thread.

Code examples

Noncompliant code example

Public Class Example

    Private Shared rwLock As New ReaderWriterLock()

    Public Sub Writer()
        rwLock.AcquireWriterLock(2000)
        Try
            ' ...
        Finally
            rwLock.ReleaseReaderLock() ' Noncompliant, will throw runtime exception
        End Try
    End Sub

    Public Sub Reader()
        rwLock.AcquireReaderLock(2000)
        Try
            ' ...
        Finally
            rwLock.ReleaseWriterLock() ' Noncompliant, will throw runtime exception
        End Try
    End Sub

End Class

Compliant solution

Public Class Example

    Private Shared rwLock As New ReaderWriterLock()

    Public Shared Sub Writer()
        rwLock.AcquireWriterLock(2000)
        Try
            ' ...
        Finally
            rwLock.ReleaseWriterLock()
        End Try
    End Sub

    Public Shared Sub Reader()
        rwLock.AcquireReaderLock(2000)
        Try
            ' ...
        Finally
            rwLock.ReleaseReaderLock()
        End Try
    End Sub

End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S7131.json ================================================ { "title": "A write lock should not be released when a read lock has been acquired and vice versa", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "30min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-7131", "sqKey": "S7131", "scope": "All", "quickfix": "infeasible", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "LOGICAL" } } ================================================ FILE: analyzers/rspec/vbnet/S7133.html ================================================

This rule raises if you acquire a lock with one of the following methods, and do not release it within the same method.

This rule will raise an issue when the code uses the disposable pattern. This pattern makes locking easy to use and delegates the responsibility to the caller. Users should accept issues in such cases, as they should appear only once for each synchronization type.

Why is this an issue?

Not releasing a lock in the same method where you acquire it, and releasing in another one, makes the code less clear and harder to maintain. You are also introducing the risk of not releasing a lock at all which can lead to deadlocks or exceptions.

Code examples

Noncompliant code example

Public Class Example

    Private Shared rwLock As New ReaderWriterLock

    Public Sub AcquireWriterLock()
        rwLock.AcquireWriterLock(2000)  ' Noncompliant, as the lock release is on the callers responsibility
    End Sub

    Public Sub DoSomething()
        ' ...
    End Sub

    Public Sub ReleaseWriterLock()
        rwLock.ReleaseWriterLock()
    End Sub

End Class

Compliant solution

Public Class Example

    Private Shared rwLock As New ReaderWriterLock

    Public Sub DoSomething()
        rwLock.AcquireWriterLock(2000)  ' Compliant, locks are released in the same method
        Try
            ' ...
        Finally
            rwLock.ReleaseWriterLock()
        End Try
    End Sub

End Class

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S7133.json ================================================ { "title": "Locks should be released within the same method", "type": "BUG", "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "5min" }, "tags": [ "symbolic-execution" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-7133", "sqKey": "S7133", "scope": "All", "quickfix": "targeted", "code": { "impacts": { "RELIABILITY": "HIGH" }, "attribute": "CONVENTIONAL" } } ================================================ FILE: analyzers/rspec/vbnet/S907.html ================================================

Why is this an issue?

GoTo is an unstructured control flow statement. It makes code less readable and maintainable. Structured control flow statements such as If, For, While, or Exit should be used instead.

Noncompliant code example

    Sub GoToStatementDemo()
        Dim number As Integer = 1
        Dim sampleString As String
        ' Evaluate number and branch to appropriate label.
        If number = 1 Then GoTo Line1 Else GoTo Line2
Line1:
        sampleString = "Number equals 1"
        GoTo LastLine
Line2:
        ' The following statement never gets executed because number = 1.
        sampleString = "Number equals 2"
LastLine:
        ' Write "Number equals 1" in the Debug window.
        Debug.WriteLine(sampleString)
    End Sub

Compliant solution

    Sub GoToStatementDemo()
        Dim number As Integer = 1
        Dim sampleString As String
        ' Evaluate number and branch to appropriate label.
        If number = 1 Then
            sampleString = "Number equals 1"
        Else
            sampleString = "Number equals 2"
        End If
        Debug.WriteLine(sampleString)
    End Sub
================================================ FILE: analyzers/rspec/vbnet/S907.json ================================================ { "title": "\"GoTo\" statements should not be used", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "MEDIUM" }, "attribute": "CLEAR" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "brain-overload" ], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-907", "sqKey": "S907", "scope": "All", "quickfix": "unknown" } ================================================ FILE: analyzers/rspec/vbnet/S927.html ================================================

Why is this an issue?

Parameters are part of the method signature and its identity.

Implementing a method from an interface, a base class, or a partial method and changing one of its parameters' names will confuse and impact the method’s readability.

Interface IBankAccount
    Sub AddMoney(money As Integer)
End Interface

Class BankAccount
    Implements IBankAccount

    Private Sub AddMoney(amount As Integer) ' Noncompliant: parameter's name differs from base
        ' ...
    End Sub
End Class

To avoid any ambiguity in the code, a parameter’s name should match the initial declaration, whether its initial declaration is from an interface, a base class, or a partial method.

Interface IBankAccount
    Sub AddMoney(money As Integer)
End Interface

Class BankAccount
    Implements IBankAccount

    Private Sub AddMoney(money As Integer) ' Compliant: parameter's name match base name
        ' ...
    End Sub
End Class

Exceptions

The rule is ignored if both the parameter defined in the initial decalaration is a generic type and the implementing member’s declaration is a non-generic type.

This allows the implementing member to be more specific and provide more information.

Resources

Documentation

================================================ FILE: analyzers/rspec/vbnet/S927.json ================================================ { "title": "Parameter names should match base declaration", "type": "CODE_SMELL", "code": { "impacts": { "MAINTAINABILITY": "HIGH" }, "attribute": "IDENTIFIABLE" }, "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "10min" }, "tags": [ "suspicious" ], "defaultSeverity": "Critical", "ruleSpecification": "RSPEC-927", "sqKey": "S927", "scope": "All", "quickfix": "targeted" } ================================================ FILE: analyzers/rspec/vbnet/Sonar_way_profile.json ================================================ { "name": "Sonar way", "ruleKeys": [ "S101", "S107", "S108", "S112", "S114", "S117", "S907", "S927", "S1048", "S1066", "S1075", "S1110", "S1123", "S1125", "S1133", "S1134", "S1135", "S1155", "S1163", "S1172", "S1186", "S1192", "S1313", "S1479", "S1481", "S1542", "S1643", "S1645", "S1654", "S1656", "S1751", "S1764", "S1862", "S1871", "S1940", "S1944", "S2053", "S2068", "S2077", "S2094", "S2166", "S2178", "S2222", "S2225", "S2234", "S2257", "S2259", "S2304", "S2340", "S2342", "S2344", "S2345", "S2346", "S2347", "S2349", "S2352", "S2355", "S2358", "S2359", "S2365", "S2368", "S2372", "S2375", "S2376", "S2437", "S2551", "S2583", "S2589", "S2612", "S2692", "S2737", "S2757", "S2761", "S2925", "S2951", "S3011", "S3063", "S3329", "S3358", "S3363", "S3385", "S3431", "S3449", "S3453", "S3464", "S3466", "S3598", "S3603", "S3655", "S3776", "S3869", "S3871", "S3878", "S3889", "S3903", "S3904", "S3923", "S3926", "S3927", "S3949", "S3966", "S3981", "S3998", "S4036", "S4136", "S4143", "S4144", "S4158", "S4159", "S4201", "S4210", "S4260", "S4275", "S4277", "S4423", "S4428", "S4507", "S4545", "S4581", "S4583", "S4586", "S4663", "S4790", "S4830", "S5042", "S5443", "S5445", "S5542", "S5547", "S5659", "S5693", "S5753", "S5773", "S5856", "S5944", "S6145", "S6146", "S6418", "S6444", "S6561", "S6562", "S6575", "S6580", "S6588", "S6607", "S6608", "S6609", "S6610", "S6612", "S6613", "S6617", "S6930", "S6931", "S7130", "S7131", "S7133" ] } ================================================ FILE: analyzers/src/AssemblyInfo.Shared.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.InteropServices; [assembly: CLSCompliant(false)] [assembly: ComVisible(false)] ================================================ FILE: analyzers/src/Directory.Build.targets ================================================ $(MSBuildThisFileDirectory)..\packaging\binaries\$(ProjectName) ================================================ FILE: analyzers/src/RuleCatalog.targets ================================================ ================================================ FILE: analyzers/src/RuleDescriptorGenerator/Descriptors/Rule.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; namespace RuleDescriptorGenerator; [ExcludeFromCodeCoverage] public record Rule(string Id, RuleParameter[] Parameters); ================================================ FILE: analyzers/src/RuleDescriptorGenerator/Descriptors/RuleParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using System.Reflection; using SonarAnalyzer.Core.Common; namespace RuleDescriptorGenerator; [ExcludeFromCodeCoverage] public class RuleParameter { // To speed up the use of reflection in the PropertyValue() method for the 4 properties, we cache the PropertyInfo instances. // It's only used in a single-threaded Console application. // The RuleParameter constructor is called hundreds of times, so it's worth caching the PropertyInfo instances. private static readonly Dictionary PropertyInfoCache = []; public string Key { get; set; } public string Description { get; set; } public string Type { get; set; } public string DefaultValue { get; set; } public RuleParameter() { } public RuleParameter(object attribute) { Key = PropertyValue(attribute, nameof(RuleParameterAttribute.Key)); Description = PropertyValue(attribute, nameof(RuleParameterAttribute.Description)); Type = ToServerApi(PropertyValue(attribute, nameof(RuleParameterAttribute.Type))); DefaultValue = PropertyValue(attribute, nameof(RuleParameterAttribute.DefaultValue)); } /// /// sonar-plugin-api / RuleParamTypeTest.java contains other possibilities how to serialize enum values with single-select and multi-select: /// SINGLE_SELECT_LIST,multiple=false,values="First,Second,Third" /// SINGLE_SELECT_LIST,multiple=true,values="First,Second,Third" /// private static string ToServerApi(int type) => type switch { (int)PropertyType.String => "STRING", (int)PropertyType.Text => "TEXT", (int)PropertyType.Boolean => "BOOLEAN", (int)PropertyType.Integer => "INTEGER", (int)PropertyType.Float => "FLOAT", (int)PropertyType.RegularExpression => "REGULAR_EXPRESSION", // This will be translated to STRING by RuleParamType.parse() on the sonar-plugin-api side _ => throw new UnexpectedValueException(nameof(type), type.ToString()) }; // Reflection must be used because of the merged assemblies in the pipeline. // RuleDescriptionGenerator will not recognize the RuleParameterAttribute type because it's no longer in the SonarAnalyzer.Common module, as it was during compilation. private static T PropertyValue(object target, string propertyName) { if (!PropertyInfoCache.TryGetValue(propertyName, out var propertyInfo)) { propertyInfo = target.GetType().GetProperty(propertyName); PropertyInfoCache[propertyName] = propertyInfo; } return (T)propertyInfo.GetValue(target, null); } } ================================================ FILE: analyzers/src/RuleDescriptorGenerator/Program.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Text.Json; using SonarAnalyzer.Core.Common; using SonarAnalyzer.Core.Rules; namespace RuleDescriptorGenerator; [ExcludeFromCodeCoverage] public static class Program { public static void Main(string[] args) { if (args.Length > 1) { var analyzers = args.Skip(1).SelectMany(LoadAnalyzerTypes).ToArray(); var rules = LoadRules(analyzers); var json = JsonSerializer.Serialize(rules, new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); File.WriteAllText(args[0], json); } else { Console.WriteLine("Application expects at least two arguments: [, ...] "); Console.WriteLine("DiagnosticAnalyzer metadata from will be serialized to ."); } } private static Type[] LoadAnalyzerTypes(string path) => Assembly.LoadFrom(path) // We don't need 'Load', we have full control over the environment. It would make local build too complicated. .ExportedTypes .Where(x => !x.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(x) && !IsUtilityAnalyzer(x) && !x.Name.Contains('{')) // Due to the merging of assemblies some classes are duplicated with a '{GUID}' suffix, e.g. 'DeadStores{D6DF12A3-12E5-4602-8A69-B80EE29DFD59}' .ToArray(); private static Rule[] LoadRules(Type[] analyzers) => analyzers.Select(x => new { Type = x, Parameters = RuleParameters(x) }) .SelectMany(x => UniqueIds(x.Type).Select(id => new { Id = id, x.Parameters })) .GroupBy(x => x.Id) // Same id can be in multiple classes (see InvalidCastToInterface) .Select(x => new Rule(x.Key, x.SelectMany(x => x.Parameters).ToArray())) .ToArray(); private static RuleParameter[] RuleParameters(Type analyzer) => analyzer.GetProperties() .Select(x => x.GetCustomAttributes().SingleOrDefault(x => x.GetType().Name == nameof(RuleParameterAttribute))) .Where(x => x is not null) .Select(x => new RuleParameter(x)) .ToArray(); // One class can have the same ruleId multiple times, see S3240 private static string[] UniqueIds(Type analyzer) => ((DiagnosticAnalyzer)Activator.CreateInstance(analyzer)).SupportedDiagnostics.Select(x => x.Id).Distinct().ToArray(); private static bool IsUtilityAnalyzer(Type analyzerType) { var baseType = analyzerType; while (baseType is not null) { // this needs to be checked by name due to the merging of assemblies in the pipeline // UtilityAnalyzerBase is in the SonarAnalyzer.Core assembly if (baseType.FullName == typeof(UtilityAnalyzerBase).FullName) { return true; } baseType = baseType.BaseType; } return false; } } ================================================ FILE: analyzers/src/RuleDescriptorGenerator/RuleDescriptorGenerator.csproj ================================================  Exe net8 ================================================ FILE: analyzers/src/RuleDescriptorGenerator/packages.lock.json ================================================ { "version": 1, "dependencies": { "net8.0": { "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "I5Z2WBgFsx0G22Na1uVFPDkT6Ob4XI+g91GPN8JWldYUMlmIBcUDBfGmfr8oQPdUipvThpaU1x1xZrnNwRR8JA==", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "yllH3rSYEc0bV15CJ2T9Jtx+tSXO5/OVNb+xofuWrACn65Q5VqeFBKgcbgwpyVY/98ypPcGQIWNQL2A/L1seJg==", "dependencies": { "Microsoft.CodeAnalysis.Common": "1.3.2" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "1.2.0", "contentHash": "Cma8cBW6di16ZLibL8LYQ+cLjGzoKxpOTu/faZfDcx94ZjAGq6Nv5RO7+T1YZXqEXTZP9rt1wLVEONVpURtUqw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynCfgWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG; public static partial class CfgSerializer { private class RoslynCfgWalker { protected readonly DotWriter writer; private readonly HashSet visited = []; private readonly RoslynCfgIdProvider cfgIdProvider; private readonly int cfgId; public RoslynCfgWalker(DotWriter writer, RoslynCfgIdProvider cfgIdProvider) { this.writer = writer; this.cfgIdProvider = cfgIdProvider; cfgId = cfgIdProvider.Next(); } public void Visit(ControlFlowGraph cfg, string title) { writer.WriteGraphStart(title); VisitContent(cfg, title); writer.WriteGraphEnd(); } protected virtual void WriteEdges(BasicBlock block) { foreach (var predecessor in block.Predecessors) { var condition = string.Empty; if (predecessor.Source.ConditionKind != ControlFlowConditionKind.None) { condition = predecessor == predecessor.Source.ConditionalSuccessor ? predecessor.Source.ConditionKind.ToString() : "Else"; } var semantics = predecessor.Semantics == ControlFlowBranchSemantics.Regular ? null : predecessor.Semantics.ToString(); writer.WriteEdge(BlockId(predecessor.Source), BlockId(block), $"{semantics} {condition}".Trim()); } if (block.FallThroughSuccessor is { Destination: null }) { writer.WriteEdge(BlockId(block), "NoDestination_" + BlockId(block), block.FallThroughSuccessor.Semantics.ToString()); } } protected string BlockId(BasicBlock block) => $"cfg{cfgId}_block{block.Ordinal}"; private void VisitSubGraph(ControlFlowGraph cfg, string title) { writer.WriteSubGraphStart(cfgIdProvider.Next(), title); VisitContent(cfg, title); writer.WriteSubGraphEnd(); } private void VisitContent(ControlFlowGraph cfg, string titlePrefix) { foreach (var region in cfg.Root.NestedRegions) { Visit(cfg, region); } foreach (var block in cfg.Blocks.Where(x => !visited.Contains(x)).ToArray()) { Visit(block); } foreach (var localFunction in cfg.LocalFunctions) { var localFunctionCfg = cfg.GetLocalFunctionControlFlowGraph(localFunction, default); new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(localFunctionCfg, $"{titlePrefix}.{localFunction.Name}"); } foreach (var anonymousFunction in AnonymousFunctions(cfg)) { var anonymousFunctionCfg = cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunction, default); new RoslynCfgWalker(writer, cfgIdProvider).VisitSubGraph(anonymousFunctionCfg, $"{titlePrefix}.anonymous"); } } private void Visit(ControlFlowGraph cfg, ControlFlowRegion region) { writer.WriteSubGraphStart(cfgIdProvider.Next(), SerializeRegion(region)); foreach (var nested in region.NestedRegions) { Visit(cfg, nested); } foreach (var block in cfg.Blocks.Where(x => x.EnclosingRegion == region)) { Visit(block); } writer.WriteSubGraphEnd(); } private void Visit(BasicBlock block) { visited.Add(block); WriteNode(block); WriteEdges(block); } private void WriteNode(BasicBlock block) { var header = block.Kind.ToString().ToUpperInvariant() + " #" + block.Ordinal; writer.WriteRecordNode(BlockId(block), header, block.Operations.SelectMany(SerializeOperation).Concat(SerializeBranchValue(block.BranchValue)).ToArray()); } private static IEnumerable SerializeBranchValue(IOperation operation) => operation is null ? [] : new[] { "## BranchValue ##" }.Concat(SerializeOperation(operation)); private static IEnumerable SerializeOperation(IOperation operation) => SerializeOperation(0, null, operation).Concat(new[] { "##########" }); private static IEnumerable SerializeOperation(int level, string prefix, IOperation operation) { var prefixes = operation.GetType().GetProperties() .GroupBy(x => x.GetValue(operation) as IOperation, x => x.Name) .Where(x => x.Key is not null) .ToDictionary(x => x.Key, x => string.Join(", ", x)); var ret = new List { $"{level}#: {prefix}{operation.Serialize()}" }; foreach (var child in operation.ToSonar().Children) { ret.AddRange(SerializeOperation(level + 1, prefixes.TryGetValue(child, out var childPrefix) ? $"{level}#.{childPrefix}: " : null, child)); } return ret; } private static string SerializeRegion(ControlFlowRegion region) { var sb = new StringBuilder(); sb.Append(region.Kind.ToString()).Append(" region"); if (region.ExceptionType is not null) { sb.Append(": ").Append(region.ExceptionType); } if (region.Locals.Any()) { sb.Append(", Locals: ").Append(string.Join(", ", region.Locals.Select(x => x.Name ?? "N/A"))); } if (region.CaptureIds.Any()) { sb.Append(", Captures: ").Append(string.Join(", ", region.CaptureIds.Select(x => x.Serialize()))); // Same as IOperationExtension.SerializeSuffix } return sb.ToString(); } private static IEnumerable AnonymousFunctions(ControlFlowGraph cfg) => cfg.Blocks .SelectMany(x => x.Operations) .Concat(cfg.Blocks.Select(x => x.BranchValue).Where(x => x is not null)) .SelectMany(x => x.DescendantsAndSelf()) .Where(IFlowAnonymousFunctionOperationWrapper.IsInstance) .Select(IFlowAnonymousFunctionOperationWrapper.FromOperation); } private sealed class RoslynCfgIdProvider { private int value; public int Next() => value++; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.RoslynLvaWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG; public static partial class CfgSerializer { private sealed class RoslynLvaWalker : RoslynCfgWalker { private readonly RoslynLiveVariableAnalysis lva; public RoslynLvaWalker(RoslynLiveVariableAnalysis lva, DotWriter writer, RoslynCfgIdProvider cfgIdProvider) : base(writer, cfgIdProvider) { this.lva = lva; } protected override void WriteEdges(BasicBlock block) { foreach (var predecessor in lva.BlockPredecessors[block.Ordinal].Where(x => !block.Predecessors.Any(y => y.Source == x))) { writer.WriteEdge(BlockId(predecessor), BlockId(block), "LVA\" fontcolor=\"blue\" penwidth=\"2\" color=\"blue"); } base.WriteEdges(block); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.SonarCfgWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CFG; public static partial class CfgSerializer { private sealed class SonarCfgWalker { private readonly BlockIdProvider blockId = new(); private readonly DotWriter writer; public SonarCfgWalker(DotWriter writer) => this.writer = writer; public void Visit(IControlFlowGraph cfg, string title) { writer.WriteGraphStart(title); foreach (var block in cfg.Blocks) { Visit(block); } writer.WriteGraphEnd(); } private void Visit(Block block) { if (block is BinaryBranchBlock binaryBranchBlock) { WriteNode(block, binaryBranchBlock.BranchingNode); } else if (block is BranchBlock branchBlock) { WriteNode(block, branchBlock.BranchingNode); } else if (block is ExitBlock) { WriteNode(block); } else if (block is ForeachCollectionProducerBlock foreachBlock) { WriteNode(foreachBlock, foreachBlock.ForeachNode); } else if (block is ForInitializerBlock forBlock) { WriteNode(forBlock, forBlock.ForNode); } else if (block is JumpBlock jumpBlock) { WriteNode(jumpBlock, jumpBlock.JumpNode); } else if (block is LockBlock lockBlock) { WriteNode(lockBlock, lockBlock.LockNode); } else if (block is UsingEndBlock usingBlock) { WriteNode(usingBlock, usingBlock.UsingStatement); } else { WriteNode(block); } WriteEdges(block); } private void WriteNode(Block block, SyntaxNode terminator = null) { var header = block.GetType().Name.SplitCamelCaseToWords().First().ToUpperInvariant(); if (terminator is not null) { header += ":" + terminator.Kind().ToString().Replace("Syntax", string.Empty); } writer.WriteRecordNode(blockId.Get(block), header, block.Instructions.Select(x => x.ToString()).ToArray()); } private void WriteEdges(Block block) { foreach (var successor in block.SuccessorBlocks) { writer.WriteEdge(blockId.Get(block), blockId.Get(successor), Label()); string Label() { if (block is BinaryBranchBlock binary) { if (successor == binary.TrueSuccessorBlock) { return bool.TrueString; } else if (successor == binary.FalseSuccessorBlock) { return bool.FalseString; } } return string.Empty; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/CfgSerializer/CfgSerializer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CFG; public static partial class CfgSerializer { public static string Serialize(IControlFlowGraph cfg, string title = "SonarCfg") { var writer = new DotWriter(); new SonarCfgWalker(writer).Visit(cfg, title); return writer.ToString(); } public static string Serialize(ControlFlowGraph cfg, string title = "RoslynCfg") { var writer = new DotWriter(); new RoslynCfgWalker(writer, new RoslynCfgIdProvider()).Visit(cfg, title); return writer.ToString(); } public static string Serialize(RoslynLiveVariableAnalysis lva, string title = "RoslynCfgLva") { var writer = new DotWriter(); new RoslynLvaWalker(lva, writer, new RoslynCfgIdProvider()).Visit(lva.Cfg, title); return writer.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/CfgSerializer/DotWriter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.CFG; public class DotWriter { private readonly StringBuilder builder = new StringBuilder(); private readonly StringBuilder edges = new StringBuilder(); private bool started; public void WriteGraphStart(string graphName) { if (started) { throw new InvalidOperationException("Graph was already started"); } started = true; builder.AppendLine($"digraph \"{Encode(graphName)}\" {{"); } public void WriteGraphEnd() { if (!started) { throw new InvalidOperationException("Graph was not started"); } started = false; builder.Append(edges).AppendLine("}"); // Edges crossing subgraphs must be listed at the end of the main graph to keep nodes rendered in the correct subgraph edges.Clear(); } public void WriteSubGraphStart(int id, string title) => builder.AppendLine($"subgraph \"cluster_{id}\" {{\nlabel = \"{Encode(title)}\""); public void WriteSubGraphEnd() => builder.AppendLine("}"); public void WriteRecordNode(string id, string header, params string[] items) { // Curly braces in the label reverse the orientation of the columns/rows // Columns/rows are created with pipe // New lines are inserted with \n; \r\n does not work well. // ID [shape=record label="{
|\n\n...}"] builder.Append(id).Append(" [shape=record label=\"{").Append(header); foreach (var item in items) { builder.Append("|").Append(Encode(item)); } builder.AppendLine("}\"]"); } public void WriteNode(string id, string[] attributes) { builder.Append(id); if (attributes.Length > 0) { builder.Append(" [").Append(string.Join(" ", attributes)).Append("]"); } builder.AppendLine(); } public void WriteEdge(string startId, string endId, string label) { edges.Append(startId).Append(" -> ").Append(endId); if (!string.IsNullOrEmpty(label)) { edges.Append($" [label=\"{label}\"]"); } edges.AppendLine(); } public override string ToString() => builder.ToString(); private static string Encode(string s) => s.Replace("\r", string.Empty) .Replace("\n", @"\n") .Replace("{", @"\{") .Replace("}", @"\}") .Replace("|", @"\|") .Replace("<", @"\<") .Replace(">", @"\>") .Replace("\"", @"\"""); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Common/RoslynVersion.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Common; public static class RoslynVersion { public const int VS2017MajorVersion = 2; public const int MinimalSupportedMajorVersion = 3; public static bool IsRoslynCfgSupported(int minimalVersion = MinimalSupportedMajorVersion) => !IsVersionLessThan(minimalVersion); public static bool IsVersionLessThan(int minimalVersion = MinimalSupportedMajorVersion) => CurrentVersion().Major < minimalVersion; public static bool IsVersionLessThan(Version version) => CurrentVersion() < version; private static Version CurrentVersion() => typeof(SemanticModel).Assembly.GetName().Version; } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Common/UniqueQueue.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; namespace SonarAnalyzer.CFG.Common; internal class UniqueQueue : IEnumerable { private readonly Queue queue = new Queue(); private readonly ISet unique = new HashSet(); public void Enqueue(T item) { if (!unique.Contains(item)) { queue.Enqueue(item); unique.Add(item); } } public T Dequeue() { var ret = queue.Dequeue(); unique.Remove(ret); return ret; } public IEnumerator GetEnumerator() => queue.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/BasicBlockExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG.Extensions; public static class BasicBlockExtensions { public static bool IsEnclosedIn(this BasicBlock block, ControlFlowRegionKind kind) { var enclosing = kind == ControlFlowRegionKind.LocalLifetime ? block.EnclosingRegion : block.EnclosingNonLocalLifetimeRegion(); return enclosing.Kind == kind; } public static ControlFlowRegion EnclosingNonLocalLifetimeRegion(this BasicBlock block) => block.EnclosingRegion.EnclosingNonLocalLifetimeRegion(); public static ControlFlowRegion EnclosingRegion(this BasicBlock block, ControlFlowRegionKind kind) => block.EnclosingRegion.EnclosingRegionOrSelf(kind); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/ControlFlowGraphExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG.Extensions; public static class ControlFlowGraphExtensions { public static IEnumerable FlowAnonymousFunctionOperations(this ControlFlowGraph cfg) => cfg.Blocks .SelectMany(x => x.OperationsAndBranchValue) .SelectMany(x => x.DescendantsAndSelf()) .Where(IFlowAnonymousFunctionOperationWrapper.IsInstance) .Select(IFlowAnonymousFunctionOperationWrapper.FromOperation); // Similar to ControlFlowGraphExtensions.GetLocalFunctionControlFlowGraphInScope from Roslyn public static ControlFlowGraph FindLocalFunctionCfgInScope(this ControlFlowGraph cfg, IMethodSymbol localFunction, CancellationToken cancel) { var current = cfg; while (current is not null) { if (current.LocalFunctions.Contains(localFunction)) { return current.GetLocalFunctionControlFlowGraph(localFunction, cancel); } current = current.Parent; } throw new ArgumentOutOfRangeException(nameof(localFunction)); } public static ControlFlowGraph GetLocalFunctionControlFlowGraph(this ControlFlowGraph cfg, SyntaxNode localFunction, CancellationToken cancel) => cfg.GetLocalFunctionControlFlowGraph(cfg.LocalFunctions.Single(x => x.DeclaringSyntaxReferences.Single().GetSyntax() == localFunction), cancel); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/ControlFlowRegionExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG.Extensions; public static class ControlFlowRegionExtensions { public static IEnumerable Blocks(this ControlFlowRegion region, ControlFlowGraph cfg) => cfg.Blocks.Where((_, i) => region.FirstBlockOrdinal <= i && i <= region.LastBlockOrdinal); public static ControlFlowRegion EnclosingNonLocalLifetimeRegion(this ControlFlowRegion region) { while (region.EnclosingRegion is not null && region.Kind == ControlFlowRegionKind.LocalLifetime) { region = region.EnclosingRegion; } return region; } public static ControlFlowRegion EnclosingRegionOrSelf(this ControlFlowRegion region, ControlFlowRegionKind kind) { while (region is not null && region.Kind != kind) { if (region.Kind == ControlFlowRegionKind.Root) { return null; // Do not traverse from inner lambda CFG to the outer method CFG } region = region.EnclosingRegion; } return region; } public static ControlFlowRegion EnclosingRegion(this ControlFlowRegion region, ControlFlowRegionKind kind) => region.EnclosingRegion.EnclosingRegionOrSelf(kind); public static ControlFlowRegion NestedRegion(this ControlFlowRegion region, ControlFlowRegionKind kind) => region.NestedRegions.Single(x => x.Kind == kind); /// /// Returns all Catch, FilterAndHandler, and Finally regions that are reachable from the given try region. /// public static IEnumerable ReachableHandlers(this ControlFlowRegion tryRegion) => tryRegion is null ? [] : tryRegion.EnclosingRegion.NestedRegions.Where(x => x.Kind != ControlFlowRegionKind.Try) .Concat(ReachableHandlers(tryRegion.EnclosingRegion(ControlFlowRegionKind.Try))); // Use also all outer candidates for nested try/catch. } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/DictionaryExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Extensions; internal static class DictionaryExtensions { public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func factory) { if (!dictionary.TryGetValue(key, out var value)) { value = factory(key); dictionary.Add(key, value); } return value; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/ExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Extensions; internal static class ExpressionSyntaxExtensions { public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => (ExpressionSyntax)SyntaxNodeExtensions.RemoveParentheses(expression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/IEnumerableExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Extensions; internal static class IEnumerableExtensions { public static HashSet ToHashSet(this IEnumerable enumerable, IEqualityComparer equalityComparer = null) { enumerable ??= []; return equalityComparer is null ? new(enumerable) : new(enumerable, equalityComparer); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/IOperationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Operations.Utilities; namespace SonarAnalyzer.CFG.Extensions; public static class IOperationExtensions { [Obsolete("Use extension methods for IOperation properties instead.")] // should be made private and ObsoleteAttribute removed when there is no usage outside of this file left public static IOperationWrapperSonar ToSonar(this IOperation operation) => new(operation); [Obsolete("Use extension methods for IOperation properties instead.")] // should be made private and ObsoleteAttribute removed when there is no usage outside of this file left public static IOperationWrapperSonar ToSonar(this IOperationWrapper operation) => new(operation.WrappedOperation); public static IOperation Parent(this IOperation operation) => operation.ToSonar().Parent; public static IOperation Parent(this IOperationWrapper operation) => operation.WrappedOperation.Parent(); public static IEnumerable Children(this IOperation operation) => operation.ToSonar().Children; public static IEnumerable Children(this IOperationWrapper operation) => operation.WrappedOperation.Children(); public static string Language(this IOperation operation) => operation.ToSonar().Language; public static string Language(this IOperationWrapper operation) => operation.WrappedOperation.Language(); public static bool IsImplicit(this IOperation operation) => operation.ToSonar().IsImplicit; public static bool IsImplicit(this IOperationWrapper operation) => operation.WrappedOperation.IsImplicit(); public static SemanticModel SemanticModel(this IOperation operation) => operation.ToSonar().SemanticModel; public static SemanticModel SemanticModel(this IOperationWrapper operation) => operation.WrappedOperation.SemanticModel(); public static bool IsOutArgumentReference(this IOperation operation) => operation.ToSonar() is var wrapped && IArgumentOperationWrapper.IsInstance(wrapped.Parent) && IArgumentOperationWrapper.FromOperation(wrapped.Parent).Parameter.RefKind == RefKind.Out; public static bool IsAssignmentTarget(this IOperationWrapper operation) => operation.ToSonar().Parent is { } parent && ISimpleAssignmentOperationWrapper.IsInstance(parent) && ISimpleAssignmentOperationWrapper.FromOperation(parent).Target == operation.WrappedOperation; public static bool IsCompoundAssignmentTarget(this IOperationWrapper operation) => operation.ToSonar().Parent is { } parent && ICompoundAssignmentOperationWrapper.IsInstance(parent) && ICompoundAssignmentOperationWrapper.FromOperation(parent).Target == operation.WrappedOperation; public static bool IsOutArgument(this IOperationWrapper operation) => operation.ToSonar().Parent is { } parent && IArgumentOperationWrapper.IsInstance(parent) && IArgumentOperationWrapper.FromOperation(parent).Parameter.RefKind == RefKind.Out; public static bool IsAnyKind(this IOperation operation, params OperationKind[] kinds) => kinds.Contains(operation.Kind); public static IOperation RootOperation(this IOperation operation) { var wrapper = operation.ToSonar(); while (wrapper.Parent is not null) { wrapper = wrapper.Parent.ToSonar(); } return wrapper.Instance; } /// public static IOperation ArgumentValue(this IInvocationOperationWrapper invocation, string parameterName) => ArgumentValue(invocation.Arguments, parameterName); /// public static IOperation ArgumentValue(this IObjectCreationOperationWrapper objectCreation, string parameterName) => ArgumentValue(objectCreation.Arguments, parameterName); /// public static IOperation ArgumentValue(this IPropertyReferenceOperationWrapper propertyReference, string parameterName) => ArgumentValue(propertyReference.Arguments, parameterName); /// public static IOperation ArgumentValue(this IRaiseEventOperationWrapper raiseEvent, string parameterName) => ArgumentValue(raiseEvent.Arguments, parameterName); public static OperationExecutionOrder ToExecutionOrder(this IEnumerable operations) => new(operations, false); public static OperationExecutionOrder ToReversedExecutionOrder(this IEnumerable operations) => new(operations, true); public static string Serialize(this IOperation operation) => $"{OperationPrefix(operation)}{OperationSuffix(operation)}: {operation.Syntax}"; // This method is taken from Roslyn implementation public static IEnumerable DescendantsAndSelf(this IOperation operation) => Descendants(operation, true); public static IAnonymousFunctionOperationWrapper? AsAnonymousFunction(this IOperation operation) => operation.As(OperationKindEx.AnonymousFunction, IAnonymousFunctionOperationWrapper.FromOperation); public static IArgumentOperationWrapper? AsArgument(this IOperation operation) => operation.As(OperationKindEx.Argument, IArgumentOperationWrapper.FromOperation); public static IAssignmentOperationWrapper? AsAssignment(this IOperation operation) => operation.As(OperationKindEx.SimpleAssignment, IAssignmentOperationWrapper.FromOperation); public static ISimpleAssignmentOperationWrapper? AsSimpleAssignment(this IOperation operation) => operation.As(OperationKindEx.SimpleAssignment, ISimpleAssignmentOperationWrapper.FromOperation); public static IArrayCreationOperationWrapper? AsArrayCreation(this IOperation operation) => operation.As(OperationKindEx.ArrayCreation, IArrayCreationOperationWrapper.FromOperation); public static IArrayElementReferenceOperationWrapper? AsArrayElementReference(this IOperation operation) => operation.As(OperationKindEx.ArrayElementReference, IArrayElementReferenceOperationWrapper.FromOperation); public static IConversionOperationWrapper? AsConversion(this IOperation operation) => operation.As(OperationKindEx.Conversion, IConversionOperationWrapper.FromOperation); public static IDeclarationExpressionOperationWrapper? AsDeclarationExpression(this IOperation operation) => operation.As(OperationKindEx.DeclarationExpression, IDeclarationExpressionOperationWrapper.FromOperation); public static IDeclarationPatternOperationWrapper? AsDeclarationPattern(this IOperation operation) => operation.As(OperationKindEx.DeclarationPattern, IDeclarationPatternOperationWrapper.FromOperation); public static IFlowAnonymousFunctionOperationWrapper? AsFlowAnonymousFunction(this IOperation operation) => operation.As(OperationKindEx.FlowAnonymousFunction, IFlowAnonymousFunctionOperationWrapper.FromOperation); public static IFlowCaptureOperationWrapper? AsFlowCapture(this IOperation operation) => operation.As(OperationKindEx.FlowCapture, IFlowCaptureOperationWrapper.FromOperation); public static IFlowCaptureReferenceOperationWrapper? AsFlowCaptureReference(this IOperation operation) => operation.As(OperationKindEx.FlowCaptureReference, IFlowCaptureReferenceOperationWrapper.FromOperation); public static IForEachLoopOperationWrapper? AsForEachLoop(this IOperation operation) { if (operation is null) // null check to be consistent with other the other As methods { throw new NullReferenceException(nameof(operation)); } // Other LoopKinds (e.g. For, While) are still OperationKindEx.Loop, but cannot be cast to IForEachLoopOperationWrapper so we need an additional check return IForEachLoopOperationWrapper.IsInstance(operation) ? IForEachLoopOperationWrapper.FromOperation(operation) : null; } public static IInvocationOperationWrapper? AsInvocation(this IOperation operation) => operation.As(OperationKindEx.Invocation, IInvocationOperationWrapper.FromOperation); public static ILocalFunctionOperationWrapper? AsLocalFunction(this IOperation operation) => operation.As(OperationKindEx.LocalFunction, ILocalFunctionOperationWrapper.FromOperation); public static ILocalReferenceOperationWrapper? AsLocalReference(this IOperation operation) => operation.As(OperationKindEx.LocalReference, ILocalReferenceOperationWrapper.FromOperation); public static IIsNullOperationWrapper? AsIsNull(this IOperation operation) => operation.As(OperationKindEx.IsNull, IIsNullOperationWrapper.FromOperation); public static IIsPatternOperationWrapper? AsIsPattern(this IOperation operation) => operation.As(OperationKindEx.IsPattern, IIsPatternOperationWrapper.FromOperation); public static IParameterReferenceOperationWrapper? AsParameterReference(this IOperation operation) => operation.As(OperationKindEx.ParameterReference, IParameterReferenceOperationWrapper.FromOperation); public static IMethodReferenceOperationWrapper? AsMethodReference(this IOperation operation) => operation.As(OperationKindEx.MethodReference, IMethodReferenceOperationWrapper.FromOperation); public static IObjectCreationOperationWrapper? AsObjectCreation(this IOperation operation) => operation.As(OperationKindEx.ObjectCreation, IObjectCreationOperationWrapper.FromOperation); public static IPropertyReferenceOperationWrapper? AsPropertyReference(this IOperation operation) => operation.As(OperationKindEx.PropertyReference, IPropertyReferenceOperationWrapper.FromOperation); public static IRecursivePatternOperationWrapper? AsRecursivePattern(this IOperation operation) => operation.As(OperationKindEx.RecursivePattern, IRecursivePatternOperationWrapper.FromOperation); public static ISpreadOperationWrapper? AsSpread(this IOperation operation) => operation.As(OperationKindEx.Spread, ISpreadOperationWrapper.FromOperation); public static ITupleOperationWrapper? AsTuple(this IOperation operation) => operation.As(OperationKindEx.Tuple, ITupleOperationWrapper.FromOperation); public static IVariableDeclaratorOperationWrapper? AsVariableDeclarator(this IOperation operation) => operation.As(OperationKindEx.VariableDeclarator, IVariableDeclaratorOperationWrapper.FromOperation); public static IAddressOfOperationWrapper ToAddressOf(this IOperation operation) => IAddressOfOperationWrapper.FromOperation(operation); public static IAwaitOperationWrapper ToAwait(this IOperation operation) => IAwaitOperationWrapper.FromOperation(operation); public static IArgumentOperationWrapper ToArgument(this IOperation operation) => IArgumentOperationWrapper.FromOperation(operation); public static IArrayCreationOperationWrapper ToArrayCreation(this IOperation operation) => IArrayCreationOperationWrapper.FromOperation(operation); public static IAssignmentOperationWrapper ToAssignment(this IOperation operation) => IAssignmentOperationWrapper.FromOperation(operation); public static IArrayElementReferenceOperationWrapper ToArrayElementReference(this IOperation operation) => IArrayElementReferenceOperationWrapper.FromOperation(operation); public static IBinaryOperationWrapper ToBinary(this IOperation operation) => IBinaryOperationWrapper.FromOperation(operation); public static IBinaryPatternOperationWrapper ToBinaryPattern(this IOperation operation) => IBinaryPatternOperationWrapper.FromOperation(operation); public static ICatchClauseOperationWrapper ToCatchClause(this IOperation operation) => ICatchClauseOperationWrapper.FromOperation(operation); public static ICompoundAssignmentOperationWrapper ToCompoundAssignment(this IOperation operation) => ICompoundAssignmentOperationWrapper.FromOperation(operation); public static IConstantPatternOperationWrapper ToConstantPattern(this IOperation operation) => IConstantPatternOperationWrapper.FromOperation(operation); public static IConversionOperationWrapper ToConversion(this IOperation operation) => IConversionOperationWrapper.FromOperation(operation); public static IDeclarationPatternOperationWrapper ToDeclarationPattern(this IOperation operation) => IDeclarationPatternOperationWrapper.FromOperation(operation); public static IEventReferenceOperationWrapper ToEventReference(this IOperation operation) => IEventReferenceOperationWrapper.FromOperation(operation); public static IFieldReferenceOperationWrapper ToFieldReference(this IOperation operation) => IFieldReferenceOperationWrapper.FromOperation(operation); public static IFlowCaptureOperationWrapper ToFlowCapture(this IOperation operation) => IFlowCaptureOperationWrapper.FromOperation(operation); public static IFlowCaptureReferenceOperationWrapper ToFlowCaptureReference(this IOperation operation) => IFlowCaptureReferenceOperationWrapper.FromOperation(operation); public static IIncrementOrDecrementOperationWrapper ToIncrementOrDecrement(this IOperation operation) => IIncrementOrDecrementOperationWrapper.FromOperation(operation); public static IInvocationOperationWrapper ToInvocation(this IOperation operation) => IInvocationOperationWrapper.FromOperation(operation); public static IIsTypeOperationWrapper ToIsType(this IOperation operation) => IIsTypeOperationWrapper.FromOperation(operation); public static ILocalFunctionOperationWrapper ToLocalFunction(this IOperation operation) => ILocalFunctionOperationWrapper.FromOperation(operation); public static ILocalReferenceOperationWrapper ToLocalReference(this IOperation operation) => ILocalReferenceOperationWrapper.FromOperation(operation); public static IMemberReferenceOperationWrapper ToMemberReference(this IOperation operation) => IMemberReferenceOperationWrapper.FromOperation(operation); public static IMethodReferenceOperationWrapper ToMethodReference(this IOperation operation) => IMethodReferenceOperationWrapper.FromOperation(operation); public static INegatedPatternOperationWrapper ToNegatedPattern(this IOperation operation) => INegatedPatternOperationWrapper.FromOperation(operation); public static IObjectCreationOperationWrapper ToObjectCreation(this IOperation operation) => IObjectCreationOperationWrapper.FromOperation(operation); public static IPatternOperationWrapper ToPattern(this IOperation operation) => IPatternOperationWrapper.FromOperation(operation); public static IParameterReferenceOperationWrapper ToParameterReference(this IOperation operation) => IParameterReferenceOperationWrapper.FromOperation(operation); public static IPropertyReferenceOperationWrapper ToPropertyReference(this IOperation operation) => IPropertyReferenceOperationWrapper.FromOperation(operation); public static IRecursivePatternOperationWrapper ToRecursivePattern(this IOperation operation) => IRecursivePatternOperationWrapper.FromOperation(operation); public static IRelationalPatternOperationWrapper ToRelationalPattern(this IOperation operation) => IRelationalPatternOperationWrapper.FromOperation(operation); public static ITypePatternOperationWrapper ToTypePattern(this IOperation operation) => ITypePatternOperationWrapper.FromOperation(operation); public static ITupleOperationWrapper ToTuple(this IOperation operation) => ITupleOperationWrapper.FromOperation(operation); public static IUnaryOperationWrapper ToUnary(this IOperation operation) => IUnaryOperationWrapper.FromOperation(operation); public static IVariableDeclarationOperationWrapper ToVariableDeclaration(this IOperation operation) => IVariableDeclarationOperationWrapper.FromOperation(operation); public static IVariableDeclaratorOperationWrapper ToVariableDeclarator(this IOperation operation) => IVariableDeclaratorOperationWrapper.FromOperation(operation); public static IOperation UnwrapConversion(this IOperation operation) { while (operation?.Kind == OperationKindEx.Conversion) { operation = operation.ToConversion().Operand; } return operation; } // This method is taken from Roslyn implementation private static IEnumerable Descendants(IOperation operation, bool includeSelf) { if (operation is null) { yield break; } if (includeSelf) { yield return operation; } var stack = new Stack>(); stack.Push(operation.ToSonar().Children.GetEnumerator()); while (stack.Any()) { var iterator = stack.Pop(); if (!iterator.MoveNext()) { continue; } stack.Push(iterator); if (iterator.Current is { } current) { yield return current; stack.Push(current.ToSonar().Children.GetEnumerator()); } } } /// /// Returns the argument value corresponding to . For parameter an IArrayCreationOperation is returned. /// private static IOperation ArgumentValue(ImmutableArray arguments, string parameterName) { foreach (var operation in arguments) { var argument = operation.ToArgument(); if (argument.Parameter.Name == parameterName) { return argument.Value; } } return null; } private static string OperationPrefix(IOperation op) => op.Kind == OperationKindEx.Invalid ? "INVALID" : op.GetType().Name; private static string OperationSuffix(IOperation op) => op switch { var _ when IInvocationOperationWrapper.IsInstance(op) => ": " + IInvocationOperationWrapper.FromOperation(op).TargetMethod.Name, var _ when IFlowCaptureOperationWrapper.IsInstance(op) => ": " + IFlowCaptureOperationWrapper.FromOperation(op).Id.Serialize(), var _ when IFlowCaptureReferenceOperationWrapper.IsInstance(op) => ": " + IFlowCaptureReferenceOperationWrapper.FromOperation(op).Id.Serialize(), _ => null }; private static T? As(this IOperation operation, OperationKind kind, Func fromOperation) where T : struct => operation.Kind == kind ? fromOperation(operation) : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/IsPatternExpressionSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Extensions; public static class IsPatternExpressionSyntaxWrapperExtensions { public static bool IsNull(this IsPatternExpressionSyntaxWrapper isPatternWrapper) => isPatternWrapper.Pattern.IsNull(); public static bool IsNot(this IsPatternExpressionSyntaxWrapper isPatternWrapper) => isPatternWrapper.Pattern.RemoveParentheses() is var syntaxNode && UnaryPatternSyntaxWrapper.IsInstance(syntaxNode) && ((UnaryPatternSyntaxWrapper)syntaxNode) is var unaryPatternSyntaxWrapper && unaryPatternSyntaxWrapper.IsNot(); public static bool IsNotNull(this IsPatternExpressionSyntaxWrapper isPatternWrapper) => isPatternWrapper.Pattern.RemoveParentheses() is var syntaxNode && UnaryPatternSyntaxWrapper.IsInstance(syntaxNode) && ((UnaryPatternSyntaxWrapper)syntaxNode) is var unaryPatternSyntaxWrapper && unaryPatternSyntaxWrapper.IsNotNull(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/PatternSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.CFG.Extensions; public static class PatternSyntaxWrapperExtensions { public static bool IsNull(this PatternSyntaxWrapper patternSyntaxWrapper) => patternSyntaxWrapper.RemoveParentheses() is var syntaxNode && ConstantPatternSyntaxWrapper.IsInstance(syntaxNode) && (ConstantPatternSyntaxWrapper)syntaxNode is var constantPattern && constantPattern.Expression.Kind() == SyntaxKind.NullLiteralExpression; public static bool IsNot(this PatternSyntaxWrapper patternSyntaxWrapper) => patternSyntaxWrapper.RemoveParentheses().Kind() == SyntaxKindEx.NotPattern; public static SyntaxNode RemoveParentheses(this PatternSyntaxWrapper patternSyntaxWrapper) => patternSyntaxWrapper.SyntaxNode.RemoveParentheses(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/PropertyInfoExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using System.Reflection; namespace SonarAnalyzer.CFG.Extensions; internal static class PropertyInfoExtensions { public static T ReadCached(this PropertyInfo property, object instance, ref T cache) where T : class => cache ??= (T)property.GetValue(instance); public static T ReadCached(this PropertyInfo property, object instance, ref T? cache) where T : struct => cache ??= (T)property.GetValue(instance); public static T ReadCached(this PropertyInfo property, object instance, Func createInstance, ref T cache) where T : class => cache ??= createInstance(property.GetValue(instance)); public static ImmutableArray ReadCached(this PropertyInfo property, object instance, ref ImmutableArray cache) => ReadCached(property, instance, x => (T)x, ref cache); public static ImmutableArray ReadCached(this PropertyInfo property, object instance, Func createInstance, ref ImmutableArray cache) { if (cache.IsDefault) { cache = ((IEnumerable)property.GetValue(instance)).Cast().Select(createInstance).ToImmutableArray(); } return cache; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/SemanticModelExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Extensions; public static class SemanticModelExtensions { /// /// Starting .NET Framework 4.6.1, we've noticed that LINQ methods aren't resolved properly, so we need to use the CandidateSymbol. /// /// Semantic model /// /// Node for which it gets the symbol /// /// The symbol if resolved. /// The first candidate symbol if resolution failed. /// Null if no symbol was found. /// public static ISymbol GetSymbolOrCandidateSymbol(this SemanticModel model, SyntaxNode node) { var symbolInfo = model.GetSymbolInfo(node); if (symbolInfo.Symbol is not null) { return symbolInfo.Symbol; } return symbolInfo.CandidateSymbols.FirstOrDefault(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/StringExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.CFG.Extensions; public static class StringExtensions { /// /// Splits the input string to the list of words. /// /// Sequence of upper case letters is considered as single word. /// /// For example: /// /// thisIsAName => ["THIS", "IS", "A", "NAME"] /// ThisIsSMTPName => ["THIS", "IS", "SMTP", "NAME"] /// bin2hex => ["BIN", "HEX"] /// HTML => ["HTML"] /// SOME_value => ["SOME", "VALUE"] /// PEHeader => ["PE", "HEADER"] /// /// /// A string containing words. /// A list of words (all uppercase) contained in the string. public static IEnumerable SplitCamelCaseToWords(this string name) { if (name is null) { yield break; } var currentWord = new StringBuilder(); var hasLower = false; for (var i = 0; i < name.Length; i++) { var c = name[i]; if (!char.IsLetter(c)) { if (currentWord.Length > 0) { yield return currentWord.ToString(); currentWord.Clear(); hasLower = false; } continue; } if (char.IsUpper(c) && currentWord.Length > 0 && (hasLower || IsFollowedByLower(i))) { yield return currentWord.ToString(); currentWord.Clear(); hasLower = false; } currentWord.Append(char.ToUpperInvariant(c)); hasLower = hasLower || char.IsLower(c); } if (currentWord.Length > 0) { yield return currentWord.ToString(); } bool IsFollowedByLower(int i) => i + 1 < name.Length && char.IsLower(name[i + 1]); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/SyntaxNodeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Extensions; internal static class SyntaxNodeExtensions { private static readonly ISet ParenthesizedExpressionKinds = new HashSet { SyntaxKind.ParenthesizedExpression, SyntaxKindEx.ParenthesizedPattern }; public static SyntaxNode RemoveParentheses(this SyntaxNode expression) { var currentExpression = expression; while (currentExpression is not null && ParenthesizedExpressionKinds.Contains(currentExpression.Kind())) { if (currentExpression.IsKind(SyntaxKind.ParenthesizedExpression)) { currentExpression = ((ParenthesizedExpressionSyntax)currentExpression).Expression; } else { currentExpression = ((ParenthesizedPatternSyntaxWrapper)currentExpression).Pattern; } } return currentExpression; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Extensions/UnaryPatternSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.CFG.Extensions; public static class UnaryPatternSyntaxWrapperExtensions { public static bool IsNot(this UnaryPatternSyntaxWrapper unaryPatternSyntaxWrapper) => unaryPatternSyntaxWrapper.SyntaxNode.RemoveParentheses().Kind() == SyntaxKindEx.NotPattern; public static bool IsNotNull(this UnaryPatternSyntaxWrapper unaryPatternSyntaxWrapper) => unaryPatternSyntaxWrapper.IsNot() && unaryPatternSyntaxWrapper.Pattern.IsNull(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/LiveVariableAnalysisBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Common; namespace SonarAnalyzer.CFG.LiveVariableAnalysis; public abstract class LiveVariableAnalysisBase { protected readonly ISymbol originalDeclaration; protected readonly CancellationToken Cancel; private readonly Dictionary> blockLiveOut = new(); private readonly Dictionary> blockLiveIn = new(); private readonly HashSet captured = []; public abstract bool IsLocal(ISymbol symbol); protected abstract TBlock ExitBlock { get; } protected abstract State ProcessBlock(TBlock block); protected abstract IEnumerable ReversedBlocks(); protected abstract IEnumerable Successors(TBlock block); protected abstract IEnumerable Predecessors(TBlock block); public TCfg Cfg { get; } public IReadOnlyCollection CapturedVariables => captured; protected LiveVariableAnalysisBase(TCfg cfg, ISymbol originalDeclaration, CancellationToken cancel) { Cfg = cfg; this.originalDeclaration = originalDeclaration; Cancel = cancel; } /// /// LiveIn variables are alive when entering block. They are read inside the block or any of it's successors. /// public IEnumerable LiveIn(TBlock block) => blockLiveIn[block].Except(captured); /// /// LiveOut variables are alive when exiting block. They are read in any of it's successors. /// public IEnumerable LiveOut(TBlock block) => blockLiveOut[block].Except(captured); protected void Analyze() { var states = new Dictionary(); var queue = new UniqueQueue(); foreach (var block in ReversedBlocks()) { var state = ProcessBlock(block); captured.UnionWith(state.Captured); states.Add(block, state); blockLiveIn.Add(block, new HashSet()); blockLiveOut.Add(block, new HashSet()); queue.Enqueue(block); } while (queue.Any()) { Cancel.ThrowIfCancellationRequested(); var block = queue.Dequeue(); var liveOut = blockLiveOut[block]; // note that on the PHP LVA impl, the `liveOut` gets cleared before being updated foreach (var successorLiveIn in Successors(block).Select(x => blockLiveIn[x]).Where(x => x.Any())) { liveOut.UnionWith(successorLiveIn); } // liveIn = UsedBeforeAssigned + (LiveOut - Assigned) var liveIn = states[block].UsedBeforeAssigned.Concat(liveOut.Except(states[block].Assigned)).ToHashSet(); // Don't enqueue predecessors if nothing changed. if (!liveIn.SetEquals(blockLiveIn[block])) { blockLiveIn[block] = liveIn; foreach (var predecessor in Predecessors(block)) { queue.Enqueue(predecessor); } } } if (blockLiveOut[ExitBlock].Any()) { throw new InvalidOperationException("Out of exit block should be empty"); } } protected abstract class State { public HashSet Assigned { get; } = []; // Kill: The set of variables that are assigned a value. public HashSet UsedBeforeAssigned { get; } = []; // Gen: The set of variables that are used before any assignment. public HashSet ProcessedLocalFunctions { get; } = []; public HashSet Captured { get; } = []; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/LiveVariableAnalysis/RoslynLiveVariableAnalysis.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Syntax.Utilities; namespace SonarAnalyzer.CFG.LiveVariableAnalysis; public sealed class RoslynLiveVariableAnalysis : LiveVariableAnalysisBase { private readonly Dictionary> flowCaptures = []; private readonly Dictionary> blockPredecessors = []; private readonly Dictionary> blockSuccessors = []; private readonly SyntaxClassifierBase syntaxClassifier; internal ImmutableDictionary> BlockPredecessors => blockPredecessors.ToImmutableDictionary(); protected override BasicBlock ExitBlock => Cfg.ExitBlock; public RoslynLiveVariableAnalysis(ControlFlowGraph cfg, SyntaxClassifierBase syntaxClassifier, CancellationToken cancel) : base(cfg, OriginalDeclaration(cfg.OriginalOperation), cancel) { this.syntaxClassifier = syntaxClassifier; foreach (var ordinal in cfg.Blocks.Select(x => x.Ordinal)) { blockPredecessors.Add(ordinal, []); blockSuccessors.Add(ordinal, []); } foreach (var block in cfg.Blocks) { BuildBranches(block); } ResolveCaptures(cfg); Analyze(); } public IEnumerable ParameterOrLocalSymbols(IOperation operation) { var candidates = operation switch { _ when operation.AsParameterReference() is { } parameterReference => [parameterReference.Parameter], _ when operation.AsLocalReference() is { } localReference => [localReference.Local], _ when operation.AsFlowCaptureReference() is { } flowCaptureReference => flowCaptures.TryGetValue(flowCaptureReference.Id, out var symbols) ? symbols : [], _ => [] }; return candidates.Where(IsLocal); } public override bool IsLocal(ISymbol symbol) => originalDeclaration.Equals(symbol?.ContainingSymbol); protected override IEnumerable ReversedBlocks() => Cfg.Blocks.Reverse(); protected override IEnumerable Predecessors(BasicBlock block) => blockPredecessors[block.Ordinal]; protected override IEnumerable Successors(BasicBlock block) => blockSuccessors[block.Ordinal]; protected override State ProcessBlock(BasicBlock block) { var ret = new RoslynState(this); ret.ProcessBlock(Cfg, block); return ret; } private void ResolveCaptures(ControlFlowGraph cfg) => ResolveCaptures(cfg, []); private void ResolveCaptures(ControlFlowGraph cfg, HashSet processedLocalFunctions) { foreach (var operation in cfg.Blocks.SelectMany(x => x.OperationsAndBranchValue).ToExecutionOrder().Select(x => x.Instance)) { if (operation.AsFlowCapture() is { } flowCapture) { ProcessFlowCapture(flowCapture); } else if (operation.AsFlowAnonymousFunction() is { Symbol.IsStatic: false } flowAnonymousFunction) { ResolveCaptures(cfg.GetAnonymousFunctionControlFlowGraph(flowAnonymousFunction, Cancel), processedLocalFunctions); } else if (operation.AsInvocation() is { } invocation && HandleLocalFunction(processedLocalFunctions, invocation.TargetMethod.ConstructedFrom) is { } invocationLocalFunction) { ResolveCaptures(cfg.FindLocalFunctionCfgInScope(invocationLocalFunction, Cancel), processedLocalFunctions); } else if (operation.AsMethodReference() is { } reference && HandleLocalFunction(processedLocalFunctions, reference.Method.ConstructedFrom) is { } referenceLocalFunction) { ResolveCaptures(cfg.FindLocalFunctionCfgInScope(referenceLocalFunction, Cancel), processedLocalFunctions); } } } private void ProcessFlowCapture(IFlowCaptureOperationWrapper flowCapture) { if (flowCapture.Value.AsFlowCaptureReference() is { } captureReference && flowCaptures.TryGetValue(captureReference.Id, out var symbols)) { AppendFlowCaptureReference(flowCapture.Id, symbols); } else { AppendFlowCaptureReference(flowCapture.Id, ParameterOrLocalSymbols(flowCapture.Value)); } } private static IMethodSymbol HandleLocalFunction(ISet processed, IMethodSymbol method) { // We need ConstructedFrom because TargetMethod of a generic local function invocation is not the correct symbol (has IsDefinition=False and wrong ContainingSymbol) if (method.ConstructedFrom is { MethodKind: MethodKindEx.LocalFunction, IsStatic: false } localFunction && !processed.Contains(localFunction)) { processed.Add(localFunction); return localFunction; } else { return null; } } private void AppendFlowCaptureReference(CaptureId id, IEnumerable symbols) { if (!flowCaptures.TryGetValue(id, out var list)) { list = []; flowCaptures.Add(id, list); } list.AddRange(symbols); } private void BuildBranches(BasicBlock block) { foreach (var successor in block.Successors) { if (successor.Destination is not null) { // When exiting finally region, redirect to finally instead of the normal destination AddBranch(successor.Source, successor.FinallyRegions.Any() ? Cfg.Blocks[successor.FinallyRegions.First().FirstBlockOrdinal] : successor.Destination); } else if (successor.Source.EnclosingRegion is { Kind: ControlFlowRegionKind.Finally } finallyRegion) { BuildBranchesFinally(successor.Source, finallyRegion); } foreach (var catchOrFilterRegion in successor.EnteringRegions.Where(x => x.Kind == ControlFlowRegionKind.TryAndCatch).SelectMany(CatchOrFilterRegions)) { AddBranch(block, Cfg.Blocks[catchOrFilterRegion.FirstBlockOrdinal]); } } if (block.EnclosingNonLocalLifetimeRegion() is { Kind: ControlFlowRegionKind.Try } tryRegion) { var catchesAll = false; if (tryRegion.EnclosingRegion(ControlFlowRegionKind.TryAndCatch) is { } tryAndCatchRegion) { foreach (var catchOrFilterRegion in CatchOrFilterRegions(tryAndCatchRegion)) { AddBranch(block, Cfg.Blocks[catchOrFilterRegion.FirstBlockOrdinal]); catchesAll = catchesAll || (catchOrFilterRegion.Kind == ControlFlowRegionKind.Catch && IsCatchAllType(catchOrFilterRegion.ExceptionType)); } } if (!catchesAll && block.EnclosingRegion(ControlFlowRegionKind.TryAndFinally)?.NestedRegion(ControlFlowRegionKind.Finally) is { } finallyRegion) { var finallyBlock = Cfg.Blocks[finallyRegion.FirstBlockOrdinal]; AddBranch(block, finallyBlock); AddPredecessorsOutsideRegion(finallyBlock); } } if (block.IsEnclosedIn(ControlFlowRegionKind.Catch) || block.IsEnclosedIn(ControlFlowRegionKind.Filter)) { BuildBranchesCatch(block); } if (block.EnclosingNonLocalLifetimeRegion() is { Kind: ControlFlowRegionKind.Finally }) { BuildBranchesToOuterCatch(block, block.EnclosingNonLocalLifetimeRegion().EnclosingRegion); } void AddPredecessorsOutsideRegion(BasicBlock destination) { // We assume that current block can throw in its first operation. Therefore predecessors outside this tryRegion need to be redirected to catch/filter/finally foreach (var predecessor in block.Predecessors.Where(x => x.Source.Ordinal < tryRegion.FirstBlockOrdinal || x.Source.Ordinal > tryRegion.LastBlockOrdinal)) { AddBranch(predecessor.Source, destination); } } } private void BuildBranchesCatch(BasicBlock source) { if (source.Successors.Any(x => x.Semantics is ControlFlowBranchSemantics.Rethrow or ControlFlowBranchSemantics.Throw)) { BuildBranchesRethrow(source); } else if (source.EnclosingRegion(ControlFlowRegionKind.TryAndCatch) is { } innerTryCatch) { BuildBranchesToOuterCatch(source, innerTryCatch); } } private void BuildBranchesToOuterCatch(BasicBlock source, ControlFlowRegion region) { if (region.EnclosingRegion(ControlFlowRegionKind.Try) is { } outerTry && outerTry.EnclosingRegion(ControlFlowRegionKind.TryAndCatch) is { } outerTryCatch) { foreach (var outerCatch in CatchOrFilterRegions(outerTryCatch)) { AddBranch(source, Cfg.Blocks[outerCatch.FirstBlockOrdinal]); } } } private void BuildBranchesFinally(BasicBlock source, ControlFlowRegion finallyRegion) { foreach (var trySuccessor in TryRegionSuccessors(source.EnclosingRegion)) { // Redirect exit from finally to the next block var destination = trySuccessor.FinallyRegions.SkipWhile(x => x != finallyRegion).Skip(1).FirstOrDefault() is { } nextOuterFinally ? Cfg.Blocks[nextOuterFinally.FirstBlockOrdinal] // Outer finally that directly follows this finally : trySuccessor.Destination; // Normal block directly after this finally AddBranch(source, destination); } } private void BuildBranchesRethrow(BasicBlock block) { var currentTryCatchRegion = block.EnclosingRegion(ControlFlowRegionKind.TryAndCatch); var reachableHandlerRegions = currentTryCatchRegion.NestedRegion(ControlFlowRegionKind.Try).ReachableHandlers(); var reachableCatchAndFinallyBlocks = reachableHandlerRegions.Where(x => x.FirstBlockOrdinal > currentTryCatchRegion.LastBlockOrdinal).SelectMany(x => x.Blocks(Cfg)); // On the use of `EnclosingRegion` below: Other than a finally region, a `Catch` region also acts as a `LocalLifetime` region. // Therefore blocks in a `catch` always have the `Catch` region as their direct parent and thus checking `EnclosingRegion` instead of `IsEnclosedIn` is sufficient. foreach (var catchBlock in reachableCatchAndFinallyBlocks.Where(x => x.EnclosingRegion.Kind is ControlFlowRegionKind.Catch)) { AddBranch(block, catchBlock); } if (reachableCatchAndFinallyBlocks.FirstOrDefault(x => x.IsEnclosedIn(ControlFlowRegionKind.Finally)) is { } finallyBlock) { AddBranch(block, finallyBlock); } } private void AddBranch(BasicBlock source, BasicBlock destination) { blockSuccessors[source.Ordinal].Add(destination); blockPredecessors[destination.Ordinal].Add(source); } private IEnumerable TryRegionSuccessors(ControlFlowRegion finallyRegion) { var tryRegion = finallyRegion.EnclosingRegion.NestedRegion(ControlFlowRegionKind.Try); return tryRegion.Blocks(Cfg).SelectMany(x => x.Successors).Where(x => x.FinallyRegions.Contains(finallyRegion)); } private static IEnumerable CatchOrFilterRegions(ControlFlowRegion tryAndCatchRegion) { foreach (var region in tryAndCatchRegion.NestedRegions) { if (region.Kind == ControlFlowRegionKind.Catch) { yield return region; } else if (region.Kind == ControlFlowRegionKind.FilterAndHandler) { yield return region.NestedRegions.Single(x => x.Kind == ControlFlowRegionKind.Filter); } } } private static bool IsCatchAllType(ITypeSymbol exceptionType) => exceptionType.SpecialType == SpecialType.System_Object // catch { ... } || exceptionType is { ContainingNamespace: { Name: nameof(System), ContainingNamespace.IsGlobalNamespace: true }, Name: nameof(Exception) }; // catch(Exception) { ... } private static ISymbol OriginalDeclaration(IOperation originalOperation) { if (originalOperation.IsAnyKind(OperationKindEx.MethodBody, OperationKindEx.Block, OperationKindEx.ConstructorBody)) { var syntax = originalOperation.Syntax.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.ArrowExpressionClause) ? originalOperation.Syntax.Parent : originalOperation.Syntax; return originalOperation.ToSonar().SemanticModel.GetDeclaredSymbol(syntax); } else { return originalOperation switch { var _ when originalOperation.AsAnonymousFunction() is { } anonymousFunction => anonymousFunction.Symbol, var _ when originalOperation.AsLocalFunction() is { } localFunction => localFunction.Symbol, _ => throw new NotSupportedException($"Operations of kind: {originalOperation.Kind} are not supported.") }; } } private sealed class RoslynState : State { private readonly RoslynLiveVariableAnalysis owner; private readonly ISet capturedLocalFunctions = new HashSet(); public RoslynState(RoslynLiveVariableAnalysis owner) => this.owner = owner; public void ProcessBlock(ControlFlowGraph cfg, BasicBlock block) { foreach (var operation in block.OperationsAndBranchValue.ToReversedExecutionOrder().Select(x => x.Instance)) { ProcessOperation(cfg, operation); } } private void ProcessOperation(ControlFlowGraph cfg, IOperation operation) { // Everything that is added here needs to be considered inside ProcessCaptured as well if (operation.AsLocalReference() is { } localReference) { ProcessParameterOrLocalReference(localReference); } else if (operation.AsParameterReference() is { } parameterReference) { ProcessParameterOrLocalReference(parameterReference); } else if (operation.AsFlowCaptureReference() is { } flowCaptureReference) { ProcessParameterOrLocalReference(flowCaptureReference); } else if (operation.AsSimpleAssignment() is { } assignment) { ProcessSimpleAssignment(assignment); } else if (operation.AsFlowAnonymousFunction() is { } flowAnonymousFunction) { ProcessFlowAnonymousFunction(cfg, flowAnonymousFunction); } else if (operation.AsInvocation() is { } invocation) { ProcessLocalFunction(cfg, invocation.TargetMethod); } else if (operation.AsMethodReference() is { } methodReference) { ProcessLocalFunction(cfg, methodReference.Method); // For .Select(variable.MethodReference), there's no LocalReferenceOperation in the CFG for variable, so we handle it from the syntax if (owner.syntaxClassifier.MemberAccessExpression(operation.Syntax) is { } expression) { var symbol = owner.Cfg.OriginalOperation.ToSonar().SemanticModel.GetSymbolInfo(expression).Symbol; if (symbol is ILocalSymbol or IParameterSymbol && owner.IsLocal(symbol)) { ProcessParameterOrLocalSymbols([symbol], symbol is IParameterSymbol { RefKind: RefKind.Out }, false); } } } } private void ProcessParameterOrLocalReference(IOperationWrapper reference) => ProcessParameterOrLocalSymbols( owner.ParameterOrLocalSymbols(reference.WrappedOperation), reference.IsOutArgument(), reference.IsAssignmentTarget() || reference.ToSonar().Parent?.Kind == OperationKindEx.FlowCapture); private void ProcessParameterOrLocalSymbols(IEnumerable symbols, bool isOutArgument, bool isAssignmentTarget) { if (isOutArgument) { Assigned.UnionWith(symbols); UsedBeforeAssigned.ExceptWith(symbols); } else if (!isAssignmentTarget) { UsedBeforeAssigned.UnionWith(symbols); } } private void ProcessSimpleAssignment(ISimpleAssignmentOperationWrapper assignment) { var targets = owner.ParameterOrLocalSymbols(assignment.Target); Assigned.UnionWith(targets); UsedBeforeAssigned.ExceptWith(targets); } private void ProcessFlowAnonymousFunction(ControlFlowGraph cfg, IFlowAnonymousFunctionOperationWrapper anonymousFunction) { if (!anonymousFunction.Symbol.IsStatic) // Performance: No need to descent into static { ProcessCaptured(cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunction, owner.Cancel)); } } private void ProcessCaptured(ControlFlowGraph cfg) { foreach (var operation in cfg.Blocks.SelectMany(x => x.OperationsAndBranchValue).SelectMany(x => x.DescendantsAndSelf())) { var symbols = owner.ParameterOrLocalSymbols(operation); if (symbols.Any()) { Captured.UnionWith(symbols); } else if (operation.AsFlowAnonymousFunction() is { } flowAnonymousFunction) { ProcessFlowAnonymousFunction(cfg, flowAnonymousFunction); } else if (operation.AsInvocation() is { } invocation) { ProcessCapturedLocalFunction(cfg, invocation.TargetMethod); } else if (operation.AsMethodReference() is { } methodReference) { ProcessCapturedLocalFunction(cfg, methodReference.Method); } } } private void ProcessCapturedLocalFunction(ControlFlowGraph cfg, IMethodSymbol method) { if (HandleLocalFunction(capturedLocalFunctions, method) is { } localFunction) { ProcessCaptured(cfg.FindLocalFunctionCfgInScope(localFunction, owner.Cancel)); } } private void ProcessLocalFunction(ControlFlowGraph cfg, IMethodSymbol method) { if (HandleLocalFunction(ProcessedLocalFunctions, method) is { } localFunction) { var localFunctionCfg = cfg.FindLocalFunctionCfgInScope(localFunction, owner.Cancel); foreach (var block in localFunctionCfg.Blocks.Reverse()) // Simplified approach, ignoring branching and try/catch/finally flows { ProcessBlock(localFunctionCfg, block); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Operations/Utilities/OperationExecutionOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; namespace SonarAnalyzer.CFG.Operations.Utilities; public class OperationExecutionOrder : IEnumerable { private readonly IEnumerable operations; private readonly bool reverseOrder; public OperationExecutionOrder(IEnumerable operations, bool reverseOrder) { this.operations = operations; this.reverseOrder = reverseOrder; } public IEnumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private sealed class Enumerator : IEnumerator { private readonly OperationExecutionOrder owner; private readonly Stack stack = new Stack(); public IOperationWrapperSonar Current { get; private set; } object IEnumerator.Current => Current; public Enumerator(OperationExecutionOrder owner) { this.owner = owner; Init(); } public bool MoveNext() { while (stack.Any()) { if (owner.reverseOrder) { var current = stack.Pop(); while (current.NextChild() is { } child) { stack.Push(new StackItem(child)); } Current = current.DisposeEnumeratorAndReturnOperation(); return true; } else if (stack.Peek().NextChild() is { } child) { stack.Push(new StackItem(child)); } else { Current = stack.Pop().DisposeEnumeratorAndReturnOperation(); return true; } } Current = default; return false; } public void Reset() { Dispose(); Init(); } public void Dispose() { while (stack.Any()) { stack.Pop().Dispose(); } } private void Init() { // We need to push them to the stack in reversed order compared to reverseOrder argument foreach (var operation in owner.reverseOrder ? owner.operations : owner.operations.Reverse()) { stack.Push(new StackItem(operation)); } } } private sealed class StackItem : IDisposable { private readonly IOperationWrapperSonar operation; private readonly IEnumerator children; public StackItem(IOperation operation) { this.operation = operation.ToSonar(); children = this.operation.Children.GetEnumerator(); } public IOperation NextChild() => children.MoveNext() ? children.Current : null; public IOperationWrapperSonar DisposeEnumeratorAndReturnOperation() { Dispose(); return operation; } public void Dispose() => children.Dispose(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Operations/Utilities/OperationFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CFG.Operations.Utilities; public abstract class OperationFinder { protected abstract bool TryFindOperation(IOperationWrapperSonar operation, out TResult result); public bool TryFind(BasicBlock block, out TResult result) => TryFind(block.OperationsAndBranchValue, out result); protected bool TryFind(IEnumerable operations, out TResult result) { var queue = new Queue(); foreach (var operation in operations) { queue.Enqueue(operation); while (queue.Any()) { var wrapper = queue.Dequeue().ToSonar(); if (TryFindOperation(wrapper, out result)) { return true; } foreach (var child in wrapper.Children) { queue.Enqueue(child); } } } result = default; return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Enterprise.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/BasicBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using System.Runtime.CompilerServices; namespace SonarAnalyzer.CFG.Roslyn; public class BasicBlock { private static readonly ConditionalWeakTable InstanceCache = new(); private static readonly PropertyInfo BranchValueProperty; private static readonly PropertyInfo ConditionalSuccessorProperty; private static readonly PropertyInfo ConditionKindProperty; private static readonly PropertyInfo EnclosingRegionProperty; private static readonly PropertyInfo FallThroughSuccessorProperty; private static readonly PropertyInfo IsReachableProperty; private static readonly PropertyInfo KindProperty; private static readonly PropertyInfo OperationsProperty; private static readonly PropertyInfo OrdinalProperty; private static readonly PropertyInfo PredecessorsProperty; private readonly object instance; private readonly Lazy> successors; private readonly Lazy> successorBlocks; private readonly Lazy> operationsAndBranchValue; private IOperation branchValue; private ControlFlowBranch conditionalSuccessor; private ControlFlowConditionKind? conditionKind; private ControlFlowRegion enclosingRegion; private ControlFlowBranch fallThroughSuccessor; private bool? isReachable; private BasicBlockKind? kind; private ImmutableArray operations; private int? ordinal; private ImmutableArray predecessors; public IOperation BranchValue => BranchValueProperty.ReadCached(instance, ref branchValue); public ControlFlowBranch ConditionalSuccessor => ConditionalSuccessorProperty.ReadCached(instance, ControlFlowBranch.Wrap, ref conditionalSuccessor); public ControlFlowConditionKind ConditionKind => ConditionKindProperty.ReadCached(instance, ref conditionKind); public ControlFlowRegion EnclosingRegion => EnclosingRegionProperty.ReadCached(instance, ControlFlowRegion.Wrap, ref enclosingRegion); public ControlFlowBranch FallThroughSuccessor => FallThroughSuccessorProperty.ReadCached(instance, ControlFlowBranch.Wrap, ref fallThroughSuccessor); public bool IsReachable => IsReachableProperty.ReadCached(instance, ref isReachable); public BasicBlockKind Kind => KindProperty.ReadCached(instance, ref kind); public ImmutableArray Operations => OperationsProperty.ReadCached(instance, ref operations); public int Ordinal => OrdinalProperty.ReadCached(instance, ref ordinal); public ImmutableArray Predecessors => PredecessorsProperty.ReadCached(instance, ControlFlowBranch.Wrap, ref predecessors); public ImmutableArray Successors => successors.Value; public ImmutableArray SuccessorBlocks => successorBlocks.Value; public ImmutableArray OperationsAndBranchValue => operationsAndBranchValue.Value; static BasicBlock() { if (TypeLoader.FlowAnalysisType("BasicBlock") is { } type) { BranchValueProperty = type.GetProperty(nameof(BranchValue)); ConditionalSuccessorProperty = type.GetProperty(nameof(ConditionalSuccessor)); ConditionKindProperty = type.GetProperty(nameof(ConditionKind)); EnclosingRegionProperty = type.GetProperty(nameof(EnclosingRegion)); FallThroughSuccessorProperty = type.GetProperty(nameof(FallThroughSuccessor)); IsReachableProperty = type.GetProperty(nameof(IsReachable)); KindProperty = type.GetProperty(nameof(Kind)); OperationsProperty = type.GetProperty(nameof(Operations)); OrdinalProperty = type.GetProperty(nameof(Ordinal)); PredecessorsProperty = type.GetProperty(nameof(Predecessors)); } } private BasicBlock(object instance) { this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); successors = new Lazy>(() => { // since Roslyn does not differentiate between pattern types in CFG, it builds unreachable block for missing // pattern match even when discard pattern option is presented. In this case we explicitly exclude this branch if (SwitchExpressionArmSyntaxWrapper.IsInstance(BranchValue?.Syntax) && DiscardPatternSyntaxWrapper.IsInstance(((SwitchExpressionArmSyntaxWrapper)BranchValue.Syntax).Pattern)) { return FallThroughSuccessor is null ? ImmutableArray.Empty : ImmutableArray.Create(FallThroughSuccessor); } else { return new[] { FallThroughSuccessor, ConditionalSuccessor }.Where(x => x != null).ToImmutableArray(); } }); successorBlocks = new Lazy>(() => Successors.Select(x => x.Destination).Where(x => x != null).ToImmutableArray()); operationsAndBranchValue = new Lazy>(() => BranchValue is null ? Operations : Operations.Add(BranchValue)); } public static BasicBlock Wrap(object instance) => instance == null ? null : InstanceCache.GetValue(instance, x => new BasicBlock(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/CfgAllPathValidator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Roslyn; public abstract class CfgAllPathValidator { private readonly ControlFlowGraph cfg; protected abstract bool IsValid(BasicBlock block); protected abstract bool IsInvalid(BasicBlock block); protected CfgAllPathValidator(ControlFlowGraph cfg) => this.cfg = cfg; public bool CheckAllPaths() { HashSet visited = []; var blocks = new Stack(); blocks.Push(cfg.EntryBlock); while (blocks.Count > 0) { var block = blocks.Pop(); if (!visited.Add(block)) { continue; // We already visited this block. (This protects from endless loops) } if (block == cfg.ExitBlock || IsInvalid(block)) { return false; } if (IsValid(block)) { continue; } if (block.SuccessorBlocks.IsEmpty) { return false; } foreach (var successorBlock in block.SuccessorBlocks) { blocks.Push(successorBlock); } } return true; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/ControlFlowBranch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using System.Runtime.CompilerServices; namespace SonarAnalyzer.CFG.Roslyn; public class ControlFlowBranch { private static readonly ConditionalWeakTable InstanceCache = new(); private static readonly PropertyInfo SourceProperty; private static readonly PropertyInfo DestinationProperty; private static readonly PropertyInfo SemanticsProperty; private static readonly PropertyInfo IsConditionalSuccessorProperty; private static readonly PropertyInfo EnteringRegionsProperty; private static readonly PropertyInfo LeavingRegionsProperty; private static readonly PropertyInfo FinallyRegionsProperty; private readonly object instance; private BasicBlock source; private BasicBlock destination; private ControlFlowBranchSemantics? semantics; private bool? isConditionalSuccessor; private ImmutableArray enteringRegions; private ImmutableArray leavingRegions; private ImmutableArray finallyRegions; public BasicBlock Source => SourceProperty.ReadCached(instance, BasicBlock.Wrap, ref source); public BasicBlock Destination => DestinationProperty.ReadCached(instance, BasicBlock.Wrap, ref destination); public ControlFlowBranchSemantics Semantics => SemanticsProperty.ReadCached(instance, ref semantics); public bool IsConditionalSuccessor => IsConditionalSuccessorProperty.ReadCached(instance, ref isConditionalSuccessor); public ImmutableArray EnteringRegions => EnteringRegionsProperty.ReadCached(instance, ControlFlowRegion.Wrap, ref enteringRegions); public ImmutableArray LeavingRegions => LeavingRegionsProperty.ReadCached(instance, ControlFlowRegion.Wrap, ref leavingRegions); public ImmutableArray FinallyRegions => FinallyRegionsProperty.ReadCached(instance, ControlFlowRegion.Wrap, ref finallyRegions); static ControlFlowBranch() { if (TypeLoader.FlowAnalysisType("ControlFlowBranch") is { } type) { SourceProperty = type.GetProperty(nameof(Source)); DestinationProperty = type.GetProperty(nameof(Destination)); SemanticsProperty = type.GetProperty(nameof(Semantics)); IsConditionalSuccessorProperty = type.GetProperty(nameof(IsConditionalSuccessor)); EnteringRegionsProperty = type.GetProperty(nameof(EnteringRegions)); LeavingRegionsProperty = type.GetProperty(nameof(LeavingRegions)); FinallyRegionsProperty = type.GetProperty(nameof(FinallyRegions)); } } private ControlFlowBranch(object instance) => this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); public static ControlFlowBranch Wrap(object instance) => instance == null ? null : InstanceCache.GetValue(instance, x => new ControlFlowBranch(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/ControlFlowGraph.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using System.Runtime.CompilerServices; using SonarAnalyzer.CFG.Common; namespace SonarAnalyzer.CFG.Roslyn; public class ControlFlowGraph { private static readonly ConditionalWeakTable InstanceCache = new(); private static readonly PropertyInfo BlocksProperty; private static readonly PropertyInfo LocalFunctionsProperty; private static readonly PropertyInfo OriginalOperationProperty; private static readonly PropertyInfo ParentProperty; private static readonly PropertyInfo RootProperty; private static readonly MethodInfo CreateMethod; private static readonly MethodInfo GetAnonymousFunctionControlFlowGraphMethod; private static readonly MethodInfo GetLocalFunctionControlFlowGraphMethod; private readonly object instance; private ImmutableArray blocks; private ImmutableArray localFunctions; private ControlFlowGraph parent; private IOperation originalOperation; private ControlFlowRegion root; public static bool IsAvailable { get; } public ImmutableArray Blocks => BlocksProperty.ReadCached(instance, BasicBlock.Wrap, ref blocks); public ImmutableArray LocalFunctions => LocalFunctionsProperty.ReadCached(instance, ref localFunctions); public IOperation OriginalOperation => OriginalOperationProperty.ReadCached(instance, ref originalOperation); public ControlFlowGraph Parent => ParentProperty.ReadCached(instance, Wrap, ref parent); public ControlFlowRegion Root => RootProperty.ReadCached(instance, ControlFlowRegion.Wrap, ref root); public BasicBlock EntryBlock => Blocks[Root.FirstBlockOrdinal]; public BasicBlock ExitBlock => Blocks[Root.LastBlockOrdinal]; static ControlFlowGraph() { if (RoslynVersion.IsRoslynCfgSupported()) { IsAvailable = true; var type = TypeLoader.FlowAnalysisType("ControlFlowGraph"); BlocksProperty = type.GetProperty(nameof(Blocks)); LocalFunctionsProperty = type.GetProperty(nameof(LocalFunctions)); OriginalOperationProperty = type.GetProperty(nameof(OriginalOperation)); ParentProperty = type.GetProperty(nameof(Parent)); RootProperty = type.GetProperty(nameof(Root)); CreateMethod = type.GetMethod(nameof(Create), new[] { typeof(SyntaxNode), typeof(SemanticModel), typeof(CancellationToken) }); GetAnonymousFunctionControlFlowGraphMethod = type.GetMethod(nameof(GetAnonymousFunctionControlFlowGraph)); GetLocalFunctionControlFlowGraphMethod = type.GetMethod(nameof(GetLocalFunctionControlFlowGraph)); } } private ControlFlowGraph(object instance) { this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); Debug.Assert(EntryBlock.Kind == BasicBlockKind.Entry, "Roslyn CFG Entry block is not the first one"); Debug.Assert(ExitBlock.Kind == BasicBlockKind.Exit, "Roslyn CFG Exit block is not the last one"); } public static ControlFlowGraph Create(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancel) => IsAvailable ? Wrap(CreateMethod.Invoke(null, new object[] { node, semanticModel, cancel })) : throw new InvalidOperationException("CFG is not available under this version of Roslyn compiler."); public ControlFlowGraph GetAnonymousFunctionControlFlowGraph(IFlowAnonymousFunctionOperationWrapper anonymousFunction, CancellationToken cancel) => GetAnonymousFunctionControlFlowGraphMethod.Invoke(instance, new object[] { anonymousFunction.WrappedOperation, cancel }) is { } anonymousFunctionCfg ? Wrap(anonymousFunctionCfg) : null; public ControlFlowGraph GetLocalFunctionControlFlowGraph(IMethodSymbol localFunction, CancellationToken cancel) => GetLocalFunctionControlFlowGraphMethod.Invoke(instance, new object[] { localFunction, cancel }) is { } localFunctionCfg ? Wrap(localFunctionCfg) : null; public static ControlFlowGraph Wrap(object instance) => instance == null ? null : InstanceCache.GetValue(instance, x => new ControlFlowGraph(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/ControlFlowGraphCache.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Roslyn; public abstract class ControlFlowGraphCacheBase { // We need to cache per compilation to avoid reusing CFGs when compilation object is altered by VS configuration changes private readonly ConditionalWeakTable> compilationCache = new(); protected abstract bool HasNestedCfg(SyntaxNode node); protected abstract bool IsLocalFunction(SyntaxNode node); public ControlFlowGraph FindOrCreate(SyntaxNode declaration, SemanticModel model, CancellationToken cancel) { var cfgInputNode = declaration switch { IndexerDeclarationSyntax indexer => indexer.ExpressionBody, PropertyDeclarationSyntax property => property.ExpressionBody, _ => declaration }; if (cfgInputNode is not null && model.GetOperation(cfgInputNode) is { } declarationOperation) { var rootSyntax = declarationOperation.RootOperation().Syntax; var nodeCache = compilationCache.GetValue(model.Compilation, x => new()); if (!nodeCache.TryGetValue(rootSyntax, out var wrapper)) { wrapper = new(ControlFlowGraph.Create(rootSyntax, model, cancel)); nodeCache[rootSyntax] = wrapper; } if (HasNestedCfg(cfgInputNode)) { // We need to go up and track all possible enclosing lambdas, local functions and other FlowAnonymousFunctionOperations foreach (var node in cfgInputNode.AncestorsAndSelf().TakeWhile(x => x != rootSyntax).Reverse()) { if (IsLocalFunction(node)) { wrapper = new(wrapper.Cfg.GetLocalFunctionControlFlowGraph(node, cancel)); } else if (wrapper.FlowOperation(node) is { WrappedOperation: not null } flowOperation) { wrapper = new(wrapper.Cfg.GetAnonymousFunctionControlFlowGraph(flowOperation, cancel)); } else if (node == cfgInputNode) { return null; // Lambda syntax is not always recognized as a FlowOperation for invalid syntaxes } } } return wrapper.Cfg; } else { return null; } } private sealed class Wrapper { private IFlowAnonymousFunctionOperationWrapper[] flowOperations; public ControlFlowGraph Cfg { get; } public Wrapper(ControlFlowGraph cfg) => Cfg = cfg; public IFlowAnonymousFunctionOperationWrapper FlowOperation(SyntaxNode node) { flowOperations ??= Cfg.FlowAnonymousFunctionOperations().ToArray(); // Avoid recomputing, it's expensive and called many times for a single CFG return flowOperations.SingleOrDefault(x => x.WrappedOperation.Syntax == node); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/ControlFlowRegion.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using System.Runtime.CompilerServices; namespace SonarAnalyzer.CFG.Roslyn; public class ControlFlowRegion { private static readonly ConditionalWeakTable InstanceCache = new(); private static readonly PropertyInfo KindProperty; private static readonly PropertyInfo EnclosingRegionProperty; private static readonly PropertyInfo ExceptionTypeProperty; private static readonly PropertyInfo FirstBlockOrdinalProperty; private static readonly PropertyInfo LastBlockOrdinalProperty; private static readonly PropertyInfo NestedRegionsProperty; private static readonly PropertyInfo LocalsProperty; private static readonly PropertyInfo LocalFunctionsProperty; private static readonly PropertyInfo CaptureIdsProperty; private readonly object instance; private ControlFlowRegionKind? kind; private ControlFlowRegion enclosingRegion; private ITypeSymbol exceptionType; private int? firstBlockOrdinal; private int? lastBlockOrdinal; private ImmutableArray nestedRegions; private ImmutableArray locals; private ImmutableArray localFunctions; private ImmutableArray captureIds; public ControlFlowRegionKind Kind => KindProperty.ReadCached(instance, ref kind); public ControlFlowRegion EnclosingRegion => EnclosingRegionProperty.ReadCached(instance, Wrap, ref enclosingRegion); public ITypeSymbol ExceptionType => ExceptionTypeProperty.ReadCached(instance, ref exceptionType); public int FirstBlockOrdinal => FirstBlockOrdinalProperty.ReadCached(instance, ref firstBlockOrdinal); public int LastBlockOrdinal => LastBlockOrdinalProperty.ReadCached(instance, ref lastBlockOrdinal); public ImmutableArray NestedRegions => NestedRegionsProperty.ReadCached(instance, Wrap, ref nestedRegions); public ImmutableArray Locals => LocalsProperty.ReadCached(instance, ref locals); public ImmutableArray LocalFunctions => LocalFunctionsProperty.ReadCached(instance, ref localFunctions); public ImmutableArray CaptureIds => CaptureIdsProperty.ReadCached(instance, x => new CaptureId(x), ref captureIds); static ControlFlowRegion() { if (TypeLoader.FlowAnalysisType("ControlFlowRegion") is { } type) { KindProperty = type.GetProperty(nameof(Kind)); EnclosingRegionProperty = type.GetProperty(nameof(EnclosingRegion)); ExceptionTypeProperty = type.GetProperty(nameof(ExceptionType)); FirstBlockOrdinalProperty = type.GetProperty(nameof(FirstBlockOrdinal)); LastBlockOrdinalProperty = type.GetProperty(nameof(LastBlockOrdinal)); NestedRegionsProperty = type.GetProperty(nameof(NestedRegions)); LocalsProperty = type.GetProperty(nameof(Locals)); LocalFunctionsProperty = type.GetProperty(nameof(LocalFunctions)); CaptureIdsProperty = type.GetProperty(nameof(CaptureIds)); } } private ControlFlowRegion(object instance) => this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); public static ControlFlowRegion Wrap(object instance) => instance == null ? null : InstanceCache.GetValue(instance, x => new ControlFlowRegion(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Roslyn/TypeLoader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Roslyn; internal static class TypeLoader { public static Type FlowAnalysisType(string typeName) => typeof(SemanticModel).Assembly.GetType("Microsoft.CodeAnalysis.FlowAnalysis." + typeName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/AbstractControlFlowGraphBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public abstract class AbstractControlFlowGraphBuilder { private class ControlFlowGraph : IControlFlowGraph { private static readonly ISet RemovableBlockTypes = new HashSet { typeof(SimpleBlock), typeof(TemporaryBlock) }; public ControlFlowGraph(List reversedBlocks, Block entryBlock, ExitBlock exitBlock) { ExitBlock = exitBlock; EntryBlock = RemoveEmptyBlocks(reversedBlocks, entryBlock); Blocks = reversedBlocks.Reverse().ToImmutableArray(); if (Blocks.OfType().Any()) { throw new InvalidOperationException("Could not construct valid control flow graph"); } ComputePredecessors(); } public IEnumerable Blocks { get; private set; } public Block EntryBlock { get; private set; } public ExitBlock ExitBlock { get; private set; } private static Block RemoveEmptyBlocks(List reversedBlocks, Block entryBlock) { var emptyBlockReplacements = new Dictionary(); var emptyBlocks = reversedBlocks.Where(b => RemovableBlockTypes.Contains(b.GetType()) && !b.ReversedInstructions.Any()); foreach (var block in emptyBlocks) { var replacementBlock = block.GetPossibleNonEmptySuccessorBlock(); if (replacementBlock != block) { emptyBlockReplacements.Add(block, replacementBlock); } } // Remove empty blocks reversedBlocks.RemoveAll(b => emptyBlockReplacements.Keys.Contains(b)); // Replace successors foreach (var block in reversedBlocks) { block.ReplaceSuccessors(emptyBlockReplacements); } // Fix entry block var newEntryBlock = entryBlock; if (emptyBlockReplacements.ContainsKey(entryBlock)) { newEntryBlock = emptyBlockReplacements[entryBlock]; } return newEntryBlock; } private void ComputePredecessors() { foreach (var block in Blocks) { foreach (var successor in block.SuccessorBlocks) { successor.EditablePredecessorBlocks.Add(block); } } } } protected readonly SyntaxNode rootNode; protected readonly SemanticModel semanticModel; protected readonly List reversedBlocks = new List(); protected readonly Stack exitTarget = new Stack(); protected AbstractControlFlowGraphBuilder(SyntaxNode node, SemanticModel semanticModel) { this.rootNode = node ?? throw new ArgumentNullException(nameof(node)); this.semanticModel = semanticModel ?? throw new ArgumentNullException(nameof(semanticModel)); this.exitTarget.Push(CreateExitBlock()); } protected abstract void PostProcessGraph(); public IControlFlowGraph Build() { var entryBlock = Build(this.rootNode, CreateBlock(this.exitTarget.Peek())); PostProcessGraph(); return new ControlFlowGraph(this.reversedBlocks, entryBlock, (ExitBlock)this.exitTarget.Pop()); } protected abstract Block Build(SyntaxNode node, Block currentBlock); #region CreateBlock* internal BinaryBranchBlock CreateBinaryBranchBlock(SyntaxNode branchingNode, Block trueSuccessor, Block falseSuccessor) => AddBlock(new BinaryBranchBlock(branchingNode, trueSuccessor, falseSuccessor)); internal SimpleBlock CreateBlock(Block successor) => AddBlock(new SimpleBlock(successor)); internal JumpBlock CreateJumpBlock(SyntaxNode jumpStatement, Block successor, Block wouldBeSuccessor = null) => AddBlock(new JumpBlock(jumpStatement, successor, wouldBeSuccessor)); internal BranchBlock CreateBranchBlock(SyntaxNode branchingNode, IEnumerable successors) => AddBlock(new BranchBlock(branchingNode, successors.ToArray())); private ExitBlock CreateExitBlock() => AddBlock(new ExitBlock()); internal TemporaryBlock CreateTemporaryBlock() => AddBlock(new TemporaryBlock()); internal T AddBlock(T block) where T : Block { this.reversedBlocks.Add(block); return block; } #endregion CreateBlock* } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/BlockIdProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public class BlockIdProvider { private readonly Dictionary map = new Dictionary(); private int counter; public string Get(Block cfgBlock) => this.map.GetOrAdd(cfgBlock, b => $"{this.counter++}"); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/BinaryBranchBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public class BinaryBranchBlock : BranchBlock { internal BinaryBranchBlock(SyntaxNode branchingNode, Block trueSuccessor, Block falseSuccessor) : base(branchingNode, trueSuccessor, falseSuccessor) { if (trueSuccessor == null) { throw new ArgumentNullException(nameof(trueSuccessor)); } if (falseSuccessor == null) { throw new ArgumentNullException(nameof(falseSuccessor)); } } public Block TrueSuccessorBlock => this.successors[0]; public Block FalseSuccessorBlock => this.successors[1]; public SyntaxNode Parent => BranchingNode.Parent; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/BinaryBranchingSimpleBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public sealed class BinaryBranchingSimpleBlock : SimpleBlock { internal BinaryBranchingSimpleBlock(SyntaxNode branchingInstruction, Block trueAndFalseSuccessor) : base(trueAndFalseSuccessor) { BranchingInstruction = branchingInstruction; } public SyntaxNode BranchingInstruction { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/Block.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { /// /// Basic building blocks of a Control Flow Graph (). /// Holds a list of instructions which have no jumps between them. /// public class Block { private readonly Lazy> instructions; private readonly Lazy> predecessorBlocks; private readonly Lazy> allSuccessors; private readonly Lazy> allPredecessors; // Protected to allow extending and mocking protected Block() { this.instructions = new Lazy>(() => ReversedInstructions.Reverse().ToImmutableArray()); this.predecessorBlocks = new Lazy>(() => EditablePredecessorBlocks.ToImmutableHashSet()); this.allSuccessors = new Lazy>(() => GetAll(this, b => b.SuccessorBlocks)); this.allPredecessors = new Lazy>(() => GetAll(this, b => b.PredecessorBlocks)); } public virtual IReadOnlyList Instructions => this.instructions.Value; public virtual IReadOnlyCollection PredecessorBlocks => this.predecessorBlocks.Value; public virtual IReadOnlyList SuccessorBlocks { get; } = ImmutableArray.Create(); internal IList ReversedInstructions { get; } = new List(); internal ISet EditablePredecessorBlocks { get; } = new HashSet(); internal virtual Block GetPossibleNonEmptySuccessorBlock() { return this; } internal virtual void ReplaceSuccessors(Dictionary replacementMapping) { } public ISet AllSuccessorBlocks => this.allSuccessors.Value; public ISet AllPredecessorBlocks => this.allPredecessors.Value; private static ISet GetAll(Block initial, Func> getNexts) { var toProcess = new Queue(); var alreadyProcesses = new HashSet(); getNexts(initial).ToList().ForEach(b => toProcess.Enqueue(b)); while (toProcess.Count != 0) { var current = toProcess.Dequeue(); if (alreadyProcesses.Contains(current)) { continue; } alreadyProcesses.Add(current); getNexts(current).ToList().ForEach(b => toProcess.Enqueue(b)); } return alreadyProcesses.ToHashSet(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/BranchBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public class BranchBlock : Block { internal BranchBlock(SyntaxNode branchingNode, params Block[] successors) { this.successors = successors ?? throw new ArgumentNullException(nameof(successors)); BranchingNode = branchingNode ?? throw new ArgumentNullException(nameof(branchingNode)); } public SyntaxNode BranchingNode { get; } protected readonly Block[] successors; public override IReadOnlyList SuccessorBlocks => ImmutableArray.Create(this.successors); internal override void ReplaceSuccessors(Dictionary replacementMapping) { for (var i = 0; i < this.successors.Length; i++) { if (replacementMapping.ContainsKey(this.successors[i])) { this.successors[i] = replacementMapping[this.successors[i]]; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/ExitBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public sealed class ExitBlock : Block { internal ExitBlock() { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/ForInitializerBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public sealed class ForInitializerBlock : SimpleBlock { internal ForInitializerBlock(ForStatementSyntax forNode, Block successor) : base(successor) { ForNode = forNode ?? throw new ArgumentNullException(nameof(forNode)); } public ForStatementSyntax ForNode { get; } internal override Block GetPossibleNonEmptySuccessorBlock() { // This block can't be removed by the CFG simplification, unlike the base class SimpleBlock return this; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/ForeachCollectionProducerBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public sealed class ForeachCollectionProducerBlock : SimpleBlock { internal ForeachCollectionProducerBlock(StatementSyntax foreachNode, Block successor) : base(successor) { ForeachNode = foreachNode ?? throw new ArgumentNullException(nameof(foreachNode)); } public StatementSyntax ForeachNode { get; } internal override Block GetPossibleNonEmptySuccessorBlock() { // This block can't be removed by the CFG simplification, unlike the base class SimpleBlock return this; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/JumpBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public sealed class JumpBlock : SimpleBlock { internal JumpBlock(SyntaxNode jumpNode, Block successor, Block wouldBeSuccessor) : base(successor) { JumpNode = jumpNode ?? throw new ArgumentNullException(nameof(jumpNode)); WouldBeSuccessor = wouldBeSuccessor; } public SyntaxNode JumpNode { get; } /// /// If there was no jump, this block would be the successor. /// It can be null, when it doesn't make sense. For example in case of lock statements. /// public Block WouldBeSuccessor { get; private set; } internal override Block GetPossibleNonEmptySuccessorBlock() { // JumpBlock can't be removed by the CFG simplification, unlike the base class SimpleBlock return this; } internal override void ReplaceSuccessors(Dictionary replacementMapping) { base.ReplaceSuccessors(replacementMapping); if (WouldBeSuccessor != null && replacementMapping.ContainsKey(WouldBeSuccessor)) { WouldBeSuccessor = replacementMapping[WouldBeSuccessor]; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/LockBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public class LockBlock : SimpleBlock { public LockBlock(LockStatementSyntax lockNode, Block successor) : base(successor) { LockNode = lockNode ?? throw new ArgumentNullException(nameof(lockNode)); } public LockStatementSyntax LockNode { get; } internal override Block GetPossibleNonEmptySuccessorBlock() { // This block can't be removed by the CFG simplification, unlike the base class SimpleBlock return this; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/SimpleBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public class SimpleBlock : Block { internal SimpleBlock(Block successor) { SuccessorBlock = successor ?? throw new ArgumentNullException(nameof(successor)); } public Block SuccessorBlock { get; internal set; } public override IReadOnlyList SuccessorBlocks => ImmutableArray.Create(SuccessorBlock); internal override void ReplaceSuccessors(Dictionary replacementMapping) { if (replacementMapping.ContainsKey(SuccessorBlock)) { SuccessorBlock = replacementMapping[SuccessorBlock]; } } internal override Block GetPossibleNonEmptySuccessorBlock() { if (ReversedInstructions.Any()) { return this; } return SuccessorBlock.GetPossibleNonEmptySuccessorBlock(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/TemporaryBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public sealed class TemporaryBlock : Block { public Block SuccessorBlock { get; set; } public override IReadOnlyList SuccessorBlocks => ImmutableArray.Create(SuccessorBlock); internal override Block GetPossibleNonEmptySuccessorBlock() { if (SuccessorBlock == null) { throw new InvalidOperationException($"{nameof(SuccessorBlock)} is null"); } return SuccessorBlock.GetPossibleNonEmptySuccessorBlock(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/Blocks/UsingEndBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public class UsingEndBlock : SimpleBlock { public UsingStatementSyntax UsingStatement { get; } public IEnumerable Identifiers { get; } public UsingEndBlock(UsingStatementSyntax usingStatement, Block successor) : base(successor) { UsingStatement = usingStatement; Identifiers = usingStatement.Declaration != null ? GetIdentifiers(usingStatement.Declaration) : GetIdentifiers(usingStatement.Expression); } private static IEnumerable GetIdentifiers(VariableDeclarationSyntax declaration) { return declaration.Variables .Select(v => v.Identifier) .ToImmutableArray(); } private static IEnumerable GetIdentifiers(ExpressionSyntax expression) { return expression.RemoveParentheses() is IdentifierNameSyntax identifier ? ImmutableArray.Create(identifier.Identifier) : expression.DescendantNodesAndSelf() .OfType() .Select(a => a.Left) .OfType() .Select(i => i.Identifier) .ToImmutableArray(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/CSharpControlFlowGraph.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public static class CSharpControlFlowGraph { public static bool TryGet(SyntaxNode node, SemanticModel semanticModel, out IControlFlowGraph cfg) { cfg = null; var body = node switch { BaseMethodDeclarationSyntax n => (SyntaxNode)n.Body ?? n.ExpressionBody(), PropertyDeclarationSyntax n => n.ExpressionBody?.Expression, IndexerDeclarationSyntax n => n.ExpressionBody?.Expression, AccessorDeclarationSyntax n => (SyntaxNode)n.Body ?? n.ExpressionBody, AnonymousFunctionExpressionSyntax n => n.Body, ArrowExpressionClauseSyntax n => n, _ when node.IsKind(SyntaxKindEx.LocalFunctionStatement) && (LocalFunctionStatementSyntaxWrapper)node is var local => (SyntaxNode)local.Body ?? local.ExpressionBody, _ => null }; try { if (body is not null) { cfg = Create(body, semanticModel); } else { return false; } } catch (Exception exc) when (exc is InvalidOperationException || exc is ArgumentException || exc is NotSupportedException) { // historically, these have been considered as expected // but we should be aware of what syntax we do not yet support // https://github.com/SonarSource/sonar-dotnet/issues/2541 } catch (Exception exc) when (exc is NotImplementedException) { Debug.Fail(exc.ToString()); } return cfg != null; } internal /* for testing */ static IControlFlowGraph Create(SyntaxNode node, SemanticModel semanticModel) => new CSharpControlFlowGraphBuilder(node, semanticModel).Build(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/CSharpControlFlowGraphBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar { public sealed class CSharpControlFlowGraphBuilder : AbstractControlFlowGraphBuilder { private const int SupportedExpressionNodeCountLimit = 500; private readonly Stack breakTarget = new Stack(); private readonly Stack continueTargets = new Stack(); private readonly Stack>> switchGotoJumpBlocks = new Stack>>(); private readonly Dictionary> gotoJumpBlocks = new Dictionary>(); private readonly Dictionary labeledStatements = new Dictionary(); private static readonly object GotoDefaultEntry = new object(); private static readonly object GotoNullEntry = new object(); public CSharpControlFlowGraphBuilder(SyntaxNode node, SemanticModel semanticModel) : base(node, semanticModel) { } #region Fix jump statements protected override void PostProcessGraph() { FixJumps(this.gotoJumpBlocks, this.labeledStatements.ToDictionary(e => e.Key, e => (Block)e.Value)); } private void FixJumps(Dictionary> jumpsToFix, Dictionary collectedJumpTargets) { foreach (var jumpToFix in jumpsToFix) { if (!collectedJumpTargets.ContainsKey(jumpToFix.Key)) { throw new InvalidOperationException("Jump to non-existent location"); } foreach (var jumpBlock in jumpToFix.Value) { reversedBlocks.Remove(jumpBlock.SuccessorBlock); jumpBlock.SuccessorBlock = collectedJumpTargets[jumpToFix.Key]; } } } #endregion Fix jump statements #region Top level Build* protected override Block Build(SyntaxNode node, Block currentBlock) { Block block = null; if (node is StatementSyntax statement) { block = BuildStatement(statement, currentBlock); } else if (node is ArrowExpressionClauseSyntax arrowExpression) { block = BuildExpression(arrowExpression.Expression, currentBlock); } else if (node is ExpressionSyntax expression) { block = BuildExpression(expression, currentBlock); } if (block == null) { throw new ArgumentException("Neither a statement, nor an expression", nameof(node)); } if (node.Parent is ConstructorDeclarationSyntax constructorDeclaration && constructorDeclaration.Initializer != null) { block = BuildConstructorInitializer(constructorDeclaration.Initializer, block); } return block; } private Block BuildConstructorInitializer(ConstructorInitializerSyntax initializer, Block currentBlock) { currentBlock.ReversedInstructions.Add(initializer); var arguments = initializer.ArgumentList == null ? Enumerable.Empty() : initializer.ArgumentList.Arguments.Select(a => a.Expression); return BuildExpressions(arguments, currentBlock); } private Block BuildStatement(StatementSyntax statement, Block currentBlock) { switch (statement.Kind()) { case SyntaxKind.Block: return BuildBlock((BlockSyntax)statement, currentBlock); case SyntaxKind.ExpressionStatement: return BuildExpression(((ExpressionStatementSyntax)statement).Expression, currentBlock); case SyntaxKind.LocalDeclarationStatement: return BuildVariableDeclaration(((LocalDeclarationStatementSyntax)statement).Declaration, currentBlock); case SyntaxKind.IfStatement: return BuildIfStatement((IfStatementSyntax)statement, currentBlock); case SyntaxKind.WhileStatement: return BuildWhileStatement((WhileStatementSyntax)statement, currentBlock); case SyntaxKind.DoStatement: return BuildDoStatement((DoStatementSyntax)statement, currentBlock); case SyntaxKind.ForStatement: return BuildForStatement((ForStatementSyntax)statement, currentBlock); case SyntaxKind.ForEachStatement: return BuildForEachStatement((ForEachStatementSyntax)statement, currentBlock); case SyntaxKindEx.ForEachVariableStatement: return BuildForEachVariableStatement((ForEachVariableStatementSyntaxWrapper)statement, currentBlock); case SyntaxKind.LockStatement: return BuildLockStatement((LockStatementSyntax)statement, currentBlock); case SyntaxKind.UsingStatement: return BuildUsingStatement((UsingStatementSyntax)statement, currentBlock); case SyntaxKind.FixedStatement: return BuildFixedStatement((FixedStatementSyntax)statement, currentBlock); case SyntaxKind.UncheckedStatement: case SyntaxKind.CheckedStatement: return BuildCheckedStatement((CheckedStatementSyntax)statement, currentBlock); case SyntaxKind.UnsafeStatement: return BuildUnsafeStatement((UnsafeStatementSyntax)statement, currentBlock); case SyntaxKind.ReturnStatement: return BuildReturnStatement((ReturnStatementSyntax)statement, currentBlock); case SyntaxKind.YieldBreakStatement: return BuildYieldBreakStatement((YieldStatementSyntax)statement, currentBlock); case SyntaxKind.ThrowStatement: return BuildThrowStatement((ThrowStatementSyntax)statement, currentBlock); case SyntaxKind.YieldReturnStatement: return BuildYieldReturnStatement((YieldStatementSyntax)statement, currentBlock); case SyntaxKind.EmptyStatement: return currentBlock; case SyntaxKind.BreakStatement: return BuildBreakStatement((BreakStatementSyntax)statement, currentBlock); case SyntaxKind.ContinueStatement: return BuildContinueStatement((ContinueStatementSyntax)statement, currentBlock); case SyntaxKind.SwitchStatement: return BuildSwitchStatement((SwitchStatementSyntax)statement, currentBlock); case SyntaxKind.GotoCaseStatement: return BuildGotoCaseStatement((GotoStatementSyntax)statement, currentBlock); case SyntaxKind.GotoDefaultStatement: return BuildGotoDefaultStatement((GotoStatementSyntax)statement, currentBlock); case SyntaxKind.GotoStatement: return BuildGotoStatement((GotoStatementSyntax)statement, currentBlock); case SyntaxKind.LabeledStatement: return BuildLabeledStatement((LabeledStatementSyntax)statement, currentBlock); case SyntaxKind.TryStatement: return BuildTryStatement((TryStatementSyntax)statement, currentBlock); case SyntaxKindEx.LocalFunctionStatement: return currentBlock; case SyntaxKind.GlobalStatement: throw new NotSupportedException($"{statement.Kind()}"); default: throw new NotSupportedException($"{statement.Kind()}"); } } private Block BuildExpression(ExpressionSyntax expression, Block currentBlock) { if (expression == null) { return currentBlock; } if (IsTooComplex(expression)) { throw new NotSupportedException("Too complex expression"); } switch (expression.Kind()) { case SyntaxKind.SimpleAssignmentExpression: return BuildSimpleAssignmentExpression((AssignmentExpressionSyntax)expression, currentBlock); case SyntaxKindEx.CoalesceAssignmentExpression: return BuildCoalesceAssignmentExpression((AssignmentExpressionSyntax)expression, currentBlock); case SyntaxKind.OrAssignmentExpression: case SyntaxKind.AndAssignmentExpression: case SyntaxKind.ExclusiveOrAssignmentExpression: case SyntaxKind.SubtractAssignmentExpression: case SyntaxKind.AddAssignmentExpression: case SyntaxKind.DivideAssignmentExpression: case SyntaxKind.MultiplyAssignmentExpression: case SyntaxKind.ModuloAssignmentExpression: case SyntaxKind.LeftShiftAssignmentExpression: case SyntaxKind.RightShiftAssignmentExpression: return BuildAssignmentExpression((AssignmentExpressionSyntax)expression, currentBlock); case SyntaxKind.LessThanExpression: case SyntaxKind.LessThanOrEqualExpression: case SyntaxKind.GreaterThanExpression: case SyntaxKind.GreaterThanOrEqualExpression: case SyntaxKind.EqualsExpression: case SyntaxKind.NotEqualsExpression: case SyntaxKind.BitwiseOrExpression: case SyntaxKind.BitwiseAndExpression: case SyntaxKind.ExclusiveOrExpression: case SyntaxKind.SubtractExpression: case SyntaxKind.AddExpression: case SyntaxKind.DivideExpression: case SyntaxKind.MultiplyExpression: case SyntaxKind.ModuloExpression: case SyntaxKind.LeftShiftExpression: case SyntaxKind.RightShiftExpression: return BuildBinaryExpression((BinaryExpressionSyntax)expression, currentBlock); case SyntaxKind.LogicalNotExpression: case SyntaxKind.BitwiseNotExpression: case SyntaxKind.UnaryMinusExpression: case SyntaxKind.UnaryPlusExpression: case SyntaxKind.PreIncrementExpression: case SyntaxKind.PreDecrementExpression: case SyntaxKind.AddressOfExpression: case SyntaxKind.PointerIndirectionExpression: { var parent = (PrefixUnaryExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Operand); } case SyntaxKind.PostIncrementExpression: case SyntaxKind.PostDecrementExpression: { var parent = (PostfixUnaryExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Operand); } case SyntaxKind.IdentifierName: case SyntaxKind.GenericName: case SyntaxKind.AliasQualifiedName: case SyntaxKind.QualifiedName: case SyntaxKind.CharacterLiteralExpression: case SyntaxKind.StringLiteralExpression: case SyntaxKind.NumericLiteralExpression: case SyntaxKind.TrueLiteralExpression: case SyntaxKind.FalseLiteralExpression: case SyntaxKind.NullLiteralExpression: case SyntaxKind.ThisExpression: case SyntaxKind.BaseExpression: case SyntaxKind.DefaultExpression: case SyntaxKindEx.DefaultLiteralExpression: case SyntaxKind.SizeOfExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.PredefinedType: case SyntaxKind.NullableType: case SyntaxKind.OmittedArraySizeExpression: case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.QueryExpression: case SyntaxKind.ArgListExpression: case SyntaxKindEx.RangeExpression: case SyntaxKindEx.IndexExpression: currentBlock.ReversedInstructions.Add(expression); return currentBlock; case SyntaxKind.PointerType: return BuildExpression(((PointerTypeSyntax)expression).ElementType, currentBlock); case SyntaxKind.ParenthesizedExpression: return BuildExpression(((ParenthesizedExpressionSyntax)expression).Expression, currentBlock); case SyntaxKind.AwaitExpression: { var parent = (AwaitExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.CheckedExpression: case SyntaxKind.UncheckedExpression: { var parent = (CheckedExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.AsExpression: case SyntaxKind.IsExpression: { var parent = (BinaryExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Left); } case SyntaxKind.CastExpression: { var parent = (CastExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.InterpolatedStringExpression: return BuildInterpolatedStringExpression((InterpolatedStringExpressionSyntax)expression, currentBlock); case SyntaxKind.InvocationExpression: return BuildInvocationExpression((InvocationExpressionSyntax)expression, currentBlock); case SyntaxKind.AnonymousObjectCreationExpression: return BuildAnonymousObjectCreationExpression((AnonymousObjectCreationExpressionSyntax)expression, currentBlock); case SyntaxKind.ObjectCreationExpression: return BuildObjectCreationExpression((ObjectCreationExpressionSyntax)expression, currentBlock); case SyntaxKind.ElementAccessExpression: return BuildElementAccessExpression((ElementAccessExpressionSyntax)expression, currentBlock); case SyntaxKind.ImplicitElementAccess: return BuildImplicitElementAccessExpression((ImplicitElementAccessSyntax)expression, currentBlock); case SyntaxKind.LogicalAndExpression: return BuildLogicalAndExpression((BinaryExpressionSyntax)expression, currentBlock); case SyntaxKind.LogicalOrExpression: return BuildLogicalOrExpression((BinaryExpressionSyntax)expression, currentBlock); case SyntaxKind.ArrayCreationExpression: return BuildArrayCreationExpression((ArrayCreationExpressionSyntax)expression, currentBlock); case SyntaxKind.ImplicitArrayCreationExpression: { var parent = (ImplicitArrayCreationExpressionSyntax)expression; var initializerBlock = BuildExpression(parent.Initializer, currentBlock); initializerBlock.ReversedInstructions.Add(parent); return initializerBlock; } case SyntaxKind.StackAllocArrayCreationExpression: { var parent = (StackAllocArrayCreationExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Type, parent.Initializer); } case SyntaxKindEx.ImplicitStackAllocArrayCreationExpression: { var parent = (ImplicitStackAllocArrayCreationExpressionSyntaxWrapper)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Initializer); } case SyntaxKind.SimpleMemberAccessExpression: case SyntaxKind.PointerMemberAccessExpression: { var parent = (MemberAccessExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.ObjectInitializerExpression: case SyntaxKind.ArrayInitializerExpression: case SyntaxKind.CollectionInitializerExpression: case SyntaxKind.ComplexElementInitializerExpression: { var parent = (InitializerExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expressions); } case SyntaxKind.MakeRefExpression: { var parent = (MakeRefExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.RefTypeExpression: { var parent = (RefTypeExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.RefValueExpression: { var parent = (RefValueExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKind.ArrayType: return BuildArrayType((ArrayTypeSyntax)expression, currentBlock); case SyntaxKind.CoalesceExpression: return BuildCoalesceExpression((BinaryExpressionSyntax)expression, currentBlock); case SyntaxKind.ConditionalExpression: return BuildConditionalExpression((ConditionalExpressionSyntax)expression, currentBlock); // these look strange in the CFG: case SyntaxKind.ConditionalAccessExpression: return BuildConditionalAccessExpression((ConditionalAccessExpressionSyntax)expression, currentBlock); case SyntaxKind.MemberBindingExpression: { var parent = (MemberBindingExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Name); } case SyntaxKind.ElementBindingExpression: { var parent = (ElementBindingExpressionSyntax)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.ArgumentList?.Arguments.Select(a => a.Expression)); } case SyntaxKindEx.IsPatternExpression: var isPatternExpression = (IsPatternExpressionSyntaxWrapper)expression; currentBlock = BuildIsPatternExpression(isPatternExpression, currentBlock); return BuildExpression(isPatternExpression.Expression, currentBlock); case SyntaxKindEx.ThrowExpression: var throwExpression = (ThrowExpressionSyntaxWrapper)expression; return BuildJumpToExitStatement(throwExpression, currentBlock, throwExpression.Expression); case SyntaxKindEx.DeclarationExpression: currentBlock.ReversedInstructions.Add(expression); return currentBlock; case SyntaxKindEx.RefExpression: { var parent = (RefExpressionSyntaxWrapper)expression; return BuildSimpleNestedExpression(parent, currentBlock, parent.Expression); } case SyntaxKindEx.SwitchExpression: return BuildSwitchExpression((SwitchExpressionSyntaxWrapper)expression, currentBlock); case SyntaxKindEx.TupleExpression: return BuildTupleExpression((TupleExpressionSyntaxWrapper)expression, currentBlock); default: throw new NotSupportedException($"{expression.Kind()}"); } } private static bool IsTooComplex(SyntaxNode node) { var count = 0; // Limit descending for performance reasons // The evaluation doesn't descend into the content of lambda expression. We need to tolerate these for Razor Layout pages. return node.DescendantNodes(x => ++count < SupportedExpressionNodeCountLimit && !IsLambda(x)).Count() >= SupportedExpressionNodeCountLimit; static bool IsLambda(SyntaxNode node) => node is LambdaExpressionSyntax; } #endregion Top level Build* #region Build* #region Build statements private Block BuildStatements(IEnumerable statements, Block currentBlock) { foreach (var statement in statements.Reverse()) { currentBlock = BuildStatement(statement, currentBlock); } return currentBlock; } private Block BuildExpressions(IEnumerable expressions, Block currentBlock) { foreach (var expression in expressions.Reverse()) { currentBlock = BuildExpression(expression, currentBlock); } return currentBlock; } #region Build label, goto, goto case, goto default private Block BuildLabeledStatement(LabeledStatementSyntax labeledStatement, Block currentBlock) { var statementBlock = BuildStatement(labeledStatement.Statement, currentBlock); var jumpBlock = CreateJumpBlock(labeledStatement, statementBlock); this.labeledStatements[labeledStatement.Identifier.ValueText] = jumpBlock; return CreateBlock(jumpBlock); } private Block BuildTryStatement(TryStatementSyntax tryStatement, Block currentBlock) { // successor - either finally of next block after try statement var catchSuccessor = currentBlock; var hasFinally = tryStatement.Finally?.Block != null; if (hasFinally) { var finallySuccessors = new List(); finallySuccessors.Add(CreateBlock(catchSuccessor)); finallySuccessors.Add(CreateBlock(this.exitTarget.Peek())); // Create a finally block that can either go to try-finally successor (happy path) or exit target (exceptional path) catchSuccessor = BuildBlock(tryStatement.Finally.Block, CreateBranchBlock(tryStatement.Finally, finallySuccessors)); // This finally block becomes current exit target stack in case we have a return inside the try/catch block this.exitTarget.Push(catchSuccessor); } var catchBlocks = tryStatement.Catches .Reverse() .Select(catchClause => { Block catchBlock = BuildBlock(catchClause.Block, CreateBlock(catchSuccessor)); if (catchClause.Filter?.FilterExpression != null) { catchBlock = BuildExpression(catchClause.Filter.FilterExpression, CreateBinaryBranchBlock(catchClause.Filter, catchBlock, catchSuccessor)); } return catchBlock; }) .ToList(); // If there is a catch with no Exception filter or equivalent we don't want to // join the tryStatement start/end blocks with the exit block because all // exceptions will be caught before going to finally var areAllExceptionsCaught = tryStatement.Catches.Any(IsCatchingAllExceptions); // try end var tryEndStatementConnections = catchBlocks.ToList(); tryEndStatementConnections.Add(catchSuccessor); // happy path, no exceptions thrown if (!areAllExceptionsCaught) // unexpected exception thrown, go to exit (through finally if present) { tryEndStatementConnections.Add(this.exitTarget.Peek()); } Block tryBody; if (tryStatement.Block.Statements.Any(s => s.IsKind(SyntaxKind.ReturnStatement))) { // there is a return inside the `try`, thus a JumpBlock directly to the finally or exit will be created var returnBlock = BuildBlock(tryStatement.Block, catchSuccessor); var connections = new List(); connections.Add(returnBlock); // if an exception is thrown, it will reach the `catch` blocks connections.AddRange(catchBlocks); tryBody = CreateBranchBlock(tryStatement, connections); } else { tryBody = BuildBlock(tryStatement.Block, CreateBranchBlock(tryStatement, tryEndStatementConnections.Distinct())); } // if this try is inside another try, the `beforeTryBlock` must have edges to the outer catch & finally blocks Block beforeTryBlock; if (currentBlock is BranchBlock possibleOuterTry && possibleOuterTry.BranchingNode.IsKind(SyntaxKind.TryStatement)) { var beforeTryConnections = possibleOuterTry.SuccessorBlocks.ToList(); beforeTryConnections.Add(tryBody); beforeTryBlock = CreateBranchBlock(tryStatement, beforeTryConnections.Distinct()); } else { // otherwise, what happens before the try is not handled by any catch or finally beforeTryBlock = CreateBlock(tryBody); } if (hasFinally) { this.exitTarget.Pop(); } return beforeTryBlock; } private static bool IsCatchingAllExceptions(CatchClauseSyntax catchClause) { if (catchClause.Declaration == null) { return true; } var exceptionTypeName = catchClause.Declaration.Type.GetText().ToString().Trim(); return catchClause.Filter == null && (exceptionTypeName == "Exception" || exceptionTypeName == "System.Exception"); } private Block BuildGotoDefaultStatement(GotoStatementSyntax statement, Block currentBlock) { if (this.switchGotoJumpBlocks.Count == 0) { throw new InvalidOperationException("goto default; outside a switch"); } var jumpBlock = CreateJumpBlock(statement, CreateTemporaryBlock(), currentBlock); var currentJumpBlocks = this.switchGotoJumpBlocks.Peek(); if (!currentJumpBlocks.ContainsKey(GotoDefaultEntry)) { currentJumpBlocks.Add(GotoDefaultEntry, new List()); } currentJumpBlocks[GotoDefaultEntry].Add(jumpBlock); return jumpBlock; } private Block BuildGotoCaseStatement(GotoStatementSyntax statement, Block currentBlock) { if (this.switchGotoJumpBlocks.Count == 0) { throw new InvalidOperationException("goto case; outside a switch"); } var jumpBlock = CreateJumpBlock(statement, CreateTemporaryBlock(), currentBlock); var currentJumpBlocks = this.switchGotoJumpBlocks.Peek(); var indexer = GetCaseIndexer(statement.Expression); if (!currentJumpBlocks.ContainsKey(indexer)) { currentJumpBlocks.Add(indexer, new List()); } currentJumpBlocks[indexer].Add(jumpBlock); return jumpBlock; } private Block BuildGotoStatement(GotoStatementSyntax statement, Block currentBlock) { var jumpBlock = CreateJumpBlock(statement, CreateTemporaryBlock(), currentBlock); if (!(statement.Expression is IdentifierNameSyntax identifier)) { throw new InvalidOperationException("goto with no identifier"); } if (!this.gotoJumpBlocks.ContainsKey(identifier.Identifier.ValueText)) { this.gotoJumpBlocks.Add(identifier.Identifier.ValueText, new List()); } this.gotoJumpBlocks[identifier.Identifier.ValueText].Add(jumpBlock); return jumpBlock; } #endregion Build label, goto, goto case, goto default #region Build switch private Block BuildSwitchStatement(SwitchStatementSyntax switchStatement, Block currentBlock) { var caseBlocksByValue = new Dictionary(); this.breakTarget.Push(currentBlock); this.switchGotoJumpBlocks.Push(new Dictionary>()); // Default section is always evaluated last, we are handling it first because // the CFG is built in reverse order var defaultSection = switchStatement.Sections.FirstOrDefault(ContainsDefaultLabel); var defaultSectionBlock = currentBlock; if (defaultSection != null) { defaultSectionBlock = BuildStatements(defaultSection.Statements, CreateBlock(currentBlock)); caseBlocksByValue[GotoDefaultEntry] = defaultSectionBlock; // All "goto default;" will jump to this block } var currentSectionBlock = defaultSectionBlock; foreach (var section in switchStatement.Sections.Reverse()) { Block sectionBlock; if (defaultSection != null && section == defaultSection) { // Skip the default section if it contains a single default label; we already handled it if (section.Labels.Count == 1) { continue; } sectionBlock = defaultSectionBlock; } else { sectionBlock = BuildStatements(section.Statements, CreateBlock(currentBlock)); } foreach (var label in section.Labels.Reverse()) { // Handle C#7 pattern matching case Block if (CasePatternSwitchLabelSyntaxWrapper.IsInstance(label)) { var casePatternSwitchLabel = (CasePatternSwitchLabelSyntaxWrapper)label; currentSectionBlock = BuildCasePattern(casePatternSwitchLabel, trueSuccessor: sectionBlock, falseSuccessor: currentSectionBlock); } else if (label is CaseSwitchLabelSyntax simpleCaseLabel) { currentSectionBlock = BuildExpression(switchStatement.Expression, CreateBinaryBranchBlock(simpleCaseLabel, sectionBlock, currentSectionBlock)); var key = GetCaseIndexer(simpleCaseLabel.Value); caseBlocksByValue[key] = sectionBlock; } } } this.breakTarget.Pop(); var gotosToFix = this.switchGotoJumpBlocks.Pop(); FixJumps(gotosToFix, caseBlocksByValue); var switchBlock = CreateBranchBlock(switchStatement, new[] { currentSectionBlock }); return BuildExpression(switchStatement.Expression, switchBlock); bool ContainsDefaultLabel(SwitchSectionSyntax s) => s.Labels.Any(l => l.IsKind(SyntaxKind.DefaultSwitchLabel)); } private Block BuildSwitchExpression(SwitchExpressionSyntaxWrapper switchExpressionSyntax, Block currentBlock) { var currentArmBlock = currentBlock; for (var index = switchExpressionSyntax.Arms.Count - 1; index >= 0; index--) { var arm = switchExpressionSyntax.Arms[index]; var isLast = index == switchExpressionSyntax.Arms.Count - 1; var armBlock = BuildExpression(arm.Expression, CreateBlock(currentBlock)); currentArmBlock = BuildArmBranch(arm, armBlock, currentArmBlock, isLast); if (!isLast) { currentArmBlock = BuildExpression(switchExpressionSyntax.GoverningExpression, currentArmBlock); } } return currentArmBlock; } private Block BuildArmBranch(SwitchExpressionArmSyntaxWrapper switchExpressionArmSyntax, Block trueSuccessor, Block falseSuccessor, bool isLast) { var newTrueSuccessor = CreateWhenCloseNewTrueSuccessor(switchExpressionArmSyntax.WhenClause, trueSuccessor, falseSuccessor); var currentBlock = CreateCurrentBlock(switchExpressionArmSyntax, newTrueSuccessor, falseSuccessor, isLast); currentBlock = BuildPatternExpression(switchExpressionArmSyntax.Pattern, currentBlock); return currentBlock; } private Block CreateCurrentBlock(SwitchExpressionArmSyntaxWrapper switchExpressionArmSyntax, Block trueSuccessor, Block falseSuccessor, bool isLast) => isLast ? trueSuccessor : (Block)CreateBinaryBranchBlock(switchExpressionArmSyntax, trueSuccessor, falseSuccessor); private Block BuildCasePattern(CasePatternSwitchLabelSyntaxWrapper casePatternSwitchLabel, Block trueSuccessor, Block falseSuccessor) { var newTrueSuccessor = CreateWhenCloseNewTrueSuccessor(casePatternSwitchLabel.WhenClause, trueSuccessor, falseSuccessor); var currentBlock = CreateBinaryBranchBlock(casePatternSwitchLabel, newTrueSuccessor, falseSuccessor); currentBlock.ReversedInstructions.Add(casePatternSwitchLabel.Pattern); return currentBlock; } private Block CreateWhenCloseNewTrueSuccessor(WhenClauseSyntaxWrapper whenClauseSyntax, Block trueSuccessor, Block falseSuccessor) => whenClauseSyntax.SyntaxNode != null ? BuildCondition(whenClauseSyntax.Condition, trueSuccessor, falseSuccessor) : trueSuccessor; private object GetCaseIndexer(ExpressionSyntax expression) { var constValue = semanticModel.GetConstantValue(expression); if (!constValue.HasValue) { throw new InvalidOperationException("Expression has no constant value"); } var indexer = constValue.Value; if (indexer == null) { indexer = GotoNullEntry; } return indexer; } #endregion Build switch #region Build jumps: break, continue, return, throw, yield break private Block BuildBreakStatement(BreakStatementSyntax breakStatement, Block currentBlock) { if (this.breakTarget.Count == 0) { throw new InvalidOperationException("break; outside a loop"); } var target = this.breakTarget.Peek(); if (currentBlock is BranchBlock possibleTryBlock && possibleTryBlock.BranchingNode.IsKind(SyntaxKind.TryStatement)) { var newSuccessors = possibleTryBlock.SuccessorBlocks.ToList(); newSuccessors.Add(target); var branchBlock = CreateBranchBlock(possibleTryBlock.BranchingNode, newSuccessors); return branchBlock; } return CreateJumpBlock(breakStatement, target, currentBlock); } private Block BuildContinueStatement(ContinueStatementSyntax continueStatement, Block currentBlock) { if (this.continueTargets.Count == 0) { throw new InvalidOperationException("continue; outside a loop"); } var target = this.continueTargets.Peek(); return CreateJumpBlock(continueStatement, target, currentBlock); } private Block BuildReturnStatement(ReturnStatementSyntax returnStatement, Block currentBlock) { return BuildJumpToExitStatement(returnStatement, currentBlock, returnStatement.Expression); } private Block BuildThrowStatement(ThrowStatementSyntax throwStatement, Block currentBlock) { return BuildJumpToExitStatement(throwStatement, currentBlock, throwStatement.Expression); } private Block BuildYieldBreakStatement(YieldStatementSyntax yieldBreakStatement, Block currentBlock) { return BuildJumpToExitStatement(yieldBreakStatement, currentBlock); } private Block BuildYieldReturnStatement(YieldStatementSyntax yieldReturnStatement, Block currentBlock) { return BuildExpression(yieldReturnStatement.Expression, CreateJumpBlock(yieldReturnStatement, currentBlock, currentBlock)); } private Block BuildJumpToExitStatement(StatementSyntax statement, Block currentBlock, ExpressionSyntax expression = null) { // When there is a `throw` inside a `try`, and there is a `catch` with a filter, // the `throw` block should point to both the `catch` and the `exit` blocks. if (currentBlock.SuccessorBlocks.Any(b => b is BinaryBranchBlock x && x.BranchingNode.IsKind(SyntaxKind.CatchFilterClause)) && currentBlock.SuccessorBlocks.Contains(this.exitTarget.Peek())) { return BuildExpression(expression, currentBlock); } return BuildExpression(expression, CreateJumpBlock(statement, this.exitTarget.Peek(), currentBlock)); } private Block BuildJumpToExitStatement(ExpressionSyntax expression, Block currentBlock, ExpressionSyntax innerExpression) { return BuildExpression(innerExpression, CreateJumpBlock(expression, this.exitTarget.Peek(), currentBlock)); } #endregion Build jumps: break, continue, return, throw, yield break #region Build lock, using, fixed, unsafe checked statements private Block BuildLockStatement(LockStatementSyntax lockStatement, Block currentBlock) { var lockStatementBlock = BuildStatement(lockStatement.Statement, CreateBlock(currentBlock)); return BuildExpression(lockStatement.Expression, CreateLockBlock(lockStatement, lockStatementBlock)); } private Block BuildUsingStatement(UsingStatementSyntax usingStatement, Block currentBlock) { var usingStatementBlock = BuildStatement(usingStatement.Statement, CreateUsingFinalizerBlock(usingStatement, currentBlock)); var usingBlock = CreateJumpBlock(usingStatement, usingStatementBlock); return usingStatement.Expression != null ? BuildExpression(usingStatement.Expression, usingBlock) : BuildVariableDeclaration(usingStatement.Declaration, usingBlock); } private Block BuildFixedStatement(FixedStatementSyntax fixedStatement, Block currentBlock) { var fixedStatementBlock = BuildStatement(fixedStatement.Statement, CreateBlock(currentBlock)); return BuildVariableDeclaration(fixedStatement.Declaration, CreateJumpBlock(fixedStatement, fixedStatementBlock)); } private Block BuildUnsafeStatement(UnsafeStatementSyntax statement, Block currentBlock) { var unsafeStatement = BuildStatement(statement.Block, CreateBlock(currentBlock)); return CreateJumpBlock(statement, unsafeStatement); } private Block BuildCheckedStatement(CheckedStatementSyntax statement, Block currentBlock) { var statementBlock = BuildStatement(statement.Block, CreateBlock(currentBlock)); return CreateJumpBlock(statement, statementBlock); } #endregion Build lock, using, fixed, unsafe checked statements #region Build loops - do, for, foreach, while private Block BuildDoStatement(DoStatementSyntax doStatement, Block currentBlock) { //// while (A) { B; } var conditionBlockTemp = CreateTemporaryBlock(); var conditionBlock = BuildCondition(doStatement.Condition, conditionBlockTemp, currentBlock); // A this.breakTarget.Push(currentBlock); this.continueTargets.Push(conditionBlock); var loopBody = BuildStatement(doStatement.Statement, CreateBlock(conditionBlock)); // B conditionBlockTemp.SuccessorBlock = loopBody; this.breakTarget.Pop(); this.continueTargets.Pop(); return CreateBlock(loopBody); } private Block BuildForStatement(ForStatementSyntax forStatement, Block currentBlock) { //// for (A; B; C) { D; } var tempLoopBlock = CreateTemporaryBlock(); var incrementorBlock = BuildExpressions(forStatement.Incrementors, CreateBlock(tempLoopBlock)); // C this.breakTarget.Push(currentBlock); this.continueTargets.Push(incrementorBlock); var forBlock = BuildStatement(forStatement.Statement, CreateBlock(incrementorBlock)); // D this.breakTarget.Pop(); this.continueTargets.Pop(); var conditionBlock = BuildExpression(forStatement.Condition, CreateBinaryBranchBlock(forStatement, forBlock, currentBlock)); // B tempLoopBlock.SuccessorBlock = conditionBlock; Block forInitializer = AddBlock(new ForInitializerBlock(forStatement, conditionBlock)); // A if (forStatement.Declaration != null) { forInitializer = BuildVariableDeclaration(forStatement.Declaration, forInitializer); } forInitializer = BuildExpressions(forStatement.Initializers, forInitializer); return forInitializer; } private Block BuildForEachVariableStatement(ForEachVariableStatementSyntaxWrapper foreachStatement, Block currentBlock) => BuildForEachStatement(foreachStatement, foreachStatement.Statement, foreachStatement.Expression, currentBlock); private Block BuildForEachStatement(ForEachStatementSyntax foreachStatement, Block currentBlock) => BuildForEachStatement(foreachStatement, foreachStatement.Statement, foreachStatement.Expression, currentBlock); private Block BuildForEachStatement(StatementSyntax foreachStatement, StatementSyntax foreachBodyStatement, ExpressionSyntax foreachExpression, Block currentBlock) { var temp = CreateTemporaryBlock(); this.breakTarget.Push(currentBlock); this.continueTargets.Push(temp); var foreachBlock = BuildStatement(foreachBodyStatement, CreateBlock(temp)); this.breakTarget.Pop(); this.continueTargets.Pop(); // Variable declaration in a foreach statement is not a VariableDeclarator, otherwise it would be added here. temp.SuccessorBlock = CreateBinaryBranchBlock(foreachStatement, foreachBlock, currentBlock); return BuildExpression(foreachExpression, AddBlock(new ForeachCollectionProducerBlock(foreachStatement, temp))); } private Block BuildWhileStatement(WhileStatementSyntax whileStatement, Block currentBlock) { var loopTempBlock = CreateTemporaryBlock(); this.breakTarget.Push(currentBlock); this.continueTargets.Push(loopTempBlock); var bodyBlock = BuildStatement(whileStatement.Statement, CreateBlock(loopTempBlock)); this.breakTarget.Pop(); this.continueTargets.Pop(); var loopCondition = BuildCondition(whileStatement.Condition, bodyBlock, currentBlock); loopTempBlock.SuccessorBlock = loopCondition; return CreateBlock(loopCondition); } #endregion Build loops - do, for, foreach, while #region Build if statement private Block BuildIfStatement(IfStatementSyntax ifStatement, Block currentBlock) { var elseBlock = ifStatement.Else?.Statement != null ? BuildStatement(ifStatement.Else.Statement, CreateBlock(currentBlock)) : currentBlock; var trueBlock = BuildStatement(ifStatement.Statement, CreateBlock(currentBlock)); var ifConditionBlock = BuildCondition(ifStatement.Condition, trueBlock, elseBlock); return ifConditionBlock; } #endregion Build if statement #region Build block private Block BuildBlock(BlockSyntax block, Block currentBlock) { return BuildStatements(block.Statements, currentBlock); } #endregion Build block #endregion Build statements #region Build expressions private Block BuildConditionalAccessExpression(ConditionalAccessExpressionSyntax conditionalAccess, Block currentBlock) { var whenNotNull = BuildExpression(conditionalAccess.WhenNotNull, CreateBlock(currentBlock)); return BuildExpression(conditionalAccess.Expression, CreateBinaryBranchBlock(conditionalAccess, currentBlock, whenNotNull)); } private Block BuildConditionalExpression(ConditionalExpressionSyntax conditional, Block currentBlock) { var falseBlock = BuildExpression(conditional.WhenFalse, CreateBlock(currentBlock)); var trueBlock = BuildExpression(conditional.WhenTrue, CreateBlock(currentBlock)); return BuildCondition(conditional.Condition, trueBlock, falseBlock); } private Block BuildCoalesceExpression(BinaryExpressionSyntax expression, Block currentBlock) { var rightBlock = BuildExpression(expression.Right, CreateBlock(currentBlock)); return BuildExpression(expression.Left, CreateBinaryBranchBlock(expression, rightBlock, currentBlock)); } private Block BuildLogicalAndExpression(BinaryExpressionSyntax expression, Block currentBlock) { var rightBlock = BuildExpression(expression.Right, AddBlock(new BinaryBranchingSimpleBlock(expression.Right, currentBlock))); return BuildExpression(expression.Left, CreateBinaryBranchBlock(expression, rightBlock, currentBlock)); } private Block BuildLogicalOrExpression(BinaryExpressionSyntax expression, Block currentBlock) { var rightBlock = BuildExpression(expression.Right, AddBlock(new BinaryBranchingSimpleBlock(expression.Right, currentBlock))); return BuildExpression(expression.Left, CreateBinaryBranchBlock(expression, currentBlock, rightBlock)); } private Block BuildArrayCreationExpression(ArrayCreationExpressionSyntax expression, Block currentBlock) { var arrayInitializerBlock = BuildExpression(expression.Initializer, currentBlock); arrayInitializerBlock.ReversedInstructions.Add(expression); return BuildExpression(expression.Type, arrayInitializerBlock); } private Block BuildElementAccessExpression(ElementAccessExpressionSyntax expression, Block currentBlock) { return BuildInvocationLikeExpression(expression, currentBlock, expression.Expression, expression.ArgumentList?.Arguments); } private Block BuildImplicitElementAccessExpression(ImplicitElementAccessSyntax expression, Block currentBlock) { return BuildInvocationLikeExpression(expression, currentBlock, null, expression.ArgumentList?.Arguments); } private Block BuildInvocationLikeExpression(ExpressionSyntax parent, Block currentBlock, ExpressionSyntax child, IEnumerable arguments) { currentBlock.ReversedInstructions.Add(parent); var isNameof = parent is InvocationExpressionSyntax invocation && IsNameof(invocation); // The nameof arguments are not evaluated at runtime and should not be added // to the block as instructions if (isNameof) { return currentBlock; } // ref arguments should be added at the end since they remove // the constraints on the arguments after all the other arguments are evaluated foreach (var arg in arguments.Reverse()) { if (arg.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword)) { currentBlock = BuildExpression(arg.Expression, currentBlock); } } foreach (var arg in arguments.Reverse()) { if (!arg.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword)) { currentBlock = BuildExpression(arg.Expression, currentBlock); } } return BuildExpression(child, currentBlock); } private Block BuildObjectCreationExpression(ObjectCreationExpressionSyntax expression, Block currentBlock) { var objectInitializerBlock = BuildExpression(expression.Initializer, currentBlock); objectInitializerBlock.ReversedInstructions.Add(expression); var arguments = expression.ArgumentList == null ? Enumerable.Empty() : expression.ArgumentList.Arguments.Select(a => a.Expression); return BuildExpressions(arguments, objectInitializerBlock); } private Block BuildAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax expression, Block currentBlock) { return BuildSimpleNestedExpression(expression, currentBlock, expression.Initializers.Select(i => i.Expression)); } private Block BuildInvocationExpression(InvocationExpressionSyntax expression, Block currentBlock) { return BuildInvocationLikeExpression(expression, currentBlock, expression.Expression, expression.ArgumentList?.Arguments); } private Block BuildInterpolatedStringExpression(InterpolatedStringExpressionSyntax expression, Block currentBlock) { return BuildSimpleNestedExpression(expression, currentBlock, expression.Contents.OfType().Select(i => i.Expression)); } private Block BuildSimpleNestedExpression(ExpressionSyntax parent, Block currentBlock, params ExpressionSyntax[] children) { return BuildSimpleNestedExpression(parent, currentBlock, (IEnumerable)children); } private Block BuildSimpleNestedExpression(ExpressionSyntax parent, Block currentBlock, IEnumerable children) { currentBlock.ReversedInstructions.Add(parent); // The nameof arguments are not evaluated at runtime and should not be added // to the block as instructions var isNameof = parent is InvocationExpressionSyntax invocation && IsNameof(invocation); return children == null || isNameof ? currentBlock : BuildExpressions(children, currentBlock); } private Block BuildBinaryExpression(BinaryExpressionSyntax expression, Block currentBlock) { currentBlock.ReversedInstructions.Add(expression); var binaryExpressionBlock = BuildExpression(expression.Right, currentBlock); return BuildExpression(expression.Left, binaryExpressionBlock); } private Block BuildAssignmentExpression(AssignmentExpressionSyntax expression, Block currentBlock) { currentBlock.ReversedInstructions.Add(expression); var binaryExpressionBlock = BuildExpression(expression.Right, currentBlock); return BuildExpression(expression.Left, binaryExpressionBlock); } private Block BuildCoalesceAssignmentExpression(AssignmentExpressionSyntax expression, Block currentBlock) { currentBlock.ReversedInstructions.Add(expression); var rightBlock = BuildExpression(expression.Right, CreateBlock(currentBlock)); return BuildExpression(expression.Left, CreateBinaryBranchBlock(expression, rightBlock, currentBlock)); } private Block BuildSimpleAssignmentExpression(AssignmentExpressionSyntax expression, Block currentBlock) { currentBlock.ReversedInstructions.Add(expression); var assignmentBlock = BuildExpression(expression.Right, currentBlock); if (!IsAssignmentWithSimpleLeftSide(expression)) { assignmentBlock = BuildExpression(expression.Left, assignmentBlock); } return assignmentBlock; } public static bool IsAssignmentWithSimpleLeftSide(AssignmentExpressionSyntax assignment) { return assignment.Left.RemoveParentheses() is IdentifierNameSyntax; } private Block BuildArrayType(ArrayTypeSyntax arrayType, Block currentBlock) { currentBlock.ReversedInstructions.Add(arrayType); var arraySizes = arrayType.RankSpecifiers.SelectMany(rs => rs.Sizes); return BuildExpressions(arraySizes, currentBlock); } private Block BuildIsPatternExpression(IsPatternExpressionSyntaxWrapper isPatternExpression, Block currentBlock) { currentBlock.ReversedInstructions.Add(isPatternExpression); return BuildPatternExpression(isPatternExpression.Pattern, currentBlock); } private Block BuildPatternExpression(PatternSyntaxWrapper patternSyntaxWrapper, Block currentBlock) { if (ConstantPatternSyntaxWrapper.IsInstance(patternSyntaxWrapper)) { var constantPattern = (ConstantPatternSyntaxWrapper)patternSyntaxWrapper; return BuildExpression(constantPattern.Expression, currentBlock); } else if (DeclarationPatternSyntaxWrapper.IsInstance(patternSyntaxWrapper)) { // Do nothing, this is just variable assignment and the Pattern itself contains // only the new variable(s), which are not enough to evaluate the assignment. // The handling should be done in SonarExplodedGraph and UcfgInstructionFactory. return currentBlock; } else if (DiscardPatternSyntaxWrapper.IsInstance(patternSyntaxWrapper)) { return currentBlock; } else if (RecursivePatternSyntaxWrapper.IsInstance(patternSyntaxWrapper)) { // The recursive pattern will be handled in SonarExplodedGraph and UcfgInstructionFactory. currentBlock.ReversedInstructions.Add(patternSyntaxWrapper); return currentBlock; } throw new NotSupportedException($"{patternSyntaxWrapper.SyntaxNode.Kind()}"); } private Block BuildTupleExpression(TupleExpressionSyntaxWrapper tuple, Block currentBlock) { currentBlock.ReversedInstructions.Add(tuple); foreach (var arg in tuple.Arguments.Reverse()) { currentBlock = BuildExpression(arg.Expression, currentBlock); } return currentBlock; } #endregion Build expressions #region Build variable declaration private Block BuildVariableDeclaration(VariableDeclarationSyntax declaration, Block currentBlock) { if (declaration == null) { return currentBlock; } var variableDeclaratorBlock = currentBlock; foreach (var variable in declaration.Variables.Reverse()) { variableDeclaratorBlock = BuildVariableDeclarator(variable, variableDeclaratorBlock); } return variableDeclaratorBlock; } private Block BuildVariableDeclarator(VariableDeclaratorSyntax variableDeclarator, Block currentBlock) { // There are variable declarations which implicitly get a value, such as foreach (var x in xs) currentBlock.ReversedInstructions.Add(variableDeclarator); var initializer = variableDeclarator.Initializer?.Value; return initializer == null ? currentBlock : BuildExpression(initializer, currentBlock); } #endregion Build variable declaration #region Create* internal LockBlock CreateLockBlock(LockStatementSyntax lockStatement, Block successor) => AddBlock(new LockBlock(lockStatement, successor)); internal UsingEndBlock CreateUsingFinalizerBlock(UsingStatementSyntax usingStatement, Block successor) => AddBlock(new UsingEndBlock(usingStatement, successor)); #endregion Create* #region Condition /// /// Builds a conditional expression with two successor blocks. The BuildExpression method /// creates a tree with only one successor. /// private Block BuildCondition(ExpressionSyntax expression, Block trueSuccessor, Block falseSuccessor) { expression = expression.RemoveParentheses(); if (expression is BinaryExpressionSyntax binaryExpression) { switch (expression.Kind()) { case SyntaxKind.LogicalOrExpression: return BuildCondition( binaryExpression.Left, trueSuccessor, BuildCondition(binaryExpression.Right, trueSuccessor, falseSuccessor)); case SyntaxKind.LogicalAndExpression: return BuildCondition( binaryExpression.Left, BuildCondition(binaryExpression.Right, trueSuccessor, falseSuccessor), falseSuccessor); case SyntaxKind.CoalesceExpression: return BuildCondition( binaryExpression.Left, BuildCondition(binaryExpression.Right, trueSuccessor, falseSuccessor), CreateBranchBlock(binaryExpression.Left, successors: new[] { trueSuccessor, falseSuccessor })); } } // Fallback to generating an additional branch block for the if statement itself. return BuildExpression(expression, AddBlock(new BinaryBranchBlock(expression, trueSuccessor, falseSuccessor))); } #endregion Condition #endregion Build* private bool IsNameof(InvocationExpressionSyntax expression) => (expression?.Expression as IdentifierNameSyntax)?.Identifier.ToString() == "nameof" && semanticModel.GetSymbolOrCandidateSymbol(expression) is not IMethodSymbol; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/CfgAllPathValidator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { public class CfgAllPathValidator { protected readonly IControlFlowGraph cfg; private readonly HashSet alreadyVisitedBlocks = new HashSet(); protected CfgAllPathValidator(IControlFlowGraph cfg) { this.cfg = cfg; } public bool CheckAllPaths() { return IsBlockValidWithSuccessors(this.cfg.EntryBlock); } private bool IsBlockValidWithSuccessors(Block block) { return !IsBlockInvalid(block) && (IsBlockValid(block) || AreAllSuccessorsValid(block)); } private bool AreAllSuccessorsValid(Block block) { this.alreadyVisitedBlocks.Add(block); if (block.SuccessorBlocks.Contains(this.cfg.ExitBlock) || !block.SuccessorBlocks.Except(this.alreadyVisitedBlocks).Any()) { return false; } return block.SuccessorBlocks .Except(this.alreadyVisitedBlocks) .All(b => IsBlockValidWithSuccessors(b)); } protected virtual bool IsBlockValid(Block block) { return false; } protected virtual bool IsBlockInvalid(Block block) { return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Sonar/IControlFlowGraph.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar { /// /// Represents a Control Flow Graph of a method or property. /// Provides access to the entry, exit and all blocks () inside the CFG. /// public interface IControlFlowGraph { IEnumerable Blocks { get;} Block EntryBlock { get; } ExitBlock ExitBlock { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.csproj ================================================  netstandard2.0 true $(MSBuildProjectDirectory)\Lightup\.generated NU1701 NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/Syntax/Utilities/SyntaxClassifierBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Syntax.Utilities; /// /// This class violates the basic principle that SE should only depend on IOperation. /// /// Anything added here needs to have extremely rare reason why it exists. /// public abstract class SyntaxClassifierBase { public abstract SyntaxNode MemberAccessExpression(SyntaxNode node); protected abstract bool IsCfgBoundary(SyntaxNode node); protected abstract bool IsStatement(SyntaxNode node); protected abstract SyntaxNode ParentLoopCondition(SyntaxNode node); // Detecting loops from CFG shape is not possible from the shape of CFG, because of nested loops. public bool IsInLoopCondition(SyntaxNode node) { while (node is not null) { if (ParentLoopCondition(node) == node) { return true; } if (IsStatement(node) || IsCfgBoundary(node)) { return false; } else { node = node.Parent; } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CFG/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.Composition": { "type": "Direct", "requested": "[1.0.27, )", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Metrics/CSharpCognitiveComplexityMetric.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; namespace SonarAnalyzer.CSharp.Metrics; public static class CSharpCognitiveComplexityMetric { public static CognitiveComplexity GetComplexity(SyntaxNode node) => GetComplexity(node, false); public static CognitiveComplexity GetComplexity(SyntaxNode node, bool onlyGlobalStatements) { var walker = new CognitiveWalker(onlyGlobalStatements); if (node.IsKind(SyntaxKindEx.LocalFunctionStatement)) { walker.VisitLocalFunction((LocalFunctionStatementSyntaxWrapper)node, true); } else { walker.SafeVisit(node); } return new CognitiveComplexity(walker.State.Complexity, walker.State.IncrementLocations.ToImmutableArray()); } private sealed class CognitiveWalker : SafeCSharpSyntaxWalker { private readonly bool onlyGlobalStatements; public CognitiveComplexityWalkerState State { get; } = new(); public CognitiveWalker(bool onlyGlobalStatements) => this.onlyGlobalStatements = onlyGlobalStatements; public override void Visit(SyntaxNode node) { if (node.IsKind(SyntaxKindEx.LocalFunctionStatement)) { VisitLocalFunction((LocalFunctionStatementSyntaxWrapper)node, false); } else if (SwitchExpressionSyntaxWrapper.IsInstance(node)) { var switchExpression = (SwitchExpressionSyntaxWrapper)node; State.IncreaseComplexityByNestingPlusOne(switchExpression.SwitchKeyword); State.VisitWithNesting(node, base.Visit); } else if (BinaryPatternSyntaxWrapper.IsInstance(node)) { var nodeKind = node.Kind(); var binaryPatternNode = (BinaryPatternSyntaxWrapper)node; if ((nodeKind == SyntaxKindEx.AndPattern || nodeKind == SyntaxKindEx.OrPattern) && !State.LogicalOperationsToIgnore.Contains(binaryPatternNode)) { var left = binaryPatternNode.Left.SyntaxNode.RemoveParentheses(); if (!left.IsKind(nodeKind)) { State.IncreaseComplexityByOne(binaryPatternNode.OperatorToken); } var right = binaryPatternNode.Right.SyntaxNode.RemoveParentheses(); if (right.IsKind(nodeKind)) { State.LogicalOperationsToIgnore.Add(right); } } base.Visit(node); } else { base.Visit(node); } } public override void VisitCompilationUnit(CompilationUnitSyntax node) { foreach (var globalStatement in node.Members.Where(x => x.IsKind(SyntaxKind.GlobalStatement))) { base.Visit(globalStatement); } if (!onlyGlobalStatements) { base.VisitCompilationUnit(node); } } public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { State.CurrentMethod = node; base.VisitMethodDeclaration(node); if (State.HasDirectRecursiveCall) { State.HasDirectRecursiveCall = false; State.IncreaseComplexity(node.Identifier, 1, "+1 (recursion)"); } } public override void VisitIfStatement(IfStatementSyntax node) { if (node.Parent.IsKind(SyntaxKind.ElseClause)) { base.VisitIfStatement(node); } else { State.IncreaseComplexityByNestingPlusOne(node.IfKeyword); State.VisitWithNesting(node, base.VisitIfStatement); } } public override void VisitElseClause(ElseClauseSyntax node) { State.IncreaseComplexityByOne(node.ElseKeyword); base.VisitElseClause(node); } public override void VisitConditionalExpression(ConditionalExpressionSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.QuestionToken); State.VisitWithNesting(node, base.VisitConditionalExpression); } public override void VisitSwitchStatement(SwitchStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.SwitchKeyword); State.VisitWithNesting(node, base.VisitSwitchStatement); } public override void VisitForStatement(ForStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.ForKeyword); State.VisitWithNesting(node, base.VisitForStatement); } public override void VisitWhileStatement(WhileStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.WhileKeyword); State.VisitWithNesting(node, base.VisitWhileStatement); } public override void VisitDoStatement(DoStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.DoKeyword); State.VisitWithNesting(node, base.VisitDoStatement); } public override void VisitForEachStatement(ForEachStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.ForEachKeyword); State.VisitWithNesting(node, base.VisitForEachStatement); } public override void VisitCatchClause(CatchClauseSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.CatchKeyword); State.VisitWithNesting(node, base.VisitCatchClause); } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (State.CurrentMethod != null && node.Expression is IdentifierNameSyntax identifierNameSyntax && node.HasExactlyNArguments(State.CurrentMethod.ParameterList.Parameters.Count) && string.Equals(identifierNameSyntax.Identifier.ValueText, State.CurrentMethod.Identifier.ValueText, StringComparison.Ordinal)) { State.HasDirectRecursiveCall = true; } base.VisitInvocationExpression(node); } public override void VisitBinaryExpression(BinaryExpressionSyntax node) { var nodeKind = node.Kind(); if ((nodeKind == SyntaxKind.LogicalAndExpression || nodeKind == SyntaxKind.LogicalOrExpression) && !State.LogicalOperationsToIgnore.Contains(node)) { var left = node.Left.RemoveParentheses(); if (!left.IsKind(nodeKind)) { State.IncreaseComplexityByOne(node.OperatorToken); } var right = node.Right.RemoveParentheses(); if (right.IsKind(nodeKind)) { State.LogicalOperationsToIgnore.Add(right); } } base.VisitBinaryExpression(node); } public override void VisitGotoStatement(GotoStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.GotoKeyword); base.VisitGotoStatement(node); } public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) => State.VisitWithNesting(node, base.VisitSimpleLambdaExpression); public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) => State.VisitWithNesting(node, base.VisitParenthesizedLambdaExpression); public void VisitLocalFunction(LocalFunctionStatementSyntaxWrapper localFunction, bool visitStaticLocalFunctions) { if (visitStaticLocalFunctions || !localFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) { State.VisitWithNesting(localFunction.SyntaxNode, base.Visit); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Metrics/CSharpCyclomaticComplexityMetric.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Metrics; public static class CSharpCyclomaticComplexityMetric { public class CyclomaticComplexity { public CyclomaticComplexity(ImmutableArray locations) { Locations = locations; } public ImmutableArray Locations { get; } public int Complexity => Locations.Length; } public static CyclomaticComplexity GetComplexity(SyntaxNode syntaxNode) => GetComplexity(syntaxNode, false); public static CyclomaticComplexity GetComplexity(SyntaxNode syntaxNode, bool onlyGlobalStatements) { var walker = new CyclomaticWalker(onlyGlobalStatements); if (syntaxNode.IsKind(SyntaxKindEx.LocalFunctionStatement)) { walker.VisitLocalFunction((LocalFunctionStatementSyntaxWrapper)syntaxNode); } else { walker.SafeVisit(syntaxNode); } return new CyclomaticComplexity(walker.IncrementLocations.ToImmutableArray()); } private sealed class CyclomaticWalker : SafeCSharpSyntaxWalker { private readonly bool onlyGlobalStatements; public List IncrementLocations { get; } = new(); public CyclomaticWalker(bool onlyGlobalStatements) => this.onlyGlobalStatements = onlyGlobalStatements; public override void VisitCompilationUnit(CompilationUnitSyntax node) { foreach (var globalStatement in node.Members.Where(x => x.IsKind(SyntaxKind.GlobalStatement))) { if (!IsStaticLocalFunction(globalStatement)) { base.Visit(globalStatement); } } if (!onlyGlobalStatements) { base.VisitCompilationUnit(node); } } public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (node.ExpressionBody != null || HasBody(node)) { AddLocation(node.Identifier); } base.VisitMethodDeclaration(node); } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { if (node.ExpressionBody != null || HasBody(node)) { AddLocation(node.Identifier); } base.VisitPropertyDeclaration(node); } public override void VisitOperatorDeclaration(OperatorDeclarationSyntax node) { AddLocation(node.OperatorToken); base.VisitOperatorDeclaration(node); } public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { AddLocation(node.Identifier); base.VisitConstructorDeclaration(node); } public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node) { AddLocation(node.Identifier); base.VisitDestructorDeclaration(node); } public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) { AddLocation(node.Keyword); base.VisitAccessorDeclaration(node); } public override void VisitIfStatement(IfStatementSyntax node) { AddLocation(node.IfKeyword); base.VisitIfStatement(node); } public override void VisitConditionalExpression(ConditionalExpressionSyntax node) { AddLocation(node.QuestionToken); base.VisitConditionalExpression(node); } public override void VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node) { AddLocation(node.OperatorToken); base.VisitConditionalAccessExpression(node); } public override void VisitWhileStatement(WhileStatementSyntax node) { AddLocation(node.WhileKeyword); base.VisitWhileStatement(node); } public override void VisitDoStatement(DoStatementSyntax node) { AddLocation(node.DoKeyword); base.VisitDoStatement(node); } public override void VisitForStatement(ForStatementSyntax node) { AddLocation(node.ForKeyword); base.VisitForStatement(node); } public override void VisitForEachStatement(ForEachStatementSyntax node) { AddLocation(node.ForEachKeyword); base.VisitForEachStatement(node); } public override void VisitBinaryExpression(BinaryExpressionSyntax node) { if (node.IsKind(SyntaxKind.CoalesceExpression) || node.IsKind(SyntaxKind.LogicalAndExpression) || node.IsKind(SyntaxKind.LogicalOrExpression)) { AddLocation(node.OperatorToken); } base.VisitBinaryExpression(node); } public override void VisitAssignmentExpression(AssignmentExpressionSyntax node) { if (node.IsKind(SyntaxKindEx.CoalesceAssignmentExpression)) { AddLocation(node.OperatorToken); } base.VisitAssignmentExpression(node); } public override void VisitCaseSwitchLabel(CaseSwitchLabelSyntax node) { AddLocation(node.Keyword); base.VisitCaseSwitchLabel(node); } public void VisitLocalFunction(LocalFunctionStatementSyntaxWrapper node) { AddLocation(node.Identifier); base.Visit(node.SyntaxNode); } public override void Visit(SyntaxNode node) { if (SwitchExpressionArmSyntaxWrapper.IsInstance(node)) { var arm = (SwitchExpressionArmSyntaxWrapper)node; AddLocation(arm.EqualsGreaterThanToken); } else if (node?.Kind() is SyntaxKindEx.AndPattern or SyntaxKindEx.OrPattern) { var binaryPatternNode = (BinaryPatternSyntaxWrapper)node; AddLocation(binaryPatternNode.OperatorToken); } if (!IsStaticLocalFunction(node)) { base.Visit(node); } } private void AddLocation(SyntaxToken node) => IncrementLocations.Add(new SecondaryLocation(node.GetLocation(), "+1")); private static bool HasBody(SyntaxNode node) => node.ChildNodes().AnyOfKind(SyntaxKind.Block); private static bool IsStaticLocalFunction(SyntaxNode node) => node.IsKind(SyntaxKindEx.LocalFunctionStatement) && ((LocalFunctionStatementSyntaxWrapper)node).Modifiers.Any(SyntaxKind.StaticKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Metrics/CSharpExecutableLinesMetric.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Metrics; public static class CSharpExecutableLinesMetric { public static ImmutableArray GetLineNumbers(SyntaxTree syntaxTree, SemanticModel semanticModel) { var walker = GetWalker(syntaxTree, semanticModel); walker.SafeVisit(syntaxTree.GetRoot()); return walker.ExecutableLineNumbers.ToImmutableArray(); } private static ExecutableLinesWalker GetWalker(SyntaxTree syntaxTree, SemanticModel semanticModel) => GeneratedCodeRecognizer.IsRazor(syntaxTree) ? new RazorExecutableLinesWalker(semanticModel) : new ExecutableLinesWalker(semanticModel); private class ExecutableLinesWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel model; public HashSet ExecutableLineNumbers { get; } = new(); protected virtual bool AddExecutableLineNumbers(Location location) { ExecutableLineNumbers.Add(location.LineNumberToReport()); return true; } public ExecutableLinesWalker(SemanticModel model) => this.model = model; public override void DefaultVisit(SyntaxNode node) { if (FindExecutableLines(node)) { base.DefaultVisit(node); } } private bool FindExecutableLines(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.AttributeList: return false; case SyntaxKind.CheckedStatement: case SyntaxKind.UncheckedStatement: case SyntaxKind.LockStatement: case SyntaxKind.FixedStatement: case SyntaxKind.UnsafeStatement: case SyntaxKind.UsingStatement: case SyntaxKind.EmptyStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.DoStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.ForStatement: case SyntaxKind.WhileStatement: case SyntaxKind.IfStatement: case SyntaxKind.LabeledStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.ConditionalAccessExpression: case SyntaxKind.ConditionalExpression: case SyntaxKind.GotoStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: case SyntaxKind.YieldBreakStatement: case SyntaxKind.YieldReturnStatement: case SyntaxKind.SimpleMemberAccessExpression: case SyntaxKind.InvocationExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.ArrayInitializerExpression: return AddExecutableLineNumbers(node.GetLocation()); case SyntaxKind.StructDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKindEx.RecordDeclaration: case SyntaxKindEx.RecordStructDeclaration: return !HasExcludedCodeAttribute(node, ((BaseTypeDeclarationSyntax)node).AttributeLists, true); case SyntaxKind.MethodDeclaration: case SyntaxKind.ConstructorDeclaration: return !HasExcludedCodeAttribute(node, ((BaseMethodDeclarationSyntax)node).AttributeLists, true); case SyntaxKind.PropertyDeclaration: return !HasExcludedCodeAttribute(node, ((BasePropertyDeclarationSyntax)node).AttributeLists, true); case SyntaxKind.EventDeclaration: return !HasExcludedCodeAttribute(node, ((BasePropertyDeclarationSyntax)node).AttributeLists, false); case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: case SyntaxKind.GetAccessorDeclaration: case SyntaxKindEx.InitAccessorDeclaration: return !HasExcludedCodeAttribute(node, ((AccessorDeclarationSyntax)node).AttributeLists, false); default: return true; } } private bool HasExcludedCodeAttribute(SyntaxNode node, SyntaxList attributeLists, bool canBePartial) { var hasExcludeFromCodeCoverageAttribute = attributeLists.SelectMany(x => x.Attributes).Any(IsExcludedAttribute); return hasExcludeFromCodeCoverageAttribute || !canBePartial ? hasExcludeFromCodeCoverageAttribute : model.GetDeclaredSymbol(node) is { Kind: SymbolKind.Method or SymbolKind.Property or SymbolKind.NamedType} symbol && symbol.HasAttribute(KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute); } private bool IsExcludedAttribute(AttributeSyntax attribute) => attribute.IsKnownType(KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute, model); } private sealed class RazorExecutableLinesWalker : ExecutableLinesWalker { public RazorExecutableLinesWalker(SemanticModel model) : base(model) { } protected override bool AddExecutableLineNumbers(Location location) { var mappedLocation = location.GetMappedLineSpan(); if (mappedLocation.HasMappedPath) { ExecutableLineNumbers.Add(mappedLocation.StartLinePosition.LineNumberToReport()); } return true; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Metrics/CSharpMetrics.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; namespace SonarAnalyzer.CSharp.Metrics; public class CSharpMetrics : MetricsBase { private readonly Lazy> lazyExecutableLines; public override ImmutableArray ExecutableLines => lazyExecutableLines.Value; public CSharpMetrics(SyntaxTree tree, SemanticModel semanticModel) : base(tree) { if (tree.GetRoot().Language != LanguageNames.CSharp) { throw new ArgumentException(InitializationErrorTextPattern, nameof(tree)); } lazyExecutableLines = new Lazy>(() => CSharpExecutableLinesMetric.GetLineNumbers(tree, semanticModel)); } protected override int ComputeCognitiveComplexity(SyntaxNode node) => CSharpCognitiveComplexityMetric.GetComplexity(node).Complexity; public override int ComputeCyclomaticComplexity(SyntaxNode node) => CSharpCyclomaticComplexityMetric.GetComplexity(node).Complexity; protected override bool IsClass(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.ClassDeclaration: case SyntaxKindEx.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKindEx.RecordStructDeclaration: case SyntaxKind.InterfaceDeclaration: return IsInSameFile(node.GetLocation().GetMappedLineSpan()); default: return false; } } protected override bool IsCommentTrivia(SyntaxTrivia trivia) => trivia.IsComment(); protected override bool IsEndOfFile(SyntaxToken token) => token.IsKind(SyntaxKind.EndOfFileToken); protected override bool IsFunction(SyntaxNode node) { switch (node.Kind()) { case SyntaxKindEx.LocalFunctionStatement: return true; case SyntaxKind.PropertyDeclaration: return ((PropertyDeclarationSyntax)node).ExpressionBody is not null; case SyntaxKind.ConstructorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.DestructorDeclaration: case SyntaxKind.OperatorDeclaration: return ((BaseMethodDeclarationSyntax)node).HasBodyOrExpressionBody(); // Non-abstract, non-interface methods case SyntaxKind.MethodDeclaration: var methodDeclaration = (BaseMethodDeclarationSyntax)node; return methodDeclaration.HasBodyOrExpressionBody() // Non-abstract, non-interface methods && IsInSameFile(methodDeclaration.GetLocation().GetMappedLineSpan()); // Excluding razor functions that are not mapped case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: case SyntaxKindEx.InitAccessorDeclaration: var accessor = (AccessorDeclarationSyntax)node; if (accessor.HasBodyOrExpressionBody()) { return true; } if (accessor is { Parent.Parent: BasePropertyDeclarationSyntax { RawKind: (int)SyntaxKind.PropertyDeclaration or (int)SyntaxKind.EventDeclaration } basePropertyNode }) { return !basePropertyNode.Modifiers.Any(x => x.IsAnyKind(SyntaxKind.AbstractKeyword, SyntaxKind.PartialKeyword)) && !basePropertyNode.Parent.IsKind(SyntaxKind.InterfaceDeclaration); } // Unexpected return false; default: return false; } } protected override bool IsNoneToken(SyntaxToken token) => token.IsKind(SyntaxKind.None); protected override bool IsStatement(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.BreakStatement: case SyntaxKind.CheckedStatement: case SyntaxKind.ContinueStatement: case SyntaxKind.DoStatement: case SyntaxKind.EmptyStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.FixedStatement: case SyntaxKind.ForEachStatement: case SyntaxKindEx.ForEachVariableStatement: case SyntaxKind.ForStatement: case SyntaxKind.GlobalStatement: case SyntaxKind.GotoCaseStatement: case SyntaxKind.GotoDefaultStatement: case SyntaxKind.GotoStatement: case SyntaxKind.IfStatement: case SyntaxKind.LabeledStatement: case SyntaxKind.LocalDeclarationStatement: case SyntaxKindEx.LocalFunctionStatement: case SyntaxKind.LockStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.SwitchStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.TryStatement: case SyntaxKind.UncheckedStatement: case SyntaxKind.UnsafeStatement: case SyntaxKind.UsingStatement: case SyntaxKind.WhileStatement: case SyntaxKind.YieldBreakStatement: case SyntaxKind.YieldReturnStatement: return IsInSameFile(node.GetLocation().GetMappedLineSpan()); // Excluding razor statements that are not mapped case SyntaxKind.Block: return false; default: return node is StatementSyntax ? throw new InvalidOperationException($"{node.Kind()} is statement and it isn't handled.") : false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.Enterprise.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.TestFramework.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AbstractClassToInterface.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AbstractClassToInterface : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1694"; protected override string MessageFormat => "Convert this 'abstract' {0} to an interface."; protected override ILanguageFacade Language => CSharpFacade.Instance; public AbstractClassToInterface() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var symbol = (INamedTypeSymbol)c.Symbol; if (symbol.IsClass() && symbol.IsAbstract && symbol.BaseType.Is(KnownType.System_Object) && !IsRecordWithParameters(symbol) && AllMethodsAreAbstract(symbol) && !symbol.GetMembers().OfType().Any()) { foreach (var declaringSyntaxReference in symbol.DeclaringSyntaxReferences) { var node = declaringSyntaxReference.GetSyntax(); if (node is ClassDeclarationSyntax classDeclaration) { c.ReportIssue(Rule, classDeclaration.Identifier, "class"); } if (RecordDeclarationSyntaxWrapper.IsInstance(node)) { var wrapper = (RecordDeclarationSyntaxWrapper)node; c.ReportIssue(Rule, wrapper.Identifier, "record"); } } } }, SymbolKind.NamedType); private static bool IsRecordWithParameters(ISymbol symbol) => symbol.DeclaringSyntaxReferences.Any(x => x.GetSyntax() is { } node && RecordDeclarationSyntaxWrapper.IsInstance(node) && ((RecordDeclarationSyntaxWrapper)node).ParameterList is { Parameters.Count: > 0 }); private static bool AllMethodsAreAbstract(INamedTypeSymbol symbol) { var methods = symbol.GetMembers().Where(x => x is IMethodSymbol { IsImplicitlyDeclared: false }).ToArray(); return methods.Any() && Array.TrueForAll(methods, x => x.IsAbstract); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AbstractTypesShouldNotHaveConstructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AbstractTypesShouldNotHaveConstructors : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3442"; private const string MessageFormat = "Change the visibility of this constructor to '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if (c.Node.Parent.GetModifiers().Any(SyntaxKind.AbstractKeyword)) { var invalidAccessModifier = c.Node.GetModifiers().FirstOrDefault(IsPublicOrInternal); if (invalidAccessModifier != default) { c.ReportIssue(Rule, invalidAccessModifier, SuggestModifier(invalidAccessModifier)); } } }, SyntaxKind.ConstructorDeclaration); private static bool IsPublicOrInternal(SyntaxToken token) => token.IsKind(SyntaxKind.PublicKeyword) || token.IsKind(SyntaxKind.InternalKeyword); private static string SuggestModifier(SyntaxToken token) => token.IsKind(SyntaxKind.InternalKeyword) ? "private protected" : "protected"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AllBranchesShouldNotHaveSameImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AllBranchesShouldNotHaveSameImplementation : AllBranchesShouldNotHaveSameImplementationBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( context => Analyze(context, (SwitchExpressionSyntaxWrapper)context.Node), SyntaxKindEx.SwitchExpression); context.RegisterNodeAction( new SwitchStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.SwitchStatement); context.RegisterNodeAction( new TernaryStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.ConditionalExpression); context.RegisterNodeAction( new IfStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.ElseClause); } private static void Analyze(SonarSyntaxNodeReportingContext context, SwitchExpressionSyntaxWrapper switchExpression) { var arms = switchExpression.Arms; if (arms.Count < 2) { return; } var firstArm = arms[0]; if (switchExpression.HasDiscardPattern() && arms.Skip(1).All(arm => SyntaxFactory.AreEquivalent(arm.Expression, firstArm.Expression))) { context.ReportIssue(rule, switchExpression.SwitchKeyword, StatementsMessage); } } private class IfStatementAnalyzer : IfStatementAnalyzerBase { protected override bool IsLastElseInChain(ElseClauseSyntax elseSyntax) => !(elseSyntax.Statement is IfStatementSyntax); protected override IEnumerable GetStatements(ElseClauseSyntax elseSyntax) => new[] { elseSyntax.Statement }; protected override IEnumerable> GetIfBlocksStatements(ElseClauseSyntax elseSyntax, out IfStatementSyntax topLevelIf) { var allStatements = new List>(); var currentElse = elseSyntax; topLevelIf = null; while (currentElse?.Parent is IfStatementSyntax currentIf) { topLevelIf = currentIf; allStatements.Add(new[] { currentIf.Statement }); currentElse = currentIf.Parent as ElseClauseSyntax; } return allStatements; } protected override Location GetLocation(IfStatementSyntax topLevelIf) => topLevelIf.IfKeyword.GetLocation(); } private class TernaryStatementAnalyzer : TernaryStatementAnalyzerBase { protected override SyntaxNode GetWhenFalse(ConditionalExpressionSyntax ternaryStatement) => ternaryStatement.WhenFalse.RemoveParentheses(); protected override SyntaxNode GetWhenTrue(ConditionalExpressionSyntax ternaryStatement) => ternaryStatement.WhenTrue.RemoveParentheses(); protected override Location GetLocation(ConditionalExpressionSyntax ternaryStatement) => ternaryStatement.Condition.CreateLocation(ternaryStatement.QuestionToken); } private class SwitchStatementAnalyzer : SwitchStatementAnalyzerBase { protected override bool AreEquivalent(SwitchSectionSyntax section1, SwitchSectionSyntax section2) => SyntaxFactory.AreEquivalent(section1.Statements, section2.Statements); protected override IEnumerable GetSections(SwitchStatementSyntax switchStatement) => switchStatement.Sections; protected override bool HasDefaultLabel(SwitchStatementSyntax switchStatement) => switchStatement.HasDefaultLabel(); protected override Location GetLocation(SwitchStatementSyntax switchStatement) => switchStatement.SwitchKeyword.GetLocation(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AlwaysSetDateTimeKind.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AlwaysSetDateTimeKind : AlwaysSetDateTimeKindBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind ObjectCreationExpression => SyntaxKind.ObjectCreationExpression; protected override string[] ValidNames { get; } = new[] { nameof(DateTime) }; protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterNodeAction(c => { if (IsDateTimeConstructorWithoutKindParameter(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKindEx.ImplicitObjectCreationExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AnonymousDelegateEventUnsubscribe.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AnonymousDelegateEventUnsubscribe : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3244"; private const string MessageFormat = "Unsubscribe with the same delegate that was used for the subscription."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; if (c.Model.GetSymbolInfo(assignment.Left).Symbol is IEventSymbol @event && assignment.Right is AnonymousFunctionExpressionSyntax) { c.ReportIssue(rule, assignment.OperatorToken.CreateLocation(assignment)); } }, SyntaxKind.SubtractAssignmentExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ArgumentSpecifiedForCallerInfoParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ArgumentSpecifiedForCallerInfoParameter : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3236"; private const string MessageFormat = "Remove this argument from the method call; it hides the caller information."; private static readonly ImmutableArray CallerInfoAttributesToReportOn = ImmutableArray.Create( KnownType.System_Runtime_CompilerServices_CallerArgumentExpressionAttribute, KnownType.System_Runtime_CompilerServices_CallerFilePathAttribute, KnownType.System_Runtime_CompilerServices_CallerLineNumberAttribute); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (new CSharpMethodParameterLookup((InvocationExpressionSyntax)c.Node, c.Model) is { MethodSymbol: { } } methodParameterLookup && !(methodParameterLookup.MethodSymbol.ContainingType.Is(KnownType.System_Diagnostics_Debug) && (methodParameterLookup.MethodSymbol.Name == "Assert")) && methodParameterLookup.GetAllArgumentParameterMappings() is { } argumentMappings) { foreach (var argumentMapping in argumentMappings.Where(x => x.Symbol.GetAttributes(CallerInfoAttributesToReportOn).Any() && !IsArgumentPassthroughOfParameter(c.Model, x.Node, x.Symbol))) { c.ReportIssue(Rule, argumentMapping.Node); } } }, SyntaxKind.InvocationExpression); private static bool IsArgumentPassthroughOfParameter(SemanticModel model, ArgumentSyntax argument, IParameterSymbol targetParameter) => model.GetSymbolInfo(argument.Expression).Symbol is IParameterSymbol sourceParameter // the argument passed to the method is itself an parameter. // Let's check if it has the same attributes. && sourceParameter.GetAttributes(CallerInfoAttributesToReportOn).ToList() is var sourceAttributes && targetParameter.GetAttributes(CallerInfoAttributesToReportOn).ToList() is var targetAttributes && targetAttributes.All(x => sourceAttributes.Any(y => x.AttributeClass.Name == y.AttributeClass.Name)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ArrayCovariance.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ArrayCovariance : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2330"; private const string MessageFormat = "Refactor the code to not rely on potentially unsafe array conversions."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(RaiseOnArrayCovarianceInSimpleAssignmentExpression, SyntaxKind.SimpleAssignmentExpression); context.RegisterNodeAction(RaiseOnArrayCovarianceInVariableDeclaration, SyntaxKind.VariableDeclaration); context.RegisterNodeAction(RaiseOnArrayCovarianceInInvocationExpression, SyntaxKind.InvocationExpression); context.RegisterNodeAction(RaiseOnArrayCovarianceInCastExpression, SyntaxKind.CastExpression); } private static void RaiseOnArrayCovarianceInSimpleAssignmentExpression(SonarSyntaxNodeReportingContext context) { var assignment = (AssignmentExpressionSyntax)context.Node; VerifyExpression(assignment.Right, context.Model.GetTypeInfo(assignment.Left).Type, context); } private static void RaiseOnArrayCovarianceInVariableDeclaration(SonarSyntaxNodeReportingContext context) { var variableDeclaration = (VariableDeclarationSyntax)context.Node; var baseType = context.Model.GetTypeInfo(variableDeclaration.Type).Type; foreach (var declaration in variableDeclaration.Variables.Where(syntax => syntax.Initializer != null)) { VerifyExpression(declaration.Initializer.Value, baseType, context); } } private static void RaiseOnArrayCovarianceInInvocationExpression(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; var methodParameterLookup = new CSharpMethodParameterLookup(invocation, context.Model); foreach (var argument in invocation.ArgumentList.Arguments) { if (!methodParameterLookup.TryGetSymbol(argument, out var parameter) || parameter.IsParams) { continue; } VerifyExpression(argument.Expression, parameter.Type, context); } } private static void RaiseOnArrayCovarianceInCastExpression(SonarSyntaxNodeReportingContext context) { var castExpression = (CastExpressionSyntax)context.Node; var baseType = context.Model.GetTypeInfo(castExpression.Type).Type; VerifyExpression(castExpression.Expression, baseType, context); } private static void VerifyExpression(SyntaxNode node, ITypeSymbol baseType, SonarSyntaxNodeReportingContext context) { foreach (var pair in GetPossibleTypes(node, context.Model).Where(pair => AreCovariantArrayTypes(pair.Symbol, baseType))) { context.ReportIssue(Rule, pair.Node); } } private static bool AreCovariantArrayTypes(ITypeSymbol typeDerivedArray, ITypeSymbol typeBaseArray) { if (typeDerivedArray == null || !(typeBaseArray is {Kind: SymbolKind.ArrayType}) || typeDerivedArray.Kind != SymbolKind.ArrayType) { return false; } var typeDerivedElement = ((IArrayTypeSymbol)typeDerivedArray).ElementType; var typeBaseElement = ((IArrayTypeSymbol)typeBaseArray).ElementType; return typeDerivedElement.BaseType?.ConstructedFrom.DerivesFrom(typeBaseElement) == true; } private static IEnumerable GetPossibleTypes(SyntaxNode syntax, SemanticModel semanticModel) { while (syntax is ParenthesizedExpressionSyntax parenthesizedExpression) { syntax = parenthesizedExpression.Expression; } if (syntax is ConditionalExpressionSyntax conditionalExpression) { yield return new NodeTypePair(conditionalExpression.WhenTrue, semanticModel); yield return new NodeTypePair(conditionalExpression.WhenFalse, semanticModel); } else if (syntax.IsKind(SyntaxKind.CoalesceExpression)) { var binaryExpression = (BinaryExpressionSyntax)syntax; yield return new NodeTypePair(binaryExpression.Left, semanticModel); yield return new NodeTypePair(binaryExpression.Right, semanticModel); } else { yield return new NodeTypePair(syntax, semanticModel); } } private readonly struct NodeTypePair { public ITypeSymbol Symbol { get; } public SyntaxNode Node { get; } public NodeTypePair(SyntaxNode node, SemanticModel model) { Symbol = model.GetTypeInfo(node).Type; Node = node; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ArrayPassedAsParams.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ArrayPassedAsParams : ArrayPassedAsParamsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] ExpressionKinds { get; } = [ SyntaxKind.ObjectCreationExpression, SyntaxKind.InvocationExpression, SyntaxKindEx.ImplicitObjectCreationExpression, SyntaxKind.Attribute ]; protected override CSharpSyntaxNode LastArgumentIfArrayCreation(SyntaxNode expression) { CSharpSyntaxNode lastArgument = expression switch { AttributeSyntax { ArgumentList.Arguments: { Count: > 0 } args } when args.Last().NameEquals is null => args.Last(), _ when ArgumentList(expression) is { Arguments: { Count: > 0 } args } => args.Last(), _ => null }; return IsArrayCreation(lastArgument) ? lastArgument : null; } protected override ITypeSymbol ArrayElementType(CSharpSyntaxNode argument, SemanticModel model) => ArgumentExpression(argument) switch { ArrayCreationExpressionSyntax arrayCreation => model.GetTypeInfo(arrayCreation.Type.ElementType).Type, ImplicitArrayCreationExpressionSyntax implicitArrayCreation => (model.GetTypeInfo(implicitArrayCreation).Type as IArrayTypeSymbol)?.ElementType, _ => null }; private static BaseArgumentListSyntax ArgumentList(SyntaxNode expression) => expression switch { ObjectCreationExpressionSyntax { } creation => creation.ArgumentList, InvocationExpressionSyntax { } invocation => invocation.ArgumentList, _ when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(expression) => ((ImplicitObjectCreationExpressionSyntaxWrapper)expression).ArgumentList, _ => null }; private static bool IsArrayCreation(CSharpSyntaxNode argument) { var expression = ArgumentExpression(argument); return expression switch { ArrayCreationExpressionSyntax { Initializer: not null } => true, ImplicitArrayCreationExpressionSyntax => true, _ when CollectionExpressionSyntaxWrapper.IsInstance(expression) => !ContainsSpread((CollectionExpressionSyntaxWrapper)expression), _ => false }; } // [x, y, ..z] is not possible to be passed as params private static bool ContainsSpread(CollectionExpressionSyntaxWrapper expression) => expression.Elements.Any(x => x.SyntaxNode.IsKind(SyntaxKindEx.SpreadElement)); private static ExpressionSyntax ArgumentExpression(CSharpSyntaxNode node) => node switch { ArgumentSyntax arg => arg.Expression, AttributeArgumentSyntax arg => arg.Expression, _ => null }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/AnnotateApiActionsWithHttpVerb.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AnnotateApiActionsWithHttpVerb : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6965"; private const string MessageFormat = "REST API controller actions should be annotated with the appropriate HTTP verb attribute."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray HttpMethodAttributes = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttribute, KnownType.Microsoft_AspNetCore_Mvc_AcceptVerbsAttribute); // AcceptVerbs is treated as an exception public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (!compilationStartContext.Compilation.ReferencesNetCoreControllers()) { return; } compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { var controllerSymbol = (INamedTypeSymbol)symbolStartContext.Symbol; var controllerAttributes = controllerSymbol.GetAttributesWithInherited(); if (controllerSymbol.IsControllerType() && controllerAttributes.Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute)) && !IgnoresApiExplorer(controllerAttributes)) { symbolStartContext.RegisterSyntaxNodeAction(c => { var methodNode = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodNode); var methodAttributes = methodSymbol.GetAttributesWithInherited(); if (methodSymbol.IsControllerActionMethod() && !methodSymbol.IsAbstract && !methodAttributes.Any(x => x.AttributeClass.DerivesFromAny(HttpMethodAttributes)) && !IgnoresApiExplorer(methodAttributes)) { c.ReportIssue(Rule, methodNode.Identifier.GetLocation()); } }, SyntaxKind.MethodDeclaration); } }, SymbolKind.NamedType); }); // Tracks [ApiExplorerSettings(IgnoreApi = true)] private static bool IgnoresApiExplorer(IEnumerable attributes) => attributes.FirstOrDefault(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiExplorerSettingsAttribute)) is { } apiExplorerSettings && apiExplorerSettings.TryGetAttributeValue("IgnoreApi", out var ignoreApi) && ignoreApi; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ApiControllersShouldNotDeriveDirectlyFromController.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ApiControllersShouldNotDeriveDirectlyFromController : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S6961"; private const string MessageFormat = "Inherit from ControllerBase instead of Controller."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet ViewIdentifiers = [ "Json", "OnActionExecuted", "OnActionExecuting", "OnActionExecutionAsync", "PartialView", "TempData", "View", "ViewBag", "ViewComponent", "ViewData", "ViewResult" ]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (!compilationStartContext.Compilation.ReferencesNetCoreControllers()) { return; } compilationStartContext.RegisterSymbolStartAction(symbolStartContext => CheckController(symbolStartContext), SymbolKind.NamedType); }); private static void CheckController(SonarSymbolStartAnalysisContext context) { var controllerSymbol = (INamedTypeSymbol)context.Symbol; if (controllerSymbol.IsControllerType() && controllerSymbol.IsPubliclyAccessible() && controllerSymbol.AnyAttributeDerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute) && controllerSymbol.BaseType.Is(KnownType.Microsoft_AspNetCore_Mvc_Controller)) { var shouldReportController = true; context.RegisterSyntaxNodeAction(nodeContext => { if (ViewIdentifiers.Contains(nodeContext.Node.GetName())) { shouldReportController = false; } }, SyntaxKind.IdentifierName); context.RegisterSymbolEndAction(symbolEndContext => { if (shouldReportController) { ReportIssue(symbolEndContext, controllerSymbol); } }); } } private static void ReportIssue(SonarSymbolReportingContext context, INamedTypeSymbol controllerSymbol) { var reportLocations = controllerSymbol.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Select(x => x.BaseList?.DescendantNodes().FirstOrDefault(x => x is TypeSyntax && x.NameIs("Controller"))?.GetLocation()) .OfType(); foreach (var location in reportLocations) { context.ReportIssue(CSharpFacade.Instance.GeneratedCodeRecognizer, Rule, location); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix : SonarCodeFix { internal const string Title = "Inherit from ControllerBase instead of Controller."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ApiControllersShouldNotDeriveDirectlyFromController.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var toReplace = root.FindNode(context.Diagnostics.First().Location.SourceSpan) as BaseTypeSyntax; var replacement = root.ReplaceNode(toReplace, SyntaxFactory.SimpleBaseType(SyntaxFactory.IdentifierName("ControllerBase").WithTriviaFrom(toReplace))) .WithAdditionalAnnotations(Formatter.Annotation); context.RegisterCodeFix( Title, x => Task.FromResult(context.Document.WithSyntaxRoot(replacement)), context.Diagnostics); return Task.CompletedTask; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/AvoidUnderPosting.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidUnderPosting : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6964"; private const string MessageFormat = "Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray IgnoredTypes = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Http_IFormCollection, KnownType.Microsoft_AspNetCore_Http_IFormFile, KnownType.Microsoft_AspNetCore_Http_IFormFileCollection); private static readonly ImmutableArray IgnoredAttributes = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_ModelBinding_BindNeverAttribute, KnownType.Microsoft_AspNetCore_Mvc_ModelBinding_BindRequiredAttribute, KnownType.Newtonsoft_Json_JsonIgnoreAttribute, KnownType.Newtonsoft_Json_JsonRequiredAttribute, KnownType.System_ComponentModel_DataAnnotations_RangeAttribute, KnownType.System_Text_Json_Serialization_JsonIgnoreAttribute, KnownType.System_Text_Json_Serialization_JsonRequiredAttribute); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!compilationStart.Compilation.ReferencesNetCoreControllers()) { return; } var examinedTypes = new ConcurrentDictionary(); compilationStart.RegisterSymbolStartAction(symbolStart => { var type = (INamedTypeSymbol)symbolStart.Symbol; if (type.IsControllerType()) { symbolStart.RegisterSyntaxNodeAction(nodeContext => ProcessControllerMethods(nodeContext, examinedTypes), SyntaxKind.MethodDeclaration); } }, SymbolKind.NamedType); }); private static void ProcessControllerMethods(SonarSyntaxNodeReportingContext context, ConcurrentDictionary examinedTypes) { if (context.Model.GetDeclaredSymbol(context.Node) is IMethodSymbol method && method.IsControllerActionMethod()) { var modelParameterTypes = method.Parameters .Where(x => !HasValidateNeverAttribute(x)) .SelectMany(x => RelatedTypesToExamine(x.Type, method.ContainingType)) .Distinct(); foreach (var modelParameterType in modelParameterTypes) { CheckInvalidProperties(modelParameterType, context, examinedTypes); } } } private static void CheckInvalidProperties(INamedTypeSymbol parameterType, SonarSyntaxNodeReportingContext context, ConcurrentDictionary examinedTypes) { var declaredProperties = new List(); GetAllDeclaredProperties(parameterType, examinedTypes, declaredProperties); var invalidProperties = declaredProperties .Where(x => !IsExcluded(x)) .Select(x => x.GetFirstSyntaxRef()) .Where(x => !IsInitialized(x)); foreach (var property in invalidProperties) { context.ReportIssue(Rule, property.GetIdentifier()?.GetLocation()); } static bool IsExcluded(IPropertySymbol property) => CanBeNull(property.Type) || property.HasAnyAttribute(IgnoredAttributes) || IsNewtonsoftJsonPropertyRequired(property) || property.IsRequired(); static bool IsNewtonsoftJsonPropertyRequired(IPropertySymbol property) => property.GetAttributes(KnownType.Newtonsoft_Json_JsonPropertyAttribute).FirstOrDefault() is { } attribute && attribute.TryGetAttributeValue("Required", out int required) && (required is 1 or 2); // Required.AllowNull = 1, Required.Always = 2, https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Required.htm } private static bool IgnoreType(ITypeSymbol type) => type is not INamedTypeSymbol namedType // e.g. dynamic type || namedType.IsAny(IgnoredTypes) || !CanBeUsedInModelBinding(namedType); private static bool CanBeUsedInModelBinding(INamedTypeSymbol type) => !type.IsTupleType() // Tuples are not supported (unless a custom Model Binder is used) && (type.Constructors.Any(x => x.Parameters.Length == 0) // The type must have a parameterless constructor, unless || type.IsValueType // - it's a value type || type.IsRecord() // - it's a record type || type.IsInterface() // - it's an interface (although the type that implements will be actually used) || type.Is(KnownType.System_String)); // - it has a custom Model Binder (e.g. System.String has one) private static bool CanBeNull(ITypeSymbol type) => type is ITypeParameterSymbol { HasValueTypeConstraint: false } || type.IsReferenceType || type.IsNullableValueType(); private static void GetAllDeclaredProperties(ITypeSymbol type, ConcurrentDictionary examinedTypes, List declaredProperties) { if (type is INamedTypeSymbol namedType && examinedTypes.TryAdd(namedType, true) && !IgnoreType(namedType) && !HasValidateNeverAttribute(type)) { var properties = namedType.GetMembers() .OfType() .Where(x => x.GetEffectiveAccessibility() == Accessibility.Public && x.SetMethod?.DeclaredAccessibility is Accessibility.Public && !HasValidateNeverAttribute(x) && x.DeclaringSyntaxReferences.Length > 0 && !IgnoreType(x.Type)); foreach (var property in properties) { declaredProperties.Add(property); if (property.Type.DeclaringSyntaxReferences.Length > 0) { GetAllDeclaredProperties(property.Type, examinedTypes, declaredProperties); } } ITypeSymbol[] relatedTypes = [namedType.BaseType, .. namedType.TypeArguments]; foreach (var relatedType in relatedTypes) { GetAllDeclaredProperties(relatedType, examinedTypes, declaredProperties); } } } // We only consider Model types that are in the same assembly as the Controller, because Roslyn can't raise an issue when the location is in a different assembly than the one being analyzed. private static IEnumerable RelatedTypesToExamine(ITypeSymbol type, ITypeSymbol controllerType) => type switch { IArrayTypeSymbol array => RelatedTypesToExamine(array.ElementType, controllerType), INamedTypeSymbol collection when collection.DerivesOrImplements(KnownType.System_Collections_Generic_IEnumerable_T) => collection.TypeArguments.SelectMany(x => RelatedTypesToExamine(x, controllerType)), INamedTypeSymbol namedType when type.IsInSameAssembly(controllerType) && !type.HasAttribute(KnownType.Microsoft_AspNetCore_Mvc_ModelBinding_BindNeverAttribute) => [namedType], _ => [] }; private static bool HasValidateNeverAttribute(ISymbol symbol) => symbol.HasAttribute(KnownType.Microsoft_AspNetCore_Mvc_ModelBinding_Validation_ValidateNeverAttribute); private static bool IsInitialized(SyntaxNode node) => node is ParameterSyntax { Default: not null } or PropertyDeclarationSyntax { Initializer: not null }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BackslashShouldBeAvoidedInAspNetRoutes : BackslashShouldBeAvoidedInAspNetRoutesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds => [SyntaxKind.Argument, SyntaxKind.AttributeArgument]; protected override bool IsNamedAttributeArgument(SyntaxNode node) => node is AttributeArgumentSyntax { NameEquals: not null }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/CallModelStateIsValid.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CallModelStateIsValid : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6967"; private const string MessageFormat = "ModelState.IsValid should be checked in controller actions."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly SyntaxKind[] PropertyAccessSyntaxNodesToVisit = [ SyntaxKind.ConditionalAccessExpression, SyntaxKind.SimpleMemberAccessExpression, SyntaxKindEx.Subpattern]; private static readonly ImmutableArray IgnoredArgumentTypes = ImmutableArray.Create( KnownType.System_Object, KnownType.System_String, KnownType.System_Threading_CancellationToken); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { // The rule ignores the project completely if any of these conditions are met: // - the project doesn't reference ASP.NET MVC // - the project references the FluentValidation library: // - as an alternative to using ModelState.IsValid // - this can made more accurate: check if those validation methods are used in the controller actions rather than just checking whether the library is referenced // - the [ApiController] attribute is applied on the assembly level: this results in the attribute being applied to every Controller class in the project if (compilationStart.Compilation.ReferencesNetCoreControllers() && compilationStart.Compilation.GetTypeByMetadataName(KnownType.FluentValidation_IValidator) is null && !compilationStart.Compilation.Assembly.HasAttribute(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute)) { compilationStart.RegisterSymbolStartAction(symbolStart => { var type = (INamedTypeSymbol)symbolStart.Symbol; if (type.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase) && !HasApiControllerAttribute(type) && !HasActionFilterAttribute(type)) { symbolStart.RegisterCodeBlockStartAction(ProcessCodeBlock); } }, SymbolKind.NamedType); } }); private static void ProcessCodeBlock(SonarCodeBlockStartAnalysisContext codeBlockContext) { if (codeBlockContext.CodeBlock is MethodDeclarationSyntax methodDeclaration && codeBlockContext.OwningSymbol is IMethodSymbol methodSymbol && !methodSymbol.Parameters.All(IgnoreParameter) && methodSymbol.IsControllerActionMethod() && !HasActionFilterAttribute(methodSymbol)) { var isModelValidated = false; codeBlockContext.RegisterNodeAction(nodeContext => { if (!isModelValidated) { isModelValidated = IsCheckingValidityProperty(nodeContext.Node, nodeContext.Model); } }, PropertyAccessSyntaxNodesToVisit); codeBlockContext.RegisterNodeAction(nodeContext => { if (!isModelValidated) { isModelValidated = IsTryValidateInvocation(nodeContext.Node, nodeContext.Model); } }, SyntaxKind.InvocationExpression); codeBlockContext.RegisterCodeBlockEndAction(blockEnd => { if (!isModelValidated) { blockEnd.ReportIssue(Rule, methodDeclaration.Identifier); } }); } } private static bool IgnoreParameter(IParameterSymbol parameter) => !parameter.GetAttributes().Any(x => x.AttributeClass.DerivesFrom(KnownType.System_ComponentModel_DataAnnotations_ValidationAttribute)) && (parameter.Type.TypeKind == TypeKind.Dynamic || parameter.Type.IsAny(IgnoredArgumentTypes)); private static bool HasApiControllerAttribute(ITypeSymbol type) => type.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute)); private static bool HasActionFilterAttribute(ISymbol symbol) => symbol.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_Filters_ActionFilterAttribute)); private static bool IsCheckingValidityProperty(SyntaxNode node, SemanticModel model) => node.GetIdentifier() is { ValueText: "IsValid" or "ValidationState" } nodeIdentifier && model.GetSymbolInfo(nodeIdentifier.Parent).Symbol is IPropertySymbol propertySymbol && propertySymbol.ContainingType.Is(KnownType.Microsoft_AspNetCore_Mvc_ModelBinding_ModelStateDictionary); private static bool IsTryValidateInvocation(SyntaxNode node, SemanticModel model) => node is InvocationExpressionSyntax invocation && invocation.GetName() == "TryValidateModel" && model.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol method && method.ContainingType.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersHaveMixedResponsibilities.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ControllersHaveMixedResponsibilities : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6960"; private const string MessageFormat = "This controller has multiple responsibilities and could be split into {0} smaller controllers."; private const string UnspeakableIndexerName = "I"; // All indexers are considered part of the same group public enum MemberType { Service, Action, } private static readonly HashSet ExcludedWellKnownServices = [ "ILogger", "IMediator", "IMapper", "IConfiguration", "IBus", "IMessageBus", "IHttpClientFactory" ]; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (!compilationStartContext.Compilation.ReferencesNetCoreControllers()) { return; } compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { var symbol = (INamedTypeSymbol)symbolStartContext.Symbol; if (symbol.IsCoreApiController() && symbol.BaseType.Is(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase) && !symbol.IsAbstract) { var relevantMembers = RelevantMembers(symbol); if (relevantMembers.Count < 2) { return; } var dependencies = new ConcurrentStack(); symbolStartContext.RegisterCodeBlockStartAction(PopulateDependencies(relevantMembers, dependencies)); symbolStartContext.RegisterSymbolEndAction(CalculateAndReportOnResponsibilities(symbol, relevantMembers, dependencies)); } }, SymbolKind.NamedType); }); private static Action> PopulateDependencies( ImmutableDictionary relevantMembers, ConcurrentStack dependencies) => codeBlockStartContext => { if (BlockName(codeBlockStartContext.CodeBlock) is { } blockName) { codeBlockStartContext.RegisterNodeAction(c => { if (c.Node.GetName() is { } dependencyName && relevantMembers.ContainsKey(blockName) && relevantMembers.ContainsKey(dependencyName)) { dependencies.Push(new(blockName, dependencyName)); } }, SyntaxKind.IdentifierName); } }; private static Action CalculateAndReportOnResponsibilities( INamedTypeSymbol controllerSymbol, ImmutableDictionary relevantMembers, ConcurrentStack dependencies) => symbolEndContext => { if (ResponsibilityGroups(relevantMembers, dependencies) is { Count: > 1 } responsibilityGroups) { var secondaryLocations = SecondaryLocations(controllerSymbol, responsibilityGroups).ToList(); foreach (var primaryLocation in IdentifierLocations(controllerSymbol)) { symbolEndContext.ReportIssue(Rule, primaryLocation, secondaryLocations, responsibilityGroups.Count.ToString()); } } }; private static List> ResponsibilityGroups( ImmutableDictionary relevantMembers, ConcurrentStack dependencies) { var dependencySets = new DisjointSets(relevantMembers.Keys); foreach (var dependency in dependencies) { dependencySets.Union(dependency.From, dependency.To); } return dependencySets .GetAllSets() // Filter out sets of only actions or only services .Where(x => x.Exists(x => relevantMembers[x] == MemberType.Service) && x.Exists(x => relevantMembers[x] == MemberType.Action)) .ToList(); } private static string BlockName(SyntaxNode block) => block switch { AccessorDeclarationSyntax { Parent.Parent: PropertyDeclarationSyntax property } => property.GetName(), AccessorDeclarationSyntax { Parent.Parent: IndexerDeclarationSyntax } => UnspeakableIndexerName, ArrowExpressionClauseSyntax { Parent: PropertyDeclarationSyntax property } => property.GetName(), ArrowExpressionClauseSyntax { Parent: IndexerDeclarationSyntax } => UnspeakableIndexerName, MethodDeclarationSyntax method => method.GetName(), PropertyDeclarationSyntax property => property.GetName(), _ => null }; private static ImmutableDictionary RelevantMembers(INamedTypeSymbol symbol) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var member in symbol.GetMembers().Where(x => !x.IsStatic)) { switch (member) { // Constructors are not considered because they have to be split anyway // Accessors are not considered because they are part of properties, that are considered as a whole case IMethodSymbol method when !method.IsConstructor() && method.MethodKind != MethodKind.StaticConstructor && method.AssociatedSymbol is not IPropertySymbol: builder.Add(method.Name, MemberType.Action); break; // Indexers are treated as methods with an unspeakable name case IPropertySymbol { IsIndexer: true }: builder.Add(UnspeakableIndexerName, MemberType.Action); break; // Primary constructor parameters may or may not generate fields, and must be considered case IMethodSymbol { IsPrimaryConstructor: true } method: builder.AddRange(method.Parameters.Where(IsService).Select(x => new KeyValuePair(x.Name, MemberType.Service))); break; // Backing fields are excluded for auto-properties, since they are considered part of the property case IFieldSymbol field when IsService(field) && !field.IsImplicitlyDeclared: builder.Add(field.Name, MemberType.Service); break; case IPropertySymbol property when IsService(property): builder.Add(property.Name, MemberType.Service); break; } } return builder.ToImmutable(); } private static bool IsService(ISymbol symbol) => !ExcludedWellKnownServices.Contains(symbol.GetSymbolType().Name); private static IEnumerable SecondaryLocations(INamedTypeSymbol controllerSymbol, List> sets) { for (var setIndex = 0; setIndex < sets.Count; setIndex++) { foreach (var memberLocation in sets[setIndex].SelectMany(MemberLocations)) { yield return new SecondaryLocation(memberLocation, $"May belong to responsibility #{setIndex + 1}."); } } IEnumerable MemberLocations(string memberName) => controllerSymbol.GetMembers(memberName).OfType().SelectMany(IdentifierLocations); } private static IEnumerable IdentifierLocations(ISymbol symbol) where T : SyntaxNode => symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).OfType().Select(x => x.GetIdentifier()?.GetLocation()); private record struct Dependency(string From, string To); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/ControllersReuseClient.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ControllersReuseClient : ReuseClientBase { private const string DiagnosticId = "S6962"; private const string MessageFormat = "Reuse HttpClient instances rather than create new ones with each controller action invocation."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override ImmutableArray ReusableClients => ImmutableArray.Create(KnownType.System_Net_Http_HttpClient); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (!compilationStartContext.Compilation.ReferencesNetCoreControllers()) { return; } compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { var symbol = (INamedTypeSymbol)symbolStartContext.Symbol; if (symbol.IsControllerType()) { symbolStartContext.RegisterSyntaxNodeAction(nodeContext => { var node = nodeContext.Node; if (IsInPublicMethod(node) && IsReusableClient(nodeContext) && !IsInsideConstructor(node) && !IsAssignedForReuse(nodeContext)) { nodeContext.ReportIssue(Rule, node); } }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); } }, SymbolKind.NamedType); }); public static bool IsInPublicMethod(SyntaxNode node) => node.FirstAncestorOrSelf() is not { } method || (method.FirstAncestorOrSelf() is null && method.Modifiers.Any(x => x.IsKind(SyntaxKind.PublicKeyword))); private static bool IsInsideConstructor(SyntaxNode node) => node.HasAncestor(SyntaxKind.ConstructorDeclaration, SyntaxKindEx.PrimaryConstructorBaseType); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/RouteTemplateShouldNotStartWithSlash.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RouteTemplateShouldNotStartWithSlash : RouteTemplateShouldNotStartWithSlashBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/SpecifyRouteAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SpecifyRouteAttribute() : SonarDiagnosticAnalyzer(DiagnosticId) { private const string DiagnosticId = "S6934"; private const string SecondaryLocationMessage = "By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level."; protected override string MessageFormat => "Specify the RouteAttribute when an HttpMethodAttribute or RouteAttribute is specified at an action level."; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!UsesAttributeRouting(compilationStart.Compilation)) { return; } compilationStart.RegisterSymbolStartAction(symbolStart => { if (symbolStart.Symbol.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesOrImplements(KnownType.Microsoft_AspNetCore_Mvc_Routing_IRouteTemplateProvider))) { return; } var secondaryLocations = new ConcurrentStack(); symbolStart.RegisterSyntaxNodeAction(nodeContext => { var methodDeclaration = (MethodDeclarationSyntax)nodeContext.Node; if (nodeContext.Model.GetDeclaredSymbol(methodDeclaration, nodeContext.Cancel) is { } method && !method.ContainingType.IsAbstract && method.IsControllerActionMethod() && method.GetAttributesWithInherited().Any(x => !CanBeIgnored(x.GetAttributeRouteTemplate()))) { secondaryLocations.Push(methodDeclaration.Identifier.ToSecondaryLocation(SecondaryLocationMessage)); } }, SyntaxKind.MethodDeclaration); symbolStart.RegisterSymbolEndAction(symbolEnd => ReportIssues(symbolEnd, symbolEnd.Symbol, secondaryLocations)); }, SymbolKind.NamedType); }); private void ReportIssues(SonarSymbolReportingContext context, ISymbol symbol, ConcurrentStack secondaryLocations) { if (secondaryLocations.IsEmpty) { return; } foreach (var declaration in symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax())) { if (declaration.GetIdentifier() is { } identifier) { context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, Rule, identifier.GetLocation(), secondaryLocations); } } } private static bool UsesAttributeRouting(Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttribute) is not null; private static bool CanBeIgnored(string template) => string.IsNullOrEmpty(template) // See: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing#combining-attribute-routes // Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. || template.StartsWith("/") || template.StartsWith("~/"); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AspNet/UseAspNetModelBinding.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseAspNetModelBinding : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6932"; private const string UseAspNetModelBindingMessage = "Use model binding instead of accessing the raw request data"; private const string UseIFormFileBindingMessage = "Use IFormFile or IFormFileCollection binding instead"; private static readonly KnownType[] ActionFilterTypes = [ KnownType.Microsoft_AspNetCore_Mvc_Filters_IActionFilter, KnownType.Microsoft_AspNetCore_Mvc_Filters_IAsyncActionFilter]; protected override string MessageFormat => "{0}"; protected override ILanguageFacade Language => CSharpFacade.Instance; public UseAspNetModelBinding() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { var descriptors = GetDescriptors(compilationStartContext.Compilation); if (descriptors.ArgumentDescriptors.Any() || descriptors.PropertyAccessDescriptors.Any()) { RegisterForSymbols(compilationStartContext, descriptors); } }); private void RegisterForSymbols(SonarCompilationStartAnalysisContext compilationStartContext, Descriptors descriptors) => compilationStartContext.RegisterSymbolStartAction(symbolStart => { // If the user overrides any action filters, model binding may not be working as expected. // Then we do not want to raise on expressions that originate from parameters. // See the OverridesController.Undecidable test cases for details. var hasActionFiltersOverrides = false; var candidates = new ConcurrentStack(); // In SymbolEnd, we filter the candidates based on the overriding we learn on the go. if (((INamedTypeSymbol)symbolStart.Symbol).IsControllerType()) { symbolStart.RegisterCodeBlockStartAction(codeBlockStart => { if (IsOverridingFilterMethods(codeBlockStart.OwningSymbol)) { // We do not want to raise in ActionFilter overrides and so we do not register. // The SymbolEndAction needs to be made aware, that there are // ActionFilter overrides, so it can filter out some candidates. hasActionFiltersOverrides = true; } else { RegisterCodeBlockActions(codeBlockStart, descriptors, candidates); } }); } symbolStart.RegisterSymbolEndAction(symbolEnd => { foreach (var candidate in candidates.Where(x => !(hasActionFiltersOverrides && x.OriginatesFromParameter))) { symbolEnd.ReportIssue(Rule, candidate.Location, candidate.Message); } }); }, SymbolKind.NamedType); private void RegisterCodeBlockActions( SonarCodeBlockStartAnalysisContext codeBlockStart, Descriptors descriptors, ConcurrentStack controllerCandidates) { // Within a single code block, access via constant and variable keys could be mixed. // We only want to raise, if all access were done via constants. var allConstantAccesses = true; var codeBlockCandidates = new ConcurrentStack(); var (argumentDescriptors, propertyAccessDescriptors) = descriptors; if (argumentDescriptors.Any()) { codeBlockStart.RegisterNodeAction(nodeContext => { var argument = (ArgumentSyntax)nodeContext.Node; var model = nodeContext.Model; if (allConstantAccesses && AddMatchingArgumentToCandidates(model, codeBlockCandidates, argument, argumentDescriptors) && model.GetConstantValue(argument.Expression) is not { HasValue: true, Value: string }) { allConstantAccesses = false; } }, SyntaxKind.Argument); } if (propertyAccessDescriptors.Any()) { codeBlockStart.RegisterNodeAction(nodeContext => { // The property access of Request.Form.Files can be replaced by an IFormFile binding. // Any access to a "Files" property is therefore noncompliant. This is different from the Argument handling above. var memberAccess = (MemberAccessExpressionSyntax)nodeContext.Node; var context = new PropertyAccessContext(memberAccess, nodeContext.Model, memberAccess.Name.Identifier.ValueText); if (Language.Tracker.PropertyAccess.MatchProperty(propertyAccessDescriptors)(context) // form.Files is okay, if "form" is a parameter, because IFormCollection binding is considered appropriate for binding as well && nodeContext.Model.GetSymbolInfo(memberAccess.Expression).Symbol is not IParameterSymbol) { codeBlockCandidates.Push(new(UseIFormFileBindingMessage, memberAccess.GetLocation(), IsOriginatingFromParameter(nodeContext.Model, memberAccess))); } }, SyntaxKind.SimpleMemberAccessExpression); } codeBlockStart.RegisterCodeBlockEndAction(codeBlockEnd => { if (allConstantAccesses && codeBlockCandidates.ToArray() is { Length: > 0 } candidates) // Net core 2.2: PushRange throws for empty arrays https://stackoverflow.com/q/7487097 { controllerCandidates.PushRange(candidates); } }); } private bool AddMatchingArgumentToCandidates( SemanticModel model, ConcurrentStack codeBlockCandidates, ArgumentSyntax argument, ArgumentDescriptor[] argumentDescriptors) { var context = new ArgumentContext(argument, model); if (Array.Exists(argumentDescriptors, x => Language.Tracker.Argument.MatchArgument(x)(context))) { codeBlockCandidates.Push(new(UseAspNetModelBindingMessage, GetPrimaryLocation(argument), IsOriginatingFromParameter(model, argument))); return true; } return false; } private static Descriptors GetDescriptors(Compilation compilation) { var argumentDescriptors = new List(); var propertyAccessDescriptors = new List(); if (compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Mvc_ControllerAttribute) is { }) { AddAspNetCoreDescriptors(argumentDescriptors, propertyAccessDescriptors); } // TODO: Add descriptors for Asp.Net MVC 4.x return new([.. argumentDescriptors], [.. propertyAccessDescriptors]); } private static void AddAspNetCoreDescriptors(List argumentDescriptors, List propertyAccessDescriptors) { const string TryGetValue = nameof(IDictionary.TryGetValue); const string ContainsKey = nameof(IDictionary.ContainsKey); argumentDescriptors.AddRange([ ArgumentDescriptor.ElementAccess(// Request.Form["id"] invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IFormCollection, invokedIndexerExpression: "Form", parameterConstraint: _ => true, // There is only a single overload and it is getter only argumentPosition: 0), ArgumentDescriptor.MethodInvocation(// Request.Form.TryGetValue("id", out _) invokedType: KnownType.Microsoft_AspNetCore_Http_IFormCollection, methodName: TryGetValue, parameterName: "key", argumentPosition: 0), ArgumentDescriptor.MethodInvocation(// Request.Form.ContainsKey("id") invokedType: KnownType.Microsoft_AspNetCore_Http_IFormCollection, methodName: ContainsKey, parameterName: "key", argumentPosition: 0), ArgumentDescriptor.ElementAccess(// Request.Headers["id"] invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IHeaderDictionary, invokedIndexerExpression: "Headers", parameterConstraint: IsGetterParameter, // Headers are read/write argumentPosition: 0), ArgumentDescriptor.MethodInvocation(// Request.Headers.TryGetValue("id", out _) invokedMemberConstraint: x => IsIDictionaryStringStringValuesInvocation(x, TryGetValue), // TryGetValue is from IDictionary here. We check the type arguments. invokedMemberNameConstraint: (name, comparison) => string.Equals(name, TryGetValue, comparison), invokedMemberNodeConstraint: IsAccessedViaHeaderDictionary, parameterConstraint: x => string.Equals(x.Name, "key", StringComparison.Ordinal), argumentListConstraint: (list, position) => list.Count == 2 && position is 0 or null, refKind: RefKind.None), ArgumentDescriptor.MethodInvocation(// Request.Headers.ContainsKey("id") invokedMemberConstraint: x => IsIDictionaryStringStringValuesInvocation(x, ContainsKey), invokedMemberNameConstraint: (name, comparison) => string.Equals(name, ContainsKey, comparison), invokedMemberNodeConstraint: IsAccessedViaHeaderDictionary, parameterConstraint: x => string.Equals(x.Name, "key", StringComparison.Ordinal), argumentListConstraint: (list, _) => list.Count == 1, refKind: RefKind.None), ArgumentDescriptor.ElementAccess(// Request.Query["id"] invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Http_IQueryCollection, invokedIndexerExpression: "Query", parameterConstraint: _ => true, // There is only a single overload and it is getter only argumentPosition: 0), ArgumentDescriptor.MethodInvocation(// Request.Query.TryGetValue("id", out _) invokedType: KnownType.Microsoft_AspNetCore_Http_IQueryCollection, methodName: TryGetValue, parameterName: "key", argumentPosition: 0), ArgumentDescriptor.ElementAccess(// Request.RouteValues["id"] invokedIndexerContainer: KnownType.Microsoft_AspNetCore_Routing_RouteValueDictionary, invokedIndexerExpression: "RouteValues", parameterConstraint: IsGetterParameter, // RouteValues are read/write argumentPosition: 0), ArgumentDescriptor.MethodInvocation(// Request.RouteValues.TryGetValue("id", out _) invokedType: KnownType.Microsoft_AspNetCore_Routing_RouteValueDictionary, methodName: TryGetValue, parameterName: "key", argumentPosition: 0)]); propertyAccessDescriptors.Add(new(KnownType.Microsoft_AspNetCore_Http_IFormCollection, "Files")); // Request.Form.Files... } // Check that the "Headers" expression in the Headers.TryGetValue("id", out _) invocation is of type IHeaderDictionary private static bool IsAccessedViaHeaderDictionary(SemanticModel model, ILanguageFacade language, SyntaxNode invocation) => invocation is InvocationExpressionSyntax { Expression: { } expression } && expression.GetLeftOfDot() is { } left && model.GetTypeInfo(left) is { Type: { } typeSymbol } && typeSymbol.Is(KnownType.Microsoft_AspNetCore_Http_IHeaderDictionary); private static bool IsOverridingFilterMethods(ISymbol owningSymbol) => (owningSymbol.GetOverriddenMember() ?? owningSymbol).ExplicitOrImplicitInterfaceImplementations().Any(x => x is IMethodSymbol { ContainingType: { } container } && container.IsAny(ActionFilterTypes)); private static bool IsOriginatingFromParameter(SemanticModel semanticModel, ArgumentSyntax argument) => GetExpressionOfArgumentParent(argument) is { } parentExpression && IsOriginatingFromParameter(semanticModel, parentExpression); private static bool IsOriginatingFromParameter(SemanticModel semanticModel, ExpressionSyntax expression) => MostLeftOfDottedChain(expression) is { } mostLeft && semanticModel.GetSymbolInfo(mostLeft).Symbol is IParameterSymbol; private static ExpressionSyntax MostLeftOfDottedChain(ExpressionSyntax root) { var current = root.GetRootConditionalAccessExpression()?.Expression ?? root; while (current.Kind() is SyntaxKind.SimpleMemberAccessExpression or SyntaxKind.ElementAccessExpression) { current = current switch { MemberAccessExpressionSyntax { Expression: { } left } => left, ElementAccessExpressionSyntax { Expression: { } left } => left, _ => throw new InvalidOperationException("Unreachable"), }; } return current; } private static ExpressionSyntax GetExpressionOfArgumentParent(ArgumentSyntax argument) => argument switch { { Parent: BracketedArgumentListSyntax { Parent: ElementBindingExpressionSyntax expression } } => expression.GetParentConditionalAccessExpression(), { Parent: BracketedArgumentListSyntax { Parent: ElementAccessExpressionSyntax { Expression: { } expression } } } => expression, { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax { Expression: { } expression } } } => expression, _ => null, }; private static Location GetPrimaryLocation(ArgumentSyntax argument) => ((SyntaxNode)GetExpressionOfArgumentParent(argument) ?? argument).GetLocation(); private static bool IsGetterParameter(IParameterSymbol parameter) => parameter.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.PropertyGet }; private static bool IsIDictionaryStringStringValuesInvocation(IMethodSymbol method, string name) => method.Is(KnownType.System_Collections_Generic_IDictionary_TKey_TValue, name) && method.ContainingType.TypeArguments is { Length: 2 } typeArguments && typeArguments[0].Is(KnownType.System_String) && typeArguments[1].Is(KnownType.Microsoft_Extensions_Primitives_StringValues); private readonly record struct ReportCandidate(string Message, Location Location, bool OriginatesFromParameter); private readonly record struct Descriptors(ArgumentDescriptor[] ArgumentDescriptors, MemberDescriptor[] PropertyAccessDescriptors); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AssertionArgsShouldBePassedInCorrectOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AssertionArgsShouldBePassedInCorrectOrder : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3415"; private const string MessageFormat = "Make sure these 2 arguments are in the correct order: expected value, actual value."; private const string Expected = "expected"; private const string Actual = "actual"; private const string NotExpected = "notExpected"; private const string ExpectedSubstring = "expectedSubstring"; private const string ActualString = "actualString"; private const string ExpectedEndString = "expectedEndString"; private const string ExpectedStartString = "expectedStartString"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly KnownAssertParameters[] NUnitParameters = [ new(KnownType.NUnit_Framework_Assert, Expected, Actual), new(KnownType.NUnit_Framework_Legacy_ClassicAssert, Expected, Actual) ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node is InvocationExpressionSyntax { ArgumentList.Arguments.Count: >= 2 } invocation && Parameters(invocation.GetName()) is { } knownAssertParameters && c.Model.GetSymbolInfo(invocation).AllSymbols() .SelectMany(symbol => symbol is IMethodSymbol { IsStatic: true, ContainingSymbol: INamedTypeSymbol container } methodSymbol ? knownAssertParameters.Select(knownParameters => FindWrongArguments(c.Model, container, methodSymbol, invocation, knownParameters)) : []) .FirstOrDefault(x => x is not null) is { ExpectedArgs: { } wrongExpected, ActualArgs: { } wrongActual }) { c.ReportIssue(Rule, CreateLocation(wrongExpected, wrongActual)); } }, SyntaxKind.InvocationExpression); private static KnownAssertParameters[] Parameters(string name) => name switch { "AreEqual" => [ new(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert, Expected, Actual), ..NUnitParameters ], "AreNotEqual" => [ new(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert, NotExpected, Actual), ..NUnitParameters ], "AreSame" => [ new(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert, Expected, Actual), ..NUnitParameters ], "AreNotSame" => [ new(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert, NotExpected, Actual), ..NUnitParameters ], "Equal" or "NotEqual" or "Same" or "NotSame" or "Equivalent" or "EquivalentWithExclusions" or "StrictEqual" or "NotStrictEqual" => [ new(KnownType.Xunit_Assert, Expected, Actual) ], "Contains" or "DoesNotContain" => [ new(KnownType.Xunit_Assert, ExpectedSubstring, ActualString), ], "EndsWith" => [ new(KnownType.Xunit_Assert, ExpectedEndString, ActualString) ], "StartsWith" => [ new(KnownType.Xunit_Assert, ExpectedStartString, ActualString) ], _ => null }; private static WrongArguments? FindWrongArguments(SemanticModel model, INamedTypeSymbol container, IMethodSymbol symbol, InvocationExpressionSyntax invocation, KnownAssertParameters knownParameters) => container.Is(knownParameters.AssertClass) && CSharpFacade.Instance.MethodParameterLookup(invocation, symbol) is var parameterLookup && parameterLookup.TryGetSyntax(knownParameters.ExpectedParameterName, out var expectedArguments) && expectedArguments.FirstOrDefault() is { } expected && !model.GetConstantValue(expected).HasValue && parameterLookup.TryGetSyntax(knownParameters.ActualParameterName, out var actualArguments) && actualArguments.FirstOrDefault() is { } actual && model.GetConstantValue(actual).HasValue ? new(expected, actual) : null; private static Location CreateLocation(SyntaxNode argument1, SyntaxNode argument2) => argument1.Span.CompareTo(argument2.Span) < 0 ? argument1.Parent.CreateLocation(argument2.Parent) : argument2.Parent.CreateLocation(argument1.Parent); private readonly record struct KnownAssertParameters(KnownType AssertClass, string ExpectedParameterName, string ActualParameterName); private readonly record struct WrongArguments(SyntaxNode ExpectedArgs, SyntaxNode ActualArgs); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AssertionsShouldBeComplete.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AssertionsShouldBeComplete : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2970"; private const string MessageFormat = "Complete the assertion"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(start => { if (start.Compilation.References(KnownAssembly.FluentAssertions)) { start.RegisterNodeAction(c => CheckInvocation(c, invocation => invocation.NameIs("Should") && c.Model.GetSymbolInfo(invocation).AllSymbols().Any(x => x is IMethodSymbol { IsExtensionMethod: true, ReturnsVoid: false, ContainingType: { } container, ReturnType: { } returnType, } && (container.Is(KnownType.FluentAssertions_AssertionExtensions) // ⬆️ Built in assertions. ⬇️ Custom assertions (the majority at least). || returnType.DerivesFrom(KnownType.FluentAssertions_Primitives_ReferenceTypeAssertions)))), SyntaxKind.InvocationExpression); } if (start.Compilation.References(KnownAssembly.NFluent)) { start.RegisterNodeAction(c => CheckInvocation(c, invocation => invocation.NameIs("That", "ThatEnum", "ThatCode", "ThatAsyncCode", "ThatDynamic") && c.Model.GetSymbolInfo(invocation) is { Symbol: IMethodSymbol { IsStatic: true, ReturnsVoid: false, ContainingType: { IsStatic: true } container } } && container.Is(KnownType.NFluent_Check)), SyntaxKind.InvocationExpression); } if (start.Compilation.References(KnownAssembly.NSubstitute)) { start.RegisterNodeAction(c => CheckInvocation(c, invocation => invocation.NameIs("Received", "DidNotReceive", "ReceivedWithAnyArgs", "DidNotReceiveWithAnyArgs", "ReceivedCalls") && c.Model.GetSymbolInfo(invocation) is { Symbol: IMethodSymbol { IsExtensionMethod: true, ReturnsVoid: false, ContainingType: { } container, } } && container.Is(KnownType.NSubstitute_SubstituteExtensions)), SyntaxKind.InvocationExpression); } }); private static void CheckInvocation(SonarSyntaxNodeReportingContext c, Func isAssertionMethod) { if (c.Node is InvocationExpressionSyntax invocation && isAssertionMethod(invocation) && !HasContinuation(invocation)) { c.ReportIssue(Rule, invocation.GetIdentifier()?.GetLocation()); } } private static bool HasContinuation(InvocationExpressionSyntax invocation) { var closeParen = invocation.ArgumentList.CloseParenToken; if (!closeParen.IsKind(SyntaxKind.CloseParenToken) || closeParen.IsMissing || !invocation.GetLastToken().Equals(closeParen)) { // Any invocation should end with ")". We are in unknown territory here. return true; } var nextToken = closeParen.GetNextToken(); if (!nextToken.IsKind(SyntaxKind.SemicolonToken)) { // There is something right to the invocation that is not a semicolon. return true; } // We are in some kind of statement context "??? Should();" // The result might be stored in a variable or returned from the method/property return nextToken.Parent switch { MethodDeclarationSyntax { ReturnType: { } returnType } => !IsVoid(returnType), { } parent when LocalFunctionStatementSyntaxWrapper.IsInstance(parent) => !IsVoid(((LocalFunctionStatementSyntaxWrapper)parent).ReturnType), PropertyDeclarationSyntax => true, AccessorDeclarationSyntax { Keyword.RawKind: (int)SyntaxKind.GetKeyword } => true, ReturnStatementSyntax => true, LocalDeclarationStatementSyntax => true, ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax or ConditionalAccessExpressionSyntax { WhenNotNull: AssignmentExpressionSyntax } } => true, _ => false, }; } private static bool IsVoid(TypeSyntax type) => type is PredefinedTypeSyntax { Keyword.RawKind: (int)SyntaxKind.VoidKeyword }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AssignmentInsideSubExpression.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AssignmentInsideSubExpression : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1121"; private const string MessageFormat = "Extract the assignment of '{0}' from this expression."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet AllowedParentExpressionKinds = [ SyntaxKind.SimpleAssignmentExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression, ]; private static readonly HashSet RelationalExpressionKinds = [ SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKindEx.IsPatternExpression ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; var topParenthesizedExpression = assignment.GetSelfOrTopParenthesizedExpression(); if (IsNonCompliantSubExpression(assignment, topParenthesizedExpression) || IsDirectlyInStatementCondition(assignment, topParenthesizedExpression)) { c.ReportIssue(Rule, assignment.OperatorToken, assignment.Left.ToString()); } }, SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression); private static bool IsNonCompliantSubExpression(AssignmentExpressionSyntax assignment, ExpressionSyntax topParenthesizedExpression) => !IsInInitializerExpression(topParenthesizedExpression) && topParenthesizedExpression.Parent.FirstAncestorOrSelf() is { } expressionParent && !IsCompliantAssignmentInsideExpression(assignment, expressionParent); private static bool IsInInitializerExpression(ExpressionSyntax expression) => expression.Parent?.Kind() is SyntaxKindEx.WithInitializerExpression or SyntaxKind.ObjectInitializerExpression; private static bool IsCompliantAssignmentInsideExpression(AssignmentExpressionSyntax assignment, ExpressionSyntax expressionParent) => IsCompliantCoalesceExpression(expressionParent, assignment) || IsCompliantNullConditionalAssignment(assignment) || (RelationalExpressionKinds.Contains(expressionParent.Kind()) && IsInStatementCondition(expressionParent)) || (AllowedParentExpressionKinds.Contains(expressionParent.Kind()) && !IsInInitializerExpression(expressionParent)); private static bool IsCompliantNullConditionalAssignment(AssignmentExpressionSyntax assignment) => assignment.AncestorsAndSelf().TakeWhile(x => x is ExpressionSyntax).OfType().LastOrDefault() is { } conditionalAccess && conditionalAccess.Parent.FirstAncestorOrSelf() is var outerExpression && (outerExpression is null || AllowedParentExpressionKinds.Contains(outerExpression.Kind())); private static bool IsCompliantCoalesceExpression(ExpressionSyntax parentExpression, AssignmentExpressionSyntax assignment) => assignment.IsKind(SyntaxKind.SimpleAssignmentExpression) && CoalesceExpressionParent(parentExpression) is { } coalesceExpression && CSharpEquivalenceChecker.AreEquivalent(assignment.Left.RemoveParentheses(), coalesceExpression.Left.RemoveParentheses()); private static BinaryExpressionSyntax CoalesceExpressionParent(ExpressionSyntax parent) => parent.AncestorsAndSelf() .TakeWhile(x => x is ExpressionSyntax) .OfType() .FirstOrDefault(x => x.IsKind(SyntaxKind.CoalesceExpression)); private static bool IsDirectlyInStatementCondition(ExpressionSyntax expression, ExpressionSyntax topParenthesizedExpression) { return IsDirectlyInStatementCondition(x => x.Condition) || IsDirectlyInStatementCondition(x => x.Condition) || IsDirectlyInStatementCondition(x => x.Condition) || IsDirectlyInStatementCondition(x => x.Condition); bool IsDirectlyInStatementCondition(Func conditionSelector) where T : SyntaxNode => topParenthesizedExpression.Parent.FirstAncestorOrSelf() is { } statement && conditionSelector(statement).RemoveParentheses() == expression; } private static bool IsInStatementCondition(ExpressionSyntax expression) { return expression.GetSelfOrTopParenthesizedExpression() is var expressionOrParenthesizedParent && (IsInStatementCondition(x => x.Condition) || IsInStatementCondition(x => x.Condition) || IsInStatementCondition(x => x.Condition) || IsInStatementCondition(x => x.Condition)); bool IsInStatementCondition(Func conditionSelector) where T : SyntaxNode => expressionOrParenthesizedParent.Parent.FirstAncestorOrSelf() is { } statement && conditionSelector(statement) is { } condition && condition.Contains(expression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AsyncAwaitIdentifier.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AsyncAwaitIdentifier : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2306"; private const string MessageFormat = "Rename '{0}' to not use a contextual keyword as an identifier."; private static readonly ISet AsyncOrAwait = new HashSet { "async", "await" }; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterTreeAction( c => { foreach (var asyncOrAwaitToken in GetAsyncOrAwaitTokens(c.Tree.GetRoot()) .Where(token => !token.Parent.AncestorsAndSelf().OfType().Any())) { c.ReportIssue(rule, asyncOrAwaitToken, asyncOrAwaitToken.ToString()); } }); } private static IEnumerable GetAsyncOrAwaitTokens(SyntaxNode node) { return node.DescendantTokens() .Where(token => token.IsKind(SyntaxKind.IdentifierToken) && AsyncOrAwait.Contains(token.ToString())); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AsyncVoidMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AsyncVoidMethod : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3168"; private const string MessageFormat = "Return 'Task' instead."; private const string MsTestV1AssemblyName = "Microsoft.VisualStudio.QualityTools.UnitTestFramework"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray AllowedAsyncVoidMsTestAttributes = ImmutableArray.Create( KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyCleanupAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyInitializeAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_ClassCleanupAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_ClassInitializeAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_TestCleanupAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_TestInitializeAttribute); private static readonly HashSet ParentTypeSyntaxKinds = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.InterfaceDeclaration ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration); if (IsViolatingRule(methodSymbol) && !IsExceptionToTheRule(methodDeclaration, methodSymbol)) { c.ReportIssue(Rule, methodDeclaration.ReturnType); } }, SyntaxKind.MethodDeclaration); private static bool IsViolatingRule(IMethodSymbol methodSymbol) => methodSymbol is {IsAsync: true, ReturnsVoid: true} && methodSymbol.IsChangeable(); private static bool IsExceptionToTheRule(MethodDeclarationSyntax methodDeclaration, IMethodSymbol methodSymbol) => methodSymbol.IsEventHandler() || IsAcceptedUsage(methodDeclaration) || IsNamedAsEventHandler(methodSymbol) || HasAnyMsTestV1AllowedAttribute(methodSymbol); private static bool IsAcceptedUsage(MethodDeclarationSyntax methodDeclaration) => GetParentDeclaration(methodDeclaration) is { } parentDeclaration && parentDeclaration .DescendantNodes() .SelectMany(node => node switch { ObjectCreationExpressionSyntax objectCreation => GetIdentifierArguments(objectCreation), InvocationExpressionSyntax invocation => GetIdentifierArguments(invocation), AssignmentExpressionSyntax assignment => GetIdentifierRightHandSide(assignment), _ => Enumerable.Empty() }) .Any(x => x.Identifier.ValueText == methodDeclaration.Identifier.ValueText); private static IEnumerable GetIdentifierArguments(ObjectCreationExpressionSyntax objectCreation) => objectCreation.ArgumentList?.Arguments.Select(x => x.Expression).OfType() ?? Enumerable.Empty(); private static IEnumerable GetIdentifierArguments(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.Select(x => x.Expression).OfType(); private static IEnumerable GetIdentifierRightHandSide(AssignmentExpressionSyntax assignment) => assignment.IsKind(SyntaxKind.AddAssignmentExpression) && assignment.Right is IdentifierNameSyntax identifier ? new[] { identifier } : Enumerable.Empty(); private static SyntaxNode GetParentDeclaration(SyntaxNode syntaxNode) => syntaxNode.FirstAncestorOrSelf(x => x.IsAnyKind(ParentTypeSyntaxKinds)); private static bool IsNamedAsEventHandler(ISymbol symbol) => symbol.Name.Length > 2 && symbol.Name.StartsWith("On") && char.IsUpper(symbol.Name[2]); private static bool HasAnyMsTestV1AllowedAttribute(IMethodSymbol methodSymbol) => methodSymbol.GetAttributes().Any(x => x.AttributeClass.ContainingAssembly.Name == MsTestV1AssemblyName && x.AttributeClass.IsAny(AllowedAsyncVoidMsTestAttributes)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AvoidDateTimeNowForBenchmarking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidDateTimeNowForBenchmarking : AvoidDateTimeNowForBenchmarkingBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool ContainsDateTimeArgument(InvocationExpressionSyntax invocation, SemanticModel model) => invocation.ArgumentList.Arguments[0].Expression.IsKnownType(KnownType.System_DateTime, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AvoidExcessiveClassCoupling.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidExcessiveClassCoupling : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S1200"; private const string MessageFormat = "Split this {0} into smaller and more specialized ones to reduce its dependencies on other types from {1} to the maximum authorized {2} or less."; private const int ThresholdDefaultValue = 30; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); private static readonly ImmutableArray IgnoredTypes = ImmutableArray.Create( KnownType.Void, KnownType.System_Boolean, KnownType.System_Byte, KnownType.System_SByte, KnownType.System_Int16, KnownType.System_UInt16, KnownType.System_Int32, KnownType.System_UInt32, KnownType.System_Int64, KnownType.System_UInt64, KnownType.System_IntPtr, KnownType.System_UIntPtr, KnownType.System_Char, KnownType.System_Single, KnownType.System_Double, KnownType.System_String, KnownType.System_Object, KnownType.System_Threading_Tasks_Task, KnownType.System_Threading_Tasks_Task_T, KnownType.System_Threading_Tasks_ValueTask_TResult, KnownType.System_Action, KnownType.System_Action_T, KnownType.System_Action_T1_T2, KnownType.System_Action_T1_T2_T3, KnownType.System_Action_T1_T2_T3_T4, KnownType.System_Func_TResult, KnownType.System_Func_T_TResult, KnownType.System_Func_T1_T2_TResult, KnownType.System_Func_T1_T2_T3_TResult, KnownType.System_Func_T1_T2_T3_T4_TResult, KnownType.System_Lazy); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("max", PropertyType.Integer, "Maximum number of types a single type is allowed to depend upon", ThresholdDefaultValue)] public int Threshold { get; set; } = ThresholdDefaultValue; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var typeDeclaration = (TypeDeclarationSyntax)c.Node; if (typeDeclaration.Identifier.IsMissing || c.IsRedundantPositionalRecordContext()) { return; } var type = c.Model.GetDeclaredSymbol(typeDeclaration); var collector = new TypeDependencyCollector(c.Model, typeDeclaration); collector.SafeVisit(typeDeclaration); var dependentTypes = collector.DependentTypes .SelectMany(ExpandGenericTypes) .Distinct() .Where(IsTrackedType) .Where(t => !t.Equals(type)) .ToList(); if (dependentTypes.Count > Threshold) { c.ReportIssue(Rule, typeDeclaration.Identifier, typeDeclaration.Keyword.ValueText, dependentTypes.Count.ToString(), Threshold.ToString()); } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static bool IsTrackedType(INamedTypeSymbol namedType) => namedType.TypeKind != TypeKind.Enum && !namedType.IsAny(IgnoredTypes); /// /// Returns all type symbols reachable from the provided named type: /// the original generic definition, all containing types (recursively expanded), /// and all type arguments (recursively expanded) or constraint types for unbound generics. /// For example: /// Dictionary<string, int> returns Dictionary<TKey,TValue>, string, int /// List<T> where T : IDisposable returns List<T>, IDisposable /// Outer<int>.Inner returns Outer<T>.Inner, Outer<T>, int /// private static IEnumerable ExpandGenericTypes(INamedTypeSymbol namedType) { yield return namedType.OriginalDefinition; if (namedType.ContainingType is { } containing) { foreach (var expanded in ExpandGenericTypes(containing)) { yield return expanded; } } if (!namedType.IsGenericType) { yield break; } var typeArgs = namedType.IsUnboundGenericType ? namedType.TypeParameters.SelectMany(ConstraintTypes) : namedType.TypeArguments.OfType().SelectMany(ExpandGenericTypes); foreach (var expanded in typeArgs) { yield return expanded; } static IEnumerable ConstraintTypes(ITypeParameterSymbol typeParameter) => typeParameter.ConstraintTypes.OfType().SelectMany(ExpandGenericTypes); } private sealed class TypeDependencyCollector : SafeCSharpSyntaxWalker { private readonly SemanticModel model; private readonly TypeDeclarationSyntax originalTypeDeclaration; public ISet DependentTypes { get; } = new HashSet(); public TypeDependencyCollector(SemanticModel model, TypeDeclarationSyntax originalTypeDeclaration) { this.model = model; this.originalTypeDeclaration = originalTypeDeclaration; } // This override centralises all traversal guards: // - TypeSyntax subtrees are skipped entirely; type dependencies are collected at the parent level // via node.TypeSyntax() + AddDependentType, avoiding O(n²) GetSymbolInfo calls on every identifier. // - Nested type declarations are not drilled into; each type is analysed independently. // VisitRecordDeclaration / VisitRecordStructDeclaration are not available in this Roslyn version, // so all type-declaration kinds are handled here uniformly via the facade's TypeDeclaration array. public override void Visit(SyntaxNode node) { if (node is TypeSyntax) { return; } if (node != originalTypeDeclaration && CSharpFacade.Instance.SyntaxKind.TypeDeclaration.Contains(node.Kind())) { return; } if (node.TypeSyntax() is { } typeSyntax) { AddDependentType(typeSyntax); } base.Visit(node); } public override void VisitVariableDeclarator(VariableDeclaratorSyntax node) { if (node.Initializer is not null) { AddDependentType(model.GetTypeInfo(node.Initializer.Value)); } base.VisitVariableDeclarator(node); } public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { // Only call GetSymbolInfo if the left-hand chain is a pure name chain (identifiers and member // accesses terminating at a TypeSyntax). Invocation results, conditionals, array elements, // this/base etc. can never be type references, so we skip the semantic lookup entirely. if (!IsSimpleNameChain(node.Expression)) { base.VisitMemberAccessExpression(node); return; } if (model.GetSymbolInfo(node.Expression).Symbol is INamedTypeSymbol symbol) { AddDependentType(symbol); return; } base.VisitMemberAccessExpression(node); } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (!node.IsNameof(model)) { // GenericNameSyntax is a TypeSyntax, so Visit skips it. We must handle type arguments explicitly. if (node.Expression switch { GenericNameSyntax x => x, MemberAccessExpressionSyntax { Name: GenericNameSyntax x } => x, _ => null, } is { TypeArgumentList.Arguments: { } typeArgs }) { foreach (var typeArg in typeArgs) { AddDependentType(typeArg); } } base.VisitInvocationExpression(node); } } // Returns true if the expression is a chain of MemberAccessExpressionSyntax nodes whose // leftmost element is a TypeSyntax (IdentifierName, QualifiedName, AliasQualifiedName, etc.). // This means the chain could be a qualified type reference like Console, NS.MyClass, Outer.Inner. // Any other terminal node (ThisExpression, BaseExpression, InvocationExpression, conditional, etc.) // cannot be a type, so GetSymbolInfo does not need to be called. private static bool IsSimpleNameChain(ExpressionSyntax expression) { var current = expression; while (current is MemberAccessExpressionSyntax ma) { current = ma.Expression; } return current is TypeSyntax; } private void AddDependentType(TypeSyntax typeSyntax) { if (typeSyntax?.Unwrap() is not PredefinedTypeSyntax and { } unwrapped) { AddDependentType(model.GetSymbolInfo(unwrapped).Symbol); } } private void AddDependentType(TypeInfo typeInfo) { AddDependentType(typeInfo.Type); AddDependentType(typeInfo.ConvertedType); } private bool AddDependentType(ISymbol symbol) => symbol switch { INamedTypeSymbol named => DependentTypes.Add(named), IArrayTypeSymbol array => AddDependentType(array.ElementType), IAliasSymbol alias => AddDependentType(alias.Target), IPointerTypeSymbol pointer => AddDependentType(pointer.PointedAtType), _ => false }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AvoidExcessiveInheritance.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AvoidExcessiveInheritance : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S110"; private const string MessageFormat = "This {0} has {1} parents which is greater than {2} authorized."; private const string FilteredClassesDefaultValue = ""; private const int MaximumDepthDefaultValue = 5; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); private string filteredClasses = FilteredClassesDefaultValue; private ICollection filters = new List(); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("max", PropertyType.Integer, "Maximum depth of the inheritance tree. (Number)", MaximumDepthDefaultValue)] public int MaximumDepth { get; set; } = MaximumDepthDefaultValue; [RuleParameter( "filteredClasses", PropertyType.String, "Comma-separated list of classes or records to be filtered out of the count of inheritance. Depth " + "counting will stop when a filtered class or record is reached. For example: System.Windows.Controls.UserControl, " + "System.Windows.*. (String)", FilteredClassesDefaultValue)] public string FilteredClasses { get => filteredClasses; set { filteredClasses = value; filters = filteredClasses.Split(',').Select(WildcardPatternToRegularExpression).ToList(); } } protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } var objectTypeInfo = new ObjectTypeInfo(c.Node, c.Model); if (objectTypeInfo.Symbol is null) { return; } var thisTypeRootNamespace = GetRootNamespace(objectTypeInfo.Symbol); var baseTypesCount = objectTypeInfo.Symbol.BaseType.GetSelfAndBaseTypes() .TakeWhile(s => GetRootNamespace(s) == thisTypeRootNamespace) .Select(nts => nts.OriginalDefinition.ToDisplayString()) .TakeWhile(className => filters.All(regex => !regex.SafeIsMatch(className))) .Count(); if (baseTypesCount > MaximumDepth) { c.ReportIssue(Rule, objectTypeInfo.Identifier, objectTypeInfo.Name, baseTypesCount.ToString(), MaximumDepth.ToString()); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private static string GetRootNamespace(ISymbol symbol) { var ns = symbol.ContainingNamespace; while (ns?.ContainingNamespace?.IsGlobalNamespace is false) { ns = ns.ContainingNamespace; } return ns?.Name ?? string.Empty; } private static Regex WildcardPatternToRegularExpression(string pattern) { var regexPattern = string.Concat("^", Regex.Escape(pattern).Replace("\\*", ".*"), "$"); return new Regex(regexPattern, RegexOptions.Compiled, Constants.DefaultRegexTimeout); } private readonly struct ObjectTypeInfo { public SyntaxToken Identifier { get; } public INamedTypeSymbol Symbol { get; } public string Name { get; } public ObjectTypeInfo(SyntaxNode node, SemanticModel model) { if (node is ClassDeclarationSyntax classDeclaration) { Identifier = classDeclaration.Identifier; Symbol = model.GetDeclaredSymbol(classDeclaration); Name = "class"; } else { var wrapper = (RecordDeclarationSyntaxWrapper)node; Identifier = wrapper.Identifier; Symbol = model.GetDeclaredSymbol(wrapper); Name = "record"; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AvoidLambdaExpressionInLoopsInBlazor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidLambdaExpressionInLoopsInBlazor : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6802"; private const string MessageFormat = "Avoid using lambda expressions in loops in Blazor markup."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet AddAttributeMethods = new HashSet { "AddAttribute", "AddMultipleAttributes" }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { // If we are not in a Blazor project, we don't need to register for lambda expressions. if (cc.Compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Components_Rendering_RenderTreeBuilder) is null) { return; } cc.RegisterNodeAction(c => { var node = (LambdaExpressionSyntax)c.Node; if (IsWithinLoopBody(node) && IsWithinRenderTreeBuilderInvocation(node, c.Model)) { c.ReportIssue(Rule, node); } }, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); }); private static bool IsWithinRenderTreeBuilderInvocation(SyntaxNode node, SemanticModel semanticModel) => node.AncestorsAndSelf().Any(x => x is InvocationExpressionSyntax invocation && AddAttributeMethods.Contains(invocation.GetName()) && semanticModel.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol symbol && symbol.ContainingType.GetSymbolType().Is(KnownType.Microsoft_AspNetCore_Components_Rendering_RenderTreeBuilder)); private static bool IsWithinLoopBody(SyntaxNode node) => node.AncestorsAndSelf().Any(x => x is BlockSyntax && x.Parent is ForStatementSyntax or ForEachStatementSyntax or WhileStatementSyntax or DoStatementSyntax); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/AvoidUnsealedAttributes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidUnsealedAttributes : AvoidUnsealedAttributesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BeginInvokePairedWithEndInvoke.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BeginInvokePairedWithEndInvoke : BeginInvokePairedWithEndInvokeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind InvocationExpressionKind => SyntaxKind.InvocationExpression; protected override string CallbackParameterName => "callback"; protected override ISet ParentDeclarationKinds { get; } = new HashSet { SyntaxKind.AnonymousMethodExpression, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.PropertyDeclaration, SyntaxKind.SimpleLambdaExpression, }.ToImmutableHashSet(); protected override void VisitInvocation(EndInvokeContext context) => new InvocationWalker(context).SafeVisit(context.Root); protected override bool IsInvalidCallback(SyntaxNode callbackArg, SemanticModel semanticModel) { if (FindCallback(callbackArg, semanticModel) is { } callback) { var callbackModel = callback.EnsureCorrectSemanticModelOrDefault(semanticModel); return callback is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.ToString().Equals(EndInvoke) ? callbackModel.GetSymbolInfo(callback).Symbol is not IMethodSymbol : Language.Syntax.IsNullLiteral(callback) || !IsParentDeclarationWithEndInvoke(callback, callbackModel); } return false; } /// /// This method is looking for the callback code which can be: /// - in a identifier initializer (like a lambda) /// - passed directly as a lambda argument /// - passed as a new delegate instantiation (and the code can be inside the method declaration) /// - a mix of the above. /// private static SyntaxNode FindCallback(SyntaxNode callbackArg, SemanticModel model) { var callback = callbackArg.RemoveParentheses(); if (callback is IdentifierNameSyntax identifier) { callback = LookupIdentifierInitializer(identifier, model); } if (callback is ObjectCreationExpressionSyntax objectCreation) { callback = objectCreation.ArgumentList.Arguments.Count == 1 ? objectCreation.ArgumentList.Arguments.Single().Expression : null; if (callback is not null && callback.EnsureCorrectSemanticModelOrDefault(model) is { } safeModel && safeModel.GetSymbolInfo(callback).Symbol is IMethodSymbol methodSymbol) { callback = methodSymbol.ImplementationSyntax(); } } return callback; } private static SyntaxNode LookupIdentifierInitializer(IdentifierNameSyntax identifier, SemanticModel semantic) => semantic.GetSymbolInfo(identifier).Symbol?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is VariableDeclaratorSyntax variableDeclarator && variableDeclarator.Initializer is EqualsValueClauseSyntax equalsValueClause ? equalsValueClause.Value.RemoveParentheses() : null; private class InvocationWalker : SafeCSharpSyntaxWalker { private readonly EndInvokeContext context; public InvocationWalker(EndInvokeContext context) => this.context = context; public override void Visit(SyntaxNode node) { if (context.Visit(node)) { base.Visit(node); } } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (context.VisitInvocationExpression(node)) { base.VisitInvocationExpression(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BinaryOperationWithIdenticalExpressions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BinaryOperationWithIdenticalExpressions : BinaryOperationWithIdenticalExpressionsBase { internal static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "{0}"); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly SyntaxKind[] SyntaxKindsToCheckBinary = { SyntaxKind.SubtractExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression, SyntaxKind.LogicalOrExpression, SyntaxKind.LogicalAndExpression, SyntaxKind.BitwiseOrExpression, SyntaxKind.BitwiseAndExpression, SyntaxKind.ExclusiveOrExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression }; private static readonly SyntaxKind[] SyntaxKindsToCheckAssignment = { SyntaxKind.SubtractAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var expression = (BinaryExpressionSyntax)c.Node; ReportIfOperatorExpressionsMatch(c, expression.Left, expression.Right, expression.OperatorToken); }, SyntaxKindsToCheckBinary); context.RegisterNodeAction( c => { var expression = (AssignmentExpressionSyntax)c.Node; ReportIfOperatorExpressionsMatch(c, expression.Left, expression.Right, expression.OperatorToken); }, SyntaxKindsToCheckAssignment); context.RegisterNodeAction( c => ReportOnObjectEqualsMatches(c, (InvocationExpressionSyntax)c.Node), SyntaxKind.InvocationExpression); } private static void ReportOnObjectEqualsMatches(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation) { var methodSymbol = context.Model.GetSymbolInfo(invocation).Symbol as IMethodSymbol; var operands = GetOperands(invocation, methodSymbol); if (operands is not null && CSharpEquivalenceChecker.AreEquivalent(RemoveParentheses(operands.Item1), RemoveParentheses(operands.Item2))) { var message = string.Format(EqualsMessage, operands.Item2); context.ReportIssue(Rule, operands.Item1.GetLocation(), [operands.Item2.ToSecondaryLocation()], message); } } private static Tuple GetOperands(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) { if (methodSymbol.IsStaticObjectEquals()) { return new Tuple( invocation.ArgumentList.Arguments[0], invocation.ArgumentList.Arguments[1]); } if (methodSymbol.IsObjectEquals()) { var invokingExpression = (invocation.Expression as MemberAccessExpressionSyntax)?.Expression; if (invokingExpression != null) { return new Tuple( invokingExpression, invocation.ArgumentList.Arguments[0].Expression); } } return null; } private static SyntaxNode RemoveParentheses(SyntaxNode node) => node is ExpressionSyntax expression ? expression.RemoveParentheses() : node; private static void ReportIfOperatorExpressionsMatch(SonarSyntaxNodeReportingContext context, ExpressionSyntax left, ExpressionSyntax right, SyntaxToken operatorToken) { if (CSharpEquivalenceChecker.AreEquivalent(left.RemoveParentheses(), right.RemoveParentheses())) { var message = string.Format(OperatorMessageFormat, operatorToken); context.ReportIssue(Rule, right, [left.ToSecondaryLocation()], message); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BlazorQueryParameterRoutableComponent.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BlazorQueryParameterRoutableComponent : SonarDiagnosticAnalyzer { [Obsolete("This rule has been deprecated since 9.25")] private const string NoRouteQueryDiagnosticId = "S6803"; private const string NoRouteQueryMessageFormat = "Component parameters can only receive query parameter values in routable components."; private const string QueryTypeDiagnosticId = "S6797"; private const string QueryTypeMessageFormat = "Query parameter type '{0}' is not supported."; private static readonly DiagnosticDescriptor S6803Rule = DescriptorFactory.Create(NoRouteQueryDiagnosticId, NoRouteQueryMessageFormat); private static readonly DiagnosticDescriptor S6797Rule = DescriptorFactory.Create(QueryTypeDiagnosticId, QueryTypeMessageFormat); private static readonly ISet SupportedQueryTypes = new HashSet { KnownType.System_Boolean, KnownType.System_DateTime, KnownType.System_Decimal, KnownType.System_Double, KnownType.System_Single, KnownType.System_Int32, KnownType.System_Int64, KnownType.System_String, KnownType.System_Guid }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(S6803Rule, S6797Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (c.Compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Components_RouteAttribute) is not null) { c.RegisterSymbolAction(CheckQueryProperties, SymbolKind.Property); } }); private static void CheckQueryProperties(SonarSymbolReportingContext c) { var property = (IPropertySymbol)c.Symbol; if (property.HasAttribute(KnownType.Microsoft_AspNetCore_Components_SupplyParameterFromQueryAttribute) && property.HasAttribute(KnownType.Microsoft_AspNetCore_Components_ParameterAttribute)) { if (!property.ContainingType.HasAttribute(KnownType.Microsoft_AspNetCore_Components_RouteAttribute)) { foreach (var location in property.Locations) { c.ReportIssue(S6803Rule, location); } } else if (!SupportedQueryTypes.Any(x => IsSupportedType(property.Type, x))) { foreach (var propertyType in property.DeclaringSyntaxReferences.Select(x => ((PropertyDeclarationSyntax)x.GetSyntax()).Type)) { c.ReportIssue(S6797Rule, propertyType.GetLocation(), GetTypeName(propertyType)); } } } } private static bool IsSupportedType(ITypeSymbol type, KnownType supportType) { if (type is IArrayTypeSymbol arrayTypeSymbol) { type = arrayTypeSymbol.ElementType; } if (KnownType.System_Nullable_T.Matches(type)) { type = ((INamedTypeSymbol)type).TypeArguments[0]; } return supportType.Matches(type); } private static string GetTypeName(TypeSyntax propertyType) => propertyType switch { GenericNameSyntax genericSyntax when propertyType.NameIs(KnownType.System_Nullable_T.TypeName) => genericSyntax.TypeArgumentList.Arguments[0].GetName(), {} tuple when TupleTypeSyntaxWrapper.IsInstance(tuple) => KnownType.System_ValueTuple.TypeName, _ => propertyType.GetName() }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BooleanCheckInverted.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BooleanCheckInverted : BooleanCheckInvertedBase { private static readonly ISet UnsafeInversionOperators = new HashSet { SyntaxKind.GreaterThanToken, SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken, SyntaxKind.LessThanEqualsToken, }; private static readonly Dictionary OppositeTokens = new() { { SyntaxKind.GreaterThanToken, "<=" }, { SyntaxKind.GreaterThanEqualsToken, "<" }, { SyntaxKind.LessThanToken, ">=" }, { SyntaxKind.LessThanEqualsToken, ">" }, { SyntaxKind.EqualsEqualsToken, "!=" }, { SyntaxKind.ExclamationEqualsToken, "==" }, }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( AnalysisAction(Rule), SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); protected override bool IsUnsafeInversionOperation(BinaryExpressionSyntax expression, SemanticModel model) => expression.OperatorToken.IsAnyKind(UnsafeInversionOperators) && (IsNullable(expression.Left, model) || IsNullable(expression.Right, model) || IsConditionalAccessExpression(expression.Left) || IsConditionalAccessExpression(expression.Right) || IsFloatingPoint(expression.Left, model) || IsFloatingPoint(expression.Right, model)); protected override SyntaxNode LogicalNotNode(BinaryExpressionSyntax expression) => expression.GetSelfOrTopParenthesizedExpression().Parent is PrefixUnaryExpressionSyntax prefixUnaryExpression && prefixUnaryExpression.OperatorToken.IsKind(SyntaxKind.ExclamationToken) ? prefixUnaryExpression : null; protected override string SuggestedReplacement(BinaryExpressionSyntax expression) => OppositeTokens[expression.OperatorToken.Kind()]; private static bool IsConditionalAccessExpression(ExpressionSyntax expression) => expression.RemoveParentheses().IsKind(SyntaxKind.ConditionalAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BooleanCheckInvertedCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class BooleanCheckInvertedCodeFix : SonarCodeFix { internal const string Title = "Invert 'Boolean' check"; public override ImmutableArray FixableDiagnosticIds { get { return ImmutableArray.Create(BooleanCheckInverted.DiagnosticId); } } protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is PrefixUnaryExpressionSyntax syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var expression = syntaxNode.Operand.RemoveParentheses(); var newBinary = ChangeOperator((BinaryExpressionSyntax)expression); if (syntaxNode.Parent is ExpressionSyntax && !(syntaxNode.Parent is AssignmentExpressionSyntax)) { newBinary = SyntaxFactory.ParenthesizedExpression(newBinary); } var newRoot = root.ReplaceNode( syntaxNode, newBinary.WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static ExpressionSyntax ChangeOperator(BinaryExpressionSyntax binary) { return SyntaxFactory.BinaryExpression( OppositeExpressionKinds[binary.Kind()], binary.Left, binary.Right) .WithTriviaFrom(binary); } private static readonly Dictionary OppositeExpressionKinds = new Dictionary { {SyntaxKind.GreaterThanExpression, SyntaxKind.LessThanOrEqualExpression}, {SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression}, {SyntaxKind.LessThanExpression, SyntaxKind.GreaterThanOrEqualExpression}, {SyntaxKind.LessThanOrEqualExpression, SyntaxKind.GreaterThanExpression}, {SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression}, {SyntaxKind.NotEqualsExpression, SyntaxKind.EqualsExpression} }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BooleanLiteralUnnecessary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BooleanLiteralUnnecessary : BooleanLiteralUnnecessaryBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxToken? GetOperatorToken(SyntaxNode node) => node switch { BinaryExpressionSyntax binary => binary.OperatorToken, _ when IsPatternExpressionSyntaxWrapper.IsInstance(node) => ((IsPatternExpressionSyntaxWrapper)node).IsKeyword, _ => null, }; protected override bool IsTrue(SyntaxNode syntaxNode) => syntaxNode.IsTrue(); protected override bool IsFalse(SyntaxNode syntaxNode) => syntaxNode.IsFalse(); protected override bool IsInsideTernaryWithThrowExpression(SyntaxNode syntaxNode) => syntaxNode.Parent is ConditionalExpressionSyntax conditionalExpression && (IsThrowExpression(conditionalExpression.WhenTrue) || IsThrowExpression(conditionalExpression.WhenFalse)); protected override SyntaxNode GetLeftNode(SyntaxNode node) => node switch { BinaryExpressionSyntax binaryExpression => binaryExpression.Left, _ when IsPatternExpressionSyntaxWrapper.IsInstance(node) => ((IsPatternExpressionSyntaxWrapper)node).Expression, _ => null }; protected override SyntaxNode GetRightNode(SyntaxNode node) => node switch { BinaryExpressionSyntax binaryExpression => binaryExpression.Right, _ when IsPatternExpressionSyntaxWrapper.IsInstance(node) => ((IsPatternExpressionSyntaxWrapper)node).Pattern, _ => null }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckLogicalNot, SyntaxKind.LogicalNotExpression); context.RegisterNodeAction(CheckAndExpression, SyntaxKind.LogicalAndExpression); context.RegisterNodeAction(CheckOrExpression, SyntaxKind.LogicalOrExpression); context.RegisterNodeAction(CheckEquals, SyntaxKind.EqualsExpression, SyntaxKindEx.IsPatternExpression); context.RegisterNodeAction(CheckNotEquals, SyntaxKind.NotEqualsExpression); context.RegisterNodeAction(CheckConditional, SyntaxKind.ConditionalExpression); context.RegisterNodeAction(CheckForLoopCondition, SyntaxKind.ForStatement); base.Initialize(context); } private void CheckForLoopCondition(SonarSyntaxNodeReportingContext context) { var forLoop = (ForStatementSyntax)context.Node; if (forLoop.Condition != null && CSharpEquivalenceChecker.AreEquivalent(forLoop.Condition.RemoveParentheses(), SyntaxConstants.TrueLiteralExpression)) { context.ReportIssue(Rule, forLoop.Condition); } } private void CheckLogicalNot(SonarSyntaxNodeReportingContext context) { var logicalNot = (PrefixUnaryExpressionSyntax)context.Node; var logicalNotOperand = logicalNot.Operand.RemoveParentheses(); if (IsTrue(logicalNotOperand) || IsFalse(logicalNotOperand)) { context.ReportIssue(Rule, logicalNot.Operand); } } private void CheckConditional(SonarSyntaxNodeReportingContext context) { var conditional = (ConditionalExpressionSyntax)context.Node; var whenTrue = conditional.WhenTrue; var whenFalse = conditional.WhenFalse; if (IsThrowExpression(whenTrue) || IsThrowExpression(whenFalse)) { return; } var typeLeft = context.Model.GetTypeInfo(whenTrue).Type; var typeRight = context.Model.GetTypeInfo(whenFalse).Type; if (typeLeft.IsNullableBoolean() || typeRight.IsNullableBoolean() || typeLeft == null || typeRight == null) { return; } CheckTernaryExpressionBranches(context, whenTrue, whenFalse); } private static bool IsThrowExpression(ExpressionSyntax expressionSyntax) => ThrowExpressionSyntaxWrapper.IsInstance(expressionSyntax); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BooleanLiteralUnnecessaryCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class BooleanLiteralUnnecessaryCodeFix : SonarCodeFix { internal const string Title = "Remove the unnecessary Boolean literal(s)"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(BooleanLiteralUnnecessary.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is not ExpressionSyntax syntaxNode) { return Task.CompletedTask; } var parent = syntaxNode.Parent; syntaxNode = syntaxNode.RemoveParentheses(); if (syntaxNode is BinaryExpressionSyntax binary) { RegisterBinaryExpressionReplacement(context, root, syntaxNode, binary); return Task.CompletedTask; } if (syntaxNode is ConditionalExpressionSyntax conditional) { RegisterConditionalExpressionRemoval(context, root, conditional); return Task.CompletedTask; } if (IsPatternExpressionSyntaxWrapper.IsInstance(syntaxNode)) { RegisterPatternExpressionReplacement(context, root, (IsPatternExpressionSyntaxWrapper)syntaxNode); } if (syntaxNode is not LiteralExpressionSyntax literal) { return Task.CompletedTask; } if (parent is PrefixUnaryExpressionSyntax) { RegisterBooleanInversion(context, root, literal); return Task.CompletedTask; } if (parent is ConditionalExpressionSyntax conditionalParent) { RegisterConditionalExpressionRewrite(context, root, literal, conditionalParent); return Task.CompletedTask; } if (parent is BinaryExpressionSyntax binaryParent) { RegisterBinaryExpressionRemoval(context, root, literal, binaryParent); return Task.CompletedTask; } if (parent is ForStatementSyntax forStatement) { RegisterForStatementConditionRemoval(context, root, forStatement); return Task.CompletedTask; } return Task.CompletedTask; } private static void RegisterPatternExpressionReplacement(SonarCodeFixContext context, SyntaxNode root, IsPatternExpressionSyntaxWrapper patternExpression) { var replacement = patternExpression.Pattern.SyntaxNode.IsTrue() ? patternExpression.Expression : GetNegatedExpression(patternExpression.Expression); if (replacement.IsTrue()) { replacement = SyntaxConstants.TrueLiteralExpression; } else if (replacement.IsFalse()) { replacement = SyntaxConstants.FalseLiteralExpression; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode(patternExpression.SyntaxNode, replacement.WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterForStatementConditionRemoval(SonarCodeFixContext context, SyntaxNode root, ForStatementSyntax forStatement) => context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( forStatement, forStatement.WithCondition(null).WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static void RegisterBinaryExpressionRemoval(SonarCodeFixContext context, SyntaxNode root, LiteralExpressionSyntax literal, BinaryExpressionSyntax binaryParent) { var otherNode = binaryParent.Left.RemoveParentheses().Equals(literal) ? binaryParent.Right : binaryParent.Left; context.RegisterCodeFix( Title, c => { var newExpression = GetNegatedExpression(otherNode).WithAdditionalAnnotations(Simplifier.Annotation); var newRoot = root.ReplaceNode(binaryParent, newExpression .WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterConditionalExpressionRewrite(SonarCodeFixContext context, SyntaxNode root, LiteralExpressionSyntax literal, ConditionalExpressionSyntax conditionalParent) => context.RegisterCodeFix( Title, c => Task.FromResult(RewriteConditional(context.Document, root, literal, conditionalParent)), context.Diagnostics); private static void RegisterBooleanInversion(SonarCodeFixContext context, SyntaxNode root, LiteralExpressionSyntax literal) => context.RegisterCodeFix( Title, c => Task.FromResult(RemovePrefixUnary(context.Document, root, literal)), context.Diagnostics); private static void RegisterConditionalExpressionRemoval(SonarCodeFixContext context, SyntaxNode root, ConditionalExpressionSyntax conditional) => context.RegisterCodeFix( Title, c => Task.FromResult(RemoveConditional(context.Document, root, conditional)), context.Diagnostics); private static void RegisterBinaryExpressionReplacement(SonarCodeFixContext context, SyntaxNode root, SyntaxNode syntaxNode, BinaryExpressionSyntax binary) => context.RegisterCodeFix( Title, c => { var keepThisNode = FindNodeToKeep(binary).WithAdditionalAnnotations(Simplifier.Annotation); var newRoot = root.ReplaceNode(syntaxNode, keepThisNode .WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static SyntaxNode FindNodeToKeep(BinaryExpressionSyntax binary) { // logical and false, logical or true if (binary.IsKind(SyntaxKind.LogicalAndExpression) && (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.FalseLiteralExpression) || CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.FalseLiteralExpression))) { return SyntaxConstants.FalseLiteralExpression; } if (binary.IsKind(SyntaxKind.LogicalOrExpression) && (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.TrueLiteralExpression) || CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.TrueLiteralExpression))) { return SyntaxConstants.TrueLiteralExpression; } // ==/!= both sides booleans if (binary.IsKind(SyntaxKind.EqualsExpression) && TwoSidesAreDifferentBooleans(binary)) { return SyntaxConstants.FalseLiteralExpression; } if (binary.IsKind(SyntaxKind.EqualsExpression) && TwoSidesAreSameBooleans(binary)) { return SyntaxConstants.TrueLiteralExpression; } if (binary.IsKind(SyntaxKind.NotEqualsExpression) && TwoSidesAreSameBooleans(binary)) { return SyntaxConstants.FalseLiteralExpression; } if (binary.IsKind(SyntaxKind.NotEqualsExpression) && TwoSidesAreDifferentBooleans(binary)) { return SyntaxConstants.TrueLiteralExpression; } // ==/!= one side boolean if (binary.IsKind(SyntaxKind.EqualsExpression)) { // edge case [condition == false] -> !condition if (CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.FalseLiteralExpression)) { return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, binary.Left); } // edge case [false == condition] -> !condition if (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.FalseLiteralExpression)) { return SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, binary.Right); } } return CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.TrueLiteralExpression) || CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.FalseLiteralExpression) ? binary.Right : binary.Left; } private static bool TwoSidesAreDifferentBooleans(BinaryExpressionSyntax binary) => (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.TrueLiteralExpression) && CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.FalseLiteralExpression)) || (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.FalseLiteralExpression) && CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.TrueLiteralExpression)); private static bool TwoSidesAreSameBooleans(BinaryExpressionSyntax binary) => (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.TrueLiteralExpression) && CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.TrueLiteralExpression)) || (CSharpEquivalenceChecker.AreEquivalent(binary.Left, SyntaxConstants.FalseLiteralExpression) && CSharpEquivalenceChecker.AreEquivalent(binary.Right, SyntaxConstants.FalseLiteralExpression)); private static Document RemovePrefixUnary(Document document, SyntaxNode root, SyntaxNode literal) { if (CSharpEquivalenceChecker.AreEquivalent(literal, SyntaxConstants.TrueLiteralExpression)) { var newRoot = root.ReplaceNode(literal.Parent, SyntaxConstants.FalseLiteralExpression); return document.WithSyntaxRoot(newRoot); } else { var newRoot = root.ReplaceNode(literal.Parent, SyntaxConstants.TrueLiteralExpression); return document.WithSyntaxRoot(newRoot); } } private static Document RemoveConditional(Document document, SyntaxNode root, ConditionalExpressionSyntax conditional) { if (CSharpEquivalenceChecker.AreEquivalent(conditional.WhenTrue, SyntaxConstants.TrueLiteralExpression)) { var newRoot = root.ReplaceNode( conditional, conditional.Condition.WithAdditionalAnnotations(Formatter.Annotation)); return document.WithSyntaxRoot(newRoot); } else { var newRoot = root.ReplaceNode( conditional, GetNegatedExpression(conditional.Condition).WithAdditionalAnnotations(Formatter.Annotation)); return document.WithSyntaxRoot(newRoot); } } private static SyntaxNode ReplaceExpressionWithBinary(SyntaxNode nodeToReplace, SyntaxNode root, SyntaxKind binaryKind, ExpressionSyntax left, ExpressionSyntax right) => root.ReplaceNode( nodeToReplace, SyntaxFactory.BinaryExpression(binaryKind, left, right).WithAdditionalAnnotations(Formatter.Annotation)); private static Document RewriteConditional(Document document, SyntaxNode root, SyntaxNode syntaxNode, ConditionalExpressionSyntax conditional) { var whenTrue = conditional.WhenTrue.RemoveParentheses(); if (whenTrue.Equals(syntaxNode) && syntaxNode.IsTrue()) { var newRoot = ReplaceExpressionWithBinary( conditional, root, SyntaxKind.LogicalOrExpression, conditional.Condition, AddParenthesis(conditional.WhenFalse)); return document.WithSyntaxRoot(newRoot); } if (whenTrue.Equals(syntaxNode) && syntaxNode.IsFalse()) { var newRoot = ReplaceExpressionWithBinary( conditional, root, SyntaxKind.LogicalAndExpression, GetNegatedExpression(conditional.Condition), AddParenthesis(conditional.WhenFalse)); return document.WithSyntaxRoot(newRoot); } var whenFalse = conditional.WhenFalse.RemoveParentheses(); if (whenFalse.Equals(syntaxNode) && syntaxNode.IsTrue()) { var newRoot = ReplaceExpressionWithBinary( conditional, root, SyntaxKind.LogicalOrExpression, GetNegatedExpression(conditional.Condition), AddParenthesis(conditional.WhenTrue)); return document.WithSyntaxRoot(newRoot); } if (whenFalse.Equals(syntaxNode) && syntaxNode.IsFalse()) { var newRoot = ReplaceExpressionWithBinary( conditional, root, SyntaxKind.LogicalAndExpression, conditional.Condition, AddParenthesis(conditional.WhenTrue)); return document.WithSyntaxRoot(newRoot); } return document; } private static ExpressionSyntax GetNegatedExpression(ExpressionSyntax expression) => SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, AddParenthesis(expression)); private static ExpressionSyntax AddParenthesis(ExpressionSyntax expression) => SyntaxFactory.ParenthesizedExpression(expression).WithAdditionalAnnotations(Simplifier.Annotation); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BreakOutsideSwitch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BreakOutsideSwitch : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1227"; private const string MessageFormat = "Refactor the code in order to remove this break statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var breakNode = (BreakStatementSyntax)c.Node; if (!IsInSwitch(breakNode)) { c.ReportIssue(rule, breakNode); } }, SyntaxKind.BreakStatement); } private static bool IsInSwitch(BreakStatementSyntax node) { var ancestor = node.FirstAncestorOrSelf(e => LoopOrSwitch.Contains(e.Kind())); return ancestor != null && ancestor.IsKind(SyntaxKind.SwitchStatement); } private static IEnumerable LoopOrSwitch => new[] { SyntaxKind.SwitchStatement, SyntaxKind.WhileStatement, SyntaxKind.DoStatement, SyntaxKind.ForStatement, SyntaxKind.ForEachStatement, SyntaxKindEx.ForEachVariableStatement }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/BypassingAccessibility.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class BypassingAccessibility : BypassingAccessibilityBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public BypassingAccessibility() : base(AnalyzerConfiguration.AlwaysEnabled) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CallToAsyncMethodShouldNotBeBlocking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CallToAsyncMethodShouldNotBeBlocking : SonarDiagnosticAnalyzer { private const string MessageFormat = "Replace this use of '{0}' with '{1}'."; private const string ResultName = "Result"; private const string ContinueWithName = "ContinueWith"; private const string SleepName = "Sleep"; private const string AzureFunctionSuffix = @" Do not perform blocking operations in Azure Functions."; private static readonly DiagnosticDescriptor RuleS4462 = DescriptorFactory.Create("S4462", MessageFormat); private static readonly DiagnosticDescriptor RuleS6422 = DescriptorFactory.Create("S6422", MessageFormat + AzureFunctionSuffix); private static readonly Dictionary> InvalidMemberAccess = new() { ["GetResult"] = ImmutableArray.Create(KnownType.System_Runtime_CompilerServices_TaskAwaiter, KnownType.System_Runtime_CompilerServices_TaskAwaiter_TResult), [ResultName] = ImmutableArray.Create(KnownType.System_Threading_Tasks_Task_T), [SleepName] = ImmutableArray.Create(KnownType.System_Threading_Thread), ["Wait"] = ImmutableArray.Create(KnownType.System_Threading_Tasks_Task), ["WaitAll"] = ImmutableArray.Create(KnownType.System_Threading_Tasks_Task), ["WaitAny"] = ImmutableArray.Create(KnownType.System_Threading_Tasks_Task), }; private static readonly Dictionary MemberNameToMessageArguments = new() { ["GetResult"] = new[] { "Task.GetAwaiter.GetResult", "await" }, [ResultName] = new[] { "Task.Result", "await" }, [SleepName] = new[] { "Thread.Sleep", "await Task.Delay" }, ["Wait"] = new[] { "Task.Wait", "await" }, ["WaitAll"] = new[] { "Task.WaitAll", "await Task.WhenAll" }, ["WaitAny"] = new[] { "Task.WaitAny", "await Task.WhenAny" }, }; private static readonly Dictionary TaskThreadPoolCalls = new() { ["StartNew"] = KnownType.System_Threading_Tasks_TaskFactory, ["Run"] = KnownType.System_Threading_Tasks_Task, }; private static readonly Dictionary WaitForMultipleTasksExecutionCalls = new() { ["WhenAll"] = KnownType.System_Threading_Tasks_Task, ["WaitAll"] = KnownType.System_Threading_Tasks_Task, }; private static readonly Dictionary WaitForSingleExecutionCalls = new() { ["Wait"] = KnownType.System_Threading_Tasks_Task, ["RunSynchronously"] = KnownType.System_Threading_Tasks_Task, }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(RuleS4462, RuleS6422); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(ReportOnViolation, SyntaxKind.SimpleMemberAccessExpression); private static void ReportOnViolation(SonarSyntaxNodeReportingContext context) { var simpleMemberAccess = (MemberAccessExpressionSyntax)context.Node; var memberAccessNameName = simpleMemberAccess.GetName(); if (memberAccessNameName == null || !InvalidMemberAccess.ContainsKey(memberAccessNameName) || IsResultInContinueWithCall(memberAccessNameName, simpleMemberAccess) || IsChainedAfterThreadPoolCall(context.Model, simpleMemberAccess) || simpleMemberAccess.IsInNameOfArgument(context.Model) || simpleMemberAccess.HasAncestor(SyntaxKind.GlobalStatement)) { return; } var possibleMemberAccesses = InvalidMemberAccess[memberAccessNameName]; var memberAccessSymbol = context.Model.GetSymbolInfo(simpleMemberAccess).Symbol; if (memberAccessSymbol?.ContainingType == null || !memberAccessSymbol.ContainingType.ConstructedFrom.IsAny(possibleMemberAccesses)) { return; } if (simpleMemberAccess.FirstAncestorOrSelf() is { } enclosingMethod) { if (memberAccessNameName == SleepName && !enclosingMethod.Modifiers.Any(SyntaxKind.AsyncKeyword)) { return; // Thread.Sleep should not be used only in async methods } if (context.Model.GetDeclaredSymbol(enclosingMethod).IsMainMethod()) { return; // Main methods are not subject to deadlock issue so no need to report an issue } if (memberAccessNameName == ResultName && IsAwaited(context, simpleMemberAccess)) { return; // No need to report an issue on a waited object } } context.ReportIssue(context.IsAzureFunction() ? RuleS6422 : RuleS4462, simpleMemberAccess, MemberNameToMessageArguments[memberAccessNameName]); } private static bool IsAwaited(SonarSyntaxNodeReportingContext context, MemberAccessExpressionSyntax simpleMemberAccess) { return context.Model.GetSymbolInfo(simpleMemberAccess.Expression).Symbol is { } accessedSymbol && simpleMemberAccess.FirstAncestorOrSelf() is { } currentStatement && currentStatement.GetPreviousStatements().Any(ContainsAwaitedInvocation); bool ContainsAwaitedInvocation(StatementSyntax statement) => statement.DescendantNodes().OfType().Where(x => x.Expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)).Any(IsTaskAwaited); bool IsTaskAwaited(InvocationExpressionSyntax invocation) => IsAwaitForMultipleTasksExecutionCall(context.Model, invocation, accessedSymbol) || IsAwaitForSingleTaskExecutionCall(context.Model, invocation, accessedSymbol); } private static bool IsAwaitForMultipleTasksExecutionCall(SemanticModel model, InvocationExpressionSyntax invocation, ISymbol accessedSymbol) => IsNamedSymbolOfExpectedType(model, (MemberAccessExpressionSyntax)invocation.Expression, WaitForMultipleTasksExecutionCalls) && invocation.ArgumentList.Arguments.Any(x => Equals(accessedSymbol, model.GetSymbolInfo(x.Expression).Symbol)); private static bool IsAwaitForSingleTaskExecutionCall(SemanticModel model, InvocationExpressionSyntax invocation, ISymbol accessedSymbol) => IsNamedSymbolOfExpectedType(model, (MemberAccessExpressionSyntax)invocation.Expression, WaitForSingleExecutionCalls) && Equals(accessedSymbol, model.GetSymbolInfo(((MemberAccessExpressionSyntax)invocation.Expression).Expression).Symbol); private static bool IsResultInContinueWithCall(string memberAccessName, MemberAccessExpressionSyntax memberAccess) => memberAccessName == ResultName && memberAccess.Expression is IdentifierNameSyntax identifierNameSyntax && identifierNameSyntax.GetName() is { } identifierName && memberAccess.FirstAncestorOrSelf(x => IsContinueWithCallWithArgumentName(x, identifierName)) is not null; private static bool IsContinueWithCallWithArgumentName(InvocationExpressionSyntax invocation, string argumentName) => invocation.Expression.NameIs(ContinueWithName) && invocation.ArgumentList.Arguments.Any(argument => IsLambdaExpressionWithArgumentName(argument.Expression, argumentName)); private static bool IsLambdaExpressionWithArgumentName(ExpressionSyntax expression, string argumentName) => expression switch { SimpleLambdaExpressionSyntax simpleLambda => simpleLambda.Parameter.Identifier.ValueText == argumentName, ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList.Parameters.Any(parameter => parameter.Identifier.ValueText == argumentName), _ => false }; private static bool IsChainedAfterThreadPoolCall(SemanticModel model, MemberAccessExpressionSyntax memberAccess) => memberAccess.Expression.DescendantNodes().OfType().Any(x => IsNamedSymbolOfExpectedType(model, x, TaskThreadPoolCalls)); private static bool IsNamedSymbolOfExpectedType(SemanticModel model, MemberAccessExpressionSyntax memberAccess, Dictionary expectedTypes) => expectedTypes.Keys.Any(memberAccess.NameIs) && model.GetSymbolInfo(memberAccess).Symbol?.ContainingType?.ConstructedFrom is { } memberAccessSymbol && memberAccessSymbol.Is(expectedTypes[memberAccess.Name.Identifier.ValueText]); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CallerInformationParametersShouldBeLast.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CallerInformationParametersShouldBeLast : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3343"; private const string MessageFormat = "Move '{0}' to the end of the parameter list."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ReportOnViolation, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static void ReportOnViolation(SonarSyntaxNodeReportingContext context) { var methodDeclaration = context.Node; var parameterList = methodDeclaration.ParameterList(); if (parameterList is null or { Parameters.Count: 0 }) { return; } var methodSymbol = context.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol == null || methodSymbol.IsOverride || methodSymbol.InterfaceMembers().Any()) { return; } ParameterSyntax noCallerInfoParameter = null; foreach (var parameter in parameterList.Parameters.Reverse()) { if (parameter.AttributeLists.GetAttributes(KnownType.CallerInfoAttributes, context.Model).Any()) { if (noCallerInfoParameter != null && HasIdentifier(parameter)) { context.ReportIssue(Rule, parameter, parameter.Identifier.Text); } } else { noCallerInfoParameter = parameter; } } } private static bool HasIdentifier(ParameterSyntax parameter) => !string.IsNullOrEmpty(parameter.Identifier.Text); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CastConcreteTypeToInterface.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CastConcreteTypeToInterface : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3215"; private const string MessageFormat = "Remove this cast and edit the interface to add the missing functionality."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var castExpression = (CastExpressionSyntax)c.Node; CheckForIssue(c, castExpression.Expression, castExpression.Type); }, SyntaxKind.CastExpression); context.RegisterNodeAction( c => { var castExpression = (BinaryExpressionSyntax)c.Node; CheckForIssue(c, castExpression.Left, castExpression.Right); }, SyntaxKind.AsExpression); } private static void CheckForIssue(SonarSyntaxNodeReportingContext context, SyntaxNode fromExpression, SyntaxNode toExpression) { var castedFrom = context.Model.GetTypeInfo(fromExpression).Type; var castedTo = context.Model.GetTypeInfo(toExpression).Type; if (castedFrom.Is(TypeKind.Interface) && castedFrom.DeclaringSyntaxReferences.Any() && castedTo.Is(TypeKind.Class) && !castedTo.Is(KnownType.System_Object)) { context.ReportIssue(Rule, context.Node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CastShouldNotBeDuplicated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CastShouldNotBeDuplicated : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3247"; private const string MessageFormat = "{0}"; private const string UsePatternMatchingCheckMessage = "Replace this type-check-and-cast sequence to use pattern matching."; private const string RemoveRedundantCastAnotherVariableMessage = "Remove this cast and use the appropriate variable."; private const string RemoveRedundantCastMessage = "Remove this redundant cast."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(IsExpression, SyntaxKind.IsExpression); context.RegisterNodeAction(IsPatternExpression, SyntaxKindEx.IsPatternExpression); context.RegisterNodeAction(SwitchExpressionArm, SyntaxKindEx.SwitchExpressionArm); context.RegisterNodeAction(CasePatternSwitchLabel, SyntaxKindEx.CasePatternSwitchLabel); } private static void CasePatternSwitchLabel(SonarSyntaxNodeReportingContext analysisContext) { var casePatternSwitch = (CasePatternSwitchLabelSyntaxWrapper)analysisContext.Node; if (casePatternSwitch.SyntaxNode.GetFirstNonParenthesizedParent().GetFirstNonParenthesizedParent() is SwitchStatementSyntax parentSwitchStatement) { ProcessPatternExpression(analysisContext, casePatternSwitch.Pattern, parentSwitchStatement.Expression, parentSwitchStatement); } } private static void SwitchExpressionArm(SonarSyntaxNodeReportingContext analysisContext) { var isSwitchExpression = (SwitchExpressionArmSyntaxWrapper)analysisContext.Node; var parent = isSwitchExpression.SyntaxNode.GetFirstNonParenthesizedParent(); if (parent.IsKind(SyntaxKindEx.SwitchExpression)) { var switchExpression = (SwitchExpressionSyntaxWrapper)parent; ProcessPatternExpression(analysisContext, isSwitchExpression.Pattern, switchExpression.GoverningExpression, isSwitchExpression); } } private static void IsPatternExpression(SonarSyntaxNodeReportingContext analysisContext) { var isPatternExpression = (IsPatternExpressionSyntaxWrapper)analysisContext.Node; if (isPatternExpression.SyntaxNode.GetFirstNonParenthesizedParent() is IfStatementSyntax parentIfStatement) { ProcessPatternExpression(analysisContext, isPatternExpression.Pattern, isPatternExpression.Expression, parentIfStatement.Statement); } } private static void IsExpression(SonarSyntaxNodeReportingContext analysisContext) { var isExpression = (BinaryExpressionSyntax)analysisContext.Node; if (isExpression.Right is TypeSyntax castType && isExpression.GetFirstNonParenthesizedParent() is IfStatementSyntax parentIfStatement) { ReportPatternAtMainVariable(analysisContext, isExpression.Left, isExpression.GetLocation(), parentIfStatement.Statement, castType, UsePatternMatchingCheckMessage); } } private static Location[] DuplicatedCastLocations(SonarSyntaxNodeReportingContext context, SyntaxNode parentStatement, TypeSyntax castType, SyntaxNode typedVariable) { var typeExpressionSymbol = context.Model.GetSymbolInfo(typedVariable).Symbol ?? context.Model.GetDeclaredSymbol(typedVariable); return typeExpressionSymbol is null ? [] : parentStatement.DescendantNodes().Where(IsDuplicatedCast).Select(x => x.GetLocation()).ToArray(); bool IsDuplicatedCast(SyntaxNode node) { if (node is CastExpressionSyntax cast) { return IsDuplicatedCastOnSameSymbol(cast.Expression, cast.Type); } else if (node is BinaryExpressionSyntax binary && binary.Kind() is SyntaxKind.AsExpression or SyntaxKind.IsExpression) { return IsDuplicatedCastOnSameSymbol(binary.Left, binary.Right); } else { return false; } } bool IsDuplicatedCastOnSameSymbol(ExpressionSyntax expression, SyntaxNode type) => type.WithoutTrivia().IsEquivalentTo(castType.WithoutTrivia()) && IsCastOnSameSymbol(expression) && !CSharpFacade.Instance.Syntax.IsInExpressionTree(context.Model, expression); // see https://github.com/SonarSource/sonar-dotnet/issues/8735#issuecomment-1943419398 bool IsCastOnSameSymbol(ExpressionSyntax expression) => IsEquivalentVariable(expression, typedVariable) && Equals(context.Model.GetSymbolInfo(expression).Symbol, typeExpressionSymbol); } private static void ProcessPatternExpression(SonarSyntaxNodeReportingContext analysisContext, SyntaxNode isPattern, SyntaxNode mainVariableExpression, SyntaxNode parentStatement) { foreach (var expressionPatternPair in ((ExpressionSyntax)mainVariableExpression).MapToPattern(isPattern)) { var pattern = expressionPatternPair.Value; var leftVariable = expressionPatternPair.Key; var targetTypes = GetTypesFromPattern(pattern); var rightPartsToCheck = new Dictionary>(); foreach (var subPattern in pattern.DescendantNodesAndSelf().Where(x => x?.Kind() is SyntaxKindEx.DeclarationPattern or SyntaxKindEx.RecursivePattern)) { if (DeclarationPatternSyntaxWrapper.IsInstance(subPattern) && (DeclarationPatternSyntaxWrapper)subPattern is var declarationPattern) { rightPartsToCheck.Add(declarationPattern.Designation.SyntaxNode, new Tuple(declarationPattern.Type, subPattern.GetLocation())); } else if ((RecursivePatternSyntaxWrapper)subPattern is { Designation.SyntaxNode: { }, Type: { } } recursivePattern) { rightPartsToCheck.Add(recursivePattern.Designation.SyntaxNode, new Tuple(recursivePattern.Type, subPattern.GetLocation())); } } var mainVarMsg = rightPartsToCheck.Any() ? RemoveRedundantCastAnotherVariableMessage : RemoveRedundantCastMessage; foreach (var targetType in targetTypes) { ReportPatternAtMainVariable(analysisContext, leftVariable, leftVariable.GetLocation(), parentStatement, targetType, mainVarMsg); } foreach (var variableTypePair in rightPartsToCheck) { ReportPatternAtCastLocation(analysisContext, variableTypePair.Key, variableTypePair.Value.Item2, parentStatement, variableTypePair.Value.Item1, RemoveRedundantCastMessage); } } } private static IEnumerable GetTypesFromPattern(SyntaxNode pattern) { var targetTypes = new HashSet(); if (RecursivePatternSyntaxWrapper.IsInstance(pattern) && ((RecursivePatternSyntaxWrapper)pattern is { PositionalPatternClause.SyntaxNode: { } } recursivePattern)) { foreach (var subpattern in recursivePattern.PositionalPatternClause.Subpatterns) { AddPatternType(subpattern.Pattern, targetTypes); } } else if (BinaryPatternSyntaxWrapper.IsInstance(pattern) && (BinaryPatternSyntaxWrapper)pattern is { } binaryPattern) { AddPatternType(binaryPattern.Left, targetTypes); AddPatternType(binaryPattern.Right, targetTypes); } else if (ListPatternSyntaxWrapper.IsInstance(pattern) && (ListPatternSyntaxWrapper)pattern is { } listPattern) { foreach (var subpattern in listPattern.Patterns) { AddPatternType(subpattern, targetTypes); } } else { AddPatternType(pattern, targetTypes); } return targetTypes; static void AddPatternType(SyntaxNode pattern, ISet targetTypes) { if (GetType(pattern) is { } patternType) { targetTypes.Add(patternType); } } } private static TypeSyntax GetType(SyntaxNode pattern) { if (ConstantPatternSyntaxWrapper.IsInstance(pattern)) { return ((ConstantPatternSyntaxWrapper)pattern).Expression as TypeSyntax; } else if (DeclarationPatternSyntaxWrapper.IsInstance(pattern)) { return ((DeclarationPatternSyntaxWrapper)pattern).Type; } else if (RecursivePatternSyntaxWrapper.IsInstance(pattern)) { return ((RecursivePatternSyntaxWrapper)pattern).Type; } return null; } private static void ReportPatternAtMainVariable(SonarSyntaxNodeReportingContext context, SyntaxNode variableExpression, Location mainLocation, SyntaxNode parentStatement, TypeSyntax castType, string message) { var duplicatedCastLocations = DuplicatedCastLocations(context, parentStatement, castType, variableExpression); if (duplicatedCastLocations.Any()) { context.ReportIssue(Rule, mainLocation, duplicatedCastLocations.ToSecondary(), message); } } private static void ReportPatternAtCastLocation(SonarSyntaxNodeReportingContext context, SyntaxNode variableExpression, Location patternLocation, SyntaxNode parentStatement, TypeSyntax castType, string message) { var duplicatedCastLocations = DuplicatedCastLocations(context, parentStatement, castType, variableExpression); foreach (var castLocation in duplicatedCastLocations) { context.ReportIssue(Rule, castLocation, [patternLocation.ToSecondary()], message); } } private static bool IsEquivalentVariable(ExpressionSyntax expression, SyntaxNode typedVariable) { var left = CleanupExpression(typedVariable).WithoutTrivia(); var right = CleanupExpression(expression).WithoutTrivia(); return left.IsEquivalentTo(right) || (StandaloneIdentifier(left) is { } leftIdentifier && leftIdentifier == StandaloneIdentifier(right)); static string StandaloneIdentifier(SyntaxNode node) => node switch { IdentifierNameSyntax name => name.Identifier.ValueText, _ when node.IsKind(SyntaxKindEx.SingleVariableDesignation) => ((SingleVariableDesignationSyntaxWrapper)node).Identifier.ValueText, _ => null }; } private static SyntaxNode CleanupExpression(SyntaxNode node) { while (node is ParenthesizedExpressionSyntax parenthesized) { node = parenthesized.Expression; } return node is MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax } memberAccess ? memberAccess.Name : node; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CatchEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CatchEmpty : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2486"; private const string MessageFormat = "Handle the exception or explain in a comment why it can be ignored."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var catchClause = (CatchClauseSyntax)c.Node; if (!HasStatements(catchClause) && !HasComments(catchClause) && IsGenericCatch(catchClause, c.Model)) { c.ReportIssue(rule, c.Node); } }, SyntaxKind.CatchClause); } private static bool IsGenericCatch(CatchClauseSyntax catchClause, SemanticModel semanticModel) { if (catchClause.Declaration == null) { return true; } if (catchClause.Filter != null) { return false; } var type = semanticModel.GetTypeInfo(catchClause.Declaration.Type).Type; return type.Is(KnownType.System_Exception); } private static bool HasComments(CatchClauseSyntax catchClause) { return catchClause.Block.OpenBraceToken.TrailingTrivia.Any(IsCommentTrivia) || catchClause.Block.CloseBraceToken.LeadingTrivia.Any(IsCommentTrivia); } private static bool IsCommentTrivia(SyntaxTrivia trivia) { return trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) || trivia.IsKind(SyntaxKind.SingleLineCommentTrivia); } private static bool HasStatements(CatchClauseSyntax catchClause) { return catchClause.Block.Statements.Any(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CatchRethrow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CatchRethrow : CatchRethrowBase { private static readonly BlockSyntax ThrowBlock = SyntaxFactory.Block(SyntaxFactory.ThrowStatement()); protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool ContainsOnlyThrow(CatchClauseSyntax currentCatch) => CSharpEquivalenceChecker.AreEquivalent(currentCatch.Block, ThrowBlock); protected override CatchClauseSyntax[] AllCatches(SyntaxNode node) => ((TryStatementSyntax)node).Catches.ToArray(); protected override SyntaxNode DeclarationType(CatchClauseSyntax catchClause) => catchClause.Declaration?.Type; protected override bool HasFilter(CatchClauseSyntax catchClause) => catchClause.Filter is not null; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(RaiseOnInvalidCatch, SyntaxKind.TryStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CatchRethrowCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CatchRethrowCodeFix : SonarCodeFix { internal const string Title = "Remove redundant catch"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CatchRethrow.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (!(syntaxNode.Parent is TryStatementSyntax tryStatement)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = CalculateNewRoot(root, syntaxNode, tryStatement); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static SyntaxNode CalculateNewRoot(SyntaxNode root, SyntaxNode currentNode, TryStatementSyntax tryStatement) { var isTryRemovable = tryStatement.Catches.Count == 1 && tryStatement.Finally == null; return isTryRemovable ? root.ReplaceNode( tryStatement, tryStatement.Block.Statements.Select(st => st.WithAdditionalAnnotations(Formatter.Annotation))) : root.RemoveNode(currentNode, SyntaxRemoveOptions.KeepNoTrivia); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CertificateValidationCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CertificateValidationCheck : CertificateValidationCheckBase< SyntaxKind, ArgumentSyntax, ExpressionSyntax, IdentifierNameSyntax, AssignmentExpressionSyntax, InvocationExpressionSyntax, ParameterSyntax, VariableDeclaratorSyntax, ParenthesizedLambdaExpressionSyntax, MemberAccessExpressionSyntax> { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override HashSet MethodDeclarationKinds { get; } = [SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement]; protected override HashSet TypeDeclarationKinds { get; } = CSharpFacade.Instance.SyntaxKind.TypeDeclaration.ToHashSet(); internal override MethodParameterLookupBase CreateParameterLookup(SyntaxNode argumentListNode, IMethodSymbol method) => argumentListNode switch { InvocationExpressionSyntax invocation => new CSharpMethodParameterLookup(invocation.ArgumentList, method), _ when ObjectCreationFactory.TryCreate(argumentListNode) is { ArgumentList: { } argumentList } => new CSharpMethodParameterLookup(argumentList, method), _ => throw new ArgumentException("Unexpected type.", nameof(argumentListNode)) // This should be throw only by bad usage of this method, not by input dependency }; protected override void Initialize(SonarAnalysisContext context) { // Handling of += syntax context.RegisterNodeAction(CheckAssignmentSyntax, SyntaxKind.AddAssignmentExpression); // Handling of = syntax context.RegisterNodeAction(CheckAssignmentSyntax, SyntaxKind.SimpleAssignmentExpression); // Handling of constructor parameter syntax (SslStream) context.RegisterNodeAction(CheckConstructorParameterSyntax, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); } protected override SyntaxNode FindRootTypeDeclaration(SyntaxNode node) => base.FindRootTypeDeclaration(node) ?? node.FirstAncestorOrSelf()?.Parent; protected override Location ExpressionLocation(SyntaxNode expression) => // For Lambda expression extract location of the parentheses only to separate them from secondary location of "true" ((expression is ParenthesizedLambdaExpressionSyntax lambda) ? lambda.ParameterList : expression).GetLocation(); protected override void SplitAssignment(AssignmentExpressionSyntax assignment, out IdentifierNameSyntax leftIdentifier, out ExpressionSyntax right) { leftIdentifier = assignment.Left.DescendantNodesAndSelf().OfType().LastOrDefault(); right = assignment.Right; } protected override IEqualityComparer CreateNodeEqualityComparer() => new CSharpSyntaxNodeEqualityComparer(); protected override ExpressionSyntax[] FindReturnAndThrowExpressions(InspectionContext c, SyntaxNode block) => block.DescendantNodes().OfType().Select(x => x.Expression) // Throw statements #2825. x.Expression can be NULL for standalone Throw and we need that one as well. .Concat(block.DescendantNodes().OfType().Select(x => x.Expression)) .ToArray(); protected override bool IsTrueLiteral(ExpressionSyntax expression) => expression?.RemoveParentheses().Kind() == SyntaxKind.TrueLiteralExpression; protected override ExpressionSyntax VariableInitializer(VariableDeclaratorSyntax variable) => variable.Initializer?.Value; protected override ImmutableArray LambdaLocations(InspectionContext c, ParenthesizedLambdaExpressionSyntax lambda) { if (lambda.Body is BlockSyntax block) { return BlockLocations(c, block); } if (lambda.Body is ExpressionSyntax expr && IsTrueLiteral(expr)) // LiteralExpressionSyntax or ParenthesizedExpressionSyntax like (((true))) { return new[] { lambda.Body.GetLocation() }.ToImmutableArray(); // Code was found guilty for lambda (...) => true } return ImmutableArray.Empty; } protected override SyntaxNode LocalVariableScope(VariableDeclaratorSyntax variable) => variable.FirstAncestorOrSelf(); protected override SyntaxNode ExtractArgumentExpressionNode(SyntaxNode expression) => expression.RemoveParentheses(); protected override SyntaxNode SyntaxFromReference(SyntaxReference reference) => reference.GetSyntax(); // VB.NET has more complicated logic } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CheckArgumentException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CheckArgumentException : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3928"; private const string MessageFormat = "{0}"; private const string ParameterLessConstructorMessage = "Use a constructor overloads that allows a more meaningful exception message to be provided."; private const string ConstructorParametersInverted = "ArgumentException constructor arguments have been inverted."; private const string InvalidParameterName = "The parameter name '{0}' is not declared in the argument list."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ImmutableArray ArgumentExceptionTypesToCheck = ImmutableArray.Create( KnownType.System_ArgumentException, KnownType.System_ArgumentNullException, KnownType.System_ArgumentOutOfRangeException, KnownType.System_DuplicateWaitObjectException); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(CheckForIssue, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); private static void CheckForIssue(SonarSyntaxNodeReportingContext analysisContext) { var objectCreation = ObjectCreationFactory.Create(analysisContext.Node); var methodSymbol = objectCreation.MethodSymbol(analysisContext.Model); if (methodSymbol?.ContainingType == null || !methodSymbol.ContainingType.IsAny(ArgumentExceptionTypesToCheck)) { return; } if (objectCreation.ArgumentList == null || objectCreation.ArgumentList.Arguments.Count == 0) { analysisContext.ReportIssue(Rule, objectCreation.Expression, ParameterLessConstructorMessage); return; } var parameterAndMessage = RetrieveParameterAndMessageArgumentValue(methodSymbol, objectCreation, analysisContext.Model); var constructorParameterArgument = parameterAndMessage.Item1; var constructorMessageArgument = parameterAndMessage.Item2; if (!constructorParameterArgument.HasValue) { // can't check non-constant strings OR argument is not set return; } var methodArgumentNames = GetMethodArgumentNames(objectCreation.Expression).ToHashSet(); if (!methodArgumentNames.Contains(TakeOnlyBeforeDot(constructorParameterArgument))) { var message = constructorMessageArgument.HasValue && methodArgumentNames.Contains(TakeOnlyBeforeDot(constructorMessageArgument)) ? ConstructorParametersInverted : string.Format(InvalidParameterName, constructorParameterArgument.Value); analysisContext.ReportIssue(Rule, objectCreation.Expression, message); } } private static Tuple, Optional> RetrieveParameterAndMessageArgumentValue(IMethodSymbol methodSymbol, IObjectCreation objectCreation, SemanticModel semanticModel) { var parameterNameValue = default(Optional); var messageValue = default(Optional); for (var i = 0; i < methodSymbol.Parameters.Length; i++) { var argument = objectCreation.ArgumentList.Arguments[i]; var argumentExpression = objectCreation.ArgumentList.Arguments[i].Expression; var argumentName = argument.NameColon != null ? argument.NameColon.Name.Identifier.ValueText : methodSymbol.Parameters[i].MetadataName; if (argumentName.Equals("paramName", StringComparison.Ordinal) || argumentName.Equals("parameterName", StringComparison.Ordinal)) { parameterNameValue = semanticModel.GetConstantValue(argumentExpression); } else if (argumentName.Equals("message", StringComparison.Ordinal)) { messageValue = semanticModel.GetConstantValue(argumentExpression); } } return new Tuple, Optional>(parameterNameValue, messageValue); } private static IEnumerable GetMethodArgumentNames(SyntaxNode creationSyntax) { var node = creationSyntax.AncestorsAndSelf().FirstOrDefault(ancestor => ancestor is SimpleLambdaExpressionSyntax or ParenthesizedLambdaExpressionSyntax or AccessorDeclarationSyntax or BaseMethodDeclarationSyntax or IndexerDeclarationSyntax or PropertyDeclarationSyntax or CompilationUnitSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(ancestor)); var parameterList = node switch { SimpleLambdaExpressionSyntax simpleLambda => new[] { simpleLambda.Parameter.Identifier.ValueText }, BaseMethodDeclarationSyntax method => IdentifierNames(method.ParameterList), ParenthesizedLambdaExpressionSyntax lambda => IdentifierNames(lambda.ParameterList), AccessorDeclarationSyntax accessor => AccessorIdentifierNames(accessor), IndexerDeclarationSyntax indexerDeclaration => IdentifierNames(indexerDeclaration.ParameterList), PropertyDeclarationSyntax propertyDeclaration => ParentParameterList(propertyDeclaration), CompilationUnitSyntax => new[] { "args" }, { } when LocalFunctionStatementSyntaxWrapper.IsInstance(node) => IdentifierNames(((LocalFunctionStatementSyntaxWrapper)node).ParameterList), _ => Enumerable.Empty() }; return parameterList.Union(ParentParameterList(creationSyntax)); static IEnumerable IdentifierNames(BaseParameterListSyntax parameterList) => parameterList.Parameters.Select(x => x.Identifier.ValueText); static IEnumerable AccessorIdentifierNames(AccessorDeclarationSyntax accessor) { var arguments = new List(); if (accessor.FirstAncestorOrSelf() is { } indexer) { arguments.AddRange(IdentifierNames(indexer.ParameterList)); } if (accessor.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration) { if (accessor.Parent.Parent is PropertyDeclarationSyntax propertyDeclaration) { arguments.Add(propertyDeclaration.Identifier.Text); } arguments.Add("value"); } return arguments; } static IEnumerable ParentParameterList(SyntaxNode node) => node?.Ancestors().OfType().FirstOrDefault() is { } typeDeclaration ? GetIdentifierNames(typeDeclaration.ParameterList()) : Enumerable.Empty(); static IEnumerable GetIdentifierNames(ParameterListSyntax parameterList) => parameterList is null ? Enumerable.Empty() : IdentifierNames(parameterList); } private static string TakeOnlyBeforeDot(Optional value) => (value.Value as string)?.Split('.').FirstOrDefault(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CheckFileLicense.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CheckFileLicense : CheckFileLicenseBase { internal const string HeaderFormatDefaultValue = @"/* * * Copyright (c) - * * Please configure this header in your SonarCloud/SonarQube quality profile. * You can also set it in SonarLint.xml additional file for SonarLint or standalone NuGet analyzer. */ "; [RuleParameter(HeaderFormatRuleParameterKey, PropertyType.Text, "Expected copyright and license header.", HeaderFormatDefaultValue)] public override string HeaderFormat { get; set; } = HeaderFormatDefaultValue; protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CheckFileLicenseCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CheckFileLicenseCodeFix : SonarCodeFix { internal const string Title = "Add or update license header"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CheckFileLicense.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); if (!diagnostic.Properties.Any() || !diagnostic.Properties.ContainsKey(CheckFileLicense.IsRegularExpressionPropertyKey) || !diagnostic.Properties.ContainsKey(CheckFileLicense.HeaderFormatPropertyKey)) { return Task.CompletedTask; } if (!bool.TryParse(diagnostic.Properties[CheckFileLicense.IsRegularExpressionPropertyKey], out var b) || b) { return Task.CompletedTask; } var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); context.RegisterCodeFix( Title, c => { var fileHeaderTrivias = CreateFileHeaderTrivias(diagnostic.Properties[CheckFileLicense.HeaderFormatPropertyKey]); var newRoot = root.ReplaceNode(syntaxNode, syntaxNode.WithLeadingTrivia(fileHeaderTrivias)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static IEnumerable CreateFileHeaderTrivias(string comment) { return new[] { SyntaxFactory.Comment(comment), SyntaxFactory.CarriageReturnLineFeed }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassAndMethodName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassAndMethodName : SonarDiagnosticAnalyzer { private const string MethodNameDiagnosticId = "S100"; private const string TypeNameDiagnosticId = "S101"; private const string MessageFormat = "Rename {0} '{1}' to match pascal case naming rules, {2}."; private const string MessageFormatNonUnderscore = "consider using '{0}'"; private const string MessageFormatUnderscore = "trim underscores from the name"; private static readonly DiagnosticDescriptor MethodNameRule = DescriptorFactory.Create(MethodNameDiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor TypeNameRule = DescriptorFactory.Create(TypeNameDiagnosticId, MessageFormat); private static readonly ImmutableArray ComRelatedTypes = ImmutableArray.Create( KnownType.System_Runtime_InteropServices_ComImportAttribute, KnownType.System_Runtime_InteropServices_InterfaceTypeAttribute); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(MethodNameRule, TypeNameRule); internal static IEnumerable SplitToParts(string name) { var currentWord = new StringBuilder(name.Length); foreach (var c in name) { if (char.IsUpper(c)) { if (currentWord.Length > 0 && !char.IsUpper(currentWord[currentWord.Length - 1])) { yield return currentWord.ToString(); currentWord.Clear(); } currentWord.Append(c); } else if (char.IsLower(c)) { if (currentWord.Length > 1 && char.IsUpper(currentWord[currentWord.Length - 1])) { var lastChar = currentWord[currentWord.Length - 1]; currentWord.Length--; yield return currentWord.ToString(); currentWord.Clear(); currentWord.Append(lastChar); } currentWord.Append(c); } else { if (currentWord.Length > 0) { yield return currentWord.ToString(); currentWord.Clear(); } yield return c.ToString(); } } if (currentWord.Length > 0) { yield return currentWord.ToString(); } } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } CheckTypeName(c); }, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); context.RegisterNodeAction(c => { var identifier = GetDeclarationIdentifier(c.Node); CheckMemberName(c, identifier); }, SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration, SyntaxKindEx.LocalFunctionStatement); } private static void CheckTypeName(SonarSyntaxNodeReportingContext context) { var typeDeclaration = (BaseTypeDeclarationSyntax)context.Node; var identifier = typeDeclaration.Identifier; var symbol = context.Model.GetDeclaredSymbol(typeDeclaration); if (symbol.GetAttributes(ComRelatedTypes).Any()) { return; } if (identifier.ValueText.StartsWith("_", StringComparison.Ordinal) || identifier.ValueText.EndsWith("_", StringComparison.Ordinal)) { context.ReportIssue(TypeNameRule, identifier, typeDeclaration.GetDeclarationTypeName(), identifier.ValueText, MessageFormatUnderscore); return; } if (typeDeclaration is ClassDeclarationSyntax && IsTestClassName(typeDeclaration.Identifier.ValueText)) { return; } if (symbol.DeclaringSyntaxReferences.Length > 1 && symbol.DeclaringSyntaxReferences.Any(syntax => syntax.SyntaxTree.IsConsideredGenerated(CSharpGeneratedCodeRecognizer.Instance, context.IsRazorAnalysisEnabled()))) { return; } var isNameValid = IsTypeNameValid(identifier.ValueText, requireInitialI: typeDeclaration is InterfaceDeclarationSyntax, allowInitialI: typeDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword), areUnderscoresAllowed: context.IsTestProject(), suggestion: out var suggestion); if (!isNameValid) { var messageEnding = string.Format(MessageFormatNonUnderscore, suggestion); context.ReportIssue(TypeNameRule, identifier, typeDeclaration.GetDeclarationTypeName(), identifier.ValueText, messageEnding); } } private static void CheckMemberName(SonarSyntaxNodeReportingContext context, SyntaxToken identifier) { var symbol = context.Model.GetDeclaredSymbol(context.Node); if (symbol == null) { return; } if (string.IsNullOrWhiteSpace(identifier.ValueText) || symbol.ContainingType.GetAttributes(ComRelatedTypes).Any() || symbol.InterfaceMembers().Any() || symbol.GetOverriddenMember() != null || symbol.IsExtern) { return; } if (identifier.ValueText.StartsWith("_", StringComparison.Ordinal) || identifier.ValueText.EndsWith("_", StringComparison.Ordinal)) { context.ReportIssue(MethodNameRule, identifier, context.Node.GetDeclarationTypeName(), identifier.ValueText, MessageFormatUnderscore); return; } if (identifier.ValueText.Contains("_")) { return; } if (!IsMemberNameValid(identifier.ValueText, out var suggestion)) { var messageEnding = string.Format(MessageFormatNonUnderscore, suggestion); context.ReportIssue(MethodNameRule, identifier, context.Node.GetDeclarationTypeName(), identifier.ValueText, messageEnding); } } private static bool IsMemberNameValid(string identifierName, out string suggestion) { if (identifierName.Length == 1) { suggestion = identifierName.ToUpperInvariant(); return suggestion == identifierName; } var idealNameVariant = new StringBuilder(identifierName.Length); var acceptableNameVariant = new StringBuilder(identifierName.Length); foreach (var part in SplitToParts(identifierName)) { idealNameVariant.Append(SuggestFixedCaseName(part, 1)); acceptableNameVariant.Append(SuggestFixedCaseName(part, 2)); } idealNameVariant[0] = char.ToUpperInvariant(idealNameVariant[0]); suggestion = SuggestCapitalLetterAfterNonLetter(idealNameVariant); acceptableNameVariant[0] = char.ToUpperInvariant(acceptableNameVariant[0]); var acceptableSuggestion = SuggestCapitalLetterAfterNonLetter(acceptableNameVariant); return acceptableSuggestion == identifierName || suggestion == identifierName; } private static bool IsTypeNameValid(string identifierName, bool requireInitialI, bool allowInitialI, bool areUnderscoresAllowed, out string suggestion) { if (identifierName.Length == 1) { suggestion = identifierName.ToUpperInvariant(); return suggestion == identifierName; } var idealNameVariant = new StringBuilder(identifierName.Length); var acceptableNameVariant = new StringBuilder(identifierName.Length); var parts = SplitToParts(identifierName).ToList(); for (var i = 0; i < parts.Count; i++) { var part = parts[i]; if (part.Length == 1 && part[0] == '_' && !areUnderscoresAllowed) { continue; } var ideal = i == 0 ? HandleFirstPartOfTypeName(part, requireInitialI, allowInitialI, 1) : SuggestFixedCaseName(part, 1); var acceptable = i == 0 ? HandleFirstPartOfTypeName(part, requireInitialI, allowInitialI, 2) : SuggestFixedCaseName(part, 2); idealNameVariant.Append(ideal); acceptableNameVariant.Append(acceptable); } suggestion = SuggestCapitalLetterAfterNonLetter(idealNameVariant); var acceptableSuggestion = SuggestCapitalLetterAfterNonLetter(acceptableNameVariant); return acceptableSuggestion == identifierName || suggestion == identifierName; } private static string HandleFirstPartOfTypeName(string input, bool requireInitialI, bool allowInitialI, int maxUppercase) { var startsWithI = input[0] == 'I'; if (requireInitialI) { var prefix = startsWithI ? string.Empty : "I"; return prefix + SuggestFixedCaseName(FirstCharToUpper(input), maxUppercase + 1); } var suggestionToProcess = ShouldExcludeFirstLetter() ? FirstCharToUpper(input.Substring(1)) : FirstCharToUpper(input); return SuggestFixedCaseName(suggestionToProcess, maxUppercase); bool ShouldExcludeFirstLetter() => input.Length == 1 && !allowInitialI && startsWithI && IsCharUpper(input, 0); } private static string SuggestCapitalLetterAfterNonLetter(StringBuilder suggestion) { for (var i = 1; i < suggestion.Length; i++) { if (!char.IsLetter(suggestion[i - 1]) && char.IsLower(suggestion[i])) { suggestion[i] = char.ToUpperInvariant(suggestion[i]); } } return suggestion.ToString(); } private static string SuggestFixedCaseName(string input, int maxUppercaseCount) { var upper = input.Take(maxUppercaseCount); var lower = input.Skip(maxUppercaseCount).Select(char.ToLowerInvariant); return new string(upper.Concat(lower).ToArray()); } private static string FirstCharToUpper(string input) => input.Length > 0 ? char.ToUpperInvariant(input[0]) + input.Substring(1) : input; private static bool IsCharUpper(string input, int idx) => idx >= 0 && idx < input.Length && char.IsUpper(input[idx]); private static SyntaxToken GetDeclarationIdentifier(SyntaxNode declaration) => declaration.Kind() switch { SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)declaration).Identifier, SyntaxKind.PropertyDeclaration => ((PropertyDeclarationSyntax)declaration).Identifier, SyntaxKindEx.LocalFunctionStatement => ((LocalFunctionStatementSyntaxWrapper)declaration).Identifier, _ => throw new InvalidOperationException("Method can only be called on known registered syntax kinds") }; private static bool IsTestClassName(string className) => className != "ITest" && className != "ITests" && (className.EndsWith("Test") || className.EndsWith("Tests")); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassNamedException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassNamedException : ClassNamedExceptionBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassNotInstantiatable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassNotInstantiatable : ClassNotInstantiatableBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IEnumerable CollectRemovableDeclarations(INamedTypeSymbol namedType, Compilation compilation, string messageArg) { var typeDeclarations = new CSharpRemovableDeclarationCollector(namedType, compilation).TypeDeclarations; return typeDeclarations.Select(x => new ConstructorContext(x, Diagnostic.Create(Rule, x.Node.Identifier.GetLocation(), x.Node.GetDeclarationTypeName(), messageArg))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsEmptyAndNotPartial(SyntaxNode node) => node is TypeDeclarationSyntax { Members.Count: 0 } typeDeclaration && !typeDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)) && LacksParameterizedPrimaryConstructor(node); protected override BaseTypeDeclarationSyntax GetIfHasDeclaredBaseClassOrInterface(SyntaxNode node) => node is TypeDeclarationSyntax { BaseList: not null } declaration ? declaration : null; // Unlike in VB.NET there's no way to know for certain - by only looking at the syntax tree - from the declared types whether they are classes or interfaces. // Unless the class has more than one base type, because then one of them has to be an interface, as there can't be more than one base class. protected override bool HasInterfaceOrGenericBaseClass(BaseTypeDeclarationSyntax declaration) => declaration.BaseList.Types.Count > 1 // implements at least one interface || declaration.BaseList.Types.Any(x => x.Type is GenericNameSyntax); // or a generic class/interface protected override bool HasAnyAttribute(SyntaxNode node) => node is TypeDeclarationSyntax { AttributeLists.Count: > 0 }; protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeDeclarationSyntax)node).Keyword.ValueText; protected override bool HasConditionalCompilationDirectives(SyntaxNode node) => node.DescendantNodes(descendIntoTrivia: true) .Any(x => x.Kind() is SyntaxKind.IfDirectiveTrivia or SyntaxKind.ElifDirectiveTrivia or SyntaxKind.ElseDirectiveTrivia or SyntaxKind.EndIfDirectiveTrivia); private static bool LacksParameterizedPrimaryConstructor(SyntaxNode node) => IsParameterlessClass(node) || IsParameterlessRecord(node); private static bool IsParameterlessClass(SyntaxNode node) => node is ClassDeclarationSyntax declaration && LacksParameters(declaration.ParameterList(), declaration.BaseList); private static bool IsParameterlessRecord(SyntaxNode node) => RecordDeclarationSyntax(node) is { } declaration && LacksParameters(declaration.ParameterList, declaration.BaseList); private static bool LacksParameters(ParameterListSyntax parameterList, BaseListSyntax baseList) => parameterList?.Parameters is not { Count: > 0 } && BaseTypeSyntax(baseList) is not { ArgumentList.Arguments.Count: > 0 }; private static RecordDeclarationSyntaxWrapper? RecordDeclarationSyntax(SyntaxNode node) => RecordDeclarationSyntaxWrapper.IsInstance(node) ? (RecordDeclarationSyntaxWrapper)node : null; private static PrimaryConstructorBaseTypeSyntaxWrapper? BaseTypeSyntax(BaseListSyntax list) => list?.Types.FirstOrDefault() is { } type && PrimaryConstructorBaseTypeSyntaxWrapper.IsInstance(type) ? (PrimaryConstructorBaseTypeSyntaxWrapper)type : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassWithEqualityShouldImplementIEquatable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassWithEqualityShouldImplementIEquatable : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3897"; private const string MessageFormat = "Implement 'IEquatable<{0}>'."; private const string EqualsMethodName = nameof(object.Equals); private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; var classSymbol = c.Model.GetDeclaredSymbol(classDeclaration); if (classSymbol == null || ImplementsIEquatableInterface(classSymbol) || classDeclaration.Identifier.IsMissing) { return; } classSymbol.GetMembers(EqualsMethodName) .OfType() .Where(IsIEquatableEqualsMethodCandidate) .ToList() .ForEach(ms => c.ReportIssue(rule, classDeclaration.Identifier, ms.Parameters[0].Type.Name)); }, SyntaxKind.ClassDeclaration); } private bool ImplementsIEquatableInterface(ITypeSymbol classSymbol) => classSymbol.AllInterfaces.Any(@interface => @interface.ConstructedFrom.Is(KnownType.System_IEquatable_T) && @interface.TypeArguments.Length == 1 && @interface.TypeArguments[0].Equals(classSymbol)); private static bool IsIEquatableEqualsMethodCandidate(IMethodSymbol methodSymbol) { return methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == EqualsMethodName && !methodSymbol.IsOverride && methodSymbol.DeclaredAccessibility == Accessibility.Public && methodSymbol.ReturnType.Is(KnownType.System_Boolean) && methodSymbol.Parameters.Length == 1 && methodSymbol.Parameters[0].Type.Equals(methodSymbol.ContainingType); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ClassWithOnlyStaticMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClassWithOnlyStaticMember : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1118"; private const string MessageFormat = "{0}"; private const string MessageFormatConstructor = "Hide this public constructor by making it '{0}'."; private const string MessageFormatPrimaryConstructor = "Remove this primary constructor."; private const string MessageFormatStaticClass = "Add a '{0}' constructor or the 'static' keyword to the class declaration."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet ProblematicConstructorAccessibility = new HashSet { Accessibility.Public, Accessibility.Internal }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var namedType = c.Symbol as INamedTypeSymbol; if (!namedType.IsClass()) { return; } CheckClasses(c, namedType); CheckConstructors(c, namedType); }, SymbolKind.NamedType); private static void CheckClasses(SonarSymbolReportingContext context, INamedTypeSymbol utilityClass) { if (!ClassIsRelevant(utilityClass)) { return; } var reportMessage = string.Format(MessageFormatStaticClass, utilityClass.IsSealed ? SyntaxConstants.Private : SyntaxConstants.Protected); foreach (var syntaxReference in utilityClass.DeclaringSyntaxReferences) { if (syntaxReference.GetSyntax() is ClassDeclarationSyntax classDeclarationSyntax) { context.ReportIssue(Rule, classDeclarationSyntax.Identifier, reportMessage); } } } private static void CheckConstructors(SonarSymbolReportingContext context, INamedTypeSymbol utilityClass) { if (!ClassQualifiesForIssue(utilityClass) || !HasMembersAndAllAreStaticExceptConstructors(utilityClass)) { return; } foreach (var constructor in utilityClass.GetMembers() .Where(IsConstructor) .Where(symbol => ProblematicConstructorAccessibility.Contains(symbol.DeclaredAccessibility))) { var syntaxReferences = constructor.DeclaringSyntaxReferences; foreach (var syntaxReference in syntaxReferences) { switch (syntaxReference.GetSyntax()) { case ConstructorDeclarationSyntax constructorDeclaration: var reportMessage = string.Format(MessageFormatConstructor, utilityClass.IsSealed ? SyntaxConstants.Private : SyntaxConstants.Protected); context.ReportIssue(Rule, constructorDeclaration.Identifier, reportMessage); break; case ClassDeclarationSyntax classDeclaration when classDeclaration.ParameterList() is { Parameters.Count: 0 }: context.ReportIssue(Rule, classDeclaration.Identifier, MessageFormatPrimaryConstructor); break; default: break; } } } } private static bool ClassIsRelevant(INamedTypeSymbol @class) => ClassQualifiesForIssue(@class) && HasOnlyQualifyingMembers(@class, @class.GetMembers().Where(member => !member.IsImplicitlyDeclared).ToList()); private static bool ClassQualifiesForIssue(INamedTypeSymbol @class) => !@class.IsStatic && !@class.IsAbstract && !@class.AllInterfaces.Any() && @class.BaseType.Is(KnownType.System_Object); private static bool HasOnlyQualifyingMembers(INamedTypeSymbol @class, IList members) => members.Any() && members.All(member => member.IsStatic) && !ClassUsedAsInstanceInMembers(@class, members); private static bool ClassUsedAsInstanceInMembers(INamedTypeSymbol @class, IList members) => members.OfType().Any(member => @class.Equals(member.ReturnType) || member.Parameters.Any(parameter => @class.Equals(parameter.Type))) || members.OfType().Any(member => @class.Equals(member.Type)) || members.OfType().Any(member => @class.Equals(member.Type)); private static bool HasMembersAndAllAreStaticExceptConstructors(INamedTypeSymbol @class) { var membersExceptConstructors = @class.GetMembers() .Where(member => !IsConstructor(member)) .ToList(); return HasOnlyQualifyingMembers(@class, membersExceptConstructors); } private static bool IsConstructor(ISymbol member) => member is IMethodSymbol { MethodKind: MethodKind.Constructor, IsImplicitlyDeclared: false }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsCatchExceptions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AzureFunctionsCatchExceptions : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6421"; private const string MessageFormat = "Wrap Azure Function body in try/catch block."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsAzureFunction()) { var method = (MethodDeclarationSyntax)c.Node; var walker = new Walker(c.Model); if (walker.SafeVisit(method.GetBodyOrExpressionBody()) && walker.HasInvocationOutsideTryCatch) { c.ReportIssue(Rule, method.Identifier); } } }, SyntaxKind.MethodDeclaration); private sealed class Walker : SafeCSharpSyntaxWalker { private readonly SemanticModel semanticModel; public Walker(SemanticModel semanticModel) => this.semanticModel = semanticModel; public bool HasInvocationOutsideTryCatch { get; private set; } public override void Visit(SyntaxNode node) { if (!HasInvocationOutsideTryCatch // Stop walking when we know the answer && !(node.Kind() is SyntaxKind.CatchClause or // Do not visit content of "catch". It doesn't make sense to wrap logging in catch in another try/catch. SyntaxKind.AnonymousMethodExpression or SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKindEx.LocalFunctionStatement)) { base.Visit(node); } } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (!node.IsNameof(semanticModel)) { HasInvocationOutsideTryCatch = true; } } public override void VisitTryStatement(TryStatementSyntax node) { if (!node.Catches.Any(CatchesAllExceptions)) { base.VisitTryStatement(node); } } private static bool CatchesAllExceptions(CatchClauseSyntax catchClause) => catchClause.Declaration is null || (catchClause.Declaration.Type.NameIs(nameof(Exception)) && catchClause.Filter is null); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsLogFailures.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AzureFunctionsLogFailures : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6423"; private const string MessageFormat = "Log exception via ILogger with LogLevel Information, Warning, Error, or Critical."; private static readonly int[] InvalidLogLevel = { 0, // Trace 1, // Debug 6, // None }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var catchClause = (CatchClauseSyntax)c.Node; if (c.AzureFunctionMethod() is { } entryPoint && HasLoggerInScope(entryPoint)) { var walker = new LoggerCallWalker(c.Model, c.Cancel); walker.SafeVisit(catchClause.Block); // Exception handling in the filter clause preserves log scopes and is therefore recommended // See https://blog.stephencleary.com/2020/06/a-new-pattern-for-exception-logging.html walker.SafeVisit(catchClause.Filter?.FilterExpression); if (!walker.HasValidLoggerCall) { c.ReportIssue(Rule, catchClause.CatchKeyword.GetLocation(), walker.InvalidLoggerInvocationLocations); } } }, SyntaxKind.CatchClause); private static bool HasLoggerInScope(IMethodSymbol entryPoint) => entryPoint.Parameters.Any(x => x.Type.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger)) // Instance method entry points might have access to an ILogger via injected fields/properties // https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection || (entryPoint is { IsStatic: false, ContainingType: { } container } && HasLoggerMember(container)); internal static bool HasLoggerMember(ITypeSymbol typeSymbol) { var isOriginalType = true; foreach (var type in typeSymbol.GetSelfAndBaseTypes()) { if (type.GetMembers().Any(x => x.Kind is SymbolKind.Field or SymbolKind.Property && (isOriginalType || x.GetEffectiveAccessibility() != Accessibility.Private) && x.GetSymbolType()?.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger) is true)) { return true; } isOriginalType = false; } return false; } private sealed class LoggerCallWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel model; private readonly CancellationToken cancel; private List invalidInvocations; public bool HasValidLoggerCall { get; private set; } public IEnumerable InvalidLoggerInvocationLocations => invalidInvocations ?? []; public LoggerCallWalker(SemanticModel model, CancellationToken cancel) { this.model = model; this.cancel = cancel; } public override void Visit(SyntaxNode node) { if (!HasValidLoggerCall) { cancel.ThrowIfCancellationRequested(); base.Visit(node); } } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (model.GetSymbolInfo(node, cancel).Symbol is IMethodSymbol { ReceiverType: { } receiver } methodSymbol && receiver.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger)) { if (IsValidLogCall(node, methodSymbol)) { HasValidLoggerCall = true; } else { invalidInvocations ??= new(); invalidInvocations.Add(node.ToSecondaryLocation()); } } base.VisitInvocationExpression(node); } public override void VisitArgument(ArgumentSyntax node) { HasValidLoggerCall = HasValidLoggerCall || model.GetTypeInfo(node.Expression, cancel).Type?.DerivesOrImplements(KnownType.Microsoft_Extensions_Logging_ILogger) is true; base.VisitArgument(node); } private bool IsValidLogCall(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) => methodSymbol switch { // Wellknown LoggerExtensions methods invocations { IsExtensionMethod: true } when methodSymbol.ContainingType.Is(KnownType.Microsoft_Extensions_Logging_LoggerExtensions) => methodSymbol.Name switch { "LogInformation" or "LogWarning" or "LogError" or "LogCritical" => true, "Log" => IsPassingValidLogLevel(invocation, methodSymbol), "LogTrace" or "LogDebug" or "BeginScope" => false, _ => true, // Some unknown extension method on LoggerExtensions was called. Avoid FPs and assume it logs something. }, { IsExtensionMethod: true } => true, // Any other extension method is assumed to log something to avoid FP. _ => // Instance invocations on an ILogger instance. methodSymbol.Name switch { "Log" => IsPassingValidLogLevel(invocation, methodSymbol), "IsEnabled" or "BeginScope" => false, _ => true, // Some unknown method on an ILogger was called. Avoid FPs and assume it logs something. }, }; private bool IsPassingValidLogLevel(InvocationExpressionSyntax invocation, IMethodSymbol symbol) => symbol.Parameters.FirstOrDefault(x => x.Name == "logLevel") is { } logLevelParameter && new CSharpMethodParameterLookup(invocation, symbol).TryGetNonParamsSyntax(logLevelParameter, out var argumentSyntax) && argumentSyntax.FindConstantValue(model) is int logLevel ? !InvalidLogLevel.Contains(logLevel) : true; // Compliant: Some non-constant value is passed as loglevel or there is no logLevel parameter } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsReuseClients.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AzureFunctionsReuseClients : ReuseClientBase { private const string DiagnosticId = "S6420"; private const string MessageFormat = "Reuse client instances rather than creating new ones with each function invocation."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override ImmutableArray ReusableClients => ImmutableArray.Create( KnownType.System_Net_Http_HttpClient, // ComosDb (DocumentClient is superseded by CosmosClient) KnownType.Microsoft_Azure_Documents_Client_DocumentClient, KnownType.Microsoft_Azure_Cosmos_CosmosClient, // Servicebus V5 KnownType.Microsoft_Azure_ServiceBus_Management_ManagementClient, KnownType.Microsoft_Azure_ServiceBus_QueueClient, KnownType.Microsoft_Azure_ServiceBus_SessionClient, KnownType.Microsoft_Azure_ServiceBus_SubscriptionClient, KnownType.Microsoft_Azure_ServiceBus_TopicClient, // Servicebus V7 KnownType.Azure_Messaging_ServiceBus_ServiceBusClient, KnownType.Azure_Messaging_ServiceBus_Administration_ServiceBusAdministrationClient, // Storage KnownType.Azure_Storage_Blobs_BlobServiceClient, KnownType.Azure_Storage_Queues_QueueServiceClient, KnownType.Azure_Storage_Files_Shares_ShareServiceClient, KnownType.Azure_Storage_Files_DataLake_DataLakeServiceClient, // Resource manager KnownType.Azure_ResourceManager_ArmClient); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.AzureFunctionMethod() is not null && IsReusableClient(c) && !IsAssignedForReuse(c)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/AzureFunctionsStateless.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AzureFunctionsStateless : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6419"; private const string MessageFormat = "Do not modify a static state from Azure Function."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckTarget(c, ((AssignmentExpressionSyntax)c.Node).Left), SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression); context.RegisterNodeAction( c => CheckTarget(c, ((PrefixUnaryExpressionSyntax)c.Node).Operand), SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression); context.RegisterNodeAction( c => CheckTarget(c, ((PostfixUnaryExpressionSyntax)c.Node).Operand), SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression); context.RegisterNodeAction(c => { var argument = (ArgumentSyntax)c.Node; if (argument.RefOrOutKeyword != default) { CheckTarget(c, argument.Expression); } }, SyntaxKind.Argument); } private static void CheckTarget(SonarSyntaxNodeReportingContext context, ExpressionSyntax target) { if (context.IsAzureFunction() && context.Model.GetSymbolInfo((target as ElementAccessExpressionSyntax)?.Expression ?? target).Symbol is { } symbol && symbol.IsStatic) { context.ReportIssue(Rule, target); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CloudNative/DurableEntityInterfaceRestrictions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DurableEntityInterfaceRestrictions : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6424"; private const string MessageFormat = "Use valid entity interface. {0} {1}."; private const string SignalEntityName = "SignalEntity"; private const string SignalEntityAsyncName = "SignalEntityAsync"; private const string CreateEntityProxyName = "CreateEntityProxy"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var name = (GenericNameSyntax)c.Node; if (name.Identifier.ValueText is SignalEntityName or SignalEntityAsyncName or CreateEntityProxyName && name.TypeArgumentList.Arguments.Count == 1 && c.Model.GetSymbolInfo(name).Symbol is IMethodSymbol method && IsRestrictedMethod(method) && method.TypeArguments.Single() is INamedTypeSymbol { TypeKind: not TypeKind.Error } entityInterface && InterfaceErrorMessage(entityInterface) is { } message) { c.ReportIssue(Rule, name, entityInterface.Name, message); } }, SyntaxKind.GenericName); private static bool IsRestrictedMethod(IMethodSymbol method) => method.Is(KnownType.Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableEntityContext, SignalEntityName) || method.Is(KnownType.Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableEntityClient, SignalEntityAsyncName) || method.Is(KnownType.Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableOrchestrationContext, CreateEntityProxyName); private static string InterfaceErrorMessage(INamedTypeSymbol entityInterface) { if (entityInterface.TypeKind != TypeKind.Interface) { return "is not an interface"; } else if (entityInterface.IsGenericType) { return "is generic"; } else { var members = new[] { entityInterface }.Concat(entityInterface.AllInterfaces).SelectMany(x => x.GetMembers()).ToArray(); return members.Any() ? members.Select(MemberErrorMessage).WhereNotNull().FirstOrDefault() : "is empty"; } } private static string MemberErrorMessage(ISymbol member) { if (member is not IMethodSymbol method) { return $@"contains {member.Kind.ToString().ToLower()} ""{member.Name}"". Only methods are allowed"; } else if (method.IsGenericMethod) { return $@"contains generic method ""{method.Name}"""; } else if (method.Parameters.Length > 1) { return $@"contains method ""{method.Name}"" with {method.Parameters.Length} parameters. Zero or one are allowed"; } else if (!(method.ReturnsVoid || method.ReturnType.Is(KnownType.System_Threading_Tasks_Task) || method.ReturnType.Is(KnownType.System_Threading_Tasks_Task_T))) { return $@"contains method ""{method.Name}"" with invalid return type. Only ""void"", ""Task"" and ""Task"" are allowed"; } else { return null; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CognitiveComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.CSharp.Metrics; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CognitiveComplexity : CognitiveComplexityBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction(c => { if (c.IsTopLevelMain) { CheckComplexity( c, compilationUnit => compilationUnit, _ => Location.Create(c.Node.SyntaxTree, TextSpan.FromBounds(0, 0)), node => CSharpCognitiveComplexityMetric.GetComplexity(node, true), "top-level file", Threshold); } }, SyntaxKind.CompilationUnit); context.RegisterNodeAction( c => CheckComplexity( c, m => m, m => m.Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "method", Threshold), SyntaxKind.MethodDeclaration); // Here, we only care about arrowed properties, others will be handled by the accessor. context.RegisterNodeAction( c => CheckComplexity( c, p => p.ExpressionBody, p => p.Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "property", PropertyThreshold), SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => CheckComplexity( c, co => co, co => co.Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "constructor", Threshold), SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction( c => CheckComplexity( c, d => d, d => d.Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "destructor", Threshold), SyntaxKind.DestructorDeclaration); context.RegisterNodeAction( c => CheckComplexity( c, o => o, o => o.OperatorToken.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "operator", Threshold), SyntaxKind.OperatorDeclaration); context.RegisterNodeAction( c => CheckComplexity( c, a => a, a => a.Keyword.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "accessor", PropertyThreshold), SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.AddAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration); context.RegisterNodeAction( c => CheckComplexity( c, f => f, f => f.Declaration.Variables[0].Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "field", Threshold), SyntaxKind.FieldDeclaration); context.RegisterNodeAction(c => { if (((LocalFunctionStatementSyntaxWrapper)c.Node).Modifiers.Any(SyntaxKind.StaticKeyword)) { CheckComplexity( c, m => m, m => ((LocalFunctionStatementSyntaxWrapper)m).Identifier.GetLocation(), CSharpCognitiveComplexityMetric.GetComplexity, "static local function", Threshold); } }, SyntaxKindEx.LocalFunctionStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CollectionEmptinessChecking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CollectionEmptinessChecking : CollectionEmptinessCheckingBase { protected override string MessageFormat => "Use '.Any()' to test whether this 'IEnumerable<{0}>' is empty or not."; protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CollectionEmptinessCheckingCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CollectionEmptinessCheckingCodeFix : SonarCodeFix { private const string Title = "Use Any() instead"; private readonly CSharpFacade language = CSharpFacade.Instance; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionEmptinessChecking.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { if (root.FindNode(context.Diagnostics.First().Location.SourceSpan)?.FirstAncestorOrSelf() is { } binary) { var binaryLeft = binary.Left; var binaryRight = binary.Right; if (language.ExpressionNumericConverter.ConstantIntValue(binaryLeft) is { } left) { Simplify(root, binary, binaryRight, language.Syntax.ComparisonKind(binary).Mirror().Compare(left), context); } else if (language.ExpressionNumericConverter.ConstantIntValue(binaryRight) is { } right) { Simplify(root, binary, binaryLeft, language.Syntax.ComparisonKind(binary).Compare(right), context); } } return Task.CompletedTask; } private static void Simplify(SyntaxNode root, ExpressionSyntax expression, ExpressionSyntax countExpression, CountComparisonResult comparisonResult, SonarCodeFixContext context) => context.RegisterCodeFix( Title, _ => Replacement(root, expression, (InvocationExpressionSyntax)countExpression, comparisonResult, context), context.Diagnostics); private static Task Replacement(SyntaxNode root, ExpressionSyntax expression, InvocationExpressionSyntax count, CountComparisonResult comparison, SonarCodeFixContext context) { var any = IsExtension(count) ? AnyFromExtension(count) : AnyFromStaticMethod(count); SyntaxNode replacement = comparison == CountComparisonResult.Empty ? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, any) : any; return Task.FromResult(context.Document.WithSyntaxRoot(root.ReplaceNode(expression, replacement).WithAdditionalAnnotations(Formatter.Annotation))); } private static InvocationExpressionSyntax AnyFromExtension(InvocationExpressionSyntax count) { var memberAccess = (MemberAccessExpressionSyntax)count.Expression; var name = memberAccess.WithName(SyntaxFactory.IdentifierName(nameof(Enumerable.Any))); return SyntaxFactory.InvocationExpression(name, count.ArgumentList); } private static InvocationExpressionSyntax AnyFromStaticMethod(InvocationExpressionSyntax count) { var expression = count.ArgumentList.Arguments[0].Expression; var name = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expression, SyntaxFactory.IdentifierName(nameof(Enumerable.Any))); var arguments = SyntaxFactory.ArgumentList(count.ArgumentList.Arguments.RemoveAt(0)); return SyntaxFactory.InvocationExpression(name, arguments); } private static bool IsExtension(InvocationExpressionSyntax count) => !count.ArgumentList.Arguments.Any() || !((MemberAccessExpressionSyntax)count.Expression).Expression.NameIs(nameof(Enumerable)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CollectionPropertiesShouldBeReadOnly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CollectionPropertiesShouldBeReadOnly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4004"; private const string MessageFormat = "Make the '{0}' property read-only by removing the property setter or making it private."; private static readonly ImmutableArray CollectionTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_ICollection_T, KnownType.System_Collections_ICollection); private static readonly ImmutableArray IgnoredCollectionTypes = ImmutableArray.Create( KnownType.System_Array, KnownType.System_Security_PermissionSet); private static readonly ISet PrivateOrInternalAccessibility = new HashSet { Accessibility.Private, Accessibility.Internal, }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var propertyDeclaration = (PropertyDeclarationSyntax)c.Node; var propertySymbol = c.Model.GetDeclaredSymbol(propertyDeclaration); if (propertyDeclaration.AccessorList != null && !propertyDeclaration.AccessorList.Accessors.AnyOfKind(SyntaxKindEx.InitAccessorDeclaration) && propertySymbol != null && HasPublicSetter(propertySymbol) && IsObservedCollectionType(propertySymbol)) { c.ReportIssue(Rule, propertyDeclaration.Identifier, propertySymbol.Name); } }, SyntaxKind.PropertyDeclaration); private static bool IsObservedCollectionType(IPropertySymbol propertySymbol) => !propertySymbol.IsOverride && !propertySymbol.HasAttribute(KnownType.System_Runtime_Serialization_DataMemberAttribute) && !propertySymbol.HasAttribute(KnownType.Microsoft_AspNetCore_Components_ParameterAttribute) && !propertySymbol.ContainingType.HasAttribute(KnownType.System_SerializableAttribute) && propertySymbol.Type.OriginalDefinition.DerivesOrImplementsAny(CollectionTypes) && !propertySymbol.Type.OriginalDefinition.DerivesOrImplementsAny(IgnoredCollectionTypes) && !IsInterfaceImplementation(propertySymbol); private static bool HasPublicSetter(IPropertySymbol propertySymbol) => propertySymbol.SetMethod != null && !PrivateOrInternalAccessibility.Contains(propertySymbol.GetEffectiveAccessibility()) && !PrivateOrInternalAccessibility.Contains(propertySymbol.SetMethod.DeclaredAccessibility); private static bool IsInterfaceImplementation(IPropertySymbol propertySymbol) { foreach (var @interface in propertySymbol.ContainingType.Interfaces) { if (@interface.GetMembers() .OfType() .Where(x => x.Name == propertySymbol.Name) .Any(x => Equals(propertySymbol.ContainingType.FindImplementationForInterfaceMember(x), propertySymbol))) { return true; } } return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CollectionQuerySimplification.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CollectionQuerySimplification : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2971"; private const string MessageFormat = "{0}"; internal const string MessageUseInstead = "Use {0} here instead."; internal const string MessageDropAndChange = "Drop '{0}' and move the condition into the '{1}'."; internal const string MessageDropFromMiddle = "Drop this useless call to '{0}' or replace it by 'AsEnumerable' if " + "you are using LINQ to Entities."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ISet MethodNamesWithPredicate = new HashSet { "Any", "LongCount", "Count", "First", "FirstOrDefault", "Last", "LastOrDefault", "Single", "SingleOrDefault", }; private static readonly ISet MethodNamesForTypeCheckingWithSelect = new HashSet { "Any", "LongCount", "Count", "First", "FirstOrDefault", "Last", "LastOrDefault", "Single", "SingleOrDefault", "SkipWhile", "TakeWhile", }; private static readonly ISet MethodNamesToCollection = new HashSet { "ToList", "ToArray", }; private static readonly ISet IgnoredMethodNames = new HashSet { "AsEnumerable", // ignored as it is somewhat cleaner way to cast to IEnumerable and has no side effects }; private static readonly ISet AsIsSyntaxKinds = new HashSet { SyntaxKind.AsExpression, SyntaxKind.IsExpression }; private const string WhereMethodName = "Where"; private const int WherePredicateTypeArgumentNumber = 2; private const string SelectMethodName = "Select"; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckExtensionMethodsOnIEnumerable, SyntaxKind.InvocationExpression); context.RegisterNodeAction(CheckToCollectionCalls, SyntaxKind.InvocationExpression); context.RegisterNodeAction(CheckCountCall, SyntaxKind.InvocationExpression); } private static void CheckCountCall(SonarSyntaxNodeReportingContext context) { const string CountName = "Count"; var invocation = (InvocationExpressionSyntax)context.Node; if (invocation is { ArgumentList.Arguments.Count: 0, Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: CountName, Expression: { } memberAccessExpression } } && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol { Name: CountName } methodSymbol && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) && HasCountProperty(memberAccessExpression, context.Model)) { context.ReportIssue(Rule, GetReportLocation(invocation), string.Format(MessageUseInstead, $"'{CountName}' property")); } static bool HasCountProperty(ExpressionSyntax expression, SemanticModel semanticModel) => semanticModel.GetTypeInfo(expression).Type.GetMembers(CountName).OfType().Any(); } private static void CheckToCollectionCalls(SonarSyntaxNodeReportingContext context) { var outerInvocation = (InvocationExpressionSyntax)context.Node; if (context.Model.GetSymbolInfo(outerInvocation).Symbol is not IMethodSymbol outerMethodSymbol || !MethodExistsOnIEnumerable(outerMethodSymbol, context.Model)) { return; } var innerInvocation = GetInnerInvocation(outerInvocation, outerMethodSymbol); if (innerInvocation == null) { return; } if (context.Model.GetSymbolInfo(innerInvocation).Symbol is IMethodSymbol innerMethodSymbol && IsToCollectionCall(innerMethodSymbol)) { context.ReportIssue(Rule, GetReportLocation(innerInvocation), GetToCollectionCallsMessage(context, innerInvocation, innerMethodSymbol)); } } private static bool MethodExistsOnIEnumerable(IMethodSymbol methodSymbol, SemanticModel semanticModel) { if (IgnoredMethodNames.Contains(methodSymbol.Name)) { return false; } if (methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T)) { return true; } var enumerableType = semanticModel.Compilation.GetTypeByMetadataName(KnownType.System_Linq_Enumerable); if (enumerableType == null) { return false; } var members = enumerableType.GetMembers(methodSymbol.Name).OfType(); return members.Any(member => ParametersMatch(methodSymbol.OriginalDefinition, member)); } private static bool ParametersMatch(IMethodSymbol originalDefinition, IMethodSymbol member) { var parameterIndexOffset = originalDefinition.IsExtensionMethod ? 0 : 1; if (originalDefinition.Parameters.Length + parameterIndexOffset != member.Parameters.Length) { return false; } for (var i = 1; i < member.Parameters.Length; i++) { if (!originalDefinition.Parameters[i - parameterIndexOffset].Type.Equals(member.Parameters[i].Type)) { return false; } } return true; } private static bool IsToCollectionCall(IMethodSymbol methodSymbol) => MethodNamesToCollection.Contains(methodSymbol.Name) && (methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) || methodSymbol.ContainingType.ConstructedFrom.Is(KnownType.System_Collections_Generic_List_T)); private static string GetToCollectionCallsMessage(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) => IsLinqDatabaseQuery(invocation, context.Model) ? string.Format(MessageUseInstead, "'AsEnumerable'") : string.Format(MessageDropFromMiddle, methodSymbol.Name); private static bool IsLinqDatabaseQuery(InvocationExpressionSyntax node, SemanticModel model) { while (node?.Operands().Left is { } left) { if (GetNodeTypeSymbol(left, model).DerivesOrImplementsAny(KnownType.DatabaseBaseQueryTypes)) { return true; } node = left as InvocationExpressionSyntax; } return false; } private static ITypeSymbol GetNodeTypeSymbol(SyntaxNode node, SemanticModel model) => node.RemoveParentheses() switch { QueryExpressionSyntax { FromClause: { } fromClause } => GetNodeTypeSymbol(fromClause.Expression, model), { } n => model.GetTypeInfo(n).Type }; private static void CheckExtensionMethodsOnIEnumerable(SonarSyntaxNodeReportingContext context) { var outerInvocation = (InvocationExpressionSyntax)context.Node; if (context.Model.GetSymbolInfo(outerInvocation).Symbol is not IMethodSymbol outerMethodSymbol || !outerMethodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T)) { return; } var innerInvocation = GetInnerInvocation(outerInvocation, outerMethodSymbol); if (innerInvocation == null) { return; } if (context.Model.GetSymbolInfo(innerInvocation).Symbol is not IMethodSymbol innerMethodSymbol || !innerMethodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T)) { return; } if (CheckForSimplifiable(context, outerMethodSymbol, outerInvocation, innerMethodSymbol, innerInvocation)) { return; } CheckForCastSimplification(context, outerMethodSymbol, outerInvocation, innerMethodSymbol, innerInvocation); } private static InvocationExpressionSyntax GetInnerInvocation(InvocationExpressionSyntax outerInvocation, IMethodSymbol outerMethodSymbol) { if (outerMethodSymbol.MethodKind == MethodKind.ReducedExtension) { if (outerInvocation.Expression is MemberAccessExpressionSyntax memberAccess) { return memberAccess.Expression as InvocationExpressionSyntax; } } else { var argument = outerInvocation.ArgumentList.Arguments.FirstOrDefault(); if (argument != null) { return argument.Expression as InvocationExpressionSyntax; } if (outerInvocation.Expression is MemberAccessExpressionSyntax memberAccess) { return memberAccess.Expression as InvocationExpressionSyntax; } } return null; } private static List GetReducedArguments(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation) => methodSymbol.MethodKind == MethodKind.ReducedExtension ? invocation.ArgumentList.Arguments.ToList() : invocation.ArgumentList.Arguments.Skip(1).ToList(); private static void CheckForCastSimplification(SonarSyntaxNodeReportingContext context, IMethodSymbol outerMethodSymbol, InvocationExpressionSyntax outerInvocation, IMethodSymbol innerMethodSymbol, InvocationExpressionSyntax innerInvocation) { if (MethodNamesForTypeCheckingWithSelect.Contains(outerMethodSymbol.Name) && innerMethodSymbol.Name == SelectMethodName && IsFirstExpressionInLambdaIsNullChecking(outerMethodSymbol, outerInvocation) && TryGetCastInLambda(SyntaxKind.AsExpression, innerMethodSymbol, innerInvocation, out var typeNameInInner)) { context.ReportIssue(Rule, GetReportLocation(innerInvocation), string.Format(MessageUseInstead, $"'OfType<{typeNameInInner}>()'")); } if (outerMethodSymbol.Name == SelectMethodName && innerMethodSymbol.Name == WhereMethodName && IsExpressionInLambdaIsCast(outerMethodSymbol, outerInvocation, out var typeNameInOuter) && TryGetCastInLambda(SyntaxKind.IsExpression, innerMethodSymbol, innerInvocation, out typeNameInInner) && typeNameInOuter == typeNameInInner) { context.ReportIssue(Rule, GetReportLocation(innerInvocation), string.Format(MessageUseInstead, $"'OfType<{typeNameInInner}>()'")); } } private static Location GetReportLocation(InvocationExpressionSyntax invocation) => invocation.Expression is not MemberAccessExpressionSyntax memberAccess ? invocation.Expression.GetLocation() : memberAccess.Name.GetLocation(); private static bool IsExpressionInLambdaIsCast(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation, out string typeName) => TryGetCastInLambda(SyntaxKind.AsExpression, methodSymbol, invocation, out typeName) || TryGetCastInLambda(methodSymbol, invocation, out typeName); private static bool IsFirstExpressionInLambdaIsNullChecking(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation) { var arguments = GetReducedArguments(methodSymbol, invocation); if (arguments.Count != 1) { return false; } var expression = arguments[0].Expression; var binaryExpression = GetExpressionFromLambda(expression).RemoveParentheses() as BinaryExpressionSyntax; var lambdaParameter = GetLambdaParameter(expression); while (binaryExpression != null) { if (!binaryExpression.IsKind(SyntaxKind.LogicalAndExpression)) { return binaryExpression.IsKind(SyntaxKind.NotEqualsExpression) && IsNullChecking(binaryExpression, lambdaParameter); } binaryExpression = binaryExpression.Left.RemoveParentheses() as BinaryExpressionSyntax; } return false; } private static bool IsNullChecking(BinaryExpressionSyntax binaryExpression, string lambdaParameter) { if (CSharpEquivalenceChecker.AreEquivalent(SyntaxConstants.NullLiteralExpression, binaryExpression.Left.RemoveParentheses()) && binaryExpression.Right.RemoveParentheses().ToString() == lambdaParameter) { return true; } if (CSharpEquivalenceChecker.AreEquivalent(SyntaxConstants.NullLiteralExpression, binaryExpression.Right.RemoveParentheses()) && binaryExpression.Left.RemoveParentheses().ToString() == lambdaParameter) { return true; } return false; } private static ExpressionSyntax GetExpressionFromLambda(ExpressionSyntax expression) { if (expression is not SimpleLambdaExpressionSyntax lambda) { var parenthesizedLambda = expression as ParenthesizedLambdaExpressionSyntax; return parenthesizedLambda?.Body as ExpressionSyntax; } return lambda.Body as ExpressionSyntax; } private static string GetLambdaParameter(ExpressionSyntax expression) { if (expression is SimpleLambdaExpressionSyntax lambda) { return lambda.Parameter.Identifier.ValueText; } if (expression is not ParenthesizedLambdaExpressionSyntax parenthesizedLambda || parenthesizedLambda.ParameterList.Parameters.Count == 0) { return null; } return parenthesizedLambda.ParameterList.Parameters[0].Identifier.ValueText; } private static bool TryGetCastInLambda(SyntaxKind asOrIs, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation, out string type) { type = null; if (!AsIsSyntaxKinds.Contains(asOrIs)) { return false; } var arguments = GetReducedArguments(methodSymbol, invocation); if (arguments.Count != 1) { return false; } var expression = arguments[0].Expression; var lambdaParameter = GetLambdaParameter(expression); if (GetExpressionFromLambda(expression).RemoveParentheses() is not BinaryExpressionSyntax lambdaBody || lambdaParameter == null || !lambdaBody.IsKind(asOrIs)) { return false; } var castedExpression = lambdaBody.Left.RemoveParentheses(); if (lambdaParameter != castedExpression.ToString()) { return false; } type = lambdaBody.Right.ToString(); return true; } private static bool TryGetCastInLambda(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation, out string type) { type = null; var arguments = GetReducedArguments(methodSymbol, invocation); if (arguments.Count != 1) { return false; } var expression = arguments[0].Expression; var lambdaParameter = GetLambdaParameter(expression); if (GetExpressionFromLambda(expression).RemoveParentheses() is not CastExpressionSyntax castExpression || lambdaParameter == null) { return false; } var castedExpression = castExpression.Expression.RemoveParentheses(); if (lambdaParameter != castedExpression.ToString()) { return false; } type = castExpression.Type.ToString(); return true; } private static bool CheckForSimplifiable(SonarSyntaxNodeReportingContext context, IMethodSymbol outerMethodSymbol, InvocationExpressionSyntax outerInvocation, IMethodSymbol innerMethodSymbol, InvocationExpressionSyntax innerInvocation) { if (MethodIsNotUsingPredicate(outerMethodSymbol, outerInvocation) && innerMethodSymbol.Name == WhereMethodName && innerMethodSymbol.Parameters.Any(symbol => (symbol.Type as INamedTypeSymbol)?.TypeArguments.Length == WherePredicateTypeArgumentNumber)) { context.ReportIssue(Rule, GetReportLocation(innerInvocation), string.Format(MessageDropAndChange, WhereMethodName, outerMethodSymbol.Name)); return true; } return false; } private static bool MethodIsNotUsingPredicate(IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation) { var arguments = GetReducedArguments(methodSymbol, invocation); return arguments.Count == 0 && MethodNamesWithPredicate.Contains(methodSymbol.Name); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CollectionsShouldImplementGenericInterface.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CollectionsShouldImplementGenericInterface : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3909"; private const string MessageFormat = "Refactor this collection to implement '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly Dictionary NonGenericToGenericMapping = new() { { KnownType.System_Collections_ICollection, "System.Collections.Generic.ICollection" }, { KnownType.System_Collections_IList, "System.Collections.Generic.IList" }, { KnownType.System_Collections_IEnumerable, "System.Collections.Generic.IEnumerable" }, { KnownType.System_Collections_CollectionBase, "System.Collections.ObjectModel.Collection" } }; private static readonly ImmutableArray GenericTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_ICollection_T, KnownType.System_Collections_Generic_IList_T, KnownType.System_Collections_Generic_IEnumerable_T, KnownType.System_Collections_ObjectModel_Collection_T); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var typeDeclaration = (BaseTypeDeclarationSyntax)c.Node; var implementedTypes = typeDeclaration.BaseList?.Types; if (implementedTypes is null || c.IsRedundantPositionalRecordContext()) { return; } var containingType = (INamedTypeSymbol)c.ContainingSymbol; var typeSymbols = containingType.Interfaces.Concat([containingType.BaseType]).WhereNotNull().ToImmutableArray(); if (typeSymbols.Any(x => x.OriginalDefinition.IsAny(GenericTypes))) { return; } foreach (var typeSymbol in typeSymbols) { if (SuggestGenericCollectionType(typeSymbol) is { } suggestedGenericType) { c.ReportIssue(Rule, typeDeclaration.Identifier, suggestedGenericType); } } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static string SuggestGenericCollectionType(ITypeSymbol typeSymbol) => NonGenericToGenericMapping.FirstOrDefault(pair => pair.Key.Matches(typeSymbol)).Value; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CommentKeyword.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CommentKeyword : CommentKeywordBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsComment(SyntaxTrivia trivia) => trivia.IsComment(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CommentedOutCode.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CommentedOutCode : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S125"; internal const string MessageFormat = "Remove this commented out code."; private const int CommentMarkLength = 2; private static readonly string[] CodeEndings = ["{", ";}", "{}"]; private static readonly string[] CodeParts = ["++", "catch(", "switch(", "try{", "else{"]; private static readonly string[] CodePartsWithRelationalOperator = ["for(", "if(", "while("]; private static readonly string[] RelationalOperators = ["<", ">", "<=", ">=", "==", "!="]; // Groups 1 and 2 capture the first two words for keyword detection. private static readonly Regex SentencePattern = new( @"^\s*(?:[*\-]|->|=>)?\s*(\w+)[.,?:!']*\s+(\w+)[.,?:!']*\s+(?:\w+[.,?:!']*\s+)*\w+[.,?:!']*$", RegexOptions.None, Constants.DefaultRegexTimeout); private static readonly HashSet CodeKeywords = [ "abstract", "as", "async", "await", "base", "bool", "break", "byte", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "using", "virtual", "void", "volatile", "while", "yield", ]; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); internal static bool IsCode(string line) { var checkedLine = line.Replace(" ", string.Empty).Replace("\t", string.Empty); if (checkedLine.Contains("License") || checkedLine.Contains("c++") || checkedLine.Contains("C++")) { return false; } return EndsWithCode(checkedLine, line) || ContainsCodeParts(checkedLine) || ContainsMultipleLogicalOperators(checkedLine) || ContainsCodePartsWithRelationalOperator(checkedLine); } protected override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction(c => { foreach (var token in c.Tree.GetRoot().DescendantTokens()) { CheckTrivia(c, token.LeadingTrivia); CheckTrivia(c, token.TrailingTrivia); } }); private static void CheckTrivia(SonarSyntaxTreeReportingContext context, SyntaxTriviaList trivia) { var shouldReport = true; foreach (var trivium in trivia) { // comment start is checked because of https://github.com/dotnet/roslyn/issues/10003 if (trivium.IsKind(SyntaxKind.MultiLineCommentTrivia) && !trivium.ToFullString().TrimStart().StartsWith("/**", StringComparison.Ordinal)) { CheckMultilineComment(context, trivium); shouldReport = true; } else if (shouldReport && trivium.IsKind(SyntaxKind.SingleLineCommentTrivia) && !trivium.ToFullString().TrimStart().StartsWith("///", StringComparison.Ordinal) && IsCode(trivium.ToString().Substring(CommentMarkLength))) { context.ReportIssue(Rule, trivium.GetLocation()); shouldReport = false; } } } private static void CheckMultilineComment(SonarSyntaxTreeReportingContext context, SyntaxTrivia trivia) { var triviaLines = TriviaContent().Split(Constants.LineTerminators, StringSplitOptions.None); for (var triviaLineNumber = 0; triviaLineNumber < triviaLines.Length; triviaLineNumber++) { if (IsCode(triviaLines[triviaLineNumber])) { var triviaStartingLineNumber = trivia.GetLocation().StartLine(); var lineNumber = triviaStartingLineNumber + triviaLineNumber; var lineSpan = context.Tree.GetText().Lines[lineNumber].Span; var commentLineSpan = lineSpan.Intersection(trivia.GetLocation().SourceSpan); var location = Location.Create(context.Tree, commentLineSpan ?? lineSpan); context.ReportIssue(Rule, location); return; } } string TriviaContent() { var content = trivia.ToString().Substring(CommentMarkLength); return content.EndsWith("*/", StringComparison.Ordinal) ? content.Substring(0, content.Length - CommentMarkLength) : content; } } private static bool ContainsMultipleLogicalOperators(string checkedLine) { const int lengthOfOperator = 2; const int operatorCountLimit = 3; var lineLengthWithoutLogicalOperators = checkedLine.Replace("&&", string.Empty).Replace("||", string.Empty).Length; return checkedLine.Length - lineLengthWithoutLogicalOperators >= operatorCountLimit * lengthOfOperator; } private static bool ContainsCodeParts(string checkedLine) => CodeParts.Any(checkedLine.Contains); private static bool ContainsCodePartsWithRelationalOperator(string checkedLine) { return CodePartsWithRelationalOperator.Any(ContainsRelationalOperator); bool ContainsRelationalOperator(string codePart) { var index = checkedLine.IndexOf(codePart, StringComparison.Ordinal); return index >= 0 && RelationalOperators.Any(x => checkedLine.IndexOf(x, index, StringComparison.Ordinal) >= 0); } } private static bool EndsWithCode(string checkedLine, string originalLine) => checkedLine == "}" || CodeEndings.Any(x => checkedLine.EndsWith(x, StringComparison.Ordinal)) || (checkedLine.EndsWith(";", StringComparison.Ordinal) && !LooksLikeSentence(originalLine.Trim().TrimEnd(';'))); private static bool LooksLikeSentence(string trimmedLine) => SentencePattern.SafeMatch(trimmedLine) is { Success: true } match && !(CodeKeywords.Contains(match.Groups[1].Value) && CodeKeywords.Contains(match.Groups[2].Value)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CommentedOutCodeCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CommentedOutCodeCodeFix : SonarCodeFix { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CommentedOutCode.DiagnosticId); public override FixAllProvider GetFixAllProvider() => null; protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var comment = root.FindTrivia(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true); if (comment.IsKind(SyntaxKind.SingleLineCommentTrivia) || IsCode(comment)) { context.RegisterCodeFix( CommentedOutCode.MessageFormat, c => new Context(comment).ChangeDocument(context.Document, root), context.Diagnostics); } return Task.CompletedTask; } private static bool IsCode(SyntaxTrivia trivia) => trivia.LineNumbers().Count() == 1 || Array.TrueForAll(Lines(trivia), IsCode); private static bool IsCode(string line) { var trimmed = line.Trim(); return trimmed == "/*" || trimmed == "*/" || trimmed == "*" || string.IsNullOrEmpty(trimmed) || CommentedOutCode.IsCode(line); } private static string[] Lines(SyntaxTrivia trivia) => trivia.ToFullString().Split(Constants.LineTerminators, StringSplitOptions.None); private sealed class Context { private bool IsTrailing { get; } private SyntaxTrivia Comment { get; } private List Leading { get; } private List Trailing { get; } private SyntaxToken Token => Comment.Token; public Context(SyntaxTrivia comment) { Comment = comment; Leading = Token.LeadingTrivia.ToList(); Trailing = Token.TrailingTrivia.ToList(); IsTrailing = Trailing.Remove(comment); if (!IsTrailing) { Leading.Remove(comment); } } public Task ChangeDocument(Document document, SyntaxNode root) => Task.FromResult(document.WithSyntaxRoot(root.ReplaceToken(Token, NewToken()))); private SyntaxToken NewToken() => IsTrailing ? NewTrailing() : NewLeading(); private SyntaxToken NewTrailing() { var trailing = ShareLine(Token, Comment) ? Trailing.Where(KeepTrailing).ToList() : Trailing; // this can happen for multi line comments within an expression. Like: // int /* commented code */ MethodCall() if (trailing.Count == 0 && ShareLine(Token.GetNextToken(), Comment)) { trailing.Add(SyntaxFactory.Space); } return Token .WithLeadingTrivia(Leading) .WithTrailingTrivia(trailing); } private bool KeepTrailing(SyntaxTrivia trivia) => trivia.IsKind(SyntaxKind.EndOfLineTrivia) || !ShareLine(trivia, Comment); private SyntaxToken NewLeading() => Token .WithLeadingTrivia(Leading.Where(t => !ShareLine(t, Comment))) .WithTrailingTrivia(Trailing); private static bool ShareLine(SyntaxToken token, SyntaxTrivia trivia) => ShareLine(token.LineNumbers(), trivia.LineNumbers()); private static bool ShareLine(SyntaxTrivia l, SyntaxTrivia r) => ShareLine(l.LineNumbers(), r.LineNumbers()); private static bool ShareLine(IEnumerable l, IEnumerable r) => l.Intersect(r).Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CommentsShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CommentsShouldNotBeEmpty : CommentsShouldNotBeEmptyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsValidTriviaType(SyntaxTrivia trivia) => trivia.IsComment(); protected override string GetCommentText(SyntaxTrivia trivia) => trivia.Kind() switch { SyntaxKind.SingleLineCommentTrivia => GetSingleLineText(trivia), SyntaxKind.MultiLineCommentTrivia => GetMultiLineText(trivia), SyntaxKind.SingleLineDocumentationCommentTrivia => GetSingleLineDocumentationText(trivia), SyntaxKind.MultiLineDocumentationCommentTrivia => GetMultiLineDocumentationText(trivia), }; // // private static string GetSingleLineText(SyntaxTrivia trivia) => trivia.ToString().Trim().Substring(2); // /// private static string GetSingleLineDocumentationText(SyntaxTrivia trivia) { var stringBuilder = new StringBuilder(); foreach (var line in trivia.ToFullString().Split(Constants.LineTerminators, StringSplitOptions.None)) { var trimmedLine = line.TrimStart(null); trimmedLine = trimmedLine.StartsWith("///") ? trimmedLine.Substring(3).Trim() : trimmedLine.TrimEnd(null); stringBuilder.Append(trimmedLine); } return stringBuilder.ToString(); } // /* */ private static string GetMultiLineText(SyntaxTrivia trivia) => ParseMultiLine(trivia.ToString(), 2); // Length of "/*" // /** */ private static string GetMultiLineDocumentationText(SyntaxTrivia trivia) => ParseMultiLine(trivia.ToFullString(), 3); // Length of "/**" private static string ParseMultiLine(string commentText, int initialTrimSize) { commentText = commentText.Trim().Substring(initialTrimSize); if (commentText.EndsWith("*/", StringComparison.Ordinal)) // Might be unclosed, still reported { commentText = commentText.Substring(0, commentText.Length - 2); } var stringBuilder = new StringBuilder(); foreach (var line in commentText.Split(Constants.LineTerminators, StringSplitOptions.None)) { var trimmedLine = line.TrimStart(null); if (trimmedLine.StartsWith("*", StringComparison.Ordinal)) { trimmedLine = trimmedLine.TrimStart('*'); } stringBuilder.Append(trimmedLine.Trim()); } return stringBuilder.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ComparableInterfaceImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ComparableInterfaceImplementation : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1210"; private const string MessageFormat = "When implementing {0}, you should also override {1}."; private const string ObjectEquals = nameof(object.Equals); private static readonly ImmutableArray ComparableInterfaces = ImmutableArray.Create( KnownType.System_IComparable, KnownType.System_IComparable_T); private static readonly ComparisonKind[] ComparisonKinds = Enum.GetValues(typeof(ComparisonKind)).Cast() .Where(x => x != ComparisonKind.None) .OrderBy(x => x) .ToArray(); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var classDeclaration = (TypeDeclarationSyntax)c.Node; if (c.Model.GetDeclaredSymbol(classDeclaration) is { } classSymbol && !classSymbol.BaseType.GetSelfAndBaseTypes().Any(t => t.ImplementsAny(ComparableInterfaces)) && !(classSymbol.ContainingType is not null && classSymbol.GetEffectiveAccessibility() is Accessibility.Private or Accessibility.ProtectedAndInternal) && ImplementedComparableInterfaces(classSymbol) is var implementedComparableInterfaces && implementedComparableInterfaces.Any() && MembersToOverride(classSymbol.GetMembers().OfType()).ToList() is { } membersToOverride && membersToOverride.Any()) { c.ReportIssue( Rule, classDeclaration.Identifier, string.Join(" or ", implementedComparableInterfaces), membersToOverride.JoinAnd()); } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static IEnumerable ImplementedComparableInterfaces(INamedTypeSymbol classSymbol) => classSymbol.Interfaces .Where(x => x.OriginalDefinition.IsAny(ComparableInterfaces)) .Select(x => x.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)) .ToList(); private static IEnumerable MembersToOverride(IEnumerable methods) { if (!methods.Any(KnownMethods.IsObjectEquals)) { yield return ObjectEquals; } var overridenOperators = methods .Where(x => x.MethodKind == MethodKind.UserDefinedOperator) .Select(x => x.ComparisonKind()); foreach (var comparisonKind in ComparisonKinds.Except(overridenOperators)) { yield return comparisonKind.ToDisplayString(AnalyzerLanguage.CSharp); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CompareNaN.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CompareNaN : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2688"; private const string MessageFormat = "{0}"; private const string MessageFormatEquality = "Use {0}.IsNaN() instead."; private const string MessageFormatComparison = "Do not compare a number with {0}.NaN."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly Dictionary KnownTypeAliasMap = new() { { KnownType.System_Double, "double" }, { KnownType.System_Single, "float" }, }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var binaryExpressionSyntax = (BinaryExpressionSyntax)c.Node; if (TryGetFloatingPointType(binaryExpressionSyntax.Left, c.Model, out var floatingPointType) || TryGetFloatingPointType(binaryExpressionSyntax.Right, c.Model, out floatingPointType)) { var messageFormat = c.Node.Kind() is SyntaxKind.EqualsExpression or SyntaxKind.NotEqualsExpression ? MessageFormatEquality : MessageFormatComparison; c.ReportIssue( Rule, binaryExpressionSyntax, string.Format(messageFormat, KnownTypeAliasMap[floatingPointType])); } }, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); private static bool TryGetFloatingPointType(SyntaxNode expression, SemanticModel semanticModel, out KnownType floatingPointType) { floatingPointType = null; if (expression is not MemberAccessExpressionSyntax memberAccess) { return false; } var fieldSymbol = semanticModel.GetSymbolInfo(memberAccess).Symbol as IFieldSymbol; if (fieldSymbol?.Name != nameof(double.NaN)) { return false; } floatingPointType = KnownType.FloatingPointNumbers.FirstOrDefault(fieldSymbol.Type.Is); return floatingPointType != null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalSimplification.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConditionalSimplification : SonarDiagnosticAnalyzer { internal const string SimplifiedOperatorKey = "SimplifiedOperator"; internal const string IsCoalesceAssignmentSupportedKey = "IsNullCoalesceAssignmentSupported"; internal const string DiagnosticId = "S3240"; private const string MessageFormat = "Use the '{0}' operator here."; private const string MessageMultipleNegation = "Simplify negation here."; private const string CoalesceAssignmentOp = "??="; private const string CoalesceOp = "??"; private const string TernaryOp = "?:"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor RuleMultipleNegation = DescriptorFactory.Create(DiagnosticId, MessageMultipleNegation); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule, RuleMultipleNegation); internal static bool IsCoalesceAssignmentCandidate(SyntaxNode conditional, ExpressionSyntax comparedToNull) => conditional?.GetFirstNonParenthesizedParent() is AssignmentExpressionSyntax parentAssignment && CSharpEquivalenceChecker.AreEquivalent(parentAssignment.Left, comparedToNull); internal static StatementSyntax ExtractSingleStatement(StatementSyntax statement) { if (statement is BlockSyntax block) { return block.Statements.Count == 1 ? block.Statements.First() : null; } return statement; } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckConditionalExpression, SyntaxKind.ConditionalExpression); context.RegisterNodeAction(CheckIfStatement, SyntaxKind.IfStatement); context.RegisterNodeAction(CheckCoalesceExpression, SyntaxKind.CoalesceExpression); context.RegisterNodeAction(CheckNotPattern, SyntaxKindEx.NotPattern); } private static void CheckNotPattern(SonarSyntaxNodeReportingContext context) { var wrapper = (UnaryPatternSyntaxWrapper)context.Node; if (wrapper.Pattern.IsNot() && GetNegationRoot(context.Node) is var negationRoot && negationRoot == context.Node) { context.ReportIssue(RuleMultipleNegation, negationRoot); } static SyntaxNode GetNegationRoot(SyntaxNode node) { while (node.Parent.Kind() == SyntaxKindEx.NotPattern) { node = node.Parent; } return node; } } private static void CheckCoalesceExpression(SonarSyntaxNodeReportingContext context) { if (context.Node.GetFirstNonParenthesizedParent() is AssignmentExpressionSyntax assignment && !assignment.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) && context.Compilation.IsCoalesceAssignmentSupported()) { var left = ((BinaryExpressionSyntax)context.Node).Left.RemoveParentheses(); if (CSharpEquivalenceChecker.AreEquivalent(assignment.Left.RemoveParentheses(), left)) { context.ReportIssue(Rule, assignment.GetLocation(), BuildCodeFixProperties(context), CoalesceAssignmentOp); } } } private static void CheckIfStatement(SonarSyntaxNodeReportingContext context) { var ifStatement = (IfStatementSyntax)context.Node; if (ifStatement.Parent is ElseClauseSyntax) { return; } var whenTrue = ExtractSingleStatement(ifStatement.Statement); var whenFalse = ExtractSingleStatement(ifStatement.Else?.Statement); if (whenTrue is null || (ifStatement.Else is not null && whenFalse is null) || (whenFalse is not null && CSharpEquivalenceChecker.AreEquivalent(whenTrue, whenFalse))) { // Equivalence handled by S1871, return; } var possiblyCoalescing = ifStatement.Condition.TryGetExpressionComparedToNull(out var comparedToNull, out var comparedIsNullInTrue) && comparedToNull.CanBeNull(context.Model); if (CanBeSimplified(context, whenTrue, whenFalse, possiblyCoalescing ? comparedToNull : null, context.Model, comparedIsNullInTrue, out var simplifiedOperator)) { context.ReportIssue(Rule, ifStatement.IfKeyword, BuildCodeFixProperties(context, simplifiedOperator), simplifiedOperator); } } private static void CheckConditionalExpression(SonarSyntaxNodeReportingContext context) { var conditional = (ConditionalExpressionSyntax)context.Node; var condition = conditional.Condition.RemoveParentheses(); var whenTrue = conditional.WhenTrue.RemoveParentheses(); var whenFalse = conditional.WhenFalse.RemoveParentheses(); if (CSharpEquivalenceChecker.AreEquivalent(whenTrue, whenFalse)) { // handled by S2758, return; } if (condition.TryGetExpressionComparedToNull(out var comparedToNull, out var comparedIsNullInTrue) && comparedToNull.CanBeNull(context.Model) && CanExpressionBeCoalescing(whenTrue, whenFalse, comparedToNull, context.Model, comparedIsNullInTrue)) { if (context.Compilation.IsCoalesceAssignmentSupported() && IsCoalesceAssignmentCandidate(conditional, comparedToNull)) { context.ReportIssue(Rule, conditional.GetFirstNonParenthesizedParent(), BuildCodeFixProperties(context), CoalesceAssignmentOp); } else { context.ReportIssue(Rule, conditional, BuildCodeFixProperties(context), CoalesceOp); } } } private static bool AreTypesCompatible(ExpressionSyntax first, ExpressionSyntax second, SemanticModel model, ITypeSymbol targetType = null) { if (first is AnonymousFunctionExpressionSyntax || second is AnonymousFunctionExpressionSyntax) { return false; } var firstType = model.GetTypeInfo(first).Type; var secondType = model.GetTypeInfo(second).Type; if (firstType is IErrorTypeSymbol || secondType is IErrorTypeSymbol) { return false; } if (IsNullAndValueType(firstType, secondType) || IsNullAndValueType(secondType, firstType)) { return false; } if (firstType is null || secondType is null) { return true; } if (targetType is not null && model.Compilation.IsTargetTypeConditionalSupported()) { return firstType.DerivesFrom(targetType) && secondType.DerivesFrom(targetType); } return firstType.Equals(secondType); } private static bool IsNullAndValueType(ITypeSymbol typeNull, ITypeSymbol typeValue) => typeNull is null && typeValue is { IsValueType: true }; private static bool CanBeSimplified(SonarSyntaxNodeReportingContext context, StatementSyntax statement1, StatementSyntax statement2, SyntaxNode comparedToNull, SemanticModel model, bool comparedIsNullInTrue, out string simplifiedOperator) { simplifiedOperator = TernaryOp; if (statement1 is ReturnStatementSyntax return1 && statement2 is ReturnStatementSyntax return2) { var retExpr1 = return1.Expression.RemoveParentheses(); var retExpr2 = return2.Expression.RemoveParentheses(); if (IsConditionalStructure(retExpr1) || IsConditionalStructure(retExpr2) || !AreTypesCompatible(return1.Expression, return2.Expression, model)) { return false; } if (comparedToNull is not null && CanExpressionBeCoalescing(retExpr1, retExpr2, comparedToNull, model, comparedIsNullInTrue)) { simplifiedOperator = CoalesceOp; } return true; } var expressionStatement2 = statement2 as ExpressionStatementSyntax; if ((statement1 is not ExpressionStatementSyntax) || (statement2 is not null && expressionStatement2 is null)) { return false; } var expression1 = ((ExpressionStatementSyntax)statement1).Expression.RemoveParentheses(); if (statement2 is null) { simplifiedOperator = context.Compilation.IsCoalesceAssignmentSupported() ? CoalesceAssignmentOp : CoalesceOp; return expression1 is AssignmentExpressionSyntax assignment && comparedIsNullInTrue && comparedToNull is not null && CSharpEquivalenceChecker.AreEquivalent(assignment.Left, comparedToNull); } if (IsConditionalStructure(statement1) || IsConditionalStructure(statement2)) { return false; } var expression2 = expressionStatement2.Expression.RemoveParentheses(); if (AreCandidateAssignments(expression1, expression2, comparedToNull, model, comparedIsNullInTrue, out simplifiedOperator)) { return true; } else if (comparedToNull is not null && CanExpressionBeCoalescing(expression1, expression2, comparedToNull, model, comparedIsNullInTrue)) { simplifiedOperator = CoalesceOp; return true; } else { return AreCandidateInvocations(expression1, expression2, null, model, comparedIsNullInTrue: false); } } private static bool AreCandidateAssignments(ExpressionSyntax expression1, ExpressionSyntax expression2, SyntaxNode compared, SemanticModel model, bool comparedIsNullInTrue, out string simplifiedOperator) { simplifiedOperator = TernaryOp; var assignment1 = expression1 as AssignmentExpressionSyntax; var assignment2 = expression2 as AssignmentExpressionSyntax; var canBeSimplified = assignment1 is not null && assignment2 is not null && CSharpEquivalenceChecker.AreEquivalent(assignment1.Left, assignment2.Left) && assignment1.Kind() == assignment2.Kind(); if (!canBeSimplified || !AreTypesCompatible(assignment1.Right, assignment2.Right, model, model.GetTypeInfo(assignment1.Left).Type)) { return false; } if (compared is not null && CanExpressionBeCoalescing(assignment1.Right, assignment2.Right, compared, model, comparedIsNullInTrue)) { simplifiedOperator = CoalesceOp; } return true; } private static bool AreCandidateInvocations(ExpressionSyntax firstExpression, ExpressionSyntax secondExpression, SyntaxNode comparedToNull, SemanticModel model, bool comparedIsNullInTrue) { if (firstExpression is not InvocationExpressionSyntax firstInvocation || secondExpression is not InvocationExpressionSyntax secondInvocation || firstInvocation.ArgumentList.Arguments.Count != secondInvocation.ArgumentList.Arguments.Count || !CSharpEquivalenceChecker.AreEquivalent(firstInvocation.Expression, secondInvocation.Expression) || model.GetSymbolInfo(firstInvocation).Symbol is not { } firstSymbol || model.GetSymbolInfo(secondInvocation).Symbol is not { } secondSymbol || !firstSymbol.Equals(secondSymbol)) { return false; } var numberOfDifferences = 0; var numberOfComparisonsToCondition = 0; for (var i = 0; i < firstInvocation.ArgumentList.Arguments.Count; i++) { var arg1 = firstInvocation.ArgumentList.Arguments[i].Expression.RemoveParentheses(); var arg2 = secondInvocation.ArgumentList.Arguments[i].Expression.RemoveParentheses(); if (!CSharpEquivalenceChecker.AreEquivalent(arg1, arg2)) { numberOfDifferences++; if (comparedToNull is not null) { var arg1IsComparedToNull = CSharpEquivalenceChecker.AreEquivalent(arg1, comparedToNull); var arg2IsComparedToNull = CSharpEquivalenceChecker.AreEquivalent(arg2, comparedToNull); if (arg1IsComparedToNull && !comparedIsNullInTrue) { numberOfComparisonsToCondition++; } if (arg2IsComparedToNull && comparedIsNullInTrue) { numberOfComparisonsToCondition++; } } if (!AreTypesCompatible(arg1, arg2, model)) { return false; } } else { if (comparedToNull is not null && CSharpEquivalenceChecker.AreEquivalent(arg1, comparedToNull)) { return false; } } } return numberOfDifferences == 1 && (comparedToNull is null || numberOfComparisonsToCondition is 1); } private static bool CanExpressionBeCoalescing(ExpressionSyntax whenTrue, ExpressionSyntax whenFalse, SyntaxNode comparedToNull, SemanticModel model, bool comparedIsNullInTrue) { if (CSharpEquivalenceChecker.AreEquivalent(whenTrue, comparedToNull)) { return !comparedIsNullInTrue; } else if (CSharpEquivalenceChecker.AreEquivalent(whenFalse, comparedToNull)) { return comparedIsNullInTrue; } else { return AreCandidateInvocations(whenTrue, whenFalse, comparedToNull, model, comparedIsNullInTrue); } } private static ImmutableDictionary BuildCodeFixProperties(SonarSyntaxNodeReportingContext c, string simplifiedOperator = null) { var ret = new Dictionary { { IsCoalesceAssignmentSupportedKey, c.Compilation.IsCoalesceAssignmentSupported().ToString() } }; if (simplifiedOperator is not null) { ret.Add(SimplifiedOperatorKey, simplifiedOperator); } return ret.ToImmutableDictionary(); } private static bool IsConditionalStructure(SyntaxNode node) => node.DescendantNodesAndSelf().Any(x => x.Kind() is SyntaxKind.ConditionalExpression or SyntaxKindEx.SwitchExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalSimplificationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ConditionalSimplificationCodeFix : SonarCodeFix { private const string Title = "Simplify condition"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ConditionalSimplification.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var oldNode = root.FindNode(diagnostic.Location.SourceSpan); if (oldNode is GlobalStatementSyntax globalStatementSyntax) { oldNode = globalStatementSyntax.Statement; } var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var newNode = Simplify(diagnostic, semanticModel, oldNode); if (newNode != null) { context.RegisterCodeFix( Title, c => { var replacement = newNode.WithTriviaFrom(oldNode).WithAdditionalAnnotations(Formatter.Annotation); var newRoot = root.ReplaceNode(oldNode, replacement); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } } private static SyntaxNode Simplify(Diagnostic diagnostic, SemanticModel semanticModel, SyntaxNode oldNode) { ExpressionSyntax compared; switch (oldNode) { case ConditionalExpressionSyntax conditional: var condition = conditional.Condition.RemoveParentheses(); var whenTrue = conditional.WhenTrue.RemoveParentheses(); var whenFalse = conditional.WhenFalse.RemoveParentheses(); condition.TryGetExpressionComparedToNull(out compared, out _); return SimplifyCoalesceExpression(new ComparedContext(diagnostic, semanticModel, compared), whenTrue, whenFalse); case IfStatementSyntax ifStatement: var ifPart = ConditionalSimplification.ExtractSingleStatement(ifStatement.Statement); var elsePart = ConditionalSimplification.ExtractSingleStatement(ifStatement.Else?.Statement); ifStatement.Condition.TryGetExpressionComparedToNull(out compared, out var _); return SimplifyIfStatement(new ComparedContext(diagnostic, semanticModel, compared), ifPart, elsePart, ifStatement.Condition.RemoveParentheses()); case AssignmentExpressionSyntax assignment: var context = new ComparedContext(diagnostic, semanticModel, null); var right = assignment.Right.RemoveParentheses(); if (right is BinaryExpressionSyntax binaryExpression && binaryExpression.Kind() == SyntaxKind.CoalesceExpression) { return CoalesceAssignmentExpression(context, assignment.Left, binaryExpression.Right); } else if (right is ConditionalExpressionSyntax conditional) { conditional.Condition.TryGetExpressionComparedToNull(out compared, out var comparedIsNullInTrue); if (context.IsCoalesceAssignmentSupported && ConditionalSimplification.IsCoalesceAssignmentCandidate(conditional, compared)) { return CoalesceAssignmentExpression(context, ((AssignmentExpressionSyntax)conditional.GetFirstNonParenthesizedParent()).Left, (comparedIsNullInTrue ? conditional.WhenTrue : conditional.WhenFalse).RemoveParentheses()); } } break; case var s when UnaryPatternSyntaxWrapper.IsInstance(s): return ReduceDoubleNegation(s); } return null; } private static SyntaxNode ReduceDoubleNegation(SyntaxNode node) { if (!UnaryPatternSyntaxWrapper.IsInstance(node)) { return node; } var parent = (UnaryPatternSyntaxWrapper)node; if (parent.IsNot() && parent.Pattern.IsNot()) { return ReduceDoubleNegation(((UnaryPatternSyntaxWrapper)parent.Pattern).Pattern); } return node; } private static StatementSyntax SimplifyIfStatement(ComparedContext context, StatementSyntax statement1, StatementSyntax statement2, ExpressionSyntax condition) { if (statement1 is ReturnStatementSyntax return1 && statement2 is ReturnStatementSyntax return2) { var retExpr1 = return1.Expression.RemoveParentheses(); var retExpr2 = return2.Expression.RemoveParentheses(); var createdExpression = context.SimplifiedOperator switch { "?:" => ConditionalExpression(condition, retExpr1, retExpr2), "??" => SimplifyCoalesceExpression(context, retExpr1, retExpr2), _ => throw new System.InvalidOperationException("Unexpected simplifiedOperator: " + context.SimplifiedOperator) }; return SyntaxFactory.ReturnStatement(createdExpression); } var expression = SimplifyIfExpression(context, statement1 as ExpressionStatementSyntax, statement2 as ExpressionStatementSyntax, condition); return expression == null ? null : SyntaxFactory.ExpressionStatement(expression); } private static ExpressionSyntax SimplifyIfExpression(ComparedContext context, ExpressionStatementSyntax statement1, ExpressionStatementSyntax statement2, ExpressionSyntax condition) { bool isCoalescing; var expression1 = statement1.Expression.RemoveParentheses(); switch (context.SimplifiedOperator) { case "?:": isCoalescing = false; break; case "??": if (statement2 == null) { return CoalesceAssignmentExpression(context, expression1); } isCoalescing = true; break; case "??=": return CoalesceAssignmentExpression(context, expression1); default: throw new System.InvalidOperationException("Unexpected simplifiedOperator: " + context.SimplifiedOperator); } var expression2 = statement2.Expression.RemoveParentheses(); return SimplifyAssignmentExpression(context, expression1, expression2, condition, isCoalescing) ?? SimplifyInvocationExpression(context, expression1, expression2, condition, isCoalescing); } private static ExpressionSyntax SimplifyAssignmentExpression(ComparedContext context, ExpressionSyntax expression1, ExpressionSyntax expression2, ExpressionSyntax condition, bool isCoalescing) { if (expression1 is AssignmentExpressionSyntax assignment1 && expression2 is AssignmentExpressionSyntax assignment2 && assignment1.Kind() == assignment2.Kind() && CSharpEquivalenceChecker.AreEquivalent(assignment1.Left, assignment2.Left)) { var createdExpression = isCoalescing ? SimplifyCoalesceExpression(context, assignment1.Right, assignment2.Right) : ConditionalExpression(condition, assignment1.Right, assignment2.Right); return SyntaxFactory.AssignmentExpression(assignment1.Kind(), assignment1.Left, createdExpression); } return null; } private static ExpressionSyntax SimplifyCoalesceExpression(ComparedContext context, ExpressionSyntax whenTrue, ExpressionSyntax whenFalse) { if (CSharpEquivalenceChecker.AreEquivalent(whenTrue, context.Compared)) { return CoalesceExpression(context.Compared, whenFalse); } else if (CSharpEquivalenceChecker.AreEquivalent(whenFalse, context.Compared)) { return CoalesceExpression(context.Compared, whenTrue); } return SimplifyInvocationExpression(context, whenTrue, whenFalse, null, isCoalescing: true); } private static ExpressionSyntax CoalesceAssignmentExpression(ComparedContext context, ExpressionSyntax assignmentExpression) => assignmentExpression is AssignmentExpressionSyntax originalAssignment ? CoalesceAssignmentExpression(context, originalAssignment.Left, originalAssignment.Right) : null; private static ExpressionSyntax CoalesceAssignmentExpression(ComparedContext context, ExpressionSyntax left, ExpressionSyntax right) { if (context.IsCoalesceAssignmentSupported) { return SyntaxFactory.AssignmentExpression(SyntaxKindEx.CoalesceAssignmentExpression, left, right); } return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, CoalesceExpression(left, right)); } private static ExpressionSyntax CoalesceExpression(ExpressionSyntax left, ExpressionSyntax right) => SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, left, right); private static ConditionalExpressionSyntax ConditionalExpression(ExpressionSyntax condition, ExpressionSyntax truePart, ExpressionSyntax falsePart) => SyntaxFactory.ConditionalExpression(condition, truePart, falsePart); private static ExpressionSyntax SimplifyInvocationExpression(ComparedContext context, ExpressionSyntax expression1, ExpressionSyntax expression2, ExpressionSyntax condition, bool isCoalescing) { if (expression1 is InvocationExpressionSyntax methodCall1 && expression2 is InvocationExpressionSyntax methodCall2 && context.SemanticModel.GetSymbolInfo(methodCall1).Symbol is IMethodSymbol methodSymbol1 && context.SemanticModel.GetSymbolInfo(methodCall2).Symbol is IMethodSymbol methodSymbol2 && methodSymbol1.Equals(methodSymbol2)) { return methodCall1.WithArgumentList(SimplifyInvocationArguments(context, methodCall1, methodCall2, condition, isCoalescing)); } return null; } private static ArgumentListSyntax SimplifyInvocationArguments(ComparedContext context, InvocationExpressionSyntax methodCall1, InvocationExpressionSyntax methodCall2, ExpressionSyntax condition, bool isCoalescing) { var newArgumentList = SyntaxFactory.ArgumentList(); for (var i = 0; i < methodCall1.ArgumentList.Arguments.Count; i++) { var arg1 = methodCall1.ArgumentList.Arguments[i]; var arg2 = methodCall2.ArgumentList.Arguments[i]; var expr1 = arg1.Expression.RemoveParentheses(); var expr2 = arg2.Expression.RemoveParentheses(); if (CSharpEquivalenceChecker.AreEquivalent(expr1, expr2)) { newArgumentList = newArgumentList.AddArguments(arg1.WithExpression(expr1)); } else { ExpressionSyntax createdExpression; if (isCoalescing) { var arg1Compared = CSharpEquivalenceChecker.AreEquivalent(expr1, context.Compared); createdExpression = SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, context.Compared, arg1Compared ? expr2 : expr1); } else { createdExpression = SyntaxFactory.ConditionalExpression(condition, expr1, expr2); } newArgumentList = newArgumentList.AddArguments(SyntaxFactory.Argument(arg1.NameColon, arg1.RefOrOutKeyword, createdExpression)); } } return newArgumentList; } private class ComparedContext { public readonly ExpressionSyntax Compared; public readonly SemanticModel SemanticModel; public readonly bool IsCoalesceAssignmentSupported; /// /// Value is set only for IfStatement checks. /// public readonly string SimplifiedOperator; public ComparedContext(Diagnostic diagnostic, SemanticModel semanticModel, ExpressionSyntax compared) { Compared = compared; SemanticModel = semanticModel; IsCoalesceAssignmentSupported = bool.Parse(diagnostic.Properties[ConditionalSimplification.IsCoalesceAssignmentSupportedKey]); diagnostic.Properties.TryGetValue(ConditionalSimplification.SimplifiedOperatorKey, out SimplifiedOperator); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalStructureSameCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConditionalStructureSameCondition : ConditionalStructureSameConditionBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var ifStatement = (IfStatementSyntax)c.Node; var currentCondition = ifStatement.Condition; if (ifStatement.PrecedingConditionsInConditionChain().FirstOrDefault(x => CSharpEquivalenceChecker.AreEquivalent(currentCondition, x)) is { } precedingCondition) { c.ReportIssue(rule, currentCondition, [precedingCondition.ToSecondaryLocation()], precedingCondition.LineNumberToReport().ToString()); } }, SyntaxKind.IfStatement); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalStructureSameImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConditionalStructureSameImplementation : ConditionalStructureSameImplementationBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet IgnoredStatementsInSwitch = new HashSet { SyntaxKind.BreakStatement, SyntaxKind.ReturnStatement, SyntaxKind.ThrowStatement, }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var ifStatement = (IfStatementSyntax)c.Node; var precedingStatements = ifStatement.PrecedingStatementsInConditionChain().ToList(); var hasElse = HasLeafElseClause(ifStatement); CheckStatement(c, ifStatement.Statement, precedingStatements, c.Model, hasElse, "branch"); if (ifStatement.Else is not null) { CheckStatement(c, ifStatement.Else.Statement, [.. precedingStatements, ifStatement.Statement], c.Model, hasElse, "branch"); } }, SyntaxKind.IfStatement); context.RegisterNodeAction( c => { var switchSection = (SwitchSectionSyntax)c.Node; var precedingSections = switchSection.PrecedingSections().ToList(); CheckStatement(c, switchSection, precedingSections, c.Model, HasDefaultClause((SwitchStatementSyntax)switchSection.Parent), "case"); }, SyntaxKind.SwitchSection); } private static bool HasDefaultClause(SwitchStatementSyntax switchStatement) => switchStatement.Sections.SelectMany(x => x.Labels).Any(x => x.IsKind(SyntaxKind.DefaultSwitchLabel)); private static bool HasLeafElseClause(IfStatementSyntax ifStatement) { while (ifStatement.Else?.Statement is IfStatementSyntax elseIfStatement) { ifStatement = elseIfStatement; } return ifStatement.Else is not null; } private static void CheckStatement(SonarSyntaxNodeReportingContext context, SyntaxNode node, IReadOnlyList precedingBranches, SemanticModel model, bool hasElse, string discriminator) { var numberOfStatements = StatementsCount(node); if (!hasElse && numberOfStatements == 1) { if (precedingBranches.Any() && precedingBranches.All(x => AreEquivalentStatements(node, x, model))) { ReportSyntaxNode(context, node, precedingBranches[precedingBranches.Count - 1], discriminator); } } else if (numberOfStatements > 1 && precedingBranches.FirstOrDefault(x => AreEquivalentStatements(node, x, model)) is { } equivalentStatement) { ReportSyntaxNode(context, node, equivalentStatement, discriminator); } } private static bool AreEquivalentStatements(SyntaxNode node, SyntaxNode otherNode, SemanticModel model) => new EquivalentNodeCompare(node, otherNode) switch { (SwitchSectionSyntax { Statements: var statements }, SwitchSectionSyntax { Statements: var otherStatements }) => CSharpEquivalenceChecker.AreEquivalent(statements, otherStatements) && HaveTheSameInvocations(statements, otherStatements, model), (BlockSyntax refBlock, BlockSyntax otherBlock) => CSharpEquivalenceChecker.AreEquivalent(refBlock, otherBlock) && HaveTheSameInvocations(refBlock, otherBlock, model), _ => false, // Should not happen }; private static int StatementsCount(SyntaxNode node) { // Get all child statements from the node, in case of a switch section, we need to handle the case where the statements are wrapped in a block var statements = node is SwitchSectionSyntax switchSection ? switchSection.Statements.OfType().SelectMany(x => x.Statements) .Union(switchSection.Statements.Where(x => !x.IsKind(SyntaxKind.Block))) : node.ChildNodes(); return statements.Count(IsApprovedStatement); } private static void ReportSyntaxNode(SonarSyntaxNodeReportingContext context, SyntaxNode node, SyntaxNode precedingNode, string errorMessageDiscriminator) => context.ReportIssue(Rule, node, [precedingNode.ToSecondaryLocation()], precedingNode.LineNumberToReport().ToString(), errorMessageDiscriminator); private static bool IsApprovedStatement(SyntaxNode statement) => !statement.IsAnyKind(IgnoredStatementsInSwitch); private static bool HaveTheSameInvocations(SyntaxList first, SyntaxList second, SemanticModel model) { var referenceInvocations = first.SelectMany(x => x.DescendantNodes().OfType()).ToArray(); var candidateInvocations = second.SelectMany(x => x.DescendantNodes().OfType()).ToArray(); return HaveTheSameInvocations(referenceInvocations, candidateInvocations, model); } private static bool HaveTheSameInvocations(SyntaxNode first, SyntaxNode second, SemanticModel model) { var referenceInvocations = first.DescendantNodes().OfType().ToArray(); var candidateInvocations = second.DescendantNodes().OfType().ToArray(); return HaveTheSameInvocations(referenceInvocations, candidateInvocations, model); } private static bool HaveTheSameInvocations(InvocationExpressionSyntax[] referenceInvocations, InvocationExpressionSyntax[] candidateInvocations, SemanticModel model) { if (referenceInvocations.Length != candidateInvocations.Length) { return false; } for (var i = 0; i < referenceInvocations.Length; i++) { if (!referenceInvocations[i].IsEqualTo(candidateInvocations[i], model)) { return false; } } return true; } private readonly record struct EquivalentNodeCompare(SyntaxNode RefNode, SyntaxNode OtherNode); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalsShouldStartOnNewLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConditionalsShouldStartOnNewLine : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3972"; private const string MessageFormat = "Move this 'if' to a new line or add the missing 'else'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var ifKeyword = ((IfStatementSyntax)c.Node).IfKeyword; if (TryGetPreviousTokenInSameLine(ifKeyword, out var previousTokenInSameLine) && previousTokenInSameLine.IsKind(SyntaxKind.CloseBraceToken)) { c.ReportIssue(rule, ifKeyword, [previousTokenInSameLine.ToSecondaryLocation()]); } }, SyntaxKind.IfStatement); } private static bool TryGetPreviousTokenInSameLine(SyntaxToken token, out SyntaxToken previousToken) { previousToken = token.GetPreviousToken(); return previousToken.Line() == token.Line(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConditionalsWithSameCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConditionalsWithSameCondition : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2760"; private const string MessageFormat = "This condition was just checked on line {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckMatchingExpressionsInSucceedingStatements(c, x => x.Condition), SyntaxKind.IfStatement); context.RegisterNodeAction( c => CheckMatchingExpressionsInSucceedingStatements(c, x => x.Expression), SyntaxKind.SwitchStatement); } private static void CheckMatchingExpressionsInSucceedingStatements(SonarSyntaxNodeReportingContext context, Func expression) where T : StatementSyntax { var currentStatement = (T)context.Node; if (currentStatement is { PrecedingStatement: T previousStatement }) { var currentExpression = expression(currentStatement); var previousExpression = expression(previousStatement); if (CSharpEquivalenceChecker.AreEquivalent(currentExpression, previousExpression) && !ContainsPossibleUpdate(previousStatement, currentExpression, context.Model)) { context.ReportIssue(Rule, currentExpression, previousExpression.LineNumberToReport().ToString()); } } } private static bool ContainsPossibleUpdate(StatementSyntax statement, ExpressionSyntax expression, SemanticModel semanticModel) { var checkedSymbols = expression.DescendantNodesAndSelf().OfType().Select(x => semanticModel.GetSymbolInfo(x).Symbol).WhereNotNull().ToHashSet(); var checkedSymbolNames = checkedSymbols.Select(x => x.Name).ToArray(); var statementDescendents = statement.DescendantNodesAndSelf().ToArray(); return statementDescendents.OfType().Any(x => HasCheckedSymbol(x.Left)) || statementDescendents.OfType().Any(x => HasCheckedSymbol(x.Operand)) || statementDescendents.OfType().Any(x => HasCheckedSymbol(x.Operand)); bool HasCheckedSymbol(SyntaxNode node) => checkedSymbolNames.Any(node.ToStringContains) && semanticModel.GetSymbolInfo(node).Symbol is { } symbol && checkedSymbols.Contains(symbol); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConstructorArgumentValueShouldExist.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConstructorArgumentValueShouldExist : ConstructorArgumentValueShouldExistBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode GetFirstAttributeArgument(AttributeSyntax attributeSyntax) => attributeSyntax.ArgumentList?.Arguments.FirstOrDefault(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConstructorOverridableCall.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConstructorOverridableCall : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1699"; private const string MessageFormat = "Remove this call from a constructor to the overridable '{0}' method."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolStartAction(c => { if (c.Symbol is IMethodSymbol { MethodKind: MethodKind.Constructor, ContainingType.IsSealed: false } constructor && !constructor.ContainingType.DerivesFrom(KnownType.Nancy_NancyModule)) { c.RegisterSyntaxNodeAction(cc => CheckOverridableCallInConstructor(cc, constructor), SyntaxKind.InvocationExpression); } }, SymbolKind.Method); private static void CheckOverridableCallInConstructor(SonarSyntaxNodeReportingContext context, IMethodSymbol constructor) { var invocationExpression = (InvocationExpressionSyntax)context.Node; if (invocationExpression.EnclosingScope().Kind() is not SyntaxKind.ThisConstructorInitializer and not SyntaxKind.BaseConstructorInitializer && invocationExpression.Expression is IdentifierNameSyntax or MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax } && context.Model.GetEnclosingSymbol(invocationExpression.SpanStart).Equals(constructor) && context.Model.GetSymbolInfo(invocationExpression.Expression).Symbol is IMethodSymbol methodSymbol && IsMethodOverridable(methodSymbol)) { context.ReportIssue(Rule, invocationExpression.Expression, methodSymbol.Name); } } private static bool IsMethodOverridable(IMethodSymbol methodSymbol) => methodSymbol.IsVirtual || methodSymbol.IsAbstract || (methodSymbol.IsOverride && !methodSymbol.IsSealed); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ConsumeValueTaskCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConsumeValueTaskCorrectly : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S5034"; internal const string MessageFormat = "{0}"; // 'await', 'AsTask', 'Result' and '.GetAwaiter().GetResult()' should be called only once on a ValueTask private const string ConsumeOnlyOnceMessage = "Refactor this 'ValueTask' usage to consume it only once."; private const string SecondaryConsumeOnlyOnceMessage = "The 'ValueTask' is consumed here again"; // 'Result' and '.GetAwaiter().GetResult()' should be consumed inside an 'if (valueTask.IsCompletedSuccessfully)' private const string ConsumeOnlyIfCompletedMessage = "Refactor this 'ValueTask' usage to consume the result only if the operation has completed successfully."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray ValueTaskTypes = new[] { KnownType.System_Runtime_CompilerServices_ValueTaskAwaiter, KnownType.System_Runtime_CompilerServices_ValueTaskAwaiter_TResult, KnownType.System_Threading_Tasks_ValueTask, KnownType.System_Threading_Tasks_ValueTask_TResult }.ToImmutableArray(); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var walker = new ConsumeValueTaskWalker(c.Model); walker.SafeVisit(c.Node); foreach (var syntaxNodes in walker.SymbolUsages.Values) { if (syntaxNodes.Count > 1) { c.ReportIssue(Rule, syntaxNodes[0], syntaxNodes.Skip(1).ToSecondaryLocations(SecondaryConsumeOnlyOnceMessage), ConsumeOnlyOnceMessage); } } foreach (var node in walker.ConsumedButNotCompleted) { c.ReportIssue(Rule, node, ConsumeOnlyIfCompletedMessage); } }, // when visiting a method or another member with logic inside, lambdas and local functions will be visited as well SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.PropertyDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.MethodDeclaration); private sealed class ConsumeValueTaskWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel model; // The key is the 'ValueTask' variable symbol, the value is a list of nodes where it is consumed public IDictionary> SymbolUsages { get; } // A list of 'ValueTask' nodes on which '.Result' or '.GetAwaiter().GetResult()' has been invoked when the operation has not yet completed public IList ConsumedButNotCompleted { get; } // List of 'ValueTask' symbols which have been accessed for 'IsCompletedSuccessfully' public ISet VerifiedForSuccessfulCompletion { get; } public ConsumeValueTaskWalker(SemanticModel model) { this.model = model; SymbolUsages = new Dictionary>(); ConsumedButNotCompleted = new List(); VerifiedForSuccessfulCompletion = new HashSet(); } /** * Check if 'await' is done on a 'ValueTask' */ public override void VisitAwaitExpression(AwaitExpressionSyntax node) { if (node.Expression is IdentifierNameSyntax identifierName && model.GetSymbolInfo(identifierName).Symbol is ISymbol symbol && symbol.GetSymbolType().OriginalDefinition.IsAny(ValueTaskTypes)) { AddToSymbolUsages(symbol, identifierName); } base.VisitAwaitExpression(node); } /** * Check if it's the wanted method on a ValueTask * - we treat AsTask() like await - always add it to the list * - for GetAwaiter().GetResult() - ignore the call if it's called inside an 'if (valueTask.IsCompletedSuccessfully)' */ public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.Expression is MemberAccessExpressionSyntax memberAccess) { if (node.IsMethodInvocation(ValueTaskTypes, "AsTask", model) && GetLeftMostIdentifier(memberAccess) is IdentifierNameSyntax identifier1) { AddToSymbolUsages(this.model.GetSymbolInfo(identifier1).Symbol, identifier1); } if (node.IsMethodInvocation(ValueTaskTypes, "GetResult", model) && GetLeftMostIdentifier(memberAccess) is IdentifierNameSyntax identifier2 && model.GetSymbolInfo(identifier2).Symbol is ISymbol symbol2 && !VerifiedForSuccessfulCompletion.Contains(symbol2)) { AddToSymbolUsages(symbol2, identifier2); ConsumedButNotCompleted.Add(identifier2); } } base.VisitInvocationExpression(node); } /** * Check if ".Result" is accessed on a 'ValueTask' * - ignore the call if it's called inside an 'if (valueTask.IsCompletedSuccessfully)' */ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { if (node.IsPropertyInvocation(ValueTaskTypes, "Result", model) && GetLeftMostIdentifier(node) is IdentifierNameSyntax identifierName && this.model.GetSymbolInfo(identifierName).Symbol is ISymbol symbol && !VerifiedForSuccessfulCompletion.Contains(symbol)) { AddToSymbolUsages(symbol, identifierName); ConsumedButNotCompleted.Add(identifierName); } base.VisitMemberAccessExpression(node); } public override void VisitIfStatement(IfStatementSyntax node) { var valueTaskMemberAccess = node.Condition.DescendantNodesAndSelf().FirstOrDefault(n => n is MemberAccessExpressionSyntax memberAccess && memberAccess.IsPropertyInvocation(ValueTaskTypes, "IsCompletedSuccessfully", model)); if (valueTaskMemberAccess is MemberAccessExpressionSyntax member && GetLeftMostIdentifier(member) is IdentifierNameSyntax identifierName && this.model.GetSymbolInfo(identifierName).Symbol is ISymbol symbol && !VerifiedForSuccessfulCompletion.Contains(symbol)) { VerifiedForSuccessfulCompletion.Add(symbol); } base.VisitIfStatement(node); } private SyntaxNode GetLeftMostIdentifier(MemberAccessExpressionSyntax memberAccess) { if (memberAccess.Expression is InvocationExpressionSyntax invocation && invocation.Expression is MemberAccessExpressionSyntax innerMemberAccess && innerMemberAccess.Expression is IdentifierNameSyntax leftMostIdentifier) { return leftMostIdentifier; } else if (memberAccess.Expression is IdentifierNameSyntax identifierName) { return identifierName; } else if (memberAccess.Expression is MemberAccessExpressionSyntax leftMemberAccess && leftMemberAccess.Name is IdentifierNameSyntax name) { // gets 'task' from 'this.Task.Result' or 'Foo.Task.Result' return name; } return memberAccess.Expression; } private void AddToSymbolUsages(ISymbol symbol, SyntaxNode node) { if (SymbolUsages.TryGetValue(symbol, out var syntaxNodes)) { syntaxNodes.Add(node); } else { SymbolUsages.Add(symbol, [node]); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ControlCharacterInString.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ControlCharacterInString : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2479"; private const string MessageFormat = "Replace the control character at position {0} by its escape sequence '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly IDictionary EscapedControlCharacters = new Dictionary { {'\u0000', "\\0"}, {'\u0001', "\\u0001"}, {'\u0002', "\\u0002"}, {'\u0003', "\\u0003"}, {'\u0004', "\\u0004"}, {'\u0005', "\\u0005"}, {'\u0006', "\\u0006"}, {'\u0007', "\\a"}, {'\u0008', "\\b"}, {'\u0009', "\\t"}, {'\u000A', "\\n"}, {'\u000B', "\\v"}, {'\u000C', "\\f"}, {'\u000D', "\\r"}, {'\u000E', "\\u000E"}, {'\u000F', "\\u000F"}, {'\u0010', "\\u0010"}, {'\u0011', "\\u0011"}, {'\u0012', "\\u0012"}, {'\u0013', "\\u0013"}, {'\u0014', "\\u0014"}, {'\u0015', "\\u0015"}, {'\u0016', "\\u0016"}, {'\u0017', "\\u0017"}, {'\u0018', "\\u0018"}, {'\u0019', "\\u0019"}, {'\u001A', "\\u001A"}, {'\u001B', "\\u001B"}, {'\u001C', "\\u001C"}, {'\u001D', "\\u001D"}, {'\u001E', "\\u001E"}, {'\u001F', "\\u001F"}, {'\u007F', "\\u007F"}, {'\u1680', "\\u1680"}, {'\u2000', "\\u2000"}, {'\u2001', "\\u2001"}, {'\u2002', "\\u2002"}, {'\u2003', "\\u2003"}, {'\u2004', "\\u2004"}, {'\u2005', "\\u2005"}, {'\u2006', "\\u2006"}, {'\u2007', "\\u2007"}, {'\u2008', "\\u2008"}, {'\u2009', "\\u2009"}, {'\u200A', "\\u200A"}, {'\u200B', "\\u200B"}, {'\u200C', "\\u200C"}, {'\u200D', "\\u200D"}, {'\u2028', "\\u2028"}, {'\u2029', "\\u2029"}, {'\u202F', "\\u202F"}, {'\u205F', "\\u205F"}, {'\u2060', "\\u2060"}, {'\u3000', "\\u3000"}, {'\uFEFF', "\\uFEFF"}, }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckControlCharacter(c, ((LiteralExpressionSyntax)c.Node).Token.Text, 0), SyntaxKind.StringLiteralExpression, SyntaxKindEx.Utf8StringLiteralExpression); context.RegisterNodeAction( c => CheckControlCharacter(c, ((InterpolatedStringTextSyntax)c.Node).TextToken.Text, 1), SyntaxKind.InterpolatedStringText); } private static void CheckControlCharacter(SonarSyntaxNodeReportingContext c, string text, int displayPosIncrement) { if (IsInescapableString(c.Node) || IsInescepableInterpolatedString(c.Node.Parent) || IsInescapableUtf8String(c.Node)) { return; } for (var charPos = 0; charPos < text.Length; charPos++) { if (EscapedControlCharacters.TryGetValue(text[charPos], out var escapeSequence)) { c.ReportIssue(Rule, c.Node, (displayPosIncrement + charPos).ToString(), escapeSequence); return; } } } private static bool IsInescapableString(SyntaxNode node) => node.GetFirstToken() is var token && (token.IsVerbatimStringLiteral() || token.Kind() is SyntaxKindEx.SingleLineRawStringLiteralToken or SyntaxKindEx.MultiLineRawStringLiteralToken); private static bool IsInescepableInterpolatedString(SyntaxNode node) => node.GetFirstToken().Kind() is SyntaxKind.InterpolatedVerbatimStringStartToken or SyntaxKindEx.InterpolatedSingleLineRawStringStartToken or SyntaxKindEx.InterpolatedMultiLineRawStringStartToken; private static bool IsInescapableUtf8String(SyntaxNode node) => node.GetFirstToken().Kind() is SyntaxKindEx.Utf8SingleLineRawStringLiteralToken or SyntaxKindEx.Utf8MultiLineRawStringLiteralToken; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/CryptographicKeyShouldNotBeTooShort.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Security.Cryptography; using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CryptographicKeyShouldNotBeTooShort : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4426"; private const string MessageFormat = "Use a key length of at least {0} bits for {1} cipher algorithm.{2}"; private const string UselessAssignmentInfo = " This assignment does not update the underlying key size."; private const int MinimalCommonKeyLength = 2048; private const int MinimalEllipticCurveKeyLength = 224; private readonly Regex namedEllipticCurve = new("^(secp|sect|prime|c2tnb|c2pnb|brainpoolP|B-|K-|P-)(?\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout); private static readonly ImmutableArray BouncyCastleCurveClasses = ImmutableArray.Create( KnownType.Org_BouncyCastle_Asn1_Nist_NistNamedCurves, KnownType.Org_BouncyCastle_Asn1_Sec_SecNamedCurves, KnownType.Org_BouncyCastle_Asn1_TeleTrust_TeleTrusTNamedCurves, KnownType.Org_BouncyCastle_Asn1_X9_ECNamedCurveTable, KnownType.Org_BouncyCastle_Asn1_X9_X962NamedCurves); private static readonly ImmutableArray SystemSecurityCryptographyDsaRsa = ImmutableArray.Create( KnownType.System_Security_Cryptography_DSA, KnownType.System_Security_Cryptography_RSA); private static readonly ImmutableArray SystemSecurityCryptographyCurveClasses = ImmutableArray.Create( KnownType.System_Security_Cryptography_ECDiffieHellman, KnownType.System_Security_Cryptography_ECDsa, KnownType.System_Security_Cryptography_ECAlgorythm); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; var containingType = new Lazy(() => c.Model.GetSymbolInfo(invocation).Symbol?.ContainingType); switch (GetMethodName(invocation)) { case "Create": CheckAlgorithmCreation(c, containingType.Value, invocation); break; case "GenerateKey": CheckSystemSecurityEllipticCurve(c, containingType.Value, invocation, invocation.ArgumentList); break; case "GetByName": CheckBouncyCastleEllipticCurve(c, containingType.Value, invocation); break; case "Init": CheckBouncyCastleParametersGenerators(c, containingType.Value, invocation); break; default: // Current method is not related to any cryptographic method of interest break; } }, SyntaxKind.InvocationExpression); context.RegisterNodeAction( c => { var objectCreation = ObjectCreationFactory.Create(c.Node); var containingType = objectCreation.TypeSymbol(c.Model); CheckSystemSecurityEllipticCurve(c, containingType, objectCreation.Expression, objectCreation.ArgumentList); CheckSystemSecurityCryptographyAlgorithms(c, containingType, objectCreation); CheckBouncyCastleKeyGenerationParameters(c, containingType, objectCreation); }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); context.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; if (GetPropertyName(assignment.Left) == nameof(AsymmetricAlgorithm.KeySize) && assignment.Left is MemberAccessExpressionSyntax { Expression: { } expression } && c.Model.GetTypeInfo(expression).Type is ITypeSymbol containingType) { // Using the KeySize setter on DSACryptoServiceProvider/RSACryptoServiceProvider does not actually change the underlying key size // https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.dsacryptoserviceprovider.keysize // https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider.keysize if (containingType.IsAny(KnownType.System_Security_Cryptography_DSACryptoServiceProvider, KnownType.System_Security_Cryptography_RSACryptoServiceProvider)) { c.ReportIssue(Rule, assignment, MinimalCommonKeyLength.ToString(), CipherName(containingType), UselessAssignmentInfo); } else { CheckGenericDsaRsaCryptographyAlgorithms(c, containingType, assignment, assignment.Right); } } }, SyntaxKind.SimpleAssignmentExpression); } private void CheckAlgorithmCreation(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, InvocationExpressionSyntax invocation) { var firstParam = invocation.ArgumentList.Get(0); if (firstParam == null || containingType == null) { return; } if (containingType.IsAny(SystemSecurityCryptographyDsaRsa) && IsInvalidCommonKeyLength(c, firstParam)) { c.ReportIssue(Rule, invocation, MinimalCommonKeyLength.ToString(), CipherName(containingType), string.Empty); } else { CheckSystemSecurityEllipticCurve(c, containingType, invocation, invocation.ArgumentList); } } private void CheckBouncyCastleEllipticCurve(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, InvocationExpressionSyntax invocation) { var firstParam = invocation.ArgumentList.Get(0); if (firstParam == null || containingType == null || !containingType.IsAny(BouncyCastleCurveClasses)) { return; } if (firstParam.FindStringConstant(c.Model) is { } curveId) { CheckCurveNameKeyLength(c, invocation, curveId); } } private void CheckSystemSecurityEllipticCurve(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, SyntaxNode syntaxElement, ArgumentListSyntax argumentList) { var firstParam = argumentList.Get(0); if (firstParam == null || containingType == null || !containingType.DerivesFromAny(SystemSecurityCryptographyCurveClasses)) { return; } if (c.Model.GetSymbolInfo(firstParam).Symbol is { } paramSymbol) { CheckCurveNameKeyLength(c, syntaxElement, paramSymbol.Name); } } private void CheckCurveNameKeyLength(SonarSyntaxNodeReportingContext c, SyntaxNode syntaxElement, string curveName) { var match = namedEllipticCurve.SafeMatch(curveName); if (match.Success && int.TryParse(match.Groups["KeyLength"].Value, out var keyLength) && keyLength < MinimalEllipticCurveKeyLength) { c.ReportIssue(Rule, syntaxElement, MinimalEllipticCurveKeyLength.ToString(), "EC", string.Empty); } } private static void CheckSystemSecurityCryptographyAlgorithms(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, IObjectCreation objectCreation) { // DSACryptoServiceProvider is always noncompliant as it has a max key size of 1024 // RSACryptoServiceProvider() and RSACryptoServiceProvider(System.Security.Cryptography.CspParameters) constructors are noncompliants as they have a default key size of 1024 if (containingType.Is(KnownType.System_Security_Cryptography_DSACryptoServiceProvider) || (containingType.Is(KnownType.System_Security_Cryptography_RSACryptoServiceProvider) && HasDefaultSize(c, objectCreation.ArgumentList))) { c.ReportIssue(Rule, objectCreation.Expression, MinimalCommonKeyLength.ToString(), CipherName(containingType), string.Empty); } else { var firstParam = objectCreation.ArgumentList.Get(0); CheckGenericDsaRsaCryptographyAlgorithms(c, containingType, objectCreation.Expression, firstParam); } } private static bool HasDefaultSize(SonarSyntaxNodeReportingContext c, ArgumentListSyntax argumentList) => argumentList == null || argumentList.Arguments.Count == 0 || (argumentList.Arguments.Count == 1 && c.Model.GetTypeInfo(argumentList.Arguments[0].Expression).Type is ITypeSymbol type && type.Is(KnownType.System_Security_Cryptography_CspParameters)); private static void CheckGenericDsaRsaCryptographyAlgorithms(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, SyntaxNode syntaxElement, SyntaxNode keyLengthSyntax) { if (containingType.DerivesFromAny(SystemSecurityCryptographyDsaRsa) && keyLengthSyntax != null && IsInvalidCommonKeyLength(c, keyLengthSyntax)) { c.ReportIssue(Rule, syntaxElement, MinimalCommonKeyLength.ToString(), CipherName(containingType), string.Empty); } } private static void CheckBouncyCastleParametersGenerators(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, InvocationExpressionSyntax invocation) { if (invocation.ArgumentList.Get(0) is { } firstParam && containingType != null && containingType.IsAny(KnownType.Org_BouncyCastle_Crypto_Generators_DHParametersGenerator, KnownType.Org_BouncyCastle_Crypto_Generators_DsaParametersGenerator) && IsInvalidCommonKeyLength(c, firstParam)) { var cipherAlgorithmName = containingType.Is(KnownType.Org_BouncyCastle_Crypto_Generators_DHParametersGenerator) ? "DH" : "DSA"; c.ReportIssue(Rule, invocation, MinimalCommonKeyLength.ToString(), cipherAlgorithmName, string.Empty); } } private static void CheckBouncyCastleKeyGenerationParameters(SonarSyntaxNodeReportingContext c, ITypeSymbol containingType, IObjectCreation objectCreation) { if (objectCreation.ArgumentList.Get(2) is { } keyLengthParam && containingType.Is(KnownType.Org_BouncyCastle_Crypto_Parameters_RsaKeyGenerationParameters) && IsInvalidCommonKeyLength(c, keyLengthParam)) { c.ReportIssue(Rule, objectCreation.Expression, MinimalCommonKeyLength.ToString(), "RSA", string.Empty); } } private static bool IsInvalidCommonKeyLength(SonarSyntaxNodeReportingContext c, SyntaxNode keyLengthSyntax) => keyLengthSyntax.FindConstantValue(c.Model) is int keyLength && keyLength < MinimalCommonKeyLength; private static string GetMethodName(InvocationExpressionSyntax invocationExpression) => invocationExpression.Expression.GetIdentifier()?.ValueText; private static string GetPropertyName(ExpressionSyntax expression) => expression.GetIdentifier() is { } nameSyntax ? nameSyntax.ValueText : null; private static string CipherName(ITypeSymbol containingType) => containingType.Is(KnownType.System_Security_Cryptography_DSA) || containingType.DerivesFrom(KnownType.System_Security_Cryptography_DSA) ? "DSA" : "RSA"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DangerousGetHandleShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DangerousGetHandleShouldNotBeCalled : DangerousGetHandleShouldNotBeCalledBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DatabasePasswordsShouldBeSecure.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; using System.Xml.Linq; using System.Xml.XPath; using SonarAnalyzer.Core.Json; using SonarAnalyzer.Core.Json.Parsing; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DatabasePasswordsShouldBeSecure : TrackerHotspotDiagnosticAnalyzer { private const string DiagnosticId = "S2115"; private const string MessageFormat = "Use a secure password when connecting to this database."; private static readonly Regex Sanitizers = new(@"((integrated[_\s]security)|(trusted[_\s]connection))=(sspi|yes|true)", RegexOptions.Compiled | RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout); private static readonly MemberDescriptor[] TrackedInvocations = { new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_DbContextOptionsBuilder, "UseSqlServer"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_SqlServerDbContextOptionsExtensions, "UseSqlServer"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_DbContextOptionsBuilder, "UseMySQL"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_MySQLDbContextOptionsExtensions, "UseMySQL"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_DbContextOptionsBuilder, "UseSqlite"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_SqliteDbContextOptionsBuilderExtensions, "UseSqlite"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_DbContextOptionsBuilder, "UseOracle"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_OracleDbContextOptionsExtensions, "UseOracle"), // for UseNpgsql,the namespaces are different in .NET Core 3.1 and .NET 5 new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_DbContextOptionsBuilder, "UseNpgsql"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_NpgsqlDbContextOptionsExtensions, "UseNpgsql"), new MemberDescriptor(KnownType.Microsoft_EntityFrameworkCore_NpgsqlDbContextOptionsBuilderExtensions, "UseNpgsql"), }; protected override ILanguageFacade Language => CSharpFacade.Instance; public DatabasePasswordsShouldBeSecure() : base(AnalyzerConfiguration.AlwaysEnabled, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var inv = Language.Tracker.Invocation; inv.Track(input, inv.MatchMethod(TrackedInvocations), HasEmptyPasswordArgument()); } protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterCompilationAction(CheckWebConfig); context.RegisterCompilationAction(CheckAppSettings); } private void CheckWebConfig(SonarCompilationReportingContext c) { foreach (var fullPath in c.WebConfigFiles()) { var webConfig = File.ReadAllText(fullPath); if (webConfig.Contains("") && webConfig.ParseXDocument() is { } doc) { ReportEmptyPassword(c, doc, fullPath); } } } private void CheckAppSettings(SonarCompilationReportingContext c) { foreach (var fullPath in c.AppSettingsFiles()) { CheckAppSettingJson(c, fullPath); } } private void CheckAppSettingJson(SonarCompilationReportingContext c, string fullPath) { var appSettings = File.ReadAllText(fullPath); if (appSettings.Contains("\"ConnectionStrings\"") && JsonNode.FromString(appSettings) is { } json) { ReportEmptyPassword(c, json, fullPath); } } private void ReportEmptyPassword(SonarCompilationReportingContext c, XDocument doc, string webConfigPath) { foreach (var addAttribute in doc.XPathSelectElements("configuration/connectionStrings/add")) { if (addAttribute.Attribute("connectionString") is { } connectionString && IsVulnerable(connectionString.Value) && !HasSanitizers(connectionString.Value) && connectionString.CreateLocation(webConfigPath) is { } location) { c.ReportIssue(Rule, location); } } } private void ReportEmptyPassword(SonarCompilationReportingContext c, JsonNode doc, string appSettingsPath) { if (doc.TryGetPropertyNode("ConnectionStrings", out var connectionStrings) && connectionStrings.Kind == Kind.Object) { foreach (var key in connectionStrings.Keys) { if (connectionStrings[key] is { Kind: Kind.Value, Value: string value } connectionStringNode && IsVulnerable(value)) { c.ReportIssue(Rule, connectionStringNode.ToLocation(appSettingsPath)); } } } } private static TrackerBase.Condition HasEmptyPasswordArgument() => context => { var argumentList = ((InvocationExpressionSyntax)context.Node).ArgumentList.Arguments; return ConnectionStringArgument(argumentList)?.Expression switch { LiteralExpressionSyntax literal => IsVulnerable(literal.Token.ValueText) && !HasSanitizers(literal.Token.ValueText), InterpolatedStringExpressionSyntax interpolatedString => HasEmptyPasswordAndNoSanitizers(interpolatedString), BinaryExpressionSyntax binaryExpression => HasEmptyPasswordAndNoSanitizers(binaryExpression), _ => false }; }; // First search a named argument, then search literals, then fallback on the first argument (for constant propagation check). // This is an easy way to support explicit extension method invocation. private static ArgumentSyntax ConnectionStringArgument(SeparatedSyntaxList argumentList) => // Where(cond).First() is more efficient than First(cond) argumentList.Where(a => a.NameColon?.Name.Identifier.ValueText == "connectionString").FirstOrDefault() ?? argumentList.Where(x => x.Expression.Kind() is SyntaxKind.StringLiteralExpression or SyntaxKind.InterpolatedStringExpression or SyntaxKind.AddExpression) .FirstOrDefault() ?? argumentList.FirstOrDefault(); // For both interpolated strings and concatenation chain, it's easier to search in the string representation of the tree, rather than doing string searches for each individual // string token inside. private static bool HasEmptyPasswordAndNoSanitizers(ExpressionSyntax expression) { var toString = expression.ToString(); return IsVulnerable(toString) && !HasSanitizers(toString); } private static bool IsVulnerable(string connectionString) => connectionString.EndsWith("Password=") || connectionString.Contains("Password=;") // this is an edge case, for a string interpolation or concatenation the toString() will contain the ending " // we prefer to keep it like this for the simplicity of the implementation || connectionString.EndsWith("Password=\"") // raw string literals || connectionString.EndsWith("Password=\"\"\""); private static bool HasSanitizers(string connectionString) => Sanitizers.SafeIsMatch(connectionString); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey : DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IEnumerable TypeNodesOfTemporalKeyProperties(SonarSyntaxNodeReportingContext context) { var classDeclaration = (ClassDeclarationSyntax)context.Node; if (classDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword))) { return Enumerable.Empty(); } var className = classDeclaration.Identifier.ValueText; return classDeclaration.Members .OfType() .Where(x => IsCandidateProperty(x) && IsTemporalType(x.Type.GetName()) && IsKeyProperty(x, className)) .Select(x => x.Type); } private static bool IsCandidateProperty(PropertyDeclarationSyntax property) => property.Modifiers.Any(x => x.IsKind(SyntaxKind.PublicKeyword)) && !property.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword)) && property.AccessorList is { } accessorList && accessorList.Accessors.Any(x => x.Keyword.IsKind(SyntaxKind.GetKeyword)) && accessorList.Accessors.Any(x => x.Keyword.IsKind(SyntaxKind.SetKeyword)); private bool IsKeyProperty(PropertyDeclarationSyntax property, string className) { var propertyName = property.Identifier.ValueText; return IsKeyPropertyBasedOnName(propertyName, className) || HasKeyAttribute(property); } private bool HasKeyAttribute(PropertyDeclarationSyntax property) => property.AttributeLists .SelectMany(x => x.Attributes) .Any(x => MatchesAttributeName(x.GetName(), KeyAttributeTypeNames)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DateTimeFormatShouldNotBeHardcoded.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DateTimeFormatShouldNotBeHardcoded : DateTimeFormatShouldNotBeHardcodedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override Location HardCodedArgumentLocation(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments[0].Expression.GetLocation(); protected override bool HasInvalidFirstArgument(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.ArgumentList is { } && invocation.ArgumentList.Arguments.Any() && GetFormatArgumentExpression(invocation.ArgumentList) is { } argumentExpression && argumentExpression.FindConstantValue(semanticModel) is string { Length: > 1 }; private static ExpressionSyntax GetFormatArgumentExpression(ArgumentListSyntax argumentList) => (argumentList.GetArgumentByName("format") ?? argumentList.Arguments[0]).Expression; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DeadStores.RoslynCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CSharp.Rules { public partial class DeadStores : SonarDiagnosticAnalyzer { private class RoslynChecker : CheckerBase { private readonly RoslynLiveVariableAnalysis lva; public RoslynChecker(SonarSyntaxNodeReportingContext context, RoslynLiveVariableAnalysis lva) : base(context, lva) => this.lva = lva; protected override State CreateState(BasicBlock block) => new RoslynState(this, block); private class RoslynState : State { private readonly RoslynChecker owner; public RoslynState(RoslynChecker owner, BasicBlock block) : base(owner, block) => this.owner = owner; public override void AnalyzeBlock() { foreach (var operation in block.OperationsAndBranchValue.ToReversedExecutionOrder().Select(x => x.Instance)) { switch (operation.Kind) { case OperationKindEx.LocalReference: ProcessParameterOrLocalReference(ILocalReferenceOperationWrapper.FromOperation(operation)); break; case OperationKindEx.ParameterReference: ProcessParameterOrLocalReference(IParameterReferenceOperationWrapper.FromOperation(operation)); break; case OperationKindEx.SimpleAssignment: ProcessSimpleAssignment(ISimpleAssignmentOperationWrapper.FromOperation(operation)); break; case OperationKindEx.CompoundAssignment: ProcessCompoundAssignment(ICompoundAssignmentOperationWrapper.FromOperation(operation)); break; case OperationKindEx.DeconstructionAssignment: ProcessDeconstructionAssignment(IDeconstructionAssignmentOperationWrapper.FromOperation(operation)); break; case OperationKindEx.Increment: case OperationKindEx.Decrement: ProcessIncrementOrDecrement(IIncrementOrDecrementOperationWrapper.FromOperation(operation)); break; } } } private void ProcessParameterOrLocalReference(IOperationWrapper reference) { var symbols = owner.lva.ParameterOrLocalSymbols(reference.WrappedOperation).Where(x => IsSymbolRelevant(x)); if (reference.IsOutArgument()) { liveOut.ExceptWith(symbols); } else if (!reference.IsAssignmentTarget() && !reference.IsCompoundAssignmentTarget()) { liveOut.UnionWith(symbols); } } private void ProcessSimpleAssignment(ISimpleAssignmentOperationWrapper assignment) { var targets = ProcessAssignment(assignment, assignment.Target, assignment.Value); liveOut.ExceptWith(targets); } private void ProcessCompoundAssignment(ICompoundAssignmentOperationWrapper assignment) => ProcessAssignment(assignment, assignment.Target); private void ProcessIncrementOrDecrement(IIncrementOrDecrementOperationWrapper incrementOrDecrement) => ProcessAssignment(incrementOrDecrement, incrementOrDecrement.Target); private void ProcessDeconstructionAssignment(IDeconstructionAssignmentOperationWrapper deconstructionAssignment) { if (ITupleOperationWrapper.IsInstance(deconstructionAssignment.Target)) { foreach (var tupleElement in ITupleOperationWrapper.FromOperation(deconstructionAssignment.Target).AllElements()) { var targets = ProcessAssignment(deconstructionAssignment, tupleElement); liveOut.ExceptWith(targets); } } } private ISymbol[] ProcessAssignment(IOperationWrapper operation, IOperation target, IOperation value = null) { var targets = owner.lva.ParameterOrLocalSymbols(target).Where(IsSymbolRelevant).ToArray(); foreach (var localTarget in targets) { if (TargetRefKind(localTarget) == RefKind.None && !IsConst(localTarget) && !liveOut.Contains(localTarget) && !IsAllowedInitialization(localTarget) && !ICaughtExceptionOperationWrapper.IsInstance(value) && !target.Syntax.Parent.IsKind(SyntaxKind.ForEachStatement) && !IsMuted(target.Syntax, localTarget)) { ReportIssue(operation.WrappedOperation.Syntax.GetLocation(), localTarget); } } return targets; static bool IsConst(ISymbol localTarget) => localTarget is ILocalSymbol local && local.IsConst; static RefKind TargetRefKind(ISymbol localTarget) => // Only ILocalSymbol and IParameterSymbol can be returned by ParameterOrLocalSymbol localTarget is ILocalSymbol local ? local.RefKind() : ((IParameterSymbol)localTarget).RefKind; bool IsAllowedInitialization(ISymbol localTarget) => operation.WrappedOperation.Syntax is VariableDeclaratorSyntax variableDeclarator && variableDeclarator.Initializer != null // Avoid collision with S1481: Unused is allowed. Used only in local function is also unused in current CFG. && (IsAllowedInitializationValue(variableDeclarator.Initializer.Value, value == null ? default : value.ConstantValue) || IsUnusedInCurrentCfg(localTarget, target)); } private bool IsUnusedInCurrentCfg(ISymbol symbol, IOperation exceptTarget) { return !owner.lva.Cfg.Blocks.SelectMany(x => x.OperationsAndBranchValue).ToExecutionOrder().Any(IsUsed); bool IsUsed(IOperationWrapperSonar wrapper) => wrapper.Instance != exceptTarget && wrapper.Instance.Kind == OperationKindEx.LocalReference && ILocalReferenceOperationWrapper.FromOperation(wrapper.Instance).Local.Equals(symbol); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DeadStores.SonarCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.CFG.Sonar; using SonarAnalyzer.CSharp.Core.LiveVariableAnalysis; namespace SonarAnalyzer.CSharp.Rules { public partial class DeadStores : SonarDiagnosticAnalyzer { private class SonarChecker : CheckerBase { private readonly SyntaxNode node; public SonarChecker(SonarSyntaxNodeReportingContext context, SonarCSharpLiveVariableAnalysis lva, SyntaxNode node) : base(context, lva) => this.node = node; protected override State CreateState(Block block) => new SonarState(this, block, node); private class SonarState : State { private readonly ISet assignmentLhs = new HashSet(); private readonly SyntaxNode node; public SonarState(SonarChecker owner, Block block, SyntaxNode node) : base(owner, block) => this.node = node; public override void AnalyzeBlock() { foreach (var instruction in block.Instructions.Reverse()) { switch (instruction.Kind()) { case SyntaxKind.IdentifierName: ProcessIdentifier(instruction); break; case SyntaxKind.AddAssignmentExpression: case SyntaxKind.SubtractAssignmentExpression: case SyntaxKind.MultiplyAssignmentExpression: case SyntaxKind.DivideAssignmentExpression: case SyntaxKind.ModuloAssignmentExpression: case SyntaxKind.AndAssignmentExpression: case SyntaxKind.ExclusiveOrAssignmentExpression: case SyntaxKind.OrAssignmentExpression: case SyntaxKind.LeftShiftAssignmentExpression: case SyntaxKind.RightShiftAssignmentExpression: case SyntaxKindEx.CoalesceAssignmentExpression: ProcessOpAssignment(instruction); break; case SyntaxKind.SimpleAssignmentExpression: ProcessSimpleAssignment(instruction); break; case SyntaxKind.VariableDeclarator: ProcessVariableDeclarator(instruction); break; case SyntaxKind.PreIncrementExpression: case SyntaxKind.PreDecrementExpression: ProcessPrefixExpression(instruction); break; case SyntaxKind.PostIncrementExpression: case SyntaxKind.PostDecrementExpression: ProcessPostfixExpression(instruction); break; } } } private void ProcessIdentifier(SyntaxNode instruction) { var identifier = (IdentifierNameSyntax)instruction; var symbol = Model.GetSymbolInfo(identifier).Symbol; if (IsSymbolRelevant(symbol) && !identifier.GetSelfOrTopParenthesizedExpression().IsInNameOfArgument(Model) && IsLocal(symbol)) { if (SonarCSharpLiveVariableAnalysis.IsOutArgument(identifier)) { liveOut.Remove(symbol); } else if (!assignmentLhs.Contains(identifier)) { liveOut.Add(symbol); } } } private void ProcessOpAssignment(SyntaxNode instruction) { var assignment = (AssignmentExpressionSyntax)instruction; var left = assignment.Left.RemoveParentheses(); if (IdentifierRelevantSymbol(left) is { } symbol) { ReportOnAssignment(assignment, left, symbol); } } private void ProcessSimpleAssignment(SyntaxNode instruction) { var assignment = (AssignmentExpressionSyntax)instruction; var left = assignment.Left.RemoveParentheses(); if (IdentifierRelevantSymbol(left) is { } symbol) { ReportOnAssignment(assignment, left, symbol); liveOut.Remove(symbol); } } private void ProcessVariableDeclarator(SyntaxNode instruction) { var declarator = (VariableDeclaratorSyntax)instruction; if (Model.GetDeclaredSymbol(declarator) is ILocalSymbol symbol && IsSymbolRelevant(symbol)) { if (declarator.Initializer != null && !IsAllowedInitializationValue(declarator.Initializer.Value) && !symbol.IsConst && symbol.RefKind() == RefKind.None && !liveOut.Contains(symbol) && !IsUnusedLocal(symbol) && !IsMuted(declarator, symbol)) { var location = GetFirstLineLocationFromToken(declarator.Initializer.EqualsToken, declarator.Initializer); ReportIssue(location, symbol); } liveOut.Remove(symbol); } } private bool IsUnusedLocal(ISymbol declaredSymbol) => node.DescendantNodes() .OfType() .SelectMany(x => Model.GetSymbolInfo(x).AllSymbols()) .All(x => !x.Equals(declaredSymbol)); private void ProcessPrefixExpression(SyntaxNode instruction) { var prefixExpression = (PrefixUnaryExpressionSyntax)instruction; var parent = prefixExpression.GetSelfOrTopParenthesizedExpression(); var operand = prefixExpression.Operand.RemoveParentheses(); if (parent.Parent is ExpressionStatementSyntax && IdentifierRelevantSymbol(operand) is { } symbol && IsLocal(symbol) && !liveOut.Contains(symbol) && !IsMuted(operand)) { ReportIssue(prefixExpression.GetLocation(), symbol); } } private void ProcessPostfixExpression(SyntaxNode instruction) { var postfixExpression = (PostfixUnaryExpressionSyntax)instruction; var operand = postfixExpression.Operand.RemoveParentheses(); if (IdentifierRelevantSymbol(operand) is { } symbol && IsLocal(symbol) && !liveOut.Contains(symbol) && !IsMuted(operand)) { ReportIssue(postfixExpression.GetLocation(), symbol); } } private void ReportOnAssignment(AssignmentExpressionSyntax assignment, ExpressionSyntax left, ISymbol symbol) { if (IsLocal(symbol) && !liveOut.Contains(symbol) && !IsMuted(left)) { var location = GetFirstLineLocationFromToken(assignment.OperatorToken, assignment.Right); ReportIssue(location, symbol); } assignmentLhs.Add(left); } private bool IsMuted(SyntaxNode node) => new MutedSyntaxWalker(Model, node).IsMuted(); private static Location GetFirstLineLocationFromToken(SyntaxToken issueStartToken, SyntaxNode wholeIssue) { var line = wholeIssue.SyntaxTree.GetText().Lines[issueStartToken.GetLocation().StartLine()]; var rightSingleLine = line.Span.Intersection(TextSpan.FromBounds(issueStartToken.SpanStart, wholeIssue.Span.End)); return Location.Create(wholeIssue.SyntaxTree, TextSpan.FromBounds(issueStartToken.SpanStart, rightSingleLine.HasValue ? rightSingleLine.Value.End : issueStartToken.Span.End)); } private ISymbol IdentifierRelevantSymbol(SyntaxNode node) => node.IsKind(SyntaxKind.IdentifierName) && Model.GetSymbolInfo(node).Symbol is { } symbol && IsSymbolRelevant(symbol) ? symbol : null; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DeadStores.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Sonar; using SonarAnalyzer.CSharp.Core.LiveVariableAnalysis; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed partial class DeadStores : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1854"; private const string MessageFormat = "Remove this useless assignment to local variable '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly string[] AllowedNumericValues = ["-1", "0", "1"]; private static readonly string[] AllowedStringValues = [string.Empty]; private static readonly TypeSpecificDefaultValue[] TypeSpecificDefaultValues = [ new(KnownType.System_IntPtr, nameof(IntPtr.Zero)), new(KnownType.System_UIntPtr, nameof(UIntPtr.Zero)), new(KnownType.System_Guid, nameof(Guid.Empty)), ]; private readonly bool useSonarCfg; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public DeadStores() : this(AnalyzerConfiguration.AlwaysEnabled) { } internal /* for testing */ DeadStores(IAnalyzerConfiguration configuration) => useSonarCfg = configuration.UseSonarCfg(); protected override void Initialize(SonarAnalysisContext context) { // No need to check for ExpressionBody as it can't contain variable assignment context.RegisterNodeAction( c => CheckForDeadStores(c, c.Model.GetDeclaredSymbol(c.Node)), SyntaxKind.AddAccessorDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.InitAccessorDeclaration, SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction( c => CheckForDeadStores(c, c.Model.GetSymbolInfo(c.Node).Symbol), SyntaxKind.AnonymousMethodExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression); } private void CheckForDeadStores(SonarSyntaxNodeReportingContext context, ISymbol symbol) { if (symbol is not null) { if (useSonarCfg) { // Tuple expressions are not supported. See https://github.com/SonarSource/sonar-dotnet/issues/3094 if (!context.Node.DescendantNodes().AnyOfKind(SyntaxKindEx.TupleExpression) && CSharpControlFlowGraph.TryGet(context.Node, context.Model, out var cfg)) { var lva = new SonarCSharpLiveVariableAnalysis(cfg, symbol, context.Model, context.Cancel); var checker = new SonarChecker(context, lva, context.Node); checker.Analyze(cfg.Blocks); } } else if (context.Node.CreateCfg(context.Model, context.Cancel) is { } cfg) { var lva = new RoslynLiveVariableAnalysis(cfg, CSharpSyntaxClassifier.Instance, context.Cancel); var checker = new RoslynChecker(context, lva); checker.Analyze(cfg.Blocks); } } } private abstract class CheckerBase { private readonly LiveVariableAnalysisBase lva; private readonly SonarSyntaxNodeReportingContext context; private readonly ISet capturedVariables; protected abstract State CreateState(TBlock block); protected CheckerBase(SonarSyntaxNodeReportingContext context, LiveVariableAnalysisBase lva) { this.context = context; this.lva = lva; capturedVariables = lva.CapturedVariables.ToHashSet(); } public void Analyze(IEnumerable blocks) { foreach (var block in blocks) { var state = CreateState(block); state.AnalyzeBlock(); } } protected bool IsLocal(ISymbol symbol) => lva.IsLocal(symbol); protected abstract class State { protected readonly TBlock block; protected readonly ISet liveOut; private readonly CheckerBase owner; public abstract void AnalyzeBlock(); protected SemanticModel Model => owner.context.Model; protected State(CheckerBase owner, TBlock block) { this.owner = owner; this.block = block; liveOut = new HashSet(owner.lva.LiveOut(block)); } protected void ReportIssue(Location location, ISymbol symbol) => owner.context.ReportIssue(Rule, location, symbol.Name); protected bool IsSymbolRelevant(ISymbol symbol) => symbol is not null && !owner.capturedVariables.Contains(symbol); protected bool IsLocal(ISymbol symbol) => owner.IsLocal(symbol); protected bool IsAllowedInitializationValue(ExpressionSyntax value, Optional constantValue = default) => (constantValue.HasValue && IsAllowedInitializationConstant(constantValue.Value, value.IsKind(SyntaxKind.IdentifierName))) || value?.Kind() is SyntaxKind.DefaultExpression or SyntaxKind.TrueLiteralExpression or SyntaxKind.FalseLiteralExpression || value.IsNullLiteral() || value.IsDefaultLiteral() || IsAllowedNumericInitialization(value) || IsAllowedUnaryNumericInitialization(value) || IsAllowedStringInitialization(value) || IsTypeSpecificDefaultValue(value); protected bool IsMuted(SyntaxNode node, ISymbol symbol) => new MutedSyntaxWalker(Model, node, symbol).IsMuted(); private static bool IsAllowedInitializationConstant(object constant, bool isIdentifier) => constant is null || (isIdentifier && IsAllowedInitializationConstantIdentifier(constant)); private static bool IsAllowedInitializationConstantIdentifier(object constant) => constant is string str ? AllowedStringValues.Contains(str) : AllowedNumericValues.Contains(constant.ToString()); private static bool IsAllowedNumericInitialization(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.NumericLiteralExpression) && AllowedNumericValues.Contains(((LiteralExpressionSyntax)expression).Token.ValueText); // -1, 0 or 1 private static bool IsAllowedUnaryNumericInitialization(ExpressionSyntax expression) => expression?.Kind() is SyntaxKind.UnaryPlusExpression or SyntaxKind.UnaryMinusExpression && IsAllowedNumericInitialization(((PrefixUnaryExpressionSyntax)expression).Operand); private bool IsAllowedStringInitialization(ExpressionSyntax expression) => (expression.IsKind(SyntaxKind.StringLiteralExpression) && AllowedStringValues.Contains(((LiteralExpressionSyntax)expression).Token.ValueText)) || (expression.IsKind(SyntaxKind.InterpolatedStringExpression) && ((InterpolatedStringExpressionSyntax)expression).Contents.Count == 0) || expression.IsStringEmpty(Model); private bool IsTypeSpecificDefaultValue(ExpressionSyntax expression) => expression is MemberAccessExpressionSyntax { } memberAccess && Model.GetSymbolInfo(memberAccess.Name).Symbol is IFieldSymbol { IsStatic: true } field && TypeSpecificDefaultValues.Any(x => x.Name == field.Name && field.ContainingType.Is(x.Type)); } } private sealed record TypeSpecificDefaultValue(KnownType Type, string Name); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DebugAssertHasNoSideEffects.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DebugAssertHasNoSideEffects : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3346"; private const string MessageFormat = "Expressions used in 'Debug.Assert' should not produce side effects."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ISet sideEffectWords = new HashSet { "REMOVE", "DELETE", "PUT", "SET", "ADD", "POP", "UPDATE", "RETAIN", "INSERT", "PUSH", "APPEND", "CLEAR", "DEQUEUE", "ENQUEUE", "DISPOSE" }; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invokedMethodSyntax = c.Node as InvocationExpressionSyntax; if (IsDebugAssert(c, invokedMethodSyntax) && ContainsCallsWithSideEffects(invokedMethodSyntax)) { c.ReportIssue(rule, invokedMethodSyntax.ArgumentList); } }, SyntaxKind.InvocationExpression); private static string GetIdentifierName(InvocationExpressionSyntax invocation) { if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) { return memberAccess.Name?.Identifier.ValueText; } var memberBinding = invocation.Expression as MemberBindingExpressionSyntax; return memberBinding?.Name?.Identifier.ValueText; } private static bool IsDebugAssert(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation) => invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.ValueText == nameof(System.Diagnostics.Debug.Assert) && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol symbol && symbol.IsDebugAssert(); private static bool ContainsCallsWithSideEffects(InvocationExpressionSyntax invocation) => invocation.DescendantNodes() .OfType() .Select(GetIdentifierName) .Any(name => !string.IsNullOrEmpty(name) && name != "SetEquals" && sideEffectWords.Contains(name.SplitCamelCaseToWords().First())); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Shared.Extensions; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode AttributeTarget(AttributeArgumentSyntax attribute) => attribute.GetAncestor()?.Parent; protected override ImmutableArray ResolvableIdentifiers(SyntaxNode expression) { return expression.DescendantNodesAndSelf().Any(x => x.Kind() is SyntaxKindEx.SingleVariableDesignation) ? ImmutableArray.Empty // A variable was declared inside the expression. This would result in FPs and needs more advanced handling. : MostLeftIdentifier(expression); static ImmutableArray MostLeftIdentifier(SyntaxNode node) => node is ExpressionSyntax expression ? expression.ExtractMemberIdentifier().Select(x => x.LeftMostInMemberAccess()).OfType().ToImmutableArray() : ImmutableArray.Empty; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DeclareEventHandlersCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DeclareEventHandlersCorrectly : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3906"; private const string MessageFormat = "Change the signature of that event handler to match the specified signature."; private const int SenderArgumentPosition = 0; private const int EventArgsPosition = 1; private const int DelegateEventHandlerArgCount = 2; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => AnalyzeEventType(c, ((EventFieldDeclarationSyntax)c.Node).Declaration.Type, c.ContainingSymbol), SyntaxKind.EventFieldDeclaration); context.RegisterNodeAction( c => AnalyzeEventType(c, ((EventDeclarationSyntax)c.Node).Type, c.ContainingSymbol), SyntaxKind.EventDeclaration); } private static void AnalyzeEventType(SonarSyntaxNodeReportingContext analysisContext, TypeSyntax typeSyntax, ISymbol eventSymbol) { if (!eventSymbol.IsOverride && eventSymbol.InterfaceMembers().IsEmpty() && analysisContext.Model.GetSymbolInfo(typeSyntax).Symbol is INamedTypeSymbol eventHandlerType && eventHandlerType.DelegateInvokeMethod is { } methodSymbol && !IsCorrectEventHandlerSignature(methodSymbol)) { analysisContext.ReportIssue(Rule, typeSyntax); } } private static bool IsCorrectEventHandlerSignature(IMethodSymbol methodSymbol) => methodSymbol is { ReturnsVoid: true, Parameters: { Length: DelegateEventHandlerArgCount } parameters, } && parameters[SenderArgumentPosition] is { Name: "sender", Type: { } senderType, } && senderType.Is(KnownType.System_Object) && parameters[EventArgsPosition] is { Name: "e", Type: { } eventArgsType, } && IsDerivedFromEventArgs(eventArgsType); private static bool IsDerivedFromEventArgs(ITypeSymbol type) => type.DerivesFrom(KnownType.System_EventArgs) || (type is ITypeParameterSymbol typeParameterSymbol && typeParameterSymbol.ConstraintTypes.Any(IsDerivedFromEventArgs)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DeclareTypesInNamespaces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DeclareTypesInNamespaces : DeclareTypesInNamespacesBase { private static readonly HashSet InnerTypeKinds = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.NamespaceDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKindEx.FileScopedNamespaceDeclaration ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, }; protected override SyntaxToken GetTypeIdentifier(SyntaxNode declaration) => ((BaseTypeDeclarationSyntax)declaration).Identifier; protected override bool IsInnerTypeOrWithinNamespace(SyntaxNode declaration, SemanticModel semanticModel) => declaration.Parent.IsAnyKind(InnerTypeKinds); protected override bool IsException(SyntaxNode node) => IsTopLevelStatementPartialProgramClass(node); private static bool IsTopLevelStatementPartialProgramClass(SyntaxNode declaration) => declaration is ClassDeclarationSyntax { Identifier.Text: "Program" } classDeclaration && classDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) && declaration.Parent is CompilationUnitSyntax compilationUnit && compilationUnit.IsTopLevelMain(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DefaultSectionShouldBeFirstOrLast.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DefaultSectionShouldBeFirstOrLast : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4524"; private const string MessageFormat = "Move this 'default:' case to the beginning or end of this 'switch' statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var switchSyntax = (SwitchStatementSyntax)c.Node; var defaultLabelSectionIndex = switchSyntax.GetDefaultLabelSectionIndex(); if (defaultLabelSectionIndex > 0 && // default is not first... defaultLabelSectionIndex != (switchSyntax.Sections.Count -1)) // nor last { var defaultLabelLocation = switchSyntax.Sections[defaultLabelSectionIndex] .Labels .First(label => label.IsKind(SyntaxKind.DefaultSwitchLabel)) .GetLocation(); c.ReportIssue(rule, defaultLabelLocation); } }, SyntaxKind.SwitchStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DelegateSubtraction.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DelegateSubtraction : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3172"; private const string MessageFormat = "Review this subtraction of a chain of delegates: it may not work as you expect."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; if (!ExpressionIsSimple(assignment.Right) && IsDelegateSubtraction(assignment, c.Model)) { c.ReportIssue(Rule, assignment); } }, SyntaxKind.SubtractAssignmentExpression); context.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; if (IsTopLevelSubtraction(binary) && !BinaryIsValidSubstraction(binary) && IsDelegateSubtraction(binary, c.Model)) { c.ReportIssue(Rule, binary); } }, SyntaxKind.SubtractExpression); } private static bool BinaryIsValidSubstraction(BinaryExpressionSyntax subtraction) { var currentSubtraction = subtraction; while (currentSubtraction is not null && currentSubtraction.IsKind(SyntaxKind.SubtractExpression)) { if (!ExpressionIsSimple(currentSubtraction.Right)) { return false; } currentSubtraction = currentSubtraction.Left as BinaryExpressionSyntax; } return true; } private static bool IsTopLevelSubtraction(BinaryExpressionSyntax subtraction) => subtraction.Parent is not BinaryExpressionSyntax parent || !parent.IsKind(SyntaxKind.SubtractExpression); private static bool IsDelegateSubtraction(SyntaxNode node, SemanticModel semanticModel) => semanticModel.GetSymbolInfo(node).Symbol is IMethodSymbol subtractMethod && subtractMethod.ReceiverType.Is(TypeKind.Delegate); private static bool ExpressionIsSimple(ExpressionSyntax expression) => expression.RemoveParentheses() is IdentifierNameSyntax or MemberAccessExpressionSyntax; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableMemberInNonDisposableClass.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposableMemberInNonDisposableClass : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2931"; private const string MessageFormat = "Implement 'IDisposable' in this class and use the 'Dispose' method to call 'Dispose' on {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var namedType = (INamedTypeSymbol)c.Symbol; if (ShouldExclude(namedType)) { return; } var message = GetMessage(c, namedType); if (string.IsNullOrEmpty(message)) { return; } var typeDeclarations = new CSharpRemovableDeclarationCollector(namedType, c.Compilation).TypeDeclarations; foreach (var classDeclaration in typeDeclarations) { c.ReportIssue(Rule, classDeclaration.Node.Identifier, message); } }, SymbolKind.NamedType); private static bool ShouldExclude(ITypeSymbol typeSymbol) => !typeSymbol.IsClass() || typeSymbol.Implements(KnownType.System_IDisposable) || typeSymbol.Implements(KnownType.System_IAsyncDisposable); private static string GetMessage(SonarSymbolReportingContext context, INamespaceOrTypeSymbol namedType) { var disposableFields = namedType.GetMembers() .OfType() .Where(fs => fs.IsNonStaticNonPublicDisposableField(context.Compilation.GetLanguageVersion())) .ToHashSet(); if (disposableFields.Count == 0) { return string.Empty; } var otherInitializationsOfFields = namedType.GetMembers() .OfType() .SelectMany(m => GetAssignmentsToFieldsIn(m, context.Compilation)) .Where(f => disposableFields.Contains(f)); return disposableFields.Where(IsOwnerSinceDeclaration) .Union(otherInitializationsOfFields) .Distinct() .Select(symbol => $"'{symbol.Name}'") .OrderBy(name => name) .JoinAnd(); } private static IEnumerable GetAssignmentsToFieldsIn(ISymbol m, Compilation compilation) { if (m.DeclaringSyntaxReferences.Length != 1 || !(m.DeclaringSyntaxReferences[0].GetSyntax() is BaseMethodDeclarationSyntax method) || !method.HasBodyOrExpressionBody()) { return Enumerable.Empty(); } return method.GetBodyDescendantNodes() .OfType() .Where(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression) && n.Right is ObjectCreationExpressionSyntax) .Select(n => compilation.GetSemanticModel(method.SyntaxTree).GetSymbolInfo(n.Left).Symbol) .OfType(); } private static bool IsOwnerSinceDeclaration(ISymbol symbol) => symbol.DeclaringSyntaxReferences.SingleOrDefault()?.GetSyntax() is VariableDeclaratorSyntax varDeclarator && varDeclarator.Initializer?.Value is ObjectCreationExpressionSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2930"; private const string MessageFormat = "Dispose '{0}' when it is no longer needed."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray TrackedTypes = ImmutableArray.Create( KnownType.FluentAssertions_Execution_AssertionScope, KnownType.System_Drawing_Image, KnownType.System_Drawing_Bitmap, KnownType.System_IO_FileStream, KnownType.System_IO_StreamReader, KnownType.System_IO_StreamWriter, KnownType.System_Net_WebClient, KnownType.System_Net_Sockets_TcpClient, KnownType.System_Net_Sockets_UdpClient, KnownType.System_Threading_CancellationTokenSource); 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 { "System.IO.File.Create", "System.IO.File.Open", "System.Drawing.Image.FromFile", "System.Drawing.Image.FromStream", "System.Threading.CancellationTokenSource.CreateLinkedTokenSource" }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var namedType = (INamedTypeSymbol)c.Symbol; if (namedType.ContainingType != null || !namedType.IsClassOrStruct()) { return; } var typesDeclarationsAndSemanticModels = namedType.DeclaringSyntaxReferences .Select(x => CreateNodeAndModel(c, x)) .ToList(); var disposableObjects = new HashSet(); foreach (var typeDeclarationAndSemanticModel in typesDeclarationsAndSemanticModels) { TrackInitializedLocalsAndPrivateFields( namedType, typeDeclarationAndSemanticModel.Node, typeDeclarationAndSemanticModel.Model, disposableObjects); TrackAssignmentsToLocalsAndPrivateFields( namedType, typeDeclarationAndSemanticModel.Node, typeDeclarationAndSemanticModel.Model, disposableObjects); } if (disposableObjects.Any()) { var possiblyDisposed = new HashSet(); foreach (var typeDeclarationAndSemanticModel in typesDeclarationsAndSemanticModels) { ExcludeDisposedAndClosedLocalsAndPrivateFields( typeDeclarationAndSemanticModel.Node, typeDeclarationAndSemanticModel.Model, possiblyDisposed); ExcludeReturnedPassedAndAliasedLocalsAndPrivateFields( typeDeclarationAndSemanticModel.Node, typeDeclarationAndSemanticModel.Model, possiblyDisposed); } foreach (var disposable in disposableObjects.Where(x => !possiblyDisposed.Contains(x.Symbol))) { c.ReportIssue(Rule, disposable.Node, disposable.Symbol.Name); } } }, SymbolKind.NamedType); private static NodeAndModel CreateNodeAndModel(SonarSymbolReportingContext c, SyntaxReference syntaxReference) => new(syntaxReference.GetSyntax(), c.Compilation.GetSemanticModel(syntaxReference.SyntaxTree)); private static void TrackInitializedLocalsAndPrivateFields(INamedTypeSymbol namedType, SyntaxNode typeDeclaration, SemanticModel semanticModel, ISet disposableObjects) { var descendantNodes = GetDescendantNodes(namedType, typeDeclaration).ToList(); var localVariableDeclarations = descendantNodes.OfType() .Where(x => !x.UsingKeyword.IsKind(SyntaxKind.UsingKeyword)) .Select(x => x.Declaration); var fieldVariableDeclarations = descendantNodes.OfType() .Where(x => !x.Modifiers.Any() || x.Modifiers.Any(SyntaxKind.PrivateKeyword)) .Select(x => x.Declaration); foreach (var declaration in localVariableDeclarations.Concat(fieldVariableDeclarations)) { var trackedVariables = declaration.Variables.Where(x => x.Initializer != null && IsInstantiation(x.Initializer.Value, semanticModel)); foreach (var variableNode in trackedVariables) { disposableObjects.Add(new NodeAndSymbol(variableNode, semanticModel.GetDeclaredSymbol(variableNode))); } } } private static void TrackAssignmentsToLocalsAndPrivateFields(INamedTypeSymbol namedType, SyntaxNode typeDeclaration, SemanticModel semanticModel, ISet disposableObjects) { var simpleAssignments = GetDescendantNodes(namedType, typeDeclaration).Where(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression)) .Cast(); foreach (var simpleAssignment in simpleAssignments) { if (!IsNodeInsideUsingStatement(simpleAssignment) && IsInstantiation(simpleAssignment.Right, semanticModel) && semanticModel.GetSymbolInfo(simpleAssignment.Left).Symbol is { } referencedSymbol && IsLocalOrPrivateField(referencedSymbol)) { disposableObjects.Add(new NodeAndSymbol(simpleAssignment, referencedSymbol)); } } } 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()) : typeDeclaration.DescendantNodes(); private static bool IsLocalOrPrivateField(ISymbol symbol) => symbol.Kind == SymbolKind.Local || (symbol.Kind == SymbolKind.Field && symbol.DeclaredAccessibility == Accessibility.Private); private static void ExcludeDisposedAndClosedLocalsAndPrivateFields(SyntaxNode typeDeclaration, SemanticModel semanticModel, ISet possiblyDisposed) { var invocationsAndConditionalAccesses = typeDeclaration.DescendantNodes().Where(x => x.Kind() is SyntaxKind.InvocationExpression or SyntaxKind.ConditionalAccessExpression); foreach (var invocationOrConditionalAccess in invocationsAndConditionalAccesses) { SimpleNameSyntax name; ExpressionSyntax expression; if (invocationOrConditionalAccess is InvocationExpressionSyntax invocation) { var memberAccessNode = invocation.Expression as MemberAccessExpressionSyntax; name = memberAccessNode?.Name; expression = memberAccessNode?.Expression; } else if (invocationOrConditionalAccess is ConditionalAccessExpressionSyntax conditionalAccess) { name = ((conditionalAccess.WhenNotNull as InvocationExpressionSyntax)?.Expression as MemberBindingExpressionSyntax)?.Name; expression = conditionalAccess.Expression; } else { throw new NotSupportedException("Syntax node should be either an invocation or a conditional access expression"); } if (name != null && (DisposeMethods.Contains(name.Identifier.Text) || IsNodeInsideUsingStatement(expression)) && semanticModel.GetSymbolInfo(expression).Symbol is { } referencedSymbol && IsLocalOrPrivateField(referencedSymbol)) { possiblyDisposed.Add(referencedSymbol); } } } private static void ExcludeReturnedPassedAndAliasedLocalsAndPrivateFields(SyntaxNode typeDeclaration, SemanticModel semanticModel, ISet possiblyDisposed) { var identifiersAndSimpleMemberAccesses = typeDeclaration .DescendantNodes() .Where(n => n.IsKind(SyntaxKind.IdentifierName) || n.IsKind(SyntaxKind.SimpleMemberAccessExpression)); foreach (var identifierOrSimpleMemberAccess in identifiersAndSimpleMemberAccesses) { ExpressionSyntax expression; if (identifierOrSimpleMemberAccess.IsKind(SyntaxKind.IdentifierName)) { expression = (IdentifierNameSyntax)identifierOrSimpleMemberAccess; } else if (identifierOrSimpleMemberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression)) { var memberAccess = (MemberAccessExpressionSyntax)identifierOrSimpleMemberAccess; if (memberAccess.Expression.IsKind(SyntaxKind.ThisExpression)) { expression = memberAccess; } else { continue; } } else { throw new NotSupportedException("Syntax node should be either an identifier or a simple member access expression"); } if (IsStandaloneExpression(expression) && semanticModel.GetSymbolInfo(identifierOrSimpleMemberAccess).Symbol is { } referencedSymbol && IsLocalOrPrivateField(referencedSymbol)) { possiblyDisposed.Add(referencedSymbol); } } } private static bool IsStandaloneExpression(ExpressionSyntax expression) => !(expression.Parent is ExpressionSyntax) || (expression.Parent is AssignmentExpressionSyntax parentAsAssignment && ReferenceEquals(expression, parentAsAssignment.Right)); private static bool IsInstantiation(ExpressionSyntax expression, SemanticModel model) => IsNewTrackedTypeObjectCreation(expression, model) || IsDisposableRefStructCreation(expression, model) || IsFactoryMethodInvocation(expression, model); private static bool IsNewTrackedTypeObjectCreation(ExpressionSyntax expression, SemanticModel model) => expression?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression && model.GetTypeInfo(expression).Type is var type && type.IsAny(TrackedTypes) && model.GetSymbolInfo(expression).Symbol is IMethodSymbol constructor && !constructor.Parameters.Any(x => x.Type.ImplementsAny(DisposableTypes)); private static bool IsDisposableRefStructCreation(ExpressionSyntax expression, SemanticModel model) => expression?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression && model.GetTypeInfo(expression).Type is var type && type.IsRefStruct() && type.GetMembers().OfType().Any(x => x.Name == "Dispose"); private static bool IsFactoryMethodInvocation(ExpressionSyntax expression, SemanticModel model) => expression is InvocationExpressionSyntax invocation && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && FactoryMethods.Contains(methodSymbol.ContainingType.ToDisplayString() + "." + methodSymbol.Name); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableReturnedFromUsing.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposableReturnedFromUsing : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2997"; private const string MessageFormat = "Remove the 'using' statement; it will cause automatic disposal of {0}."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var usingStatement = (UsingStatementSyntax) c.Node; var declaration = usingStatement.Declaration; var symbolsDeclaredInUsing = new HashSet(); if (declaration != null) { symbolsDeclaredInUsing = declaration.Variables.Select(syntax => c.Model.GetDeclaredSymbol(syntax)) .WhereNotNull() .ToHashSet(); } else if (usingStatement.Expression is AssignmentExpressionSyntax assignment) { if (!(assignment.Left is IdentifierNameSyntax identifierName)) { return; } var symbol = c.Model.GetSymbolInfo(identifierName).Symbol; if (symbol == null) { return; } symbolsDeclaredInUsing = new HashSet { symbol }; } CheckReturns(c, usingStatement.UsingKeyword, usingStatement.Statement, symbolsDeclaredInUsing); }, SyntaxKind.UsingStatement); context.RegisterNodeAction( c => { var localDeclarationStatement = (LocalDeclarationStatementSyntax)c.Node; var usingKeyword = localDeclarationStatement.UsingKeyword; if (!usingKeyword.IsKind(SyntaxKind.UsingKeyword)) { return; } var declaredSymbols = localDeclarationStatement.Declaration.Variables .Select(syntax => c.Model.GetDeclaredSymbol(syntax)) .WhereNotNull() .ToHashSet(); CheckReturns(c, usingKeyword, localDeclarationStatement.Parent, declaredSymbols); }, SyntaxKind.LocalDeclarationStatement); } private static void CheckReturns(SonarSyntaxNodeReportingContext c, SyntaxToken usingKeyword, SyntaxNode body, HashSet declaredSymbols) { if (declaredSymbols.Count == 0) { return; } var returnedSymbols = GetReturnedSymbols(body, c.Model); returnedSymbols.IntersectWith(declaredSymbols); if (returnedSymbols.Any()) { c.ReportIssue(rule, usingKeyword, returnedSymbols.Select(s => $"'{s.Name}'").OrderBy(s => s).JoinAnd()); } } private static ISet GetReturnedSymbols(SyntaxNode body, SemanticModel semanticModel) { var enclosingSymbol = semanticModel.GetEnclosingSymbol(body.SpanStart); return body.DescendantNodesAndSelf() .OfType() .Where(ret => semanticModel.GetEnclosingSymbol(ret.SpanStart).Equals(enclosingSymbol)) .Select(ret => ret.Expression) .OfType() .Select(identifier => semanticModel.GetSymbolInfo(identifier).Symbol) .WhereNotNull() .ToHashSet(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableTypesNeedFinalizers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposableTypesNeedFinalizers : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4002"; private const string MessageFormat = "Implement a finalizer that calls your 'Dispose' method."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray NativeHandles = ImmutableArray.Create( KnownType.System_IntPtr, KnownType.System_UIntPtr, KnownType.System_Runtime_InteropServices_HandleRef); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (TypeDeclarationSyntax)c.Node; if (!c.IsRedundantPositionalRecordContext() && ((ITypeSymbol)c.ContainingSymbol).Implements(KnownType.System_IDisposable) && HasNativeHandleFields(declaration, c.Model) && !HasFinalizer(declaration)) { c.ReportIssue(Rule, declaration.Identifier); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private static bool HasNativeHandleFields(TypeDeclarationSyntax classDeclaration, SemanticModel semanticModel) => classDeclaration.Members .OfType() .Select(m => semanticModel.GetDeclaredSymbol(m.Declaration.Variables.FirstOrDefault())?.GetSymbolType()) .Any(si => si.IsAny(NativeHandles)); private static bool HasFinalizer(TypeDeclarationSyntax classDeclaration) => classDeclaration.Members.OfType().Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeFromDispose.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposeFromDispose : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2952"; private const string MessageFormat = "Move this 'Dispose' call into this class' own 'Dispose' method."; private const string DisposeMethodName = nameof(IDisposable.Dispose); private const string DisposeMethodExplicitName = "System.IDisposable.Dispose"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; var languageVersion = c.Compilation.GetLanguageVersion(); if (InvocationTargetAndName(invocation, out var fieldCandidate, out var name) && c.Model.GetSymbolInfo(fieldCandidate).Symbol is IFieldSymbol invocationTarget && invocationTarget.IsNonStaticNonPublicDisposableField(languageVersion) && IsDisposeMethodCalled(invocation, c.Model, languageVersion) && IsDisposableClassOrStruct(invocationTarget.ContainingType, languageVersion) && !IsCalledInsideDispose(invocation, c.Model) && FieldDeclaredInType(c.Model, invocation, invocationTarget) && !FieldDisposedInDispose(c.Model, invocationTarget)) { c.ReportIssue(Rule, name); } }, SyntaxKind.InvocationExpression); private static bool FieldDisposedInDispose(SemanticModel model, IFieldSymbol invocationTarget) => invocationTarget.ContainingSymbol is ITypeSymbol container && container.FindImplementationForInterfaceMember(IDisposableDisposeMethodSymbol(model.Compilation)) is IMethodSymbol dispose && FieldIsDisposedIn(model, invocationTarget, dispose); private static bool FieldIsDisposedIn(SemanticModel model, IFieldSymbol invocationTarget, IMethodSymbol dispose) => (dispose.PartialImplementationPart ?? dispose).DeclaringSyntaxReferences .SelectMany(x => x.GetSyntax() .DescendantNodesAndSelf(x => !(x.Kind() is SyntaxKindEx.LocalFunctionStatement or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression or SyntaxKind.AnonymousMethodExpression)) .OfType()) .Any(x => InvocationTargetAndName(x, out var target, out var name) && name.NameIs(DisposeMethodName) && x.EnsureCorrectSemanticModelOrDefault(model) is { } correctModel && correctModel.GetSymbolInfo(target).Symbol is IFieldSymbol field && field.Equals(invocationTarget) && correctModel.GetSymbolInfo(name).Symbol is IMethodSymbol invokedDispose && invokedDispose.Equals(field.Type.FindImplementationForInterfaceMember(IDisposableDisposeMethodSymbol(correctModel.Compilation)))); private static bool FieldDeclaredInType(SemanticModel model, InvocationExpressionSyntax invocation, IFieldSymbol invocationTarget) => invocation.GetTopMostContainingMethod() is { } containingMethod && containingMethod.EnsureCorrectSemanticModelOrDefault(model) is { } correctModel && (correctModel.GetDeclaredSymbol(containingMethod)?.ContainingSymbol?.Equals(invocationTarget.ContainingType) ?? true); private static bool InvocationTargetAndName(InvocationExpressionSyntax invocation, out ExpressionSyntax target, out SimpleNameSyntax name) { switch (invocation.Expression) { case MemberAccessExpressionSyntax memberAccess: name = memberAccess.Name; target = memberAccess.Expression; return true; case MemberBindingExpressionSyntax memberBinding: name = memberBinding.Name; target = memberBinding.GetParentConditionalAccessExpression().Expression; return true; default: target = null; name = null; return false; } } /// /// Classes and structs are disposable if they implement the IDisposable interface. /// Starting C# 8, "ref structs" (which cannot implement an interface) can also be disposable. /// private static bool IsDisposableClassOrStruct(INamedTypeSymbol type, LanguageVersion languageVersion) => ImplementsDisposable(type) || type.IsDisposableRefStruct(languageVersion); private static bool IsCalledInsideDispose(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => semanticModel.GetEnclosingSymbol(invocation.SpanStart) is IMethodSymbol enclosingMethodSymbol && IsMethodMatchingDisposeMethodName(enclosingMethodSymbol); /// /// Verifies that the invocation is calling the correct Dispose() method on an disposable object. /// /// /// Disposable ref structs do not implement the IDisposable interface and are supported starting C# 8. /// private static bool IsDisposeMethodCalled(InvocationExpressionSyntax invocation, SemanticModel semanticModel, LanguageVersion languageVersion) => semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && KnownMethods.IsIDisposableDispose(methodSymbol) && IDisposableDisposeMethodSymbol(semanticModel.Compilation) is { } disposeMethodSignature && (methodSymbol.Equals(methodSymbol.ContainingType.FindImplementationForInterfaceMember(disposeMethodSignature)) || methodSymbol.ContainingType.IsDisposableRefStruct(languageVersion)); private static IMethodSymbol IDisposableDisposeMethodSymbol(Compilation compilation) => compilation.SpecialTypeMethod(SpecialType.System_IDisposable, DisposeMethodName); private static bool IsMethodMatchingDisposeMethodName(IMethodSymbol enclosingMethodSymbol) => enclosingMethodSymbol.Name == DisposeMethodName || (enclosingMethodSymbol.ExplicitInterfaceImplementations.Any() && enclosingMethodSymbol.Name == DisposeMethodExplicitName); private static bool ImplementsDisposable(INamedTypeSymbol containingType) => containingType.Implements(KnownType.System_IDisposable); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeNotImplementingDispose.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisposeNotImplementingDispose : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2953"; private const string MessageFormat = "Either implement 'IDisposable.Dispose', or totally rename this method to prevent confusion."; private const string DisposeMethodName = "Dispose"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var declaredSymbol = (INamedTypeSymbol)c.Symbol; // ref structs and static classes cannot inherit from the IDisposable interface if (declaredSymbol.IsRefStruct() || declaredSymbol.IsStatic || declaredSymbol.IsExtension) { return; } var disposeMethod = c.Compilation.SpecialTypeMethod(SpecialType.System_IDisposable, "Dispose"); if (disposeMethod is null) { return; } var mightImplementDispose = new HashSet(); var namedDispose = new HashSet(); var methods = declaredSymbol.GetMembers(DisposeMethodName).OfType(); foreach (var method in methods) { CollectMethodsNamedAndImplementingDispose(method, disposeMethod, namedDispose, mightImplementDispose); } var disposeMethodsCalledFromDispose = new HashSet(); CollectInvocationsFromDisposeImplementation(disposeMethod, c.Compilation, mightImplementDispose, disposeMethodsCalledFromDispose); ReportDisposeMethods(c, namedDispose.Except(mightImplementDispose).Where(m => !disposeMethodsCalledFromDispose.Contains(m))); }, SymbolKind.NamedType); private static void CollectInvocationsFromDisposeImplementation(IMethodSymbol disposeMethod, Compilation compilation, HashSet mightImplementDispose, HashSet disposeMethodsCalledFromDispose) { foreach (var method in mightImplementDispose .Where(x => MethodIsDisposeImplementation(x, disposeMethod))) { var methodDeclarations = method.DeclaringSyntaxReferences .Select(x => new NodeAndModel(x.GetSyntax() as MethodDeclarationSyntax, compilation.GetSemanticModel(x.SyntaxTree))) .Where(x => x.Node is not null); var methodDeclaration = methodDeclarations.FirstOrDefault(x => x.Node.HasBodyOrExpressionBody()); if (methodDeclaration == default) { continue; } var invocations = methodDeclaration.Node.DescendantNodes().OfType(); foreach (var invocation in invocations) { CollectDisposeMethodsCalledFromDispose(invocation, methodDeclaration.Model, disposeMethodsCalledFromDispose); } } } private static void CollectDisposeMethodsCalledFromDispose(InvocationExpressionSyntax invocationExpression, SemanticModel model, HashSet disposeMethodsCalledFromDispose) { if (!invocationExpression.IsOnThis()) { return; } if (model.GetSymbolInfo(invocationExpression).Symbol is not IMethodSymbol invokedMethod || invokedMethod.Name != DisposeMethodName) { return; } disposeMethodsCalledFromDispose.Add(invokedMethod); } private static void ReportDisposeMethods(SonarSymbolReportingContext context, IEnumerable disposeMethods) { foreach (var disposeMethod in disposeMethods) { foreach (var location in disposeMethod.Locations) { context.ReportIssue(Rule, location, disposeMethod.PartialImplementationPart?.Locations.ToSecondary() ?? []); } } } private static void CollectMethodsNamedAndImplementingDispose(IMethodSymbol methodSymbol, IMethodSymbol disposeMethod, HashSet namedDispose, HashSet mightImplementDispose) { if (methodSymbol.Name != DisposeMethodName) { return; } namedDispose.Add(methodSymbol); if (methodSymbol.IsOverride || MethodIsDisposeImplementation(methodSymbol, disposeMethod) || MethodMightImplementDispose(methodSymbol)) { mightImplementDispose.Add(methodSymbol); } } private static bool MethodIsDisposeImplementation(IMethodSymbol methodSymbol, IMethodSymbol disposeMethod) => methodSymbol.Equals(methodSymbol.ContainingType.FindImplementationForInterfaceMember(disposeMethod)); private static bool MethodMightImplementDispose(IMethodSymbol declaredMethodSymbol) { var containingType = declaredMethodSymbol.ContainingType; if (containingType.BaseType is { Kind: SymbolKind.ErrorType }) { return true; } var interfaces = containingType.AllInterfaces; foreach (var @interface in interfaces) { if (@interface.Kind == SymbolKind.ErrorType) { return true; } var interfaceMethods = @interface.GetMembers().OfType(); if (interfaceMethods.Any(x => declaredMethodSymbol.Equals(containingType.FindImplementationForInterfaceMember(x)))) { return true; } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallAssemblyGetExecutingAssemblyMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCallAssemblyGetExecutingAssemblyMethod : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S3902"; protected override string MessageFormat => "Replace this call to 'Assembly.GetExecutingAssembly()' with 'Type.Assembly'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Reflection_Assembly, "GetExecutingAssembly") }; public DoNotCallAssemblyGetExecutingAssemblyMethod() : base(DiagnosticId) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallAssemblyLoadInvalidMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCallAssemblyLoadInvalidMethods : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S3885"; private static readonly HashSet EventHandlerSyntaxes = [ SyntaxKind.MethodDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.AnonymousMethodExpression, SyntaxKindEx.LocalFunctionStatement ]; protected override string MessageFormat => "Replace this call to '{0}' with 'Assembly.Load'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Reflection_Assembly, "LoadFrom"), new(KnownType.System_Reflection_Assembly, "LoadFile"), new(KnownType.System_Reflection_Assembly, "LoadWithPartialName") }; public DoNotCallAssemblyLoadInvalidMethods() : base(DiagnosticId) { } protected override bool IsInValidContext(InvocationExpressionSyntax invocationSyntax, SemanticModel semanticModel) => !IsInResolutionHandler(invocationSyntax, semanticModel); // Checks if the invocation is inside an event handler for the AppDomain.AssemblyResolve event. // This check creates FN for the other Resolution events: // AppDomain.TypeResolve, AppDomain.ResourceResolve, AppDomain.ReflectionOnlyAssemblyResolve. // https://learn.microsoft.com/en-us/dotnet/api/system.resolveeventargs private static bool IsInResolutionHandler(InvocationExpressionSyntax invocationSyntax, SemanticModel model) => invocationSyntax .AncestorsAndSelf() .Any(x => x.IsAnyKind(EventHandlerSyntaxes) && (model.GetSymbolInfo(x).Symbol ?? model.GetDeclaredSymbol(x)) is IMethodSymbol methodSymbol && methodSymbol.IsEventHandler() && methodSymbol.Parameters[1].Type.Is(KnownType.System_ResolveEventArgs)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallExitMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCallExitMethods : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S1147"; protected override string MessageFormat => "Remove this call to '{0}' or ensure it is really required."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Environment, "Exit"), new(KnownType.System_Windows_Forms_Application, "Exit") }; public DoNotCallExitMethods() : base(DiagnosticId) { } protected override bool IsInValidContext(InvocationExpressionSyntax invocationSyntax, SemanticModel semanticModel) => // Do not report if call is inside Main or is a TopLevelStatement. invocationSyntax.Ancestors().OfType().Any() ? invocationSyntax.Ancestors() .Any(x => x?.Kind() is SyntaxKindEx.LocalFunctionStatement or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression or SyntaxKind.AnonymousMethodExpression) : !invocationSyntax.Ancestors().OfType().Where(x => x.GetIdentifierOrDefault()?.ValueText == "Main") .Select(m => semanticModel.GetDeclaredSymbol(m)) .Select(s => s.IsMainMethod()) .FirstOrDefault(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallGCCollectMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCallGCCollectMethod : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S1215"; protected override string MessageFormat => "Refactor the code to remove this use of '{0}'."; protected override IEnumerable CheckedMethods { get; } = [ new(KnownType.System_GC, nameof(GC.Collect)), new(KnownType.System_GC, nameof(GC.GetTotalMemory)), ]; public DoNotCallGCCollectMethod() : base(DiagnosticId) { } protected override bool ShouldReportOnMethodCall(InvocationExpressionSyntax invocation, SemanticModel semanticModel, MemberDescriptor memberDescriptor) => !IsGetTotalMemoryFalse(memberDescriptor, invocation); // Do not report on GC.TotalMemory(false) private static bool IsGetTotalMemoryFalse(MemberDescriptor memberDescriptor, InvocationExpressionSyntax invocation) => memberDescriptor.Name is nameof(GC.GetTotalMemory) && invocation.ArgumentList.Arguments.FirstOrDefault() is { } argument && argument.Expression.IsKind(SyntaxKind.FalseLiteralExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallGCSuppressFinalize.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCallGCSuppressFinalize : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S3971"; protected override string MessageFormat => "Do not call 'GC.SuppressFinalize'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_GC, "SuppressFinalize") }; public DoNotCallGCSuppressFinalize() : base(DiagnosticId) { } protected override bool ShouldReportOnMethodCall(InvocationExpressionSyntax invocation, SemanticModel semanticModel, MemberDescriptor memberDescriptor) { if (invocation.FirstAncestorOrSelf() is not { } methodDeclaration || (methodDeclaration.Identifier.ValueText != "Dispose" && methodDeclaration.Identifier.ValueText != "DisposeAsync")) { // We want to report on all calls not made from a method return true; } return semanticModel.GetDeclaredSymbol(methodDeclaration) is var methodSymbol && !methodSymbol.IsIDisposableDispose() && !methodSymbol.IsIAsyncDisposableDisposeAsync(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCallMethodsCsharpBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; public abstract class DoNotCallMethodsCSharpBase : DoNotCallMethodsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected DoNotCallMethodsCSharpBase(string diagnosticId) : base(diagnosticId) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCatchNullReferenceException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCatchNullReferenceException : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1696"; private const string MessageFormat = "Do not catch NullReferenceException; test for null instead."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var catchClause = (CatchClauseSyntax)c.Node; if (IsCatchingNullReferenceException(catchClause.Declaration, c.Model)) { c.ReportIssue(rule, catchClause.Declaration.Type); return; } if (HasIsNullReferenceExceptionFilter(catchClause.Filter, c.Model, out var locationToReportOn)) { c.ReportIssue(rule, locationToReportOn); } }, SyntaxKind.CatchClause); } private static bool IsCatchingNullReferenceException(CatchDeclarationSyntax catchDeclaration, SemanticModel semanticModel) { var caughtTypeSyntax = catchDeclaration?.Type; return caughtTypeSyntax != null && semanticModel.GetTypeInfo(caughtTypeSyntax).Type.Is(KnownType.System_NullReferenceException); } private static bool HasIsNullReferenceExceptionFilter(CatchFilterClauseSyntax catchFilterClause, SemanticModel semanticModel, out Location location) { var whenExpression = catchFilterClause?.FilterExpression.RemoveParentheses(); var rightSideOfIsExpression = whenExpression != null && whenExpression.IsKind(SyntaxKind.IsExpression) ? ((BinaryExpressionSyntax)whenExpression).Right : null; if (rightSideOfIsExpression != null && semanticModel.GetTypeInfo(rightSideOfIsExpression).Type.Is(KnownType.System_NullReferenceException)) { location = rightSideOfIsExpression.GetLocation(); return true; } location = null; return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCatchSystemException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCatchSystemException : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2221"; private const string MessageFormat = "Catch a list of specific exception subtype or use exception filters instead."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var catchClause = (CatchClauseSyntax)c.Node; if (c.AzureFunctionMethod() is null && IsCatchClauseEmptyOrNotPattern(catchClause) && IsSystemException(catchClause.Declaration, c.Model) && !IsThrowTheLastStatementInTheBlock(catchClause.Block)) { c.ReportIssue(Rule, GetLocation(catchClause)); } }, SyntaxKind.CatchClause); private static bool IsCatchClauseEmptyOrNotPattern(CatchClauseSyntax catchClause) => catchClause.Filter?.FilterExpression == null || (catchClause.Filter.FilterExpression.IsKind(SyntaxKindEx.IsPatternExpression) && (IsPatternExpressionSyntaxWrapper)catchClause.Filter.FilterExpression is var patternExpression && patternExpression.SyntaxNode.DescendantNodes().AnyOfKind(SyntaxKindEx.NotPattern)); private static bool IsSystemException(CatchDeclarationSyntax catchDeclaration, SemanticModel semanticModel) { var caughtTypeSyntax = catchDeclaration?.Type; return caughtTypeSyntax == null || semanticModel.GetTypeInfo(caughtTypeSyntax).Type.Is(KnownType.System_Exception); } private static bool IsThrowTheLastStatementInTheBlock(BlockSyntax block) { var lastStatement = block?.DescendantNodes()?.LastOrDefault(); return lastStatement != null && lastStatement.AncestorsAndSelf().TakeWhile(x => !Equals(x, block)).Any(x => x is ThrowStatementSyntax); } private static Location GetLocation(CatchClauseSyntax catchClause) => catchClause.Declaration?.Type != null ? catchClause.Declaration.Type.GetLocation() : catchClause.CatchKeyword.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCheckZeroSizeCollection.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Comparison = SonarAnalyzer.Core.Syntax.Utilities.ComparisonKind; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCheckZeroSizeCollection : DoNotCheckZeroSizeCollectionBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override string IEnumerableTString => "IEnumerable"; protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterNodeAction(AnalyzeIsPatternExpression, SyntaxKindEx.IsPatternExpression); context.RegisterNodeAction(AnalyzeSwitchExpression, SyntaxKindEx.SwitchExpression); context.RegisterNodeAction(AnalyzeSwitchStatement, SyntaxKind.SwitchStatement); context.RegisterNodeAction(AnalyzePropertyPatternClause, SyntaxKindEx.PropertyPatternClause); } private void AnalyzePropertyPatternClause(SonarSyntaxNodeReportingContext c) { var propertyPatternClause = (PropertyPatternClauseSyntaxWrapper)c.Node; foreach (var subPattern in propertyPatternClause.Subpatterns) { if (subPattern.ExpressionColon.SyntaxNode is NameColonSyntax nameColon) { CheckPatternCondition(c, nameColon.Name, subPattern.Pattern.SyntaxNode.RemoveParentheses()); } else if (ExpressionColonSyntaxWrapper.IsInstance(subPattern.ExpressionColon.SyntaxNode) && (ExpressionColonSyntaxWrapper)subPattern.ExpressionColon.SyntaxNode is var expressionColon) { CheckPatternCondition(c, expressionColon.Expression, subPattern.Pattern.SyntaxNode.RemoveParentheses()); } } } private void AnalyzePatterns(SonarSyntaxNodeReportingContext c, ExpressionSyntax expression, SyntaxNode pattern) { foreach (var pair in expression.MapToPattern(pattern)) { CheckPatternCondition(c, pair.Key, pair.Value); } } private void AnalyzeIsPatternExpression(SonarSyntaxNodeReportingContext c) { var isPatternExpression = (IsPatternExpressionSyntaxWrapper)c.Node; AnalyzePatterns(c, isPatternExpression.Expression, isPatternExpression.Pattern); } private void AnalyzeSwitchExpression(SonarSyntaxNodeReportingContext c) { var switchExpression = (SwitchExpressionSyntaxWrapper)c.Node; foreach (var arm in switchExpression.Arms) { AnalyzePatterns(c, switchExpression.GoverningExpression, arm.Pattern.SyntaxNode); } } private void AnalyzeSwitchStatement(SonarSyntaxNodeReportingContext c) { var switchStatement = (SwitchStatementSyntax)c.Node; foreach (var section in switchStatement.Sections) { foreach (var label in section.Labels) { AnalyzePatterns(c, switchStatement.Expression, label); } } } private void CheckPatternCondition(SonarSyntaxNodeReportingContext context, SyntaxNode expression, SyntaxNode pattern) { if (pattern.DescendantNodesAndSelf().FirstOrDefault(x => x.IsKind(SyntaxKindEx.RelationalPattern)) is { } relationalPatternNode && ((RelationalPatternSyntaxWrapper)relationalPatternNode) is var relationalPattern && relationalPattern.OperatorToken.ToComparisonKind() is { } comparison && comparison != Comparison.None && Language.ExpressionNumericConverter.ConstantIntValue(context.Model, relationalPattern.Expression) is { } constant) { CheckExpression(context, relationalPattern.SyntaxNode, expression, constant, comparison); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotCopyArraysInProperties.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotCopyArraysInProperties : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2365"; private const string MessageFormat = "Refactor '{0}' into a method, properties should not copy collections."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray CopyingCollectionTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_Dictionary_TKey_TValue, KnownType.System_Collections_Generic_HashSet_T, KnownType.System_Collections_Generic_LinkedList_T, KnownType.System_Collections_Generic_List_T, KnownType.System_Collections_Generic_Queue_T, KnownType.System_Collections_Generic_SortedDictionary_TKey_TValue, KnownType.System_Collections_Generic_SortedList_TKey_TValue, KnownType.System_Collections_Generic_SortedSet_T, KnownType.System_Collections_Generic_Stack_T); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var property = (PropertyDeclarationSyntax)c.Node; var body = PropertyBody(property); if (body is null) { return; } var walker = new PropertyWalker(c.Model, body is ArrowExpressionClauseSyntax); walker.SafeVisit(body); foreach (var location in walker.Locations) { c.ReportIssue(Rule, location, property.Identifier.Text); } }, SyntaxKind.PropertyDeclaration); private static SyntaxNode PropertyBody(PropertyDeclarationSyntax property) => property.ExpressionBody ?? property.AccessorList .Accessors .Where(x => x.IsKind(SyntaxKind.GetAccessorDeclaration)) .Select(x => (SyntaxNode)x.Body ?? x.ExpressionBody) .FirstOrDefault(); private sealed class PropertyWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel model; private readonly List locations = []; private bool inGetterReturn; public IEnumerable Locations => locations; public PropertyWalker(SemanticModel model, bool isArrowExpression) { this.model = model; inGetterReturn = isArrowExpression; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (inGetterReturn && model.GetSymbolInfo(node).Symbol is IMethodSymbol invokedSymbol && (invokedSymbol.IsArrayClone() || invokedSymbol.IsEnumerableToList() || invokedSymbol.IsEnumerableToArray())) { locations.Add(node.Expression.GetLocation()); } } public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) { if (inGetterReturn && IsObjectCreationCopyingCollection(node)) { locations.Add(node.GetLocation()); } } public override void Visit(SyntaxNode node) { if (inGetterReturn && node.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression) && IsObjectCreationCopyingCollection(node)) { locations.Add(node.GetLocation()); } if (node.IsKind(SyntaxKindEx.LocalFunctionStatement)) { return; // Filter local function } base.Visit(node); } public override void VisitReturnStatement(ReturnStatementSyntax node) { inGetterReturn = true; base.VisitReturnStatement(node); inGetterReturn = false; } public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { // Filter Lambda } public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { // Filter Lambda } public override void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node) { // Filter Anonymous Method } public override void VisitAssignmentExpression(AssignmentExpressionSyntax node) { // Filter lazy initialization } private bool IsObjectCreationCopyingCollection(SyntaxNode node) => model.GetSymbolInfo(node).Symbol is IMethodSymbol { MethodKind: MethodKind.Constructor, Parameters.Length: > 0 } constructor && constructor.ContainingType.OriginalDefinition.IsAny(CopyingCollectionTypes) && constructor.Parameters[0] is var firstParameter && !firstParameter.Type.Is(KnownType.System_String) && firstParameter.Type.DerivesOrImplements(KnownType.System_Collections_IEnumerable); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotDecreaseMemberVisibility.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotDecreaseMemberVisibility : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4015"; private const string MessageFormat = "This member hides '{0}'. Make it non-private or seal the class."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var classDeclaration = (TypeDeclarationSyntax)c.Node; if (classDeclaration.Identifier.IsMissing || c.IsRedundantPositionalRecordContext() || !(c.ContainingSymbol is ITypeSymbol { IsSealed: false } classSymbol)) { return; } var issueReporter = new IssueReporter(classSymbol, c); foreach (var member in classDeclaration.Members) { issueReporter.ReportIssue(member); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private sealed class IssueReporter { private readonly IList allBaseClassMethods; private readonly IList allBaseClassProperties; private readonly IList allBaseClassEvents; private readonly SonarSyntaxNodeReportingContext context; public IssueReporter(ITypeSymbol classSymbol, SonarSyntaxNodeReportingContext context) { this.context = context; var allBaseClassMembers = classSymbol.BaseType .GetSelfAndBaseTypes() .SelectMany(x => x.GetMembers()) .Where(x => IsSymbolVisibleFromNamespace(x, classSymbol.ContainingNamespace)) .ToList(); allBaseClassMethods = allBaseClassMembers.OfType().ToList(); allBaseClassProperties = allBaseClassMembers.OfType().ToList(); allBaseClassEvents = allBaseClassMembers.OfType().ToList(); } public void ReportIssue(MemberDeclarationSyntax memberDeclaration) { switch (context.Model.GetDeclaredSymbol(memberDeclaration)) { case IMethodSymbol methodSymbol: ReportMethodIssue(memberDeclaration, methodSymbol); break; case IPropertySymbol propertySymbol: ReportPropertyIssue(memberDeclaration, propertySymbol); break; case IEventSymbol eventSymbol: ReportEventIssue(memberDeclaration, eventSymbol); break; } } private void ReportMethodIssue(MemberDeclarationSyntax memberDeclaration, IMethodSymbol methodSymbol) { if (memberDeclaration is MethodDeclarationSyntax methodDeclaration && !methodDeclaration.Modifiers.Any(SyntaxKind.NewKeyword) && allBaseClassMethods.FirstOrDefault(x => IsDecreasingAccess(x.DeclaredAccessibility, methodSymbol.DeclaredAccessibility, false) && IsMatchingSignature(x, methodSymbol)) is { } hidingMethod) { context.ReportIssue(Rule, methodDeclaration.Identifier, hidingMethod.ToDisplayString()); } } private void ReportPropertyIssue(MemberDeclarationSyntax memberDeclaration, IPropertySymbol propertySymbol) { if (memberDeclaration is BasePropertyDeclarationSyntax basePropertyDeclaration && !basePropertyDeclaration.Modifiers.Any(SyntaxKind.NewKeyword) && allBaseClassProperties.FirstOrDefault(x => IsDecreasingPropertyAccess(x, propertySymbol, propertySymbol.IsOverride)) is { } hidingProperty) { context.ReportIssue(Rule, GetPropertyToken(basePropertyDeclaration), hidingProperty.ToDisplayString()); } } private void ReportEventIssue(MemberDeclarationSyntax memberDeclaration, IEventSymbol eventSymbol) { if (memberDeclaration is EventDeclarationSyntax eventDeclaration && !eventDeclaration.Modifiers.Any(SyntaxKind.NewKeyword) && allBaseClassEvents.FirstOrDefault(x => IsDecreasingAccess(x.DeclaredAccessibility, eventSymbol.DeclaredAccessibility, false) && x.Name == eventSymbol.Name) is { } hidingEvent) { context.ReportIssue(Rule, eventDeclaration.Identifier, hidingEvent.ToDisplayString()); } } private static SyntaxToken GetPropertyToken(BasePropertyDeclarationSyntax propertyLike) => propertyLike switch { PropertyDeclarationSyntax property => property.Identifier, IndexerDeclarationSyntax indexer => indexer.ThisKeyword, _ => propertyLike.GetFirstToken() }; private static bool IsSymbolVisibleFromNamespace(ISymbol symbol, INamespaceSymbol ns) => symbol.DeclaredAccessibility != Accessibility.Private && (symbol.DeclaredAccessibility != Accessibility.Internal || ns.Equals(symbol.ContainingNamespace)); private static bool IsDecreasingPropertyAccess(IPropertySymbol baseProperty, IPropertySymbol propertySymbol, bool isOverride) { if (baseProperty.Name != propertySymbol.Name || !AreParameterTypesEqual(baseProperty.Parameters, propertySymbol.Parameters)) { return false; } var baseGetAccess = GetEffectiveDeclaredAccess(baseProperty.GetMethod, baseProperty.DeclaredAccessibility); var baseSetAccess = GetEffectiveDeclaredAccess(baseProperty.SetMethod, baseProperty.DeclaredAccessibility); var propertyGetAccess = GetEffectiveDeclaredAccess(propertySymbol.GetMethod, baseProperty.DeclaredAccessibility); var propertySetAccess = GetEffectiveDeclaredAccess(propertySymbol.SetMethod, baseProperty.DeclaredAccessibility); return IsDecreasingAccess(baseGetAccess, propertyGetAccess, isOverride) || IsDecreasingAccess(baseSetAccess, propertySetAccess, isOverride); } private static Accessibility GetEffectiveDeclaredAccess(ISymbol symbol, Accessibility defaultAccessibility) { if (symbol is null) { return Accessibility.NotApplicable; } return symbol.DeclaredAccessibility == Accessibility.NotApplicable ? defaultAccessibility : symbol.DeclaredAccessibility; } private static bool IsMatchingSignature(IMethodSymbol baseMethod, IMethodSymbol methodSymbol) => baseMethod.Name == methodSymbol.Name && baseMethod.TypeParameters.Length == methodSymbol.TypeParameters.Length && AreParameterTypesEqual(baseMethod.Parameters, methodSymbol.Parameters); private static bool AreParameterTypesEqual(IEnumerable first, IEnumerable second) => first.Equals(second, AreParameterTypesEqual); private static bool AreParameterTypesEqual(IParameterSymbol first, IParameterSymbol second) { if (first.RefKind != second.RefKind) { return false; } return first.Type.TypeKind == TypeKind.TypeParameter ? second.Type.TypeKind == TypeKind.TypeParameter : Equals(first.Type.OriginalDefinition, second.Type.OriginalDefinition); } private static bool IsDecreasingAccess(Accessibility baseAccess, Accessibility memberAccess, bool isOverride) { if (memberAccess == Accessibility.NotApplicable && isOverride) { return false; } return (baseAccess != Accessibility.NotApplicable && memberAccess == Accessibility.Private) || (baseAccess == Accessibility.Public && memberAccess != Accessibility.Public); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotExposeListT.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotExposeListT : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3956"; private const string MessageFormat = "Refactor this {0} to use a generic collection designed for inheritance."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var baseMethodDeclaration = (BaseMethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(baseMethodDeclaration); if (methodSymbol == null || !methodSymbol.IsPubliclyAccessible() || methodSymbol.IsOverride || !IsOrdinaryMethodOrConstructor(methodSymbol)) { return; } var methodType = methodSymbol.IsConstructor() ? "constructor" : "method"; if (baseMethodDeclaration is MethodDeclarationSyntax methodDeclaration) { ReportIfListT(c, methodDeclaration.ReturnType, methodType); } baseMethodDeclaration .ParameterList? .Parameters .ToList() .ForEach(p => ReportIfListT(c, p.Type, methodType)); }, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction( c => { var propertyDeclaration = (PropertyDeclarationSyntax)c.Node; var propertySymbol = c.Model.GetDeclaredSymbol(propertyDeclaration); if (propertySymbol != null && propertySymbol.IsPubliclyAccessible() && !propertySymbol.IsOverride && !HasXmlElementAttribute(propertySymbol)) { ReportIfListT(c, propertyDeclaration.Type, "property"); } }, SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; var variableDeclaration = fieldDeclaration.Declaration?.Variables.FirstOrDefault(); if (variableDeclaration == null) { return; } var fieldSymbol = c.Model.GetDeclaredSymbol(variableDeclaration); if (fieldSymbol != null && fieldSymbol.IsPubliclyAccessible() && !HasXmlElementAttribute(fieldSymbol)) { ReportIfListT(c, fieldDeclaration.Declaration.Type, "field"); } }, SyntaxKind.FieldDeclaration); } private static void ReportIfListT(SonarSyntaxNodeReportingContext context, TypeSyntax typeSyntax, string memberType) { if (typeSyntax != null && typeSyntax.IsKnownType(KnownType.System_Collections_Generic_List_T, context.Model)) { context.ReportIssue(Rule, typeSyntax, memberType); } } private static bool IsOrdinaryMethodOrConstructor(IMethodSymbol method) => method.MethodKind == MethodKind.Ordinary || method.MethodKind == MethodKind.Constructor; private static bool HasXmlElementAttribute(ISymbol symbol) => symbol.GetAttributes(KnownType.System_Xml_Serialization_XmlElementAttribute).Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotHardcodeCredentials.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotHardcodeCredentials : DoNotHardcodeCredentialsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void InitializeActions(SonarParametrizedAnalysisContext context) => context.RegisterCompilationStartAction( c => { c.RegisterNodeAction( new VariableDeclarationBannedWordsFinder(this).AnalysisAction(), SyntaxKind.VariableDeclarator); c.RegisterNodeAction( new AssignmentExpressionBannedWordsFinder(this).AnalysisAction(), SyntaxKind.SimpleAssignmentExpression); c.RegisterNodeAction( new StringLiteralBannedWordsFinder(this).AnalysisAction(), SyntaxKind.StringLiteralExpression); c.RegisterNodeAction( new AddExpressionBannedWordsFinder(this).AnalysisAction(), SyntaxKind.AddExpression); c.RegisterNodeAction( new InterpolatedStringBannedWordsFinder(this).AnalysisAction(), SyntaxKind.InterpolatedStringExpression); c.RegisterNodeAction( new InvocationBannedWordsFinder(this).AnalysisAction(), SyntaxKind.InvocationExpression); }); protected override bool IsSecureStringAppendCharFromConstant(SyntaxNode argumentNode, SemanticModel model) => argumentNode is ArgumentSyntax { Expression: { } argumentExpression } && argumentExpression switch { ElementAccessExpressionSyntax { Expression: { } accessed } => accessed.FindConstantValue(model) is string, // AppendChar("AP@ssw0rd"[i]) LiteralExpressionSyntax { RawKind: (int)SyntaxKind.CharacterLiteralExpression } => true, // AppendChar('P') IdentifierNameSyntax identifier when model.GetSymbolInfo(identifier) is { Symbol: ILocalSymbol { } local } // foreach (var c in someConstString) AppendChar(c) && local.DeclaringSyntaxReferences.Length == 1 && local.DeclaringSyntaxReferences[0].GetSyntax() is ForEachStatementSyntax { Expression: { } forEachExpression } && forEachExpression.FindConstantValue(model) is string => true, _ => false, }; private sealed class VariableDeclarationBannedWordsFinder : CredentialWordsFinderBase { public VariableDeclarationBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(VariableDeclaratorSyntax syntaxNode, SemanticModel model) => FindStringLiteralInVariableDeclaration(syntaxNode.Initializer.Value)?.StringValue(model); protected override string VariableName(VariableDeclaratorSyntax syntaxNode) => syntaxNode.Identifier.ValueText; protected override bool ShouldHandle(VariableDeclaratorSyntax syntaxNode, SemanticModel model) => FindStringLiteralInVariableDeclaration(syntaxNode.Initializer?.Value) is not null && (syntaxNode.IsDeclarationKnownType(KnownType.System_String, model) || syntaxNode.IsDeclarationKnownType(KnownType.System_ReadOnlySpan_T, model) // "utf8"u8 || syntaxNode.IsDeclarationKnownType(KnownType.System_Byte_Array, model)); // "utf8"u8.ToArray() private static LiteralExpressionSyntax FindStringLiteralInVariableDeclaration(ExpressionSyntax expression) => expression switch { LiteralExpressionSyntax literal => literal, InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Expression: LiteralExpressionSyntax { RawKind: (int)SyntaxKindEx.Utf8StringLiteralExpression } literal } } invocation when invocation.NameIs("ToArray") => literal, // "utf8"u8.ToArray() _ => null }; } private sealed class AssignmentExpressionBannedWordsFinder : CredentialWordsFinderBase { public AssignmentExpressionBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(AssignmentExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.Right.StringValue(model); protected override string VariableName(AssignmentExpressionSyntax syntaxNode) => (syntaxNode.Left as IdentifierNameSyntax)?.Identifier.ValueText; protected override bool ShouldHandle(AssignmentExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.IsKind(SyntaxKind.SimpleAssignmentExpression) && syntaxNode.Left.IsKnownType(KnownType.System_String, model) && syntaxNode.Right.IsKind(SyntaxKind.StringLiteralExpression); } /// /// This finder checks all string literal in the code, except VariableDeclarator, SimpleAssignmentExpression and string.Format invocation. /// These two have their own finders with precise logic and variable name checking. /// This class inspects all other standalone string literals for values considered as hardcoded passwords (in connection strings) /// based on same rules as in VariableDeclarationBannedWordsFinder and AssignmentExpressionBannedWordsFinder. /// private sealed class StringLiteralBannedWordsFinder : CredentialWordsFinderBase { public StringLiteralBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(LiteralExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.StringValue(model); // We don't have a variable for cases that this finder should handle. Cases with variable name are // handled by VariableDeclarationBannedWordsFinder and AssignmentExpressionBannedWordsFinder // Returning null is safe here, it will not be considered as a value. protected override string VariableName(LiteralExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(LiteralExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.IsKind(SyntaxKind.StringLiteralExpression) && ShouldHandle(syntaxNode.GetTopMostContainingMethod(), syntaxNode, model); private static bool ShouldHandle(SyntaxNode method, SyntaxNode current, SemanticModel model) { // We don't want to handle VariableDeclarator and SimpleAssignmentExpression, // they are implemented by other finders with better and more precise logic. while (current is not null && current != method) { switch (current.Kind()) { case SyntaxKind.VariableDeclarator: case SyntaxKind.SimpleAssignmentExpression: return false; // Direct return from nested syntaxes that must be handled by this finder // before search reaches top level VariableDeclarator or SimpleAssignmentExpression. case SyntaxKind.InvocationExpression: case SyntaxKind.AddExpression: // String concatenation is not supported by other finders return true; // Handle all arguments except those inside string.Format. InvocationBannedWordsFinder takes care of them. case SyntaxKind.Argument: return !(current.Parent.Parent is InvocationExpressionSyntax invocation && invocation.IsMethodInvocation(KnownType.System_String, "Format", model)); default: current = current.Parent; break; } } // We want to handle all other literals (property initializers, return statement and return values from lambdas, arrow functions, ...) return true; } } private sealed class AddExpressionBannedWordsFinder : CredentialWordsFinderBase { public AddExpressionBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(BinaryExpressionSyntax syntaxNode, SemanticModel model) { var left = syntaxNode.Left is BinaryExpressionSyntax precedingAdd && precedingAdd.IsKind(SyntaxKind.AddExpression) ? precedingAdd.Right : syntaxNode.Left; return left.FindStringConstant(model) is { } leftString && syntaxNode.Right.FindStringConstant(model) is { } rightString ? leftString + rightString : null; } protected override string VariableName(BinaryExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(BinaryExpressionSyntax syntaxNode, SemanticModel model) => true; } private sealed class InterpolatedStringBannedWordsFinder : CredentialWordsFinderBase { public InterpolatedStringBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(InterpolatedStringExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.Contents.JoinStr(null, x => x switch { InterpolationSyntax interpolation => interpolation.Expression.FindStringConstant(model), InterpolatedStringTextSyntax text => text.TextToken.ToString(), _ => null } ?? KeywordSeparator.ToString()); // Unknown elements resolved to separator to terminate the keyword-value sequence protected override string VariableName(InterpolatedStringExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(InterpolatedStringExpressionSyntax syntaxNode, SemanticModel model) => true; } private sealed class InvocationBannedWordsFinder : CredentialWordsFinderBase { public InvocationBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(InvocationExpressionSyntax syntaxNode, SemanticModel model) { var allArgs = syntaxNode.ArgumentList.Arguments.Select(x => x.Expression.FindStringConstant(model) ?? KeywordSeparator.ToString()); try { return string.Format(allArgs.First(), allArgs.Skip(1).ToArray()); } catch (FormatException) { return null; } } protected override string VariableName(InvocationExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(InvocationExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.IsMethodInvocation(KnownType.System_String, "Format", model) && model.GetSymbolInfo(syntaxNode).Symbol is IMethodSymbol symbol && symbol.Parameters.First().Type.Is(KnownType.System_String); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotHardcodeSecrets.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotHardcodeSecrets : DoNotHardcodeSecretsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void RegisterNodeActions(SonarCompilationStartAnalysisContext context) { context.RegisterNodeAction( ReportIssues, SyntaxKind.AddAssignmentExpression, SyntaxKind.SimpleAssignmentExpression, SyntaxKind.VariableDeclarator, SyntaxKind.PropertyDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKind.EqualsExpression); context.RegisterNodeAction(c => { var invocationExpression = (InvocationExpressionSyntax)c.Node; if (invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name.Identifier.ValueText == EqualsName && invocationExpression.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && memberAccessExpression.IsMemberAccessOnKnownType(EqualsName, KnownType.System_String, c.Model)) { ReportIssuesForEquals(c, memberAccessExpression, IdentifierAndValue(memberAccessExpression.Expression, firstArgument)); } }, SyntaxKind.InvocationExpression); } protected override SyntaxNode IdentifierRoot(SyntaxNode node) => node switch { AccessorDeclarationSyntax accessor => accessor.Parent.Parent, AssignmentExpressionSyntax assignment when assignment.Left.IsKind(SyntaxKindEx.FieldExpression) => assignment.Ancestors().OfType().FirstOrDefault(), AssignmentExpressionSyntax assignment => assignment.Left, BinaryExpressionSyntax { Left: IdentifierNameSyntax left } => left, BinaryExpressionSyntax { Right: IdentifierNameSyntax right } => right, _ => node }; protected override SyntaxNode RightHandSide(SyntaxNode node) => node switch { AssignmentExpressionSyntax assignment => assignment.Right, VariableDeclaratorSyntax variable => variable.Initializer?.Value, PropertyDeclarationSyntax property => property.Initializer?.Value, AccessorDeclarationSyntax accessor => accessor.ExpressionBody?.Expression, BinaryExpressionSyntax { Left: IdentifierNameSyntax } binary => binary.Right, BinaryExpressionSyntax { Right: IdentifierNameSyntax } binary => binary.Left, _ => null }; private static IdentifierValuePair IdentifierAndValue(ExpressionSyntax expression, ArgumentSyntax argument) => expression switch { MemberAccessExpressionSyntax or IdentifierNameSyntax or InvocationExpressionSyntax => new(expression.GetIdentifier(), argument.Expression), LiteralExpressionSyntax literal => new(argument.Expression.GetIdentifier(), literal), _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotHideBaseClassMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotHideBaseClassMethods : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4019"; private const string MessageFormat = "Remove or rename that method because it hides '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var declarationSyntax = (TypeDeclarationSyntax)c.Node; if (declarationSyntax.Identifier.IsMissing || c.IsRedundantPositionalRecordContext() || !(c.Model.GetDeclaredSymbol(declarationSyntax) is { } declaredSymbol)) { return; } var issueReporter = new IssueReporter(declaredSymbol, c); foreach (var member in declarationSyntax.Members) { issueReporter.ReportIssue(member); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private sealed class IssueReporter { private enum Match { Different, Identical, WeaklyDerived } private readonly IList allBaseTypeMethods; private readonly SonarSyntaxNodeReportingContext context; public IssueReporter(ITypeSymbol typeSymbol, SonarSyntaxNodeReportingContext context) { this.context = context; allBaseTypeMethods = GetAllBaseMethods(typeSymbol); } public void ReportIssue(MemberDeclarationSyntax memberDeclaration) { if (memberDeclaration is MethodDeclarationSyntax { Identifier: { } identifier } && context.Model.GetDeclaredSymbol(memberDeclaration) is IMethodSymbol methodSymbol && FindBaseMethodHiddenByMethod(methodSymbol) is { } baseMethodHidden) { context.ReportIssue(Rule, identifier, baseMethodHidden.ToDisplayString()); } } private static List GetAllBaseMethods(ITypeSymbol typeSymbol) => typeSymbol.BaseType .GetSelfAndBaseTypes() .SelectMany(t => t.GetMembers()) .OfType() .Where(m => IsSymbolVisibleFromNamespace(m, typeSymbol.ContainingNamespace)) .Where(m => m.Parameters.Length > 0) .Where(m => !string.IsNullOrEmpty(m.Name)) .ToList(); private static bool IsSymbolVisibleFromNamespace(ISymbol symbol, INamespaceSymbol ns) => symbol.DeclaredAccessibility != Accessibility.Private && (symbol.DeclaredAccessibility != Accessibility.Internal || ns.Equals(symbol.ContainingNamespace)); private IMethodSymbol FindBaseMethodHiddenByMethod(IMethodSymbol methodSymbol) { var baseMemberCandidates = allBaseTypeMethods.Where(m => m.Name == methodSymbol.Name); IMethodSymbol hiddenBaseMethodCandidate = null; var hasBaseMethodWithSameSignature = baseMemberCandidates.Any(baseMember => { var result = ComputeSignatureMatch(baseMember, methodSymbol); if (result == Match.WeaklyDerived) { hiddenBaseMethodCandidate ??= baseMember; } return result == Match.Identical; }); return hasBaseMethodWithSameSignature ? null : hiddenBaseMethodCandidate; } private static Match ComputeSignatureMatch(IMethodSymbol baseMethodSymbol, IMethodSymbol methodSymbol) { var baseMethodParams = baseMethodSymbol.Parameters; var methodParams = methodSymbol.Parameters; if (baseMethodParams.Length != methodParams.Length) { return Match.Different; } var signatureMatch = Match.Identical; for (var i = 0; i < methodParams.Length; i++) { var match = ComputeParameterMatch(baseMethodParams[i], methodParams[i]); if (match == Match.Different) { return Match.Different; } if (match == Match.WeaklyDerived) { signatureMatch = Match.WeaklyDerived; } } return signatureMatch; } private static Match ComputeParameterMatch(IParameterSymbol baseParam, IParameterSymbol methodParam) { if (baseParam.Type.Is(TypeKind.TypeParameter)) { return methodParam.Type.TypeKind == TypeKind.TypeParameter ? Match.Identical : Match.Different; } if (Equals(baseParam.Type.OriginalDefinition, methodParam.Type.OriginalDefinition)) { return Match.Identical; } return baseParam.Type.DerivesOrImplements(methodParam.Type) ? Match.WeaklyDerived : Match.Different; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotInstantiateSharedClasses.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotInstantiateSharedClasses : DoNotInstantiateSharedClassesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var creationSyntax = (ObjectCreationExpressionSyntax)c.Node; var createdType = c.Model.GetTypeInfo(creationSyntax).Type; if (createdType != null && createdType.GetAttributes(KnownType.System_ComponentModel_Composition_PartCreationPolicyAttribute).Any(IsShared)) { c.ReportIssue(rule, creationSyntax); } }, SyntaxKind.ObjectCreationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotLockOnSharedResource.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotLockOnSharedResource : DoNotLockOnSharedResourceBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var lockStatement = (LockStatementSyntax)c.Node; if (IsLockOnThis(lockStatement.Expression) || IsLockOnStringLiteral(lockStatement.Expression) || IsLockOnForbiddenKnownType(lockStatement.Expression, c.Model)) { c.ReportIssue(rule, lockStatement.Expression); } }, SyntaxKind.LockStatement); } private static bool IsLockOnThis(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.ThisExpression); private static bool IsLockOnStringLiteral(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.StringLiteralExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotLockWeakIdentityObjects.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotLockWeakIdentityObjects : DoNotLockWeakIdentityObjectsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind SyntaxKind { get; } = SyntaxKind.LockStatement; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotMarkEnumsWithFlags.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Numerics; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotMarkEnumsWithFlags : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4070"; private const string MessageFormat = "Remove the 'FlagsAttribute' from this enum."; private const string SecondaryMessage = "Enum value is not a power of two."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var enumDeclaration = (EnumDeclarationSyntax)c.Node; var enumSymbol = c.Model.GetDeclaredSymbol(enumDeclaration); if (!enumDeclaration.HasFlagsAttribute(c.Model) || enumDeclaration.Identifier.IsMissing || enumSymbol is null) { return; } var membersWithValues = enumSymbol.GetMembers() .OfType() .Select(member => new { Member = member, Value = GetEnumValueOrDefault(member) }) .OrderByDescending(tuple => tuple.Value) .ToList(); var allValues = membersWithValues.Select(x => x.Value) .OfType() .Distinct() .ToList(); var invalidMembers = membersWithValues.Where(tuple => !IsValidFlagValue(tuple.Value, allValues)) .Select(tuple => tuple.Member.GetFirstSyntaxRef()?.ToSecondaryLocation(SecondaryMessage)) .WhereNotNull() .ToList(); if (invalidMembers.Count > 0) { c.ReportIssue(Rule, enumDeclaration.Identifier, invalidMembers); } }, SyntaxKind.EnumDeclaration); private static BigInteger? GetEnumValueOrDefault(IFieldSymbol enumMember) => enumMember.HasConstantValue ? enumMember.ConstantValue switch // BigInteger is used here to support enums with base type of ulong. Direct cast is used to avoid string conversion. { byte x => (BigInteger)x, sbyte x => (BigInteger)x, short x => (BigInteger)x, ushort x => (BigInteger)x, int x => (BigInteger)x, uint x => (BigInteger)x, long x => (BigInteger)x, ulong x => (BigInteger)x, _ => null } : null; private static bool IsValidFlagValue(BigInteger? enumValue, IEnumerable allValues) => enumValue.HasValue && (IsZeroOrPowerOfTwo(enumValue.Value) || IsCombinationOfOtherValues(enumValue.Value, allValues)); private static bool IsZeroOrPowerOfTwo(BigInteger value) => value.IsZero || BigInteger.Abs(value).IsPowerOfTwo; private static bool IsCombinationOfOtherValues(BigInteger value, IEnumerable otherValues) { var currentValue = value; foreach (var otherValue in otherValues.SkipWhile(x => value <= x)) { if (otherValue <= currentValue) { currentValue ^= otherValue; if (currentValue == 0) { return true; } } } return currentValue == 0; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotNestTernaryOperators.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotNestTernaryOperators : DoNotNestTernaryOperatorsBase { private const string MessageFormat = "Extract this nested ternary operation into an independent statement."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node.Ancestors() .TakeWhile(x => !(x?.Kind() is SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression)) .OfType() .Any()) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.ConditionalExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotNestTypesInArguments.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotNestTypesInArguments : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4017"; private const string MessageFormat = "Refactor this method to remove the nested type argument."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { IEnumerable parameters = c.Node switch { BaseMethodDeclarationSyntax { ParameterList.Parameters.Count: > 0 } method when !ImplementsAbstractClassOrInterface(c, method) => method.ParameterList.Parameters, { } node when node.IsKind(SyntaxKindEx.LocalFunctionStatement) => ((LocalFunctionStatementSyntaxWrapper)node).ParameterList.Parameters, _ => null }; if (parameters is not null) { CheckArguments(c, parameters); } }, SyntaxKind.MethodDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKindEx.LocalFunctionStatement); private static bool ImplementsAbstractClassOrInterface(SonarSyntaxNodeReportingContext context, BaseMethodDeclarationSyntax method) => context.Model.GetDeclaredSymbol(method) is { } symbol && (symbol.GetOverriddenMember() is { IsAbstract: true } || symbol.InterfaceMembers().Any()); private static bool MaxDepthReached(SyntaxNode parameterSyntax, SemanticModel model) { var walker = new GenericWalker(2, model); walker.SafeVisit(parameterSyntax); return walker.IsMaxDepthReached; } private static void CheckArguments(SonarSyntaxNodeReportingContext context, IEnumerable arguments) { foreach (var argument in arguments.Where(x => MaxDepthReached(x, context.Model))) { context.ReportIssue(Rule, argument); } } private sealed class GenericWalker : SafeCSharpSyntaxWalker { private static readonly ImmutableArray IgnoredTypes = KnownType.SystemFuncVariants .Union(KnownType.SystemActionVariants) .Union([KnownType.System_Linq_Expressions_Expression_T]) .ToImmutableArray(); private readonly int maxDepth; private readonly SemanticModel model; private int depth; public bool IsMaxDepthReached { get; private set; } public GenericWalker(int maxDepth, SemanticModel model) { this.maxDepth = maxDepth; this.model = model; } public override void VisitGenericName(GenericNameSyntax node) { if (model.GetSymbolInfo(node).Symbol is not INamedTypeSymbol symbol) { return; } if (symbol.ConstructedFrom.IsAny(IgnoredTypes)) { base.VisitGenericName(node); return; } if (depth == maxDepth - 1) { IsMaxDepthReached = true; return; } depth++; base.VisitGenericName(node); depth--; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotOverloadOperatorEqual.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotOverloadOperatorEqual : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3875"; private const string MessageFormat = "Remove this overload of 'operator =='."; private static readonly ImmutableArray InterfacesRelyingOnOperatorEqualOverload = ImmutableArray.Create( KnownType.System_IComparable, KnownType.System_IComparable_T, KnownType.System_IEquatable_T, KnownType.System_Numerics_IEqualityOperators_TSelf_TOther_TResult); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(CheckForIssue, SyntaxKind.OperatorDeclaration); private static void CheckForIssue(SonarSyntaxNodeReportingContext analysisContext) { var declaration = (OperatorDeclarationSyntax)analysisContext.Node; if (declaration.OperatorToken.IsKind(SyntaxKind.EqualsEqualsToken) && declaration.Parent is ClassDeclarationSyntax classDeclaration && !classDeclaration.ChildNodes() .OfType() .Any(op => op.OperatorToken.IsKind(SyntaxKind.PlusToken) || op.OperatorToken.IsKind(SyntaxKind.MinusToken)) && analysisContext.Model.GetDeclaredSymbol(classDeclaration) is { } namedTypeSymbol && !namedTypeSymbol.ImplementsAny(InterfacesRelyingOnOperatorEqualOverload)) { analysisContext.ReportIssue(Rule, declaration.OperatorToken); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotOverwriteCollectionElements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotOverwriteCollectionElements : DoNotOverwriteCollectionElementsBase { private static readonly HashSet IdentifierOrLiteral = [ SyntaxKind.IdentifierName, SyntaxKind.StringLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.CharacterLiteralExpression, SyntaxKind.NullLiteralExpression, SyntaxKind.TrueLiteralExpression, SyntaxKind.FalseLiteralExpression ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( AnalysisAction, SyntaxKind.ExpressionStatement); protected override SyntaxNode GetCollectionIdentifier(ExpressionStatementSyntax statement) { var assignmentOrInvocation = GetAssignmentOrInvocation(statement); switch (assignmentOrInvocation?.Kind()) { case SyntaxKind.InvocationExpression: return GetInvokedMethodContainer((InvocationExpressionSyntax)assignmentOrInvocation) .RemoveParentheses(); case SyntaxKind.SimpleAssignmentExpression: var assignment = (AssignmentExpressionSyntax)assignmentOrInvocation; var elementAccess = assignment.Left as ElementAccessExpressionSyntax; return GetIdentifier(elementAccess?.Expression.RemoveParentheses()) .RemoveParentheses(); default: return null; } } protected override SyntaxNode GetIndexOrKey(ExpressionStatementSyntax statement) => GetIndexOrKeyArgument(statement)?.Expression.RemoveParentheses(); protected override bool IsIdentifierOrLiteral(SyntaxNode node) => node.IsAnyKind(IdentifierOrLiteral); private static SyntaxNode GetAssignmentOrInvocation(StatementSyntax statement) { if (!(statement is ExpressionStatementSyntax expressionStatement)) { return null; } var expression = expressionStatement.Expression; return expression.IsKind(SyntaxKind.ConditionalAccessExpression) ? ((ConditionalAccessExpressionSyntax)expression).WhenNotNull : expression; } private static ArgumentSyntax GetIndexOrKeyArgument(StatementSyntax statement) { var assignmentOrInvocation = GetAssignmentOrInvocation(statement); switch (assignmentOrInvocation?.Kind()) { case SyntaxKind.InvocationExpression: var invocation = (InvocationExpressionSyntax)assignmentOrInvocation; return invocation.ArgumentList.Arguments.ElementAtOrDefault(0); case SyntaxKind.SimpleAssignmentExpression: var assignment = (AssignmentExpressionSyntax)assignmentOrInvocation; return assignment.Left is ElementAccessExpressionSyntax elementAccess ? elementAccess.ArgumentList.Arguments.ElementAtOrDefault(0) : null; default: return null; } } private static SyntaxNode GetInvokedMethodContainer(InvocationExpressionSyntax invocation) { var expression = invocation.Expression.RemoveParentheses(); switch (expression.Kind()) { case SyntaxKind.SimpleMemberAccessExpression: // a.Add(x) // InvocationExpression | a.Add(x) // Expression: SimpleMemberAccess // Name: Add // Expression: a // we need this var memberAccess = (MemberAccessExpressionSyntax)expression; return memberAccess.Name.ToString() == "Add" && invocation.ArgumentList?.Arguments.Count != 1 // #2674 Do not raise on ICollection.Add(item) ? memberAccess.Expression : null; // Ignore invocations that are on methods different than Add case SyntaxKind.MemberBindingExpression: // a?.Add(x) // ConditionalExpression | a?.Add(x) // Expression: a // <-- we need this // WhenTrue: InvocationExpression | ?.Add(x) // Expression: MemberBinding // <-- we are here // Identifier: Add var conditional = expression.Parent.Parent as ConditionalAccessExpressionSyntax; return conditional?.Expression; default: return null; } } private static SyntaxNode GetIdentifier(ExpressionSyntax expression) { switch (expression?.Kind()) { case SyntaxKind.SimpleMemberAccessExpression: // a.b[index] // ElementAccess // <-- we are here // Arguments: index // Expression: SimpleMemberAccess // Expression: a // Name: b // We need a.b return expression; case SyntaxKind.IdentifierName: // a[index] // ElementAccess // <-- we are here // Arguments: index // Expression: IdentifierName // Name: a // <-- we need this return expression; default: return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotShiftByZeroOrIntSize.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotShiftByZeroOrIntSize : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2183"; private const string MessageFormatUseLargerTypeOrPromote = "Either promote shift target to a larger integer type or shift by {0} instead."; private const string MessageFormatShiftTooLarge = "Correct this shift; shift by {0} instead."; private const string MessageFormatRightShiftTooLarge = "Correct this shift; '{0}' is larger than the type size."; private const string MessageFormatUselessShift = "Remove this useless shift by {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "{0}"); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ImmutableDictionary MapKnownTypesToIntegerBitSize = new Dictionary { [KnownType.System_Int64] = 64, [KnownType.System_UInt64] = 64, [KnownType.System_Int32] = 32, [KnownType.System_UInt32] = 32, [KnownType.System_Int16] = 32, [KnownType.System_UInt16] = 32, [KnownType.System_Byte] = 32, [KnownType.System_SByte] = 32 }.ToImmutableDictionary(); private enum Shift { Left, Right } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var shiftInstances = ((MemberDeclarationSyntax)c.Node) .DescendantNodes() .Select(n => FindShiftInstance(n, c.Model)) .WhereNotNull(); var zeroShiftIssues = new List(); var linesWithShiftOperations = new HashSet(); foreach (var shift in shiftInstances) { if (shift.IsLiteralZero) { zeroShiftIssues.Add(shift); continue; } linesWithShiftOperations.Add(shift.Line); if (shift.Node is not null) { c.ReportIssue(Rule, shift.Node, shift.Description); } } foreach (var shift in zeroShiftIssues.Where(x => !ContainsShiftExpressionWithinTwoLines(linesWithShiftOperations, x.Line))) { c.ReportIssue(Rule, shift.Node, shift.Description); } }, SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration); private static bool ContainsShiftExpressionWithinTwoLines(HashSet linesWithShiftOperations, int lineNumber) => linesWithShiftOperations.Contains(lineNumber - 2) || linesWithShiftOperations.Contains(lineNumber - 1) || linesWithShiftOperations.Contains(lineNumber) || linesWithShiftOperations.Contains(lineNumber + 1) || linesWithShiftOperations.Contains(lineNumber + 2); private static Tuple GetRhsArgumentOfShiftNode(SyntaxNode node) { var binaryExpression = node as BinaryExpressionSyntax; if (binaryExpression?.OperatorToken.IsKind(SyntaxKind.LessThanLessThanToken) ?? false) { return new Tuple(Shift.Left, binaryExpression.Right); } if (binaryExpression?.OperatorToken.IsAnyKind(SyntaxKind.GreaterThanGreaterThanToken, SyntaxKindEx.GreaterThanGreaterThanGreaterThanToken) ?? false) { return new Tuple(Shift.Right, binaryExpression.Right); } var assignmentExpession = node as AssignmentExpressionSyntax; if (assignmentExpession?.OperatorToken.IsKind(SyntaxKind.LessThanLessThanEqualsToken) ?? false) { return new Tuple(Shift.Left, assignmentExpession.Right); } if (assignmentExpession?.OperatorToken.IsAnyKind(SyntaxKind.GreaterThanGreaterThanEqualsToken, SyntaxKindEx.GreaterThanGreaterThanGreaterThanEqualsToken) ?? false) { return new Tuple(Shift.Right, assignmentExpession.Right); } return null; } private static bool TryGetConstantValue(ExpressionSyntax expression, out int value) { value = 0; return expression.RemoveParentheses() is LiteralExpressionSyntax literalExpression && int.TryParse(literalExpression.Token.ValueText, out value); } private static ShiftInstance FindShiftInstance(SyntaxNode node, SemanticModel semanticModel) { var tuple = GetRhsArgumentOfShiftNode(node); if (tuple == null) { return null; } if (!TryGetConstantValue(tuple.Item2, out var shiftByCount) || semanticModel.GetTypeInfo(node).ConvertedType is not { } typeSymbol || (FindTypeSizeOrDefault(typeSymbol) is var variableBitLength && variableBitLength == 0)) { return new ShiftInstance(node); } var issueDescription = FindProblemDescription(variableBitLength, shiftByCount, tuple.Item1, out var isLiteralZero); return issueDescription == null ? new ShiftInstance(node) : new ShiftInstance(issueDescription, isLiteralZero, node); } private static int FindTypeSizeOrDefault(ITypeSymbol typeSymbol) => MapKnownTypesToIntegerBitSize .Where(kv => typeSymbol.Is(kv.Key)) .Select(kv => kv.Value) .FirstOrDefault(); private static string FindProblemDescription(int typeSizeInBits, int shiftBy, Shift shiftDirection, out bool isLiteralZero) { if (shiftBy == 0) { isLiteralZero = true; return string.Format(MessageFormatUselessShift, 0); } isLiteralZero = false; if (shiftBy < typeSizeInBits) { return null; } if (shiftDirection == Shift.Right) { return string.Format(MessageFormatRightShiftTooLarge, shiftBy); } var shiftSuggestion = shiftBy % typeSizeInBits; if (typeSizeInBits == 64) { return shiftSuggestion == 0 ? string.Format(MessageFormatUselessShift, shiftBy) : string.Format(MessageFormatShiftTooLarge, shiftSuggestion); } if (shiftSuggestion == 0) { return string.Format(MessageFormatUseLargerTypeOrPromote, "less than " + typeSizeInBits); } return string.Format(MessageFormatUseLargerTypeOrPromote, shiftSuggestion); } private sealed class ShiftInstance { public string Description { get; } public SyntaxNode Node { get; } public bool IsLiteralZero { get; } public int Line { get; } public ShiftInstance(SyntaxNode node) => Line = node.LineNumberToReport(); public ShiftInstance(string description, bool isLiteralZero, SyntaxNode node) : this(node) { Description = description; IsLiteralZero = isLiteralZero; Node = node; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotTestThisWithIsOperator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotTestThisWithIsOperator : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3060"; private const string MessageFormat = "Offload the code that's conditional on this type test to the appropriate subclass and remove the condition."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(AnalyzeIsExpression, SyntaxKind.IsExpression); context.RegisterNodeAction(AnalyzeIsPatternExpression, SyntaxKindEx.IsPatternExpression); context.RegisterNodeAction(AnalyzeSwitchExpression, SyntaxKindEx.SwitchExpression); context.RegisterNodeAction(AnalyzeSwitchStatement, SyntaxKind.SwitchStatement); } private static void AnalyzeIsExpression(SonarSyntaxNodeReportingContext context) { if (IsThisExpressionSyntax(((BinaryExpressionSyntax)context.Node).Left)) { ReportDiagnostic(context, context.Node); } } private static void AnalyzeIsPatternExpression(SonarSyntaxNodeReportingContext context) { if (IsThisExpressionSyntax(((IsPatternExpressionSyntaxWrapper)context.Node).Expression) && ContainsTypeCheckInPattern(context.Node)) { ReportDiagnostic(context, context.Node); } } private static void AnalyzeSwitchStatement(SonarSyntaxNodeReportingContext context) { var switchStatement = (SwitchStatementSyntax)context.Node; if (IsThisExpressionSyntax(switchStatement.Expression)) { ReportDiagnostic(context, switchStatement.Expression, CollectSecondaryLocations(switchStatement)); } } private static void AnalyzeSwitchExpression(SonarSyntaxNodeReportingContext context) { var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; if (IsThisExpressionSyntax(switchExpression.GoverningExpression)) { ReportDiagnostic(context, switchExpression.GoverningExpression, CollectSecondaryLocations(switchExpression)); } } private static IList CollectSecondaryLocations(SwitchStatementSyntax switchStatement) => switchStatement.Sections .SelectMany(section => section.Labels .Where(label => ContainsTypeCheckInPattern(label) || ContainsTypeCheckInCaseSwitchLabel(label)) .Select(label => new SecondaryLocation(TypeMatchLocation(label), string.Empty))) .ToList(); private static IList CollectSecondaryLocations(SwitchExpressionSyntaxWrapper switchExpression) => switchExpression.Arms.Where(arm => ContainsTypeCheckInPattern(arm.Pattern.SyntaxNode)) .Select(arm => new SecondaryLocation(arm.Pattern.SyntaxNode.GetLocation(), string.Empty)) .ToList(); private static bool ContainsTypeCheckInCaseSwitchLabel(SwitchLabelSyntax switchLabel) => switchLabel is CaseSwitchLabelSyntax caseSwitchLabel && caseSwitchLabel.Value.IsKind(SyntaxKind.IdentifierName); private static bool ContainsTypeCheckInPattern(SyntaxNode node) => node.DescendantNodesAndSelf() .Any(x => x.Kind() is SyntaxKindEx.ConstantPattern or SyntaxKindEx.DeclarationPattern or SyntaxKindEx.RecursivePattern or SyntaxKindEx.ListPattern && IsTypeCheckOnThis(x)); private static bool IsTypeCheckOnThis(SyntaxNode pattern) { if (ConstantPatternSyntaxWrapper.IsInstance(pattern)) { return ((ConstantPatternSyntaxWrapper)pattern).Expression.IsKind(SyntaxKind.IdentifierName) && IsNotInSubPattern(pattern); } else if (DeclarationPatternSyntaxWrapper.IsInstance(pattern)) { return IsNotInSubPattern(pattern); } else if (RecursivePatternSyntaxWrapper.IsInstance(pattern)) { return ((RecursivePatternSyntaxWrapper)pattern).Type != null && IsNotInSubPattern(pattern); } else if (ListPatternSyntaxWrapper.IsInstance(pattern)) { return IsNotInSubPattern(pattern); } else { return false; } } private static bool IsNotInSubPattern(SyntaxNode node) => !node.FirstAncestorOrSelf(x => x?.Kind() is SyntaxKindEx.IsPatternExpression or SyntaxKindEx.SwitchExpression or SyntaxKind.SwitchStatement or SyntaxKindEx.Subpattern) .IsKind(SyntaxKindEx.Subpattern); private static void ReportDiagnostic(SonarSyntaxNodeReportingContext context, SyntaxNode node) => context.ReportIssue(Rule, node); private static void ReportDiagnostic(SonarSyntaxNodeReportingContext context, SyntaxNode node, IList secondaryLocations) { if (secondaryLocations.Any()) { context.ReportIssue(Rule, node, secondaryLocations); } } private static Location TypeMatchLocation(SwitchLabelSyntax label) { if (label is CaseSwitchLabelSyntax caseSwitchLabel) { return caseSwitchLabel.Value.GetLocation(); } else if (CasePatternSwitchLabelSyntaxWrapper.IsInstance(label)) { return ((CasePatternSwitchLabelSyntaxWrapper)label).Pattern.SyntaxNode.GetLocation(); } else { return Location.None; } } private static bool IsThisExpressionSyntax(SyntaxNode syntaxNode) => syntaxNode.RemoveParentheses() is ThisExpressionSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotThrowFromDestructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotThrowFromDestructors : DoNotThrowFromDestructorsBase { private const string MessageFormat = "Remove this 'throw' statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { if (c.Node.FirstAncestorOrSelf() is DestructorDeclarationSyntax) { c.ReportIssue(rule, c.Node); } }, SyntaxKind.ThrowStatement, SyntaxKindEx.ThrowExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotUseCollectionInItsOwnMethodCalls.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseCollectionInItsOwnMethodCalls : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2114"; private const string MessageFormat = "Change one instance of '{0}' to a different value; {1}"; private const string AlwaysEmptyCollectionMessage = "This operation always produces an empty collection."; private const string AlwaysSameCollectionMessage = "This operation always produces the same collection."; private const string AlwaysTrueMessage = "Comparing to itself always returns true."; private const string AlwaysFalseMessage = "Comparing to itself always returns false."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet TrackedMethodNames = new HashSet { nameof(Enumerable.Except), nameof(ISet.ExceptWith), nameof(Enumerable.Intersect), nameof(ISet.IntersectWith), nameof(ISet.IsSubsetOf), nameof(ISet.IsSupersetOf), nameof(ISet.IsProperSubsetOf), nameof(ISet.IsProperSupersetOf), nameof(ISet.Overlaps), nameof(Enumerable.SequenceEqual), nameof(ISet.SetEquals), nameof(ISet.SymmetricExceptWith), nameof(Enumerable.Union), nameof(ISet.UnionWith) }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.GetMethodCallIdentifier() is { } identifier && TrackedMethodNames.Any(x => identifier.ValueText.EndsWith(x, StringComparison.Ordinal)) && OperandsToCheckIfTrackedMethod(invocation, c.Model) is { } operands && CSharpEquivalenceChecker.AreEquivalent(operands.Left, operands.Right)) { c.ReportIssue(Rule, operands.Left, [operands.Right.ToSecondaryLocation()], operands.Right.ToString(), operands.ErrorMessage); } }, SyntaxKind.InvocationExpression); private static OperandsToCheck? OperandsToCheckIfTrackedMethod(InvocationExpressionSyntax invocation, SemanticModel model) { var methodSymbol = model.GetSymbolInfo(invocation).Symbol as IMethodSymbol; if (ProcessIssueMessage(methodSymbol) is { } message) { if (methodSymbol is { IsExtensionMethod: true, MethodKind: MethodKind.Ordinary } && invocation is { ArgumentList.Arguments: { Count: >= 2 } extensionArgs }) { return new(extensionArgs[0].Expression, extensionArgs[1].Expression, message); } else if (invocation is { Expression: MemberAccessExpressionSyntax { Expression: { } invokingExpression }, ArgumentList.Arguments: { Count: >= 1 } memberArgs }) { return new(invokingExpression, memberArgs[0].Expression, message); } } return null; } private static string ProcessIssueMessage(IMethodSymbol methodSymbol) { if (methodSymbol.IsEnumerableIntersect() || methodSymbol.IsEnumerableUnion() || IsAnySetMethod(methodSymbol, nameof(ISet.UnionWith), nameof(ISet.IntersectWith))) { return AlwaysSameCollectionMessage; } else if (methodSymbol.IsEnumerableSequenceEqual() || IsAnySetMethod(methodSymbol, nameof(ISet.IsSubsetOf), nameof(ISet.IsSupersetOf), nameof(ISet.Overlaps), nameof(ISet.SetEquals))) { return AlwaysTrueMessage; } else if (methodSymbol.IsEnumerableExcept() || IsAnySetMethod(methodSymbol, nameof(ISet.ExceptWith), nameof(ISet.SymmetricExceptWith))) { return AlwaysEmptyCollectionMessage; } else if (IsAnySetMethod(methodSymbol, nameof(ISet.IsProperSubsetOf), nameof(ISet.IsProperSupersetOf), nameof(ISet.IsProperSupersetOf))) { return AlwaysFalseMessage; } return null; } private static bool IsAnySetMethod(IMethodSymbol methodSymbol, params string[] methodNames) => methodSymbol is { MethodKind: MethodKind.Ordinary, Parameters.Length: 1 } && methodNames.Contains(methodSymbol.Name, StringComparer.Ordinal) && methodSymbol.ContainingType.Implements(KnownType.System_Collections_Generic_ISet_T); private readonly record struct OperandsToCheck { public ExpressionSyntax Left { get; } public ExpressionSyntax Right { get; } public string ErrorMessage { get; } public OperandsToCheck(ExpressionSyntax left, ExpressionSyntax right, string errorMessage) { Left = left.RemoveParentheses(); Right = right.RemoveParentheses(); ErrorMessage = errorMessage; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotUseDateTimeNow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseDateTimeNow : DoNotUseDateTimeNowBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsInsideNameOf(SyntaxNode node) => node.Ancestors() .OfType() .Any(x => x.Expression is IdentifierNameSyntax { Identifier.ValueText: "nameof" }); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotUseLiteralBoolInAssertions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseLiteralBoolInAssertions : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2701"; private const string MessageFormat = "Remove or correct this assertion."; private static readonly Dictionary> TrackedTypeAndMethods = new() { [KnownType.Xunit_Assert] = [ // "True" and "False" are not here because there was no Assert.Fail in Xunit until 2020 and Assert.True(false) and Assert.False(true) were some ways to simulate it. "Equal", "NotEqual", "Same", "NotSame", "StrictEqual", "NotStrictEqual", "Equivalent" ], [KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert] = [ "AreEqual", "AreNotEqual", "AreSame", "IsFalse", "IsTrue" ], [KnownType.NUnit_Framework_Assert] = [ "AreEqual", "AreNotEqual", "AreNotSame", "AreSame", "False", "IsFalse", "IsTrue", "That", "True" ], [KnownType.NUnit_Framework_Legacy_ClassicAssert] = [ "AreEqual", "AreNotEqual", "AreNotSame", "AreSame", "False", "IsFalse", "IsTrue", "True" ], [KnownType.System_Diagnostics_Debug] = [ "Assert" ] }; private static readonly ISet BoolLiterals = new HashSet { SyntaxKind.TrueLiteralExpression, SyntaxKind.FalseLiteralExpression }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.ArgumentList != null && IsFirstOrSecondArgumentABoolLiteral(invocation.ArgumentList.Arguments) && c.Model.GetSymbolOrCandidateSymbol(invocation) is IMethodSymbol methodSymbol && IsTrackedMethod(methodSymbol) && !IsWorkingWithNullableType(methodSymbol, invocation.ArgumentList.Arguments, c.Model)) { c.ReportIssue(Rule, invocation); } }, SyntaxKind.InvocationExpression); private static bool IsWorkingWithNullableType(IMethodSymbol methodSymbol, SeparatedSyntaxList arguments, SemanticModel semanticModel) { if (methodSymbol.TypeArguments.Length == 1) // We usually expect all comparison test methods to have one generic argument { // Since we already know we are comparing with bool, no need to check Nullable, Nullable is enough return methodSymbol.TypeArguments[0].OriginalDefinition.Is(KnownType.System_Nullable_T); } else if (methodSymbol.TypeArguments.Length == 0) // But they can also work with Object (NUnit...) { if (arguments.Count != 2) { return false; } var nonBoolLiteral = IsBooleanLiteral(arguments[0]) ? arguments[1] : arguments[0]; var nonBoolType = semanticModel.GetTypeInfo(nonBoolLiteral.Expression); return nonBoolType.Type?.OriginalDefinition.Is(KnownType.System_Nullable_T) ?? false; } else { // Other case, not handled return false; } } private static bool IsFirstOrSecondArgumentABoolLiteral(SeparatedSyntaxList arguments) => arguments.Count switch { 0 => false, 1 => IsBooleanLiteral(arguments[0]), _ => IsBooleanLiteral(arguments[0]) || IsBooleanLiteral(arguments[1]), }; private static bool IsBooleanLiteral(ArgumentSyntax argument) => argument.Expression.IsAnyKind(BoolLiterals); private static bool IsTrackedMethod(ISymbol methodSymbol) => TrackedTypeAndMethods .Where(kvp => methodSymbol.ContainingType.Is(kvp.Key)) .Any(kvp => kvp.Value.Contains(methodSymbol.Name)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotUseOutRefParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseOutRefParameters : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3874"; private const string MessageFormat = "Consider refactoring this method in order to remove the need for this '{0}' modifier."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var parameter = (ParameterSyntax)c.Node; if (!parameter.Modifiers.Any(IsRefOrOut) || (parameter is { Parent: ParameterListSyntax { Parent: MethodDeclarationSyntax method } } && method.IsDeconstructor())) { return; } var modifier = parameter.Modifiers.First(IsRefOrOut); var parameterSymbol = c.Model.GetDeclaredSymbol(parameter); if (parameterSymbol?.ContainingSymbol is not IMethodSymbol containingMethod || containingMethod.IsOverride || !containingMethod.IsPubliclyAccessible() || IsTryPattern(containingMethod, modifier) || containingMethod.InterfaceMembers().Any()) { return; } c.ReportIssue(Rule, modifier, modifier.ValueText); }, SyntaxKind.Parameter); private static bool IsTryPattern(IMethodSymbol method, SyntaxToken modifier) => method.Name.StartsWith("Try", StringComparison.Ordinal) && method.ReturnType.Is(KnownType.System_Boolean) && modifier.IsKind(SyntaxKind.OutKeyword); private static bool IsRefOrOut(SyntaxToken token) => token.IsKind(SyntaxKind.RefKeyword) || token.IsKind(SyntaxKind.OutKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DoNotWriteToStandardOutput.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class DoNotWriteToStandardOutput : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S106"; private const string MessageFormat = "Remove this logging statement."; private static readonly string[] BannedConsoleMembers = ["WriteLine", "Write"]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected static DiagnosticDescriptor Rule => DescriptorFactory.Create(DiagnosticId, MessageFormat); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (c.Compilation.Options.OutputKind != OutputKind.ConsoleApplication && c.Model.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol method && method.IsAny(KnownType.System_Console, BannedConsoleMembers) && !c.Node.IsInDebugBlock() && !invocation.IsInConditionalDebug(c.Model)) { c.ReportIssue(Rule, invocation.Expression); } }, SyntaxKind.InvocationExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DontMixIncrementOrDecrementWithOtherOperators.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DontMixIncrementOrDecrementWithOtherOperators : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S881"; private const string MessageFormat = "Extract this {0} operation into a dedicated statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ISet arithmeticOperator = new HashSet { SyntaxKind.AddExpression, SyntaxKind.SubtractExpression, SyntaxKind.MultiplyExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var operatorToken = (c.Node as PrefixUnaryExpressionSyntax)?.OperatorToken ?? (c.Node as PostfixUnaryExpressionSyntax)?.OperatorToken; if (operatorToken != null && c.Node.Ancestors().FirstOrDefault(node => node.IsAnyKind(arithmeticOperator)) is BinaryExpressionSyntax) { c.ReportIssue(rule, operatorToken.Value, operatorToken.Value.IsKind(SyntaxKind.PlusPlusToken) ? "increment" : "decrement"); } }, SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression, SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DontUseTraceSwitchLevels.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DontUseTraceSwitchLevels : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6675"; private const string MessageFormat = "'Trace.{0}' should not be used with 'TraceSwitch' levels."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray TraceSwitchProperties = ImmutableArray.Create( "Level", "TraceError", "TraceInfo", "TraceVerbose", "TraceWarning"); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.GetName() is "WriteIf" or "WriteLineIf" && invocation.ArgumentList.Arguments.Count > 1 && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.Is(KnownType.System_Diagnostics_Trace) && UsesTraceSwitchAsCondition(c.Model, methodSymbol, invocation) is { } traceSwitchProperty) { c.ReportIssue(Rule, traceSwitchProperty, invocation.GetName()); } }, SyntaxKind.InvocationExpression); private static SyntaxNode UsesTraceSwitchAsCondition(SemanticModel model, IMethodSymbol methodSymbol, InvocationExpressionSyntax invocation) { var lookup = new CSharpMethodParameterLookup(invocation.ArgumentList, methodSymbol); lookup.TryGetSyntax("condition", out var expressions); var conditionArgument = expressions[0]; return conditionArgument.DescendantNodesAndSelf().FirstOrDefault(x => x is MemberAccessExpressionSyntax memberAccess && TraceSwitchProperties.Contains(memberAccess.GetName()) && model.GetTypeInfo(memberAccess.Expression).Type.DerivesFrom(KnownType.System_Diagnostics_TraceSwitch)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/DontUseTraceWrite.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DontUseTraceWrite : DoNotCallMethodsBase { private const string DiagnosticId = "S6670"; protected override string MessageFormat => "Avoid using {0}, use instead methods that specify the trace event type."; protected override IEnumerable CheckedMethods => [ new(KnownType.System_Diagnostics_Trace, "Write"), new(KnownType.System_Diagnostics_Trace, "WriteLine") ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool ShouldReportOnMethodCall(InvocationExpressionSyntax invocation, SemanticModel semanticModel, MemberDescriptor memberDescriptor) { var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(invocation).Symbol; return !methodSymbol.Parameters.Any(x => x.Name == "category"); } public DontUseTraceWrite() : base(DiagnosticId) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EmptyMethod : EmptyMethodBase { internal static readonly HashSet SupportedSyntaxKinds = [ SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override HashSet SyntaxKinds => SupportedSyntaxKinds; protected override void CheckMethod(SonarSyntaxNodeReportingContext context) { // No need to check for ExpressionBody as arrowed methods can't be empty if (context.Node.GetBody() is { } body && body.IsEmpty() && !ShouldBeExcluded(context, context.Node, context.Node.GetModifiers())) { context.ReportIssue(Rule, ReportingToken(context.Node)); } } private static bool ShouldBeExcluded(SonarSyntaxNodeReportingContext context, SyntaxNode node, SyntaxTokenList modifiers) => modifiers.Any(SyntaxKind.VirtualKeyword) // This quick check only works for methods, for accessors we need to check the symbol || (context.Model.GetDeclaredSymbol(node) is IMethodSymbol symbol && (symbol is { IsVirtual: true } || symbol is { IsOverride: true, OverriddenMethod.IsAbstract: true } || !symbol.ExplicitOrImplicitInterfaceImplementations().IsEmpty)) || (modifiers.Any(SyntaxKind.OverrideKeyword) && context.IsTestProject()); private static SyntaxToken ReportingToken(SyntaxNode node) => node switch { MethodDeclarationSyntax method => method.Identifier, AccessorDeclarationSyntax accessor => accessor.Keyword, _ => ((LocalFunctionStatementSyntaxWrapper)node).Identifier }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyMethodCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class EmptyMethodCodeFix : SonarCodeFix { internal const string TitleThrow = "Throw NotSupportedException"; internal const string TitleComment = "Add comment"; private const string LiteralNotSupportedException = "NotSupportedException"; private const string LiteralSystem = "System"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(EmptyMethod.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { if (root.FindNode(context.Diagnostics.First().Location.SourceSpan, getInnermostNodeForTie: true) .FirstAncestorOrSelf(x => x.IsAnyKind(EmptyMethod.SupportedSyntaxKinds)) .GetBody() is { CloseBraceToken.IsMissing: false, OpenBraceToken.IsMissing: false } body) { await RegisterCodeFixesForMethodsAsync(context, root, body).ConfigureAwait(false); } } private static async Task RegisterCodeFixesForMethodsAsync(SonarCodeFixContext context, SyntaxNode root, BlockSyntax methodBody) { context.RegisterCodeFix( TitleComment, c => { var newMethodBody = methodBody; newMethodBody = newMethodBody .WithOpenBraceToken(newMethodBody.OpenBraceToken .WithTrailingTrivia(SyntaxFactory.TriviaList() .Add(SyntaxFactory.EndOfLine(Environment.NewLine)))); newMethodBody = newMethodBody .WithCloseBraceToken(newMethodBody.CloseBraceToken .WithLeadingTrivia(SyntaxFactory.TriviaList() .Add(SyntaxFactory.Comment("// Method intentionally left empty.")) .Add(SyntaxFactory.EndOfLine(Environment.NewLine)))); var newRoot = methodBody.Parent is AccessorDeclarationSyntax accessor ? root.ReplaceNode( accessor, accessor.WithBody(newMethodBody.WithTriviaFrom(accessor.Body)).WithAdditionalAnnotations(Formatter.Annotation)) : root.ReplaceNode( methodBody, newMethodBody.WithTriviaFrom(methodBody).WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var systemNeedsToBeAdded = NamespaceNeedsToBeAdded(methodBody, semanticModel); var memberAccessRoot = systemNeedsToBeAdded ? (NameSyntax)SyntaxFactory.QualifiedName( SyntaxFactory.IdentifierName(LiteralSystem), SyntaxFactory.IdentifierName(LiteralNotSupportedException)) : SyntaxFactory.IdentifierName(LiteralNotSupportedException); context.RegisterCodeFix( TitleThrow, c => { var newRoot = root.ReplaceNode(methodBody, methodBody.WithStatements( SyntaxFactory.List( new StatementSyntax[] { SyntaxFactory.ThrowStatement( SyntaxFactory.ObjectCreationExpression( memberAccessRoot, SyntaxFactory.ArgumentList(), null)) })) .WithTriviaFrom(methodBody) .WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static bool NamespaceNeedsToBeAdded(BlockSyntax methodBody, SemanticModel semanticModel) => semanticModel.LookupNamespacesAndTypes(methodBody.CloseBraceToken.SpanStart) .All(x => x is not INamedTypeSymbol { IsType: true, Name: LiteralNotSupportedException, ContainingNamespace.Name: LiteralSystem }); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyNamespace.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EmptyNamespace : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3261"; private const string MessageFormat = "Remove this empty namespace."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var namespaceDeclaration = (BaseNamespaceDeclarationSyntaxWrapper)c.Node; if (!namespaceDeclaration.Members.Any()) { c.ReportIssue(Rule, namespaceDeclaration.SyntaxNode); } }, SyntaxKind.NamespaceDeclaration, SyntaxKindEx.FileScopedNamespaceDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyNamespaceCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class EmptyNamespaceCodeFix : SonarCodeFix { internal const string Title = "Remove empty namespace"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(EmptyNamespace.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (!BaseNamespaceDeclarationSyntaxWrapper.IsInstance(syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyNestedBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EmptyNestedBlock : EmptyNestedBlockBase { private static readonly HashSet AllowedContainerKinds = [ SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.AnonymousMethodExpression ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = [ SyntaxKind.Block, SyntaxKind.SwitchStatement ]; protected override IEnumerable EmptyBlocks(SyntaxNode node) => (node is SwitchStatementSyntax switchNode && IsEmpty(switchNode)) || (node is BlockSyntax blockNode && IsNestedAndEmpty(blockNode)) ? [node] : Enumerable.Empty(); private static bool IsEmpty(SwitchStatementSyntax node) => !node.Sections.Any(); private static bool IsNestedAndEmpty(BlockSyntax node) => IsNested(node) && node.IsEmpty(); private static bool IsNested(BlockSyntax node) => !node.Parent.IsAnyKind(AllowedContainerKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EmptyStatement : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1116"; private const string MessageFormat = "Remove this empty statement."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if (c.Node.Parent is not (WhileStatementSyntax or ForStatementSyntax or DoStatementSyntax)) // loops are excluded to reduce noise { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.EmptyStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EmptyStatementCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class EmptyStatementCodeFix : SonarCodeFix { internal const string Title = "Remove empty statement"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(EmptyStatement.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (!IsInBlock(syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = RemoveUnusedCode(root, syntaxNode); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static bool IsInBlock(SyntaxNode node) { return node.Parent is BlockSyntax; } internal static SyntaxNode RemoveUnusedCode(SyntaxNode root, SyntaxNode syntaxNode) { return root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepNoTrivia | SyntaxRemoveOptions.KeepEndOfLine); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EncryptionAlgorithmsShouldBeSecure.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EncryptionAlgorithmsShouldBeSecure : EncryptionAlgorithmsShouldBeSecureBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public EncryptionAlgorithmsShouldBeSecure() : base(AnalyzerConfiguration.AlwaysEnabled) { } protected override TrackerBase.Condition IsInsideObjectInitializer() => context => context.Node.FirstAncestorOrSelf() != null; protected override TrackerBase.Condition HasPkcs1PaddingArgument() => context => { var argumentList = ((InvocationExpressionSyntax)context.Node).ArgumentList; var values = argumentList.ArgumentValuesForParameter(context.Model, "padding"); return values.Length == 1 && values[0] is ExpressionSyntax valueSyntax && context.Model.GetSymbolInfo(valueSyntax).Symbol is ISymbol symbol && symbol.Name == "Pkcs1"; }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EnumNameHasEnumSuffix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EnumNameHasEnumSuffix : EnumNameHasEnumSuffixBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EnumNameShouldFollowRegex.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EnumNameShouldFollowRegex : EnumNameShouldFollowRegexBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EnumStorageNeedsToBeInt32.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EnumStorageNeedsToBeInt32 : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4022"; private const string MessageFormat = "Change this enum storage to 'Int32'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var enumDeclaration = (EnumDeclarationSyntax)c.Node; var enumBaseType = enumDeclaration?.BaseList?.Types.FirstOrDefault()?.Type; if (enumDeclaration != null && !IsDefaultOrLarger(enumBaseType, c.Model)) { c.ReportIssue(rule, enumDeclaration.Identifier); } }, SyntaxKind.EnumDeclaration); } private static bool IsDefaultOrLarger(SyntaxNode syntaxNode, SemanticModel semanticModel) { if (syntaxNode == null) { return true; } var symbolType = semanticModel.GetSymbolInfo(syntaxNode).Symbol.GetSymbolType(); return symbolType.IsAny(KnownType.System_Int32, KnownType.System_UInt32, KnownType.System_Int64, KnownType.System_UInt64); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EnumerableSumInUnchecked.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EnumerableSumInUnchecked : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2291"; private const string MessageFormat = "Refactor this code to handle 'OverflowException'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; var expression = invocation.Expression; if (expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.ValueText == "Sum" && IsSumInsideUnchecked(invocation) && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && IsSumOnInteger(methodSymbol)) { c.ReportIssue(rule, memberAccess.Name); } }, SyntaxKind.InvocationExpression); } private static bool IsSumInsideUnchecked(InvocationExpressionSyntax invocation) { SyntaxNode current = invocation; var parent = current.Parent; while (parent != null) { if (parent is TryStatementSyntax tryStatement && tryStatement.Block == current) { return false; } if (IsUncheckedExpression(parent) || IsUncheckedStatement(parent)) { return true; } current = parent; parent = parent.Parent; } return false; } private static bool IsUncheckedExpression(SyntaxNode node) { return node is CheckedExpressionSyntax uncheckedExpression && uncheckedExpression.IsKind(SyntaxKind.UncheckedExpression); } private static bool IsUncheckedStatement(SyntaxNode node) { return node is CheckedStatementSyntax uncheckedExpression && uncheckedExpression.IsKind(SyntaxKind.UncheckedStatement); } private static bool IsSumOnInteger(IMethodSymbol methodSymbol) { return methodSymbol != null && methodSymbol.Name == "Sum" && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) && IsReturnTypeCandidate(methodSymbol); } private static bool IsReturnTypeCandidate(IMethodSymbol methodSymbol) { var returnType = methodSymbol.ReturnType; if (returnType.OriginalDefinition.Is(KnownType.System_Nullable_T)) { var nullableType = (INamedTypeSymbol)returnType; if (nullableType.TypeArguments.Length != 1) { return false; } returnType = nullableType.TypeArguments[0]; } return returnType.IsAny(DisallowedTypes); } private static readonly ImmutableArray DisallowedTypes = ImmutableArray.Create( KnownType.System_Int64, KnownType.System_Int32, KnownType.System_Decimal ); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EnumsShouldNotBeNamedReserved.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EnumsShouldNotBeNamedReserved : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4016"; private const string MessageFormat = "Remove or rename this enum member."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node is EnumMemberDeclarationSyntax enumMemberDeclaration && enumMemberDeclaration.Identifier.ValueText .SplitCamelCaseToWords() .Any(w => w == "RESERVED")) { c.ReportIssue(Rule, enumMemberDeclaration); } }, SyntaxKind.EnumMemberDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EqualityOnFloatingPoint.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EqualityOnFloatingPoint : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1244"; private const string MessageFormat = "Do not check floating point {0} with exact values, use {1} instead."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly Dictionary SpecialMembers = new() { { nameof(double.NaN), nameof(double.IsNaN) }, { nameof(double.PositiveInfinity), nameof(double.IsPositiveInfinity) }, { nameof(double.NegativeInfinity), nameof(double.IsNegativeInfinity) }, }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( CheckEquality, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); context.RegisterNodeAction( CheckLogicalExpression, SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression); } private static void CheckLogicalExpression(SonarSyntaxNodeReportingContext context) { var binaryExpression = (BinaryExpressionSyntax)context.Node; if (TryGetBinaryExpression(binaryExpression.Left) is { } left && TryGetBinaryExpression(binaryExpression.Right) is { } right && CSharpEquivalenceChecker.AreEquivalent(right.Right, left.Right) && CSharpEquivalenceChecker.AreEquivalent(right.Left, left.Left) && IsIndirectEquality(context.Model, binaryExpression, left, right) is var isEquality && IsIndirectInequality(context.Model, binaryExpression, left, right) is var isInequality && (isEquality || isInequality)) { context.ReportIssue(Rule, binaryExpression, MessageEqualityPart(isEquality), "a range"); } } private static string MessageEqualityPart(bool isEquality) => isEquality ? "equality" : "inequality"; private static void CheckEquality(SonarSyntaxNodeReportingContext context) { var equals = (BinaryExpressionSyntax)context.Node; if (context.Model.GetSymbolInfo(equals).Symbol is IMethodSymbol { ContainingType: { } container } method && IsFloatingPointType(container) && (method.IsOperatorEquals() || method.IsOperatorNotEquals())) { var messageEqualityPart = MessageEqualityPart(equals.IsKind(SyntaxKind.EqualsExpression)); var proposed = ProposedMessageForMemberAccess(context, equals.Right) ?? ProposedMessageForMemberAccess(context, equals.Left) ?? ProposedMessageForIdentifier(context, equals.Right) ?? ProposedMessageForIdentifier(context, equals.Left) ?? "a range"; context.ReportIssue(Rule, equals.OperatorToken, messageEqualityPart, proposed); } } private static string ProposedMessageForMemberAccess(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) => expression is MemberAccessExpressionSyntax memberAccess && SpecialMembers.TryGetValue(memberAccess.GetName(), out var proposedMethod) && context.Model.GetTypeInfo(memberAccess).ConvertedType is { } type && IsFloatingPointType(type) ? $"'{type.ToMinimalDisplayString(context.Model, memberAccess.SpanStart)}.{proposedMethod}()'" : null; private static string ProposedMessageForIdentifier(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) => expression is IdentifierNameSyntax identifier && SpecialMembers.TryGetValue(identifier.GetName(), out var proposedMethod) && context.Model.GetSymbolInfo(identifier).Symbol is { ContainingType: { } type } && IsFloatingPointType(type) ? $"'{proposedMethod}()'" : null; // Returns true for the floating point types that suffer from equivalence problems. All .NET floating point types have this problem except `decimal.` // - Reason for excluding `decimal`: the documentation for the `decimal.Equals()` method does not have a "Precision in Comparisons" section as the other .NET floating point types. // - Power-2-based types like `double` implement `IFloatingPointIeee754`, but power-10-based `decimal` implements `IFloatingPoint`. // - `IFloatingPointIeee754` defines `Epsilon` which indicates problems with equivalence checking. private static bool IsFloatingPointType(ITypeSymbol type) => type.IsAny(KnownType.FloatingPointNumbers) || (type.Is(KnownType.System_Numerics_IEqualityOperators_TSelf_TOther_TResult) // The operator originates from a virtual static member && type is INamedTypeSymbol { TypeArguments: { } typeArguments } // Arguments of TSelf, TOther, TResult && typeArguments.Any(IsFloatingPointType)) || (type is ITypeParameterSymbol { ConstraintTypes: { } constraintTypes } // constraints of TSelf or of TSelf, TOther, TResult from IEqualityOperators && constraintTypes.Any(x => x.DerivesOrImplements(KnownType.System_Numerics_IFloatingPointIeee754_TSelf))); private static BinaryExpressionSyntax TryGetBinaryExpression(ExpressionSyntax expression) => expression.RemoveParentheses() as BinaryExpressionSyntax; private static bool IsIndirectInequality(SemanticModel semanticModel, BinaryExpressionSyntax binaryExpression, BinaryExpressionSyntax left, BinaryExpressionSyntax right) => binaryExpression.IsKind(SyntaxKind.LogicalOrExpression) && IsOperatorPair(left, right, SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken) && HasFloatingType(semanticModel, right); private static bool IsIndirectEquality(SemanticModel semanticModel, BinaryExpressionSyntax binaryExpression, BinaryExpressionSyntax left, BinaryExpressionSyntax right) => binaryExpression.IsKind(SyntaxKind.LogicalAndExpression) && IsOperatorPair(left, right, SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanEqualsToken) && HasFloatingType(semanticModel, right); private static bool HasFloatingType(SemanticModel semanticModel, BinaryExpressionSyntax binary) => IsExpressionFloatingType(semanticModel, binary.Right) || IsExpressionFloatingType(semanticModel, binary.Left); private static bool IsExpressionFloatingType(SemanticModel semanticModel, ExpressionSyntax expression) => IsFloatingPointType(semanticModel.GetTypeInfo(expression).Type); private static bool IsOperatorPair(BinaryExpressionSyntax left, BinaryExpressionSyntax right, SyntaxKind first, SyntaxKind second) => (left.OperatorToken.IsKind(first) && right.OperatorToken.IsKind(second)) || (left.OperatorToken.IsKind(second) && right.OperatorToken.IsKind(first)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EqualityOnModulus.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EqualityOnModulus : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2197"; private const string MessageFormat = "The result of this modulus operation may not be {0}."; private const string CountName = nameof(Enumerable.Count); private const string LongCountName = nameof(Enumerable.LongCount); private const string LengthName = "Length"; // Represents Array.Length and String.Length private const string LongLengthName = nameof(Array.LongLength); private const string ListCapacityName = nameof(List.Capacity); private static readonly string[] CollectionSizePropertyOrMethodNames = { CountName, LongCountName, LengthName, LongLengthName, ListCapacityName }; private static readonly CSharpExpressionNumericConverter ExpressionNumericConverter = new(); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(VisitEquality, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); private static void VisitEquality(SonarSyntaxNodeReportingContext c) { var equalsExpression = (BinaryExpressionSyntax)c.Node; if ((CheckExpression(equalsExpression.Left, equalsExpression.Right, c.Model) ?? CheckExpression(equalsExpression.Right, equalsExpression.Left, c.Model)) is { } constantValue) { c.ReportIssue(Rule, equalsExpression, constantValue < 0 ? "negative" : "positive"); } } private static int? CheckExpression(SyntaxNode node, ExpressionSyntax expression, SemanticModel model) => ExpressionNumericConverter.ConstantIntValue(node) is { } constantValue && constantValue != 0 && expression.RemoveParentheses() is BinaryExpressionSyntax binary && binary.IsKind(SyntaxKind.ModuloExpression) && !ExpressionIsAlwaysPositive(binary, model) ? constantValue : null; private static bool ExpressionIsAlwaysPositive(BinaryExpressionSyntax binaryExpression, SemanticModel semantic) { var type = semantic.GetTypeInfo(binaryExpression).Type; if (type.IsAny(KnownType.UnsignedIntegers) || type.Is(KnownType.System_UIntPtr)) { return true; } var leftExpression = binaryExpression.Left; var leftExpressionStringForm = leftExpression.ToString(); return CollectionSizePropertyOrMethodNames.Any(x => leftExpressionStringForm.Contains(x)) && semantic.GetSymbolInfo(leftExpression).Symbol is { } symbol && IsCollectionSize(symbol); } private static bool IsCollectionSize(ISymbol symbol) => IsEnumerableCountMethod(symbol) || (symbol is IPropertySymbol propertySymbol && (IsLengthProperty(propertySymbol) || IsCollectionCountProperty(propertySymbol) || IsListCapacityProperty(propertySymbol))); private static bool IsEnumerableCountMethod(ISymbol symbol) => (CountName.Equals(symbol.Name) || LongCountName.Equals(symbol.Name)) && symbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod && methodSymbol.ReceiverType is not null && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); private static bool IsLengthProperty(IPropertySymbol propertySymbol) => (LengthName.Equals(propertySymbol.Name) || LongLengthName.Equals(propertySymbol.Name)) && propertySymbol.ContainingType.IsAny(KnownType.System_Array, KnownType.System_String); private static bool IsCollectionCountProperty(IPropertySymbol propertySymbol) => CountName.Equals(propertySymbol.Name) && (propertySymbol.ContainingType.Implements(KnownType.System_Collections_Generic_ICollection_T) || propertySymbol.ContainingType.Implements(KnownType.System_Collections_Generic_IReadOnlyCollection_T)); private static bool IsListCapacityProperty(IPropertySymbol propertySymbol) => ListCapacityName.Equals(propertySymbol.Name) && propertySymbol.ContainingType.Implements(KnownType.System_Collections_Generic_IList_T); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EquatableClassShouldBeSealed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EquatableClassShouldBeSealed : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4035"; private const string MessageFormat = "Seal class '{0}' or implement 'IEqualityComparer' instead."; private const string EqualsMethodName = nameof(object.Equals); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; var classSymbol = c.Model.GetDeclaredSymbol(classDeclaration); if (classSymbol != null && !classSymbol.IsSealed && !classSymbol.IsStatic && classSymbol.IsPubliclyAccessible() && !classDeclaration.Identifier.IsMissing && HasAnyInvalidIEquatableEqualsMethod(classSymbol)) { c.ReportIssue(Rule, classDeclaration.Identifier, classDeclaration.Identifier.ValueText); } }, SyntaxKind.ClassDeclaration); private static bool HasAnyInvalidIEquatableEqualsMethod(INamedTypeSymbol classSymbol) { var equatableInterfacesByTypeName = classSymbol.Interfaces .Where(IsCompilableIEquatableTSymbol) .ToDictionary(nts => nts.TypeArguments[0].Name, nts => nts); var equalsMethodsByTypeName = classSymbol.GetMembers(EqualsMethodName) .OfType() .Where(IsIEquatableEqualsMethodCandidate) .ToDictionary(ms => ms.Parameters[0].Type.Name, ms => ms); // Checks whether any IEquatable has no implementation OR a non-virtual non-abstract implementation var hasAnyConcreteImplementation = equatableInterfacesByTypeName .Select(iequatable => equalsMethodsByTypeName.GetValueOrDefault(iequatable.Key)) .Any(associatedMethod => associatedMethod == null || !(associatedMethod.IsVirtual || associatedMethod.IsAbstract)); if (hasAnyConcreteImplementation) { return true; } // For all Equals(T) not a IEquatable implementation checks if any is non-virtual var unprocessedTypeNames = equalsMethodsByTypeName.Keys.Except(equatableInterfacesByTypeName.Keys); return unprocessedTypeNames.Any(typeName => !equalsMethodsByTypeName[typeName].IsVirtual); } private static bool IsCompilableIEquatableTSymbol(INamedTypeSymbol namedTypeSymbol) => namedTypeSymbol.ConstructedFrom.Is(KnownType.System_IEquatable_T) && namedTypeSymbol.TypeArguments.Length == 1; private static bool IsIEquatableEqualsMethodCandidate(IMethodSymbol methodSymbol) => methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == EqualsMethodName && methodSymbol.IsPubliclyAccessible() && !methodSymbol.IsOverride && methodSymbol.ReturnType.Is(KnownType.System_Boolean) && methodSymbol.Parameters.Length == 1; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EscapeLambdaParameterTypeNamedScoped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EscapeLambdaParameterTypeNamedScoped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S8381"; private const string MessageFormat = "'scoped' should be escaped when used as a type name in lambda parameters"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!compilationStart.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp14)) { compilationStart.RegisterNodeAction(c => { foreach (var parameter in ((ParenthesizedLambdaExpressionSyntax)c.Node).ParameterList.Parameters) { CheckParameter(c, parameter); } }, SyntaxKind.ParenthesizedLambdaExpression); } }); private static void CheckParameter(SonarSyntaxNodeReportingContext c, ParameterSyntax parameter) { if (parameter.Type?.Unwrap() is SimpleNameSyntax { Identifier: { } id } && IsScopedToken(id)) { c.ReportIssue(Rule, id); } else if (parameter.Type is null && IsScopedToken(parameter.Identifier)) { c.ReportIssue(Rule, parameter.Identifier); } } private static bool IsScopedToken(SyntaxToken token) => token.ValueText == "scoped" && !token.Text.StartsWith("@", StringComparison.Ordinal); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/EventHandlerDelegateShouldHaveProperArguments.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class EventHandlerDelegateShouldHaveProperArguments : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4220"; private const string MessageFormat = "{0}"; private const string NullEventArgsMessage = "Use 'EventArgs.Empty' instead of null as the event args of " + "this event invocation."; private const string NullSenderMessage = "Make the sender on this event invocation not null."; private const string NonNullSenderMessage = "Make the sender on this static event invocation null."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray EventHandlerTypes = ImmutableArray.Create( KnownType.System_EventHandler, KnownType.System_EventHandler_TEventArgs ); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.ArgumentList == null || invocation.ArgumentList.Arguments.Count != 2) { return; } if (!(c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol) || methodSymbol.MethodKind != MethodKind.DelegateInvoke || !methodSymbol.ContainingType.ConstructedFrom.IsAny(EventHandlerTypes)) { return; } var isSecondParameterNull = invocation.ArgumentList.Arguments[1].Expression .RemoveParentheses() .IsNullLiteral(); if (isSecondParameterNull) { c.ReportIssue(rule, invocation, NullEventArgsMessage); } var eventSymbol = GetEventSymbol(invocation.Expression, c.Model); if (eventSymbol == null) { return; } var isFirstParameterNull = invocation.ArgumentList.Arguments[0].Expression .RemoveParentheses() .IsNullLiteral(); if (isFirstParameterNull && !eventSymbol.IsStatic) { c.ReportIssue(rule, invocation, NullSenderMessage); } else if (!isFirstParameterNull && eventSymbol.IsStatic) { c.ReportIssue(rule, invocation, NonNullSenderMessage); } }, SyntaxKind.InvocationExpression); } private static IEventSymbol GetEventSymbol(ExpressionSyntax expression, SemanticModel model) { if (expression == null) { return null; } if (expression.IsKind(SyntaxKind.IdentifierName)) { return model.GetSymbolInfo(expression).Symbol as IEventSymbol; } if (expression is MemberAccessExpressionSyntax simpleMemberAccess) { return GetEventSymbol(simpleMemberAccess.Expression, model); } if (expression is MemberBindingExpressionSyntax) { var conditionalExpression = expression.FirstAncestorOrSelf(); var isSelf = expression == conditionalExpression?.Expression; if (isSelf) { return null; } return GetEventSymbol(conditionalExpression?.Expression, model); } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionRethrow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionRethrow : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3445"; private const string MessageFormat = "Consider using 'throw;' to preserve the stack trace."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var catchClause = (CatchClauseSyntax)c.Node; if (catchClause.Declaration == null || catchClause.Declaration.Identifier.IsKind(SyntaxKind.None)) { return; } var exceptionIdentifier = c.Model.GetDeclaredSymbol(catchClause.Declaration); if (exceptionIdentifier == null) { return; } var throws = catchClause.DescendantNodes(n => n == catchClause || !n.IsKind(SyntaxKind.CatchClause)) .OfType() .Where(t => t.Expression != null); foreach (var @throw in throws) { var thrown = c.Model.GetSymbolInfo(@throw.Expression).Symbol as ILocalSymbol; if (Equals(thrown, exceptionIdentifier)) { c.ReportIssue(rule, @throw); } } }, SyntaxKind.CatchClause); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionRethrowCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ExceptionRethrowCodeFix : SonarCodeFix { internal const string Title = "Change to 'throw;'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ExceptionRethrow.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is ThrowStatementSyntax throwStatement)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( throwStatement, SyntaxFactory.ThrowStatement().WithTriviaFrom(throwStatement)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionShouldNotBeThrownFromUnexpectedMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionShouldNotBeThrownFromUnexpectedMethods : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3877"; private const string MessageFormat = "Remove this 'throw' {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray DefaultAllowedExceptions = ImmutableArray.Create(KnownType.System_NotImplementedException); private static readonly ImmutableArray EventAccessorAllowedExceptions = ImmutableArray.Create( KnownType.System_NotImplementedException, KnownType.System_InvalidOperationException, KnownType.System_NotSupportedException, KnownType.System_ArgumentException); private static readonly HashSet TrackedOperators = [ SyntaxKind.EqualsEqualsToken, SyntaxKind.ExclamationEqualsToken, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken, SyntaxKind.LessThanEqualsToken, SyntaxKind.GreaterThanEqualsToken ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckForIssue(c, mds => IsTrackedMethod(mds, c.Model), DefaultAllowedExceptions), SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => CheckForIssue(c, cds => cds.Modifiers.Any(SyntaxKind.StaticKeyword), DefaultAllowedExceptions), SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction( c => CheckForIssue(c, IsTrackedOperator, DefaultAllowedExceptions), SyntaxKind.OperatorDeclaration); context.RegisterNodeAction( c => CheckForIssue(c, x => true, EventAccessorAllowedExceptions), SyntaxKind.AddAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration); context.RegisterNodeAction( c => CheckForIssue(c, cods => cods.ImplicitOrExplicitKeyword.IsKind(SyntaxKind.ImplicitKeyword), DefaultAllowedExceptions), SyntaxKind.ConversionOperatorDeclaration); } private static void CheckForIssue(SonarSyntaxNodeReportingContext analysisContext, Func isTrackedSyntax, ImmutableArray allowedThrowTypes) where TSyntax : SyntaxNode { var syntax = (TSyntax)analysisContext.Node; if (isTrackedSyntax(syntax)) { ReportOnInvalidThrow(analysisContext, syntax, allowedThrowTypes); } } private static bool IsTrackedOperator(OperatorDeclarationSyntax declaration) => TrackedOperators.Contains(declaration.OperatorToken.Kind()); private static bool IsTrackedMethod(MethodDeclarationSyntax declaration, SemanticModel model) => HasTrackedMethodOrAttributeName(declaration) && model.GetDeclaredSymbol(declaration) is { } methodSymbol && HasTrackedMethodOrAttributeType(methodSymbol); private static bool HasTrackedMethodOrAttributeName(MethodDeclarationSyntax declaration) { var name = declaration.Identifier.ValueText; return name == "Equals" || name == "GetHashCode" || name == "ToString" || name == "Dispose" || name == "Equals" || CanBeModuleInitializer(); bool CanBeModuleInitializer() => declaration.AttributeLists.SelectMany(x => x.Attributes).Any(x => x.ArgumentList is null && x.Name.ToStringContains("ModuleInitializer")); } private static bool HasTrackedMethodOrAttributeType(IMethodSymbol method) => method.IsObjectEquals() || method.IsObjectGetHashCode() || method.IsObjectToString() || method.IsIDisposableDispose() || method.IsImplementingInterfaceMember(KnownType.System_IEquatable_T, "Equals") || IsModuleInitializer(method); private static bool IsModuleInitializer(IMethodSymbol method) => method.AnyAttributeDerivesFrom(KnownType.System_Runtime_CompilerServices_ModuleInitializerAttribute); private static void ReportOnInvalidThrow(SonarSyntaxNodeReportingContext context, SyntaxNode node, ImmutableArray allowedTypes) { if (node.ArrowExpressionBody() is { } expressionBody && GetLocationToReport( expressionBody.Expression.DescendantNodesAndSelf().Where(ThrowExpressionSyntaxWrapper.IsInstance).Select(x => (ThrowExpressionSyntaxWrapper)x), x => x.Node, x => x.Expression) is { } throwExpressionLocation) { context.ReportIssue(Rule, throwExpressionLocation, "expression"); } else if (GetLocationToReport( node.DescendantNodes().OfType().Where(x => x.Expression is not null), x => x, x => x.Expression) is { } throwStatementLocation) { context.ReportIssue(Rule, throwStatementLocation, "statement"); } // `throwNodes` is an enumeration of either throw expressions or throw statements // Because of the ShimLayer ThrowExpression implementation, we need to provide extra boilerplate as the wrappers to extract the node and the expression. // The location is returned only if an issue should be reported. Otherwise, null is returned. Location GetLocationToReport(IEnumerable throwNodes, Func getNode, Func getExpression) => throwNodes.Select(x => new NodeAndSymbol(getNode(x), context.Model.GetSymbolInfo(getExpression(x)).Symbol)) .FirstOrDefault(x => x.Symbol is not null && ShouldReport(x.Symbol.ContainingType, allowedTypes)) .Node?.GetLocation(); } private static bool ShouldReport(INamedTypeSymbol exceptionType, ImmutableArray allowedTypes) => !exceptionType.IsAny(allowedTypes) && !exceptionType.DerivesFromAny(allowedTypes); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionsNeedStandardConstructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionsNeedStandardConstructors : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4027"; private const string MessageFormat = "Implement the missing constructors for this exception."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var classDeclaration = c.Node as ClassDeclarationSyntax; var classSymbol = c.Model.GetDeclaredSymbol(classDeclaration); if (!classDeclaration.Identifier.IsMissing && classSymbol.DerivesFrom(KnownType.System_Exception) && !HasStandardConstructors(classSymbol)) { c.ReportIssue(rule, classDeclaration.Identifier); } }, SyntaxKind.ClassDeclaration); } private static bool HasStandardConstructors(INamedTypeSymbol classSymbol) { var ctors = classSymbol.Constructors; return HasConstructor(ctors, Accessibility.Public) && HasConstructor(ctors, Accessibility.Public, KnownType.System_String) && HasConstructor(ctors, Accessibility.Public, KnownType.System_String, KnownType.System_Exception); } private static bool HasConstructor(ImmutableArray constructors, Accessibility accessibility, params KnownType[] expectedParameterTypes) { return constructors.Any(c => IsMatchingConstructor(c, accessibility, expectedParameterTypes)); } private static bool IsMatchingConstructor(IMethodSymbol constructor, Accessibility accessibility, KnownType[] expectedParameterTypes) { return constructor.DeclaredAccessibility == accessibility && SonarAnalyzer.Core.Extensions.IEnumerableExtensions.Equals(constructor.Parameters, expectedParameterTypes, (p1, p2) => p1.Type.Is(p2)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionsShouldBeLogged.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Walkers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionsShouldBeLogged : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6667"; private const string MessageFormat = "Logging in a catch clause should pass the caught exception as a parameter."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly KnownAssembly[] SupportedLoggingFrameworks = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.CastleCore, KnownAssembly.CommonLoggingCore, KnownAssembly.Log4Net, KnownAssembly.NLog ]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(SupportedLoggingFrameworks)) { cc.RegisterNodeAction(c => { var catchClauseSyntax = (CatchClauseSyntax)c.Node; var walker = new CatchLoggingInvocationWalker(c.Model); if (walker.SafeVisit(catchClauseSyntax) && !walker.IsExceptionLogged && walker.LoggingInvocationsWithoutException.Any()) { var primaryLocation = walker.LoggingInvocationsWithoutException[0].GetLocation(); var secondaryLocations = walker.LoggingInvocationsWithoutException.Skip(1).ToSecondaryLocations(MessageFormat); c.ReportIssue(Rule, primaryLocation, secondaryLocations); } }, SyntaxKind.CatchClause); } }); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionsShouldBeLoggedOrThrown.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Walkers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionsShouldBeLoggedOrThrown : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2139"; private const string MessageFormat = "Either log this exception and handle it, or rethrow it with some contextual information."; private const string LoggingStatementMessage = "Logging statement."; private const string ThrownExceptionMessage = "Thrown exception."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly KnownAssembly[] SupportedLoggingFrameworks = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.Log4Net, KnownAssembly.NLog, KnownAssembly.CastleCore, KnownAssembly.CommonLoggingCore, KnownAssembly.Serilog]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(SupportedLoggingFrameworks)) { cc.RegisterNodeAction(c => { var catchClauseSyntax = (CatchClauseSyntax)c.Node; var walker = new LoggingInvocationWalker(c.Model); if (catchClauseSyntax.Declaration?.Identifier is { } exceptionIdentifier // there is an exception to log && catchClauseSyntax.DescendantNodes().Any(x => x.Kind() is SyntaxKind.ThrowStatement or SyntaxKindEx.ThrowExpression) // and a throw statement (preliminary check) && walker.SafeVisit(catchClauseSyntax) && walker.IsExceptionLogged && walker.ThrowNode is { } throwStatement) { var secondaryLocations = new List { new(walker.LoggingInvocationWithException.GetLocation(), LoggingStatementMessage), new(throwStatement.GetLocation(), ThrownExceptionMessage) }; c.ReportIssue(Rule, exceptionIdentifier, secondaryLocations); } }, SyntaxKind.CatchClause); } }); private sealed class LoggingInvocationWalker(SemanticModel model) : CatchLoggingInvocationWalker(model) { public SyntaxNode ThrowNode { get; private set; } public override void VisitIfStatement(IfStatementSyntax node) { // Skip processing to avoid false positives. } public override void VisitSwitchStatement(SwitchStatementSyntax node) { // Skip processing to avoid false positives. } public override void VisitConditionalExpression(ConditionalExpressionSyntax node) { // Skip processing to avoid false positives. } public override void Visit(SyntaxNode node) { if (node.IsKind(SyntaxKind.CoalesceExpression)) { return; } base.Visit(node); } public override void VisitThrowStatement(ThrowStatementSyntax node) { if (ThrowNode == null && RethrowsCaughtException(node.Expression)) { ThrowNode = node; } base.VisitThrowStatement(node); } private bool RethrowsCaughtException(ExpressionSyntax expression) => expression is null || Equals(Model.GetSymbolInfo(expression).Symbol, CaughtException); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionsShouldBePublic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionsShouldBePublic : ExceptionsShouldBePublicBase { protected override string MessageFormat => "Make this exception 'public'."; protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExceptionsShouldBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExceptionsShouldBeUsed : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3984"; private const string MessageFormat = "Throw this exception or remove this useless statement."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var objectCreation = (ObjectCreationExpressionSyntax)c.Node; var parent = objectCreation.GetFirstNonParenthesizedParent(); if (parent.IsKind(SyntaxKind.ExpressionStatement) && c.Model.GetSymbolInfo(objectCreation.Type).Symbol is INamedTypeSymbol createdObjectType && createdObjectType.DerivesFrom(KnownType.System_Exception)) { c.ReportIssue(Rule, objectCreation); } }, SyntaxKind.ObjectCreationExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExcludeFromCodeCoverageAttributesNeedJustification : ExcludeFromCodeCoverageAttributesNeedJustificationBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode GetJustificationExpression(SyntaxNode node) => node is AttributeSyntax { ArgumentList.Arguments: { Count: 1 } arguments } && arguments[0] is { NameEquals.Name.Identifier.ValueText: JustificationPropertyName, Expression: { } expression } ? expression : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExpectedExceptionAttributeShouldNotBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExpectedExceptionAttributeShouldNotBeUsed : ExpectedExceptionAttributeShouldNotBeUsedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode FindExpectedExceptionAttribute(SyntaxNode node) => ((MethodDeclarationSyntax)node).AttributeLists.SelectMany(x => x.Attributes).FirstOrDefault(x => x.GetName() is "ExpectedException" or "ExpectedExceptionAttribute"); protected override bool HasMultiLineBody(SyntaxNode node) { var declaration = (MethodDeclarationSyntax)node; return declaration.ExpressionBody is null && declaration.Body?.Statements.Count > 1; } protected override bool AssertInCatchFinallyBlock(SyntaxNode node) { var walker = new CatchFinallyAssertion(); foreach (var x in node.DescendantNodes().Where(x => x.Kind() is SyntaxKind.CatchClause or SyntaxKind.FinallyClause)) { if (!walker.HasAssertion) { walker.SafeVisit(x); } } return walker.HasAssertion; } private sealed class CatchFinallyAssertion : SafeCSharpSyntaxWalker { public bool HasAssertion { get; set; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) => HasAssertion = HasAssertion || node.Expression.ToString().SplitCamelCaseToWords().Intersect(KnownMethods.AssertionMethodParts).Any(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExpressionComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExpressionComplexity : ExpressionComplexityBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override HashSet TransparentKinds { get; } = [ // Binary SyntaxKind.CoalesceExpression, SyntaxKind.BitwiseOrExpression, SyntaxKind.ExclusiveOrExpression, SyntaxKind.BitwiseAndExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LeftShiftExpression, SyntaxKind.RightShiftExpression, SyntaxKindEx.UnsignedRightShiftExpression, SyntaxKind.AddExpression, SyntaxKind.SubtractExpression, SyntaxKind.MultiplyExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression, // Unary SyntaxKind.ParenthesizedExpression, SyntaxKind.LogicalNotExpression, SyntaxKindEx.ParenthesizedPattern, SyntaxKindEx.NotPattern, ]; protected override HashSet ComplexityIncreasingKinds { get; } = [ SyntaxKind.ConditionalExpression, SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.AndPattern, SyntaxKindEx.OrPattern ]; protected override SyntaxNode[] ExpressionChildren(SyntaxNode node) => node.IsAnyKind(TransparentKinds) || node.IsAnyKind(ComplexityIncreasingKinds) ? node switch { ConditionalExpressionSyntax conditional => [conditional.Condition, conditional.WhenTrue, conditional.WhenFalse], BinaryExpressionSyntax binary => [binary.Left, binary.Right], { RawKind: (int)SyntaxKindEx.AndPattern or (int)SyntaxKindEx.OrPattern } pattern when (BinaryPatternSyntaxWrapper)pattern is var patternWrapper => [patternWrapper.Left.Node, patternWrapper.Right.Node], AssignmentExpressionSyntax assigment => [assigment.Left, assigment.Right], ParenthesizedExpressionSyntax { Expression: { } expression } => [expression], PrefixUnaryExpressionSyntax { Operand: { } operand } => [operand], { RawKind: (int)SyntaxKindEx.ParenthesizedPattern } parenthesized when (ParenthesizedPatternSyntaxWrapper)parenthesized is var parenthesizedWrapped => [parenthesizedWrapped.Pattern.Node], { RawKind: (int)SyntaxKindEx.NotPattern } negated when (UnaryPatternSyntaxWrapper)negated is var negatedWrapper => [negatedWrapper.Pattern.Node], _ => [], } : []; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExtensionMethodShouldBeInSeparateNamespace.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExtensionMethodShouldBeInSeparateNamespace : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4226"; private const string MessageFormat = "Either move this extension to another namespace or move the method inside the class itself."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; if (methodDeclaration.IsExtensionMethod() && c.Model.GetDeclaredSymbol(methodDeclaration) is { IsExtensionMethod: true, Parameters: { Length: > 0 } } methodSymbol && methodSymbol.Parameters[0].Type.Kind != SymbolKind.ErrorType && methodSymbol.Parameters[0].Type.IsClass() && methodSymbol.ContainingNamespace.Equals(methodSymbol.Parameters[0].Type.ContainingNamespace) && !methodSymbol.Parameters[0].Type.HasAttribute(KnownType.System_CodeDom_Compiler_GeneratedCodeAttribute)) { c.ReportIssue(Rule, methodDeclaration.Identifier); } }, SyntaxKind.MethodDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ExtensionMethodShouldNotExtendObject.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExtensionMethodShouldNotExtendObject : ExtensionMethodShouldNotExtendObjectBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsExtensionMethod(MethodDeclarationSyntax declaration, SemanticModel semanticModel) => declaration.IsExtensionMethod(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FieldShadowsParentField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldShadowsParentField : FieldShadowsParentFieldBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (!fieldDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.NewKeyword))) { foreach (var diagnostics in fieldDeclaration.Declaration.Variables.SelectMany(x => CheckFields(c.Model, x))) { c.ReportIssue(diagnostics); } } }, SyntaxKind.FieldDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FieldShouldBeReadonly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using FieldTuple = SonarAnalyzer.Core.Common.NodeSymbolAndModel; using TypeDeclarationTuple = SonarAnalyzer.Core.Common.NodeAndModel; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldShouldBeReadonly : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2933"; private const string MessageFormat = "Make '{0}' 'readonly'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet AssignmentKinds = new HashSet { SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression }; private static readonly ISet PrefixUnaryKinds = new HashSet { SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression }; private static readonly ISet PostfixUnaryKinds = new HashSet { SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var declaredSymbol = (INamedTypeSymbol)c.Symbol; if (!declaredSymbol.IsClassOrStruct() // Serializable classes are ignored because the serialized fields // cannot be readonly. [Nonserialized] fields could be readonly, // but all fields with attribute are ignored in the ReadonlyFieldCollector. || declaredSymbol.HasAttribute(KnownType.System_SerializableAttribute) // Partial classes are not processed. // See https://github.com/dotnet/roslyn/issues/3748 || declaredSymbol.DeclaringSyntaxReferences.Length > 1) { return; } var partialDeclarations = declaredSymbol.DeclaringSyntaxReferences .Select(reference => reference.GetSyntax()) .OfType() .Select(x => new TypeDeclarationTuple(x, c.Compilation.GetSemanticModel(x.SyntaxTree))) .Where(x => x.Model is not null); var fieldCollector = new ReadonlyFieldCollector(partialDeclarations); if (!fieldCollector.HasAssignmentToThis) { foreach (var field in fieldCollector.NonCompliantFields) { var identifier = field.Node.Identifier; c.ReportIssue(Rule, identifier, identifier.ValueText); } } }, SymbolKind.NamedType); private sealed class ReadonlyFieldCollector { private readonly ISet assignedAsReadonly; private readonly ISet excludedFields; private readonly List allFields = []; public bool HasAssignmentToThis { get; private set; } public IEnumerable NonCompliantFields { get { var reportedFields = new HashSet(assignedAsReadonly.Except(excludedFields)); return allFields.Where(x => reportedFields.Contains(x.Symbol)); } } public ReadonlyFieldCollector(IEnumerable partialTypeDeclarations) { excludedFields = new HashSet(); assignedAsReadonly = new HashSet(); foreach (var partialTypeDeclaration in partialTypeDeclarations) { var p = new PartialTypeDeclarationProcessor(partialTypeDeclaration, this); p.CollectFields(); allFields.AddRange(p.AllFields); } foreach (var attributedField in allFields.Where(ShouldBeExcluded)) { excludedFields.Add(attributedField.Symbol); } static bool ShouldBeExcluded(FieldTuple fieldTuple) => fieldTuple.Symbol.GetAttributes().Any() || (fieldTuple.Symbol.Type.IsStruct() && fieldTuple.Symbol.Type.SpecialType == SpecialType.None); } private sealed class PartialTypeDeclarationProcessor { private readonly TypeDeclarationTuple partialTypeDeclaration; private readonly ReadonlyFieldCollector readonlyFieldCollector; public IEnumerable AllFields { get; } public PartialTypeDeclarationProcessor(TypeDeclarationTuple partialTypeDeclaration, ReadonlyFieldCollector readonlyFieldCollector) { this.partialTypeDeclaration = partialTypeDeclaration; this.readonlyFieldCollector = readonlyFieldCollector; AllFields = partialTypeDeclaration.Node.ChildNodes() .OfType() .SelectMany(GetAllFields); } public void CollectFields() { CollectFieldsFromDeclarations(); CollectFieldsFromAssignments(); if (!readonlyFieldCollector.HasAssignmentToThis) { CollectFieldsFromPrefixUnaryExpressions(); CollectFieldsFromPostfixUnaryExpressions(); CollectFieldsFromArguments(); } } private IEnumerable GetAllFields(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.Declaration.Variables .Select(x => new FieldTuple(x, partialTypeDeclaration.Model.GetDeclaredSymbol(x) as IFieldSymbol, partialTypeDeclaration.Model)); private void CollectFieldsFromDeclarations() { var fieldDeclarations = AllFields.Where(x => IsFieldRelevant(x.Symbol) && x.Node.Initializer is not null); foreach (var field in fieldDeclarations) { readonlyFieldCollector.assignedAsReadonly.Add(field.Symbol); } } private void CollectFieldsFromArguments() { var arguments = partialTypeDeclaration.Node.DescendantNodes() .OfType() .Where(x => !x.RefOrOutKeyword.IsKind(SyntaxKind.None)); foreach (var argument in arguments) { // ref/out should be handled the same way as all other field assignments: ProcessExpression(argument.Expression); } } private void CollectFieldsFromPostfixUnaryExpressions() { var postfixUnaries = partialTypeDeclaration.Node.DescendantNodes() .OfType() .Where(x => PostfixUnaryKinds.Contains(x.Kind())); foreach (var postfixUnary in postfixUnaries) { ProcessExpression(postfixUnary.Operand); } } private void CollectFieldsFromPrefixUnaryExpressions() { var prefixUnaries = partialTypeDeclaration.Node.DescendantNodes() .OfType() .Where(x => PrefixUnaryKinds.Contains(x.Kind())); foreach (var prefixUnary in prefixUnaries) { ProcessExpression(prefixUnary.Operand); } } private void CollectFieldsFromAssignments() { var assignments = partialTypeDeclaration.Node.DescendantNodes() .OfType() .Where(x => AssignmentKinds.Contains(x.Kind())); foreach (var leftSide in assignments.Select(x => x.Left)) { if (leftSide.Kind() is SyntaxKind.ThisExpression && leftSide.EnclosingScope().Kind() is not SyntaxKind.ConstructorDeclaration && leftSide.Ancestors().OfType().First().Equals(partialTypeDeclaration.Node)) { readonlyFieldCollector.HasAssignmentToThis = true; return; } var leftSideExpressions = TupleExpressionsOrSelf(leftSide); foreach (var leftSideExpression in leftSideExpressions) { ProcessExpression(leftSideExpression); } } } private void ProcessExpression(ExpressionSyntax expression) { ProcessAssignedExpression(expression); ProcessAssignedTopMemberAccessExpression(expression); } private void ProcessAssignedTopMemberAccessExpression(ExpressionSyntax expression) { var topExpression = GetTopMemberAccessIfNested(expression); if (topExpression is null) { return; } var fieldSymbol = partialTypeDeclaration.Model.GetSymbolInfo(topExpression).Symbol as IFieldSymbol; if (fieldSymbol?.Type is null || !fieldSymbol.Type.IsValueType) { return; } ProcessExpressionOnField(topExpression, fieldSymbol); } private static ExpressionSyntax GetTopMemberAccessIfNested(ExpressionSyntax expression, bool isNestedMemberAccess = false) { // If expression is (this.a.b).c, we need to return this.a var noParens = expression.RemoveParentheses(); if (noParens is NameSyntax) { return isNestedMemberAccess ? noParens : null; } if (noParens is not MemberAccessExpressionSyntax memberAccess) { return null; } if (memberAccess.Expression.RemoveParentheses().IsKind(SyntaxKind.ThisExpression)) { return isNestedMemberAccess ? memberAccess : null; } return GetTopMemberAccessIfNested(memberAccess.Expression, true); } private void ProcessAssignedExpression(ExpressionSyntax expression) { var fieldSymbol = partialTypeDeclaration.Model.GetSymbolInfo(expression).Symbol as IFieldSymbol; ProcessExpressionOnField(expression, fieldSymbol); } private void ProcessExpressionOnField(ExpressionSyntax expression, IFieldSymbol fieldSymbol) { if (!IsFieldRelevant(fieldSymbol)) { return; } if (!expression.RemoveParentheses().IsOnThis()) { readonlyFieldCollector.excludedFields.Add(fieldSymbol); return; } if (partialTypeDeclaration.Model.GetEnclosingSymbol(expression.SpanStart) is not IMethodSymbol methodSymbol) { readonlyFieldCollector.excludedFields.Add(fieldSymbol); return; } if (methodSymbol.MethodKind == MethodKind.Constructor && methodSymbol.ContainingType.Equals(fieldSymbol.ContainingType)) { readonlyFieldCollector.assignedAsReadonly.Add(fieldSymbol); } else { readonlyFieldCollector.excludedFields.Add(fieldSymbol); } } private static bool IsFieldRelevant(IFieldSymbol fieldSymbol) => fieldSymbol is { IsStatic: false, IsConst: false, IsReadOnly: false, DeclaredAccessibility: Accessibility.Private }; private static IEnumerable TupleExpressionsOrSelf(ExpressionSyntax expression) { if (TupleExpressionSyntaxWrapper.IsInstance(expression)) { var assignments = new List(); foreach (var argument in ((TupleExpressionSyntaxWrapper)expression).Arguments) { assignments.Add(argument.Expression); } return assignments; } else { return [expression]; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FieldShouldBeReadonlyCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class FieldShouldBeReadonlyCodeFix : SonarCodeFix { private const string Title = "Add 'readonly' keyword"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(FieldShouldBeReadonly.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); var variableDeclarator = syntaxNode.FirstAncestorOrSelf(); if (variableDeclarator?.Parent is not VariableDeclarationSyntax variableDeclaration) { return Task.CompletedTask; } if (variableDeclaration.Variables.Count == 1) { if (variableDeclaration.Parent is not FieldDeclarationSyntax fieldDeclaration) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var readonlyToken = SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword); var newFieldDeclaration = HasAnyAccessibilityModifier(fieldDeclaration) ? fieldDeclaration.AddModifiers(readonlyToken) : fieldDeclaration.WithoutLeadingTrivia() .AddModifiers(readonlyToken.WithLeadingTrivia(fieldDeclaration.GetLeadingTrivia())); var newRoot = root.ReplaceNode(fieldDeclaration, newFieldDeclaration); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } private static bool HasAnyAccessibilityModifier(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.Modifiers.Any(modifier => modifier.IsAnyKind(SyntaxKind.PrivateKeyword, SyntaxKind.PublicKeyword, SyntaxKind.InternalKeyword, SyntaxKind.ProtectedKeyword)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FieldShouldNotBePublic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldShouldNotBePublic : FieldShouldNotBePublicBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IEnumerable Variables(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.Declaration.Variables; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FieldsShouldBeEncapsulatedInProperties.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldsShouldBeEncapsulatedInProperties : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1104"; private const string MessageFormat = "Make this field 'private' and encapsulate it in a 'public' property."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet ValidModifiers = new HashSet { SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword, SyntaxKind.ReadOnlyKeyword, SyntaxKind.ConstKeyword }; private static readonly ImmutableArray IgnoredTypes = ImmutableArray.Create( KnownType.UnityEngine_MonoBehaviour, KnownType.UnityEngine_ScriptableObject); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (fieldDeclaration.Modifiers.Any(m => ValidModifiers.Contains(m.Kind()))) { return; } var firstVariable = fieldDeclaration.Declaration.Variables[0]; var symbol = c.Model.GetDeclaredSymbol(firstVariable); var parentSymbol = c.Model.GetDeclaredSymbol(fieldDeclaration.Parent); if (symbol.ContainingType.DerivesFromAny(IgnoredTypes) || parentSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_StructLayoutAttribute) || Serializable(symbol, parentSymbol)) { return; } if (symbol.GetEffectiveAccessibility() == Accessibility.Public) { c.ReportIssue(Rule, firstVariable); } }, SyntaxKind.FieldDeclaration); private static bool Serializable(ISymbol symbol, ISymbol parentSymbol) => parentSymbol.HasAttribute(KnownType.System_SerializableAttribute) && !symbol.HasAttribute(KnownType.System_NonSerializedAttribute); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FileLines.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FileLines : FileLinesBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override bool IsEndOfFileToken(SyntaxToken token) => token.IsKind(SyntaxKind.EndOfFileToken); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FileShouldEndWithEmptyNewLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FileShouldEndWithEmptyNewLine : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S113"; private const string MessageFormat = "Add a new line at the end of the file '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction(stac => { var lastToken = stac.Tree.GetRoot().GetLastToken(); if (lastToken == default(SyntaxToken)) { return; } var isFileEndingWithNewLine = lastToken.TrailingTrivia.LastOrDefault().IsKind(SyntaxKind.EndOfLineTrivia); if (!isFileEndingWithNewLine) { stac.ReportIssue(Rule, lastToken, Path.GetFileName(stac.Tree.FilePath)); } }); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FinalizerShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FinalizerShouldNotBeEmpty : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3880"; private const string MessageFormat = "Remove this empty finalizer."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var destructorDeclaration = (DestructorDeclarationSyntax)c.Node; if (destructorDeclaration.Body?.Statements.Count == 0 && destructorDeclaration.ExpressionBody() == null) { c.ReportIssue(Rule, destructorDeclaration); } }, SyntaxKind.DestructorDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FindInsteadOfFirstOrDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FindInsteadOfFirstOrDefault : FindInsteadOfFirstOrDefaultBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FlagsEnumWithoutInitializer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FlagsEnumWithoutInitializer : FlagsEnumWithoutInitializerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override bool IsInitialized(EnumMemberDeclarationSyntax member) => member.EqualsValue != null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FlagsEnumZeroMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FlagsEnumZeroMember : FlagsEnumZeroMemberBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForLoopConditionAlwaysFalse.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ForLoopConditionAlwaysFalse : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2252"; private const string MessageFormat = "This loop will never execute."; private static readonly CSharpExpressionNumericConverter ExpressionNumericConverter = new(); private static readonly ISet ConditionsToCheck = new HashSet { SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var forNode = (ForStatementSyntax)c.Node; if (forNode.Condition is not null && (IsAlwaysFalseCondition(forNode.Condition) || IsConditionFalseAtInitialization(forNode))) { c.ReportIssue(Rule, forNode.Condition); } }, SyntaxKind.ForStatement); private bool IsAlwaysFalseCondition(ExpressionSyntax condition) => condition.IsKind(SyntaxKind.FalseLiteralExpression) || (IsLogicalNot(condition, out var logicalNode) && IsAlwaysTrueCondition(logicalNode.Operand.RemoveParentheses())); private bool IsAlwaysTrueCondition(ExpressionSyntax condition) => condition.IsKind(SyntaxKind.TrueLiteralExpression) || (IsLogicalNot(condition, out var logicalNode) && IsAlwaysFalseCondition(logicalNode.Operand.RemoveParentheses())); private static bool IsConditionFalseAtInitialization(ForStatementSyntax forNode) { var condition = forNode.Condition; if (!ConditionsToCheck.Contains(condition.Kind())) { return false; } var loopVariableDeclarationMapping = VariableDeclarationMapping(forNode.Declaration); var loopInitializerMapping = LoopInitializerMapping(forNode.Initializers); var variableNameToDecimalMapping = loopVariableDeclarationMapping .Union(loopInitializerMapping) .ToDictionary(d => d.Key, d => d.Value); var binaryCondition = (BinaryExpressionSyntax)condition; if (DecimalValue(variableNameToDecimalMapping, binaryCondition.Left) is { } leftValue && DecimalValue(variableNameToDecimalMapping, binaryCondition.Right) is { } rightValue) { return !ConditionIsTrue(condition.Kind(), leftValue, rightValue); } return false; } private static bool ConditionIsTrue(SyntaxKind syntaxKind, decimal leftValue, decimal rightValue) => syntaxKind switch { SyntaxKind.GreaterThanExpression => leftValue > rightValue, SyntaxKind.GreaterThanOrEqualExpression => leftValue >= rightValue, SyntaxKind.LessThanExpression => leftValue < rightValue, SyntaxKind.LessThanOrEqualExpression => leftValue <= rightValue, SyntaxKind.EqualsExpression => leftValue == rightValue, SyntaxKind.NotEqualsExpression => leftValue != rightValue, _ => true }; private static bool IsLogicalNot(ExpressionSyntax expression, out PrefixUnaryExpressionSyntax logicalNot) { var prefixUnaryExpression = expression.RemoveParentheses() as PrefixUnaryExpressionSyntax; logicalNot = prefixUnaryExpression; return prefixUnaryExpression is not null && prefixUnaryExpression.OperatorToken.IsKind(SyntaxKind.ExclamationToken); } private static decimal? DecimalValue(IDictionary variableNameToDecimalValue, ExpressionSyntax expression) => ExpressionNumericConverter.ConstantDecimalValue(expression) is { } parsedValue || (expression is SimpleNameSyntax simpleName && variableNameToDecimalValue.TryGetValue(simpleName.Identifier.ValueText, out parsedValue)) ? parsedValue : (decimal?)null; /// /// Retrieves the mapping of variable names to their decimal value from the variable declaration part of a for loop. /// This will find the mapping for such cases: /// /// for (var i = 0;;) {} /// /// private static IDictionary VariableDeclarationMapping(VariableDeclarationSyntax variableDeclarationSyntax) { var loopInitializerValues = new Dictionary(); if (variableDeclarationSyntax is not null) { foreach (var variableDeclaration in variableDeclarationSyntax.Variables) { if (variableDeclaration.Initializer is { } initializer && ExpressionNumericConverter.ConstantDecimalValue(initializer.Value) is { } decimalValue) { loopInitializerValues.Add(variableDeclaration.Identifier.ValueText, decimalValue); } } } return loopInitializerValues; } /// /// Retrieves the mapping of variable names to their decimal value from the initializer part of a for loop. /// This will find the mapping for such cases: /// /// int i; /// for (i = 0;;) {} /// /// private static IDictionary LoopInitializerMapping(IEnumerable initializers) { var loopInitializerValues = new Dictionary(); if (initializers is not null) { foreach (var initializer in initializers) { if (initializer.IsKind(SyntaxKind.SimpleAssignmentExpression) && initializer is AssignmentExpressionSyntax { Left: SimpleNameSyntax simpleName } assignment && ExpressionNumericConverter.ConstantDecimalValue(assignment.Right) is { } decimalValue) { loopInitializerValues.Add(simpleName.Identifier.ValueText, decimalValue); } } } return loopInitializerValues; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForLoopCounterChanged.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ForLoopCounterChanged : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S127"; private const string MessageFormat = "Do not update the stop condition variable '{0}' in the body of the for loop."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly IImmutableList SideEffectExpressions = ImmutableArray.Create( new SideEffectExpression { Kinds = ImmutableHashSet.Create(SyntaxKind.PreIncrementExpression, SyntaxKind.PreDecrementExpression), AffectedExpressions = x => ImmutableArray.Create(((PrefixUnaryExpressionSyntax)x).Operand) }, new SideEffectExpression { Kinds = ImmutableHashSet.Create(SyntaxKind.PostIncrementExpression, SyntaxKind.PostDecrementExpression), AffectedExpressions = x => ImmutableArray.Create(((PostfixUnaryExpressionSyntax)x).Operand) }, new SideEffectExpression { Kinds = ImmutableHashSet.Create( SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression), AffectedExpressions = x => ((AssignmentExpressionSyntax)x).AssignmentTargets() }); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var forNode = (ForStatementSyntax)c.Node; var loopCounters = LoopCounters(forNode, c.Model).ToList(); foreach (var affectedExpression in ComputeAffectedExpressions(forNode.Statement)) { if (c.Model.GetSymbolInfo(affectedExpression).Symbol is { } symbol && loopCounters.Contains(symbol)) { c.ReportIssue(Rule, affectedExpression, symbol.Name); } } }, SyntaxKind.ForStatement); private static IEnumerable LoopCounters(ForStatementSyntax node, SemanticModel model) { if (node.Condition is null || node.Incrementors.Count == 0) { return []; } var conditionSymbols = node.Condition.DescendantNodesAndSelf() .OfType() .Select(x => model.GetSymbolInfo(x).Symbol) .Where(x => x is ILocalSymbol or IParameterSymbol) .WhereNotNull() .ToHashSet(); return node.Incrementors .SelectMany(x => x.DescendantNodesAndSelf().OfType()) .Select(x => model.GetSymbolInfo(x).Symbol) .Where(x => x is ILocalSymbol or IParameterSymbol) .WhereNotNull() .Where(conditionSymbols.Contains) .Distinct(); } private static SyntaxNode[] ComputeAffectedExpressions(SyntaxNode node) => (from descendantNode in node.DescendantNodesAndSelf() from sideEffect in SideEffectExpressions where descendantNode.IsAnyKind(sideEffect.Kinds) from expression in sideEffect.AffectedExpressions(descendantNode) select expression).ToArray(); private readonly record struct SideEffectExpression(ImmutableHashSet Kinds, Func> AffectedExpressions); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForLoopCounterCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ForLoopCounterCondition : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1994"; private const string MessageFormat = "{0}"; private const string MessageFormatNotEmpty = "This loop's stop condition tests {0} but the incrementer updates {1}."; private const string MessageFormatEmpty = "This loop's stop incrementer updates {0} but the stop condition doesn't test any variables."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var forNode = (ForStatementSyntax)c.Node; var incrementedSymbols = GetIncrementorSymbols(forNode, c.Model).ToList(); if (!incrementedSymbols.Any()) { return; } var conditionSymbols = GetReadSymbolsCondition(forNode, c.Model).ToList(); if (conditionSymbols.Intersect(incrementedSymbols).Any()) { return; } var incrementedVariables = incrementedSymbols.Select(s => $"'{s.Name}'").OrderBy(s => s).JoinAnd(); if (conditionSymbols.Any()) { var conditionVariables = conditionSymbols.Select(s => $"'{s.Name}'").OrderBy(s => s).JoinAnd(); c.ReportIssue(Rule, forNode.Condition, string.Format(CultureInfo.InvariantCulture, MessageFormatNotEmpty, conditionVariables, incrementedVariables)); } else { c.ReportIssue(Rule, forNode.ForKeyword, string.Format(CultureInfo.InvariantCulture, MessageFormatEmpty, incrementedVariables)); } }, SyntaxKind.ForStatement); private static IEnumerable GetIncrementorSymbols(ForStatementSyntax forNode, SemanticModel semanticModel) { var accessedSymbols = new List(); foreach (var dataFlowAnalysis in forNode.Incrementors .Select(semanticModel.AnalyzeDataFlow) .Where(dataFlowAnalysis => dataFlowAnalysis.Succeeded)) { accessedSymbols.AddRange(dataFlowAnalysis.WrittenInside); accessedSymbols.AddRange(dataFlowAnalysis.ReadInside); } return accessedSymbols.Distinct(); } private static IEnumerable GetReadSymbolsCondition(ForStatementSyntax forNode, SemanticModel semanticModel) { if (forNode.Condition == null) { return Array.Empty(); } var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(forNode.Condition); return dataFlowAnalysis.Succeeded ? dataFlowAnalysis.ReadInside.Distinct() : Array.Empty(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForLoopIncrementSign.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ForLoopIncrementSign : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2251"; private const string MessageFormat = "'{0}' is {1}remented and will never reach 'stop condition'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); private enum ArithmeticOperation { None, Addition, Substraction } private enum Condition { None, Less, Greater } private readonly Dictionary incrementorToOperation = new Dictionary { { SyntaxKind.PreDecrementExpression, ArithmeticOperation.Substraction }, { SyntaxKind.PreIncrementExpression, ArithmeticOperation.Addition }, { SyntaxKind.PostDecrementExpression, ArithmeticOperation.Substraction }, { SyntaxKind.PostIncrementExpression, ArithmeticOperation.Addition }, { SyntaxKind.SubtractAssignmentExpression, ArithmeticOperation.Substraction }, { SyntaxKind.AddAssignmentExpression, ArithmeticOperation.Addition }, { SyntaxKind.AddExpression, ArithmeticOperation.Addition }, { SyntaxKind.SubtractExpression, ArithmeticOperation.Substraction } }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var forNode = (ForStatementSyntax)c.Node; var conditionSyntax = forNode.Condition; if (!(conditionSyntax is BinaryExpressionSyntax binaryCondition)) { return; } if (forNode.Incrementors.Count != 1) { return; } var incrementor = forNode.Incrementors[0]; var incrementorData = GetIncrementData(incrementor); if (incrementorData.Operation == ArithmeticOperation.None) { return; } var condition = GetCondition(binaryCondition, incrementorData.IdentifierName); if (condition == Condition.None) { return; } if (incrementorData.Operation == ArithmeticOperation.Addition && condition == Condition.Greater) { c.ReportIssue(rule, forNode.Incrementors.First(), [forNode.Condition.ToSecondaryLocation()], incrementorData.IdentifierName, "inc"); } else if (incrementorData.Operation == ArithmeticOperation.Substraction && condition == Condition.Less) { c.ReportIssue(rule, forNode.Incrementors.First(), [forNode.Condition.ToSecondaryLocation()], incrementorData.IdentifierName, "dec"); } }, SyntaxKind.ForStatement); } private IncrementData GetIncrementData(ExpressionSyntax incrementor) { var identifierName = string.Empty; var opp = ArithmeticOperation.None; var incrementorKind = incrementor.Kind(); switch (incrementorKind) { case SyntaxKind.PreDecrementExpression: case SyntaxKind.PreIncrementExpression: case SyntaxKind.PostDecrementExpression: case SyntaxKind.PostIncrementExpression: if (GetUnnaryExpressionOperand(incrementor) is IdentifierNameSyntax operand) { identifierName = operand.Identifier.ValueText; opp = incrementorToOperation.GetValueOrDefault(incrementorKind, ArithmeticOperation.None); } break; case SyntaxKind.SubtractAssignmentExpression: case SyntaxKind.AddAssignmentExpression: if (incrementor is AssignmentExpressionSyntax add && add.Left is IdentifierNameSyntax leftId && add.Right.IsKind(SyntaxKind.NumericLiteralExpression)) { identifierName = leftId.Identifier.ValueText; opp = incrementorToOperation.GetValueOrDefault(incrementorKind, ArithmeticOperation.None); } break; case SyntaxKind.SimpleAssignmentExpression: if (incrementor is AssignmentExpressionSyntax simpleAssignment && simpleAssignment.Left is IdentifierNameSyntax simpleAssignmentId && simpleAssignment.Right is BinaryExpressionSyntax right && IsVariableAndLiteralBinaryExpression(right, simpleAssignmentId.Identifier.ValueText)) { identifierName = simpleAssignmentId.Identifier.ValueText; opp = incrementorToOperation.GetValueOrDefault(right.Kind(), ArithmeticOperation.None); } break; default: break; } return new IncrementData(identifierName, opp); } private Condition GetCondition(BinaryExpressionSyntax conditionSyntax, string identifierName) { // Since the incremented variable can be on any side of the condition (i < 10 or 10 > i), // both sides of the binary expression need to be considered. if (IsIdentifier(conditionSyntax.Left, identifierName)) { if (conditionSyntax.Kind() is SyntaxKind.LessThanExpression or SyntaxKind.LessThanOrEqualExpression) { return Condition.Less; } else if (conditionSyntax.Kind() is SyntaxKind.GreaterThanExpression or SyntaxKind.GreaterThanOrEqualExpression) { return Condition.Greater; } } else if (IsIdentifier(conditionSyntax.Right, identifierName)) { if (conditionSyntax.Kind() is SyntaxKind.LessThanExpression or SyntaxKind.LessThanOrEqualExpression) { return Condition.Greater; } else if (conditionSyntax.Kind() is SyntaxKind.GreaterThanExpression or SyntaxKind.GreaterThanOrEqualExpression) { return Condition.Less; } } return Condition.None; } private bool IsVariableAndLiteralBinaryExpression(BinaryExpressionSyntax binaryExpression, string identifierName) => (IsIdentifier(binaryExpression.Left, identifierName) && binaryExpression.Right.IsKind(SyntaxKind.NumericLiteralExpression)) || (binaryExpression.Left.IsKind(SyntaxKind.NumericLiteralExpression) && IsIdentifier(binaryExpression.Right, identifierName)); private bool IsIdentifier(SyntaxNode node, string identifierName) => node is IdentifierNameSyntax identifier && identifier.NameIs(identifierName); private ExpressionSyntax GetUnnaryExpressionOperand(ExpressionSyntax syntax) { switch (syntax.Kind()) { case SyntaxKind.PreIncrementExpression: case SyntaxKind.PreDecrementExpression: var prefix = (PrefixUnaryExpressionSyntax)syntax; return prefix.Operand; case SyntaxKind.PostIncrementExpression: case SyntaxKind.PostDecrementExpression: var postfix = (PostfixUnaryExpressionSyntax)syntax; return postfix.Operand; default: break; } return default(IdentifierNameSyntax); } private class IncrementData { public string IdentifierName { get; } public ArithmeticOperation Operation { get; } public IncrementData(string identifierName, ArithmeticOperation operation) { IdentifierName = identifierName; Operation = operation; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForeachLoopExplicitConversion.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ForeachLoopExplicitConversion : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3217"; private const string MessageFormat = "Either change the type of '{0}' to '{1}' or iterate on a generic collection of type '{2}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var foreachStatement = (ForEachStatementSyntax)c.Node; var foreachInfo = c.Model.GetForEachStatementInfo(foreachStatement); if (foreachInfo.Equals(default(ForEachStatementInfo)) || foreachInfo.ElementConversion.IsImplicit || foreachInfo.ElementConversion.IsUserDefined || !foreachInfo.ElementConversion.Exists || foreachInfo.ElementType.Is(KnownType.System_Object)) { return; } c.ReportIssue( Rule, foreachStatement.Type, foreachStatement.Identifier.ValueText, foreachInfo.ElementType.ToMinimalDisplayString(c.Model, foreachStatement.Type.SpanStart), foreachStatement.Type.ToString()); }, SyntaxKind.ForEachStatement); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ForeachLoopExplicitConversionCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ForeachLoopExplicitConversionCodeFix : SonarCodeFix { private const string Title = "Filter collection for the expected type"; public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(ForeachLoopExplicitConversion.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var foreachSyntax = root.FindNode(diagnosticSpan).FirstAncestorOrSelf(); if (foreachSyntax == null) { return Task.CompletedTask; } var semanticModel = context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false).GetAwaiter().GetResult(); var enumerableHelperType = semanticModel.Compilation.GetTypeByMetadataName(KnownType.System_Linq_Enumerable); if (enumerableHelperType != null) { context.RegisterCodeFix( Title, c => { var newRoot = CalculateNewRoot(root, foreachSyntax, semanticModel); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } private static SyntaxNode CalculateNewRoot(SyntaxNode root, ForEachStatementSyntax foreachSyntax, SemanticModel semanticModel) { var collection = foreachSyntax.Expression; var typeName = foreachSyntax.Type.ToString(); var invocationToAdd = GetOfTypeInvocation(typeName, collection); var namedTypes = semanticModel.LookupNamespacesAndTypes(foreachSyntax.SpanStart).OfType(); var isUsingAlreadyThere = namedTypes.Any(KnownType.System_Linq_Enumerable.Matches); if (isUsingAlreadyThere) { return root .ReplaceNode(collection, invocationToAdd) .WithAdditionalAnnotations(Formatter.Annotation); } var usingDirectiveToAdd = SyntaxFactory.UsingDirective( SyntaxFactory.QualifiedName( SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Linq"))); var annotation = new SyntaxAnnotation("CollectionToChange"); var newRoot = root.ReplaceNode( collection, collection.WithAdditionalAnnotations(annotation)); var node = newRoot.GetAnnotatedNodes(annotation).First(); var closestNamespaceWithUsing = node.AncestorsAndSelf() .FirstOrDefault(x => BaseNamespaceDeclarationSyntaxWrapper.IsInstance(x) && ((BaseNamespaceDeclarationSyntaxWrapper)x).Usings.Count > 0); if (closestNamespaceWithUsing != null) { var namespaceDeclarationWrapper = (BaseNamespaceDeclarationSyntaxWrapper)closestNamespaceWithUsing; var namespaceWithAdditionalUsing = namespaceDeclarationWrapper.WithUsings(namespaceDeclarationWrapper.Usings.Add(usingDirectiveToAdd)); newRoot = newRoot.ReplaceNode( namespaceDeclarationWrapper, namespaceWithAdditionalUsing.SyntaxNode.WithAdditionalAnnotations(Formatter.Annotation)); } else { var compilationUnit = node.FirstAncestorOrSelf(); newRoot = compilationUnit.AddUsings(usingDirectiveToAdd); } node = newRoot.GetAnnotatedNodes(annotation).First(); return newRoot .ReplaceNode(node, invocationToAdd) .WithAdditionalAnnotations(Formatter.Annotation); } private static InvocationExpressionSyntax GetOfTypeInvocation(string typeName, ExpressionSyntax collection) { return SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, collection, SyntaxFactory.GenericName( SyntaxFactory.Identifier("OfType"), SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList().Add( SyntaxFactory.IdentifierName(typeName)))))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FrameworkTypeNaming.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FrameworkTypeNaming : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3376"; private const string MessageFormat = "Make this class name end with '{0}'."; private const int SelfAndBaseTypesCount = 2; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; var symbol = c.Model.GetDeclaredSymbol(classDeclaration); if (symbol == null) { return; } var baseTypes = symbol.BaseType.GetSelfAndBaseTypes().ToList(); if (baseTypes.Count < SelfAndBaseTypesCount || !baseTypes.Last().Is(KnownType.System_Object)) { return; } var baseTypeKey = FrameworkTypesWithEnding.Keys .FirstOrDefault(ft => baseTypes[baseTypes.Count - SelfAndBaseTypesCount].ToDisplayString().Equals(ft, System.StringComparison.Ordinal)); if (baseTypeKey == null) { return; } var baseTypeName = FrameworkTypesWithEnding[baseTypeKey]; if (symbol.Name.EndsWith(baseTypeName, System.StringComparison.Ordinal) || !baseTypes[0].Name.EndsWith(baseTypeName, System.StringComparison.Ordinal)) { return; } c.ReportIssue(Rule, classDeclaration.Identifier, baseTypeName); }, SyntaxKind.ClassDeclaration); private static readonly Dictionary FrameworkTypesWithEnding = new Dictionary { { "System.Exception", "Exception" }, { "System.EventArgs", "EventArgs" }, { "System.Attribute", "Attribute" } }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FunctionComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.CSharp.Metrics; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class FunctionComplexity : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S1541"; private const string MessageFormat = "The Cyclomatic Complexity of this {2} is {1} which is greater than {0} authorized."; private const int DefaultValueMaximum = 10; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); [RuleParameter("maximumFunctionComplexityThreshold", PropertyType.Integer, "The maximum authorized complexity.", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction(c => { if (c.IsTopLevelMain) { CheckComplexity(c, m => Location.Create(c.Node.SyntaxTree, TextSpan.FromBounds(0, 0)), "top-level file", true); } }, SyntaxKind.CompilationUnit); context.RegisterNodeAction( c => CheckComplexity(c, m => m.Identifier.GetLocation(), "method"), SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => CheckComplexity(c, p => p.Identifier.GetLocation(), p => p.ExpressionBody, "property"), SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => CheckComplexity(c, o => o.OperatorKeyword.GetLocation(), "operator"), SyntaxKind.OperatorDeclaration); context.RegisterNodeAction( c => CheckComplexity(c, co => co.Identifier.GetLocation(), "constructor"), SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction( c => CheckComplexity(c, d => d.Identifier.GetLocation(), "destructor"), SyntaxKind.DestructorDeclaration); context.RegisterNodeAction(c => { if (((LocalFunctionStatementSyntaxWrapper)c.Node).Modifiers.Any(SyntaxKind.StaticKeyword)) { CheckComplexity(c, d => ((LocalFunctionStatementSyntaxWrapper)d).Identifier.GetLocation(), "static local function"); } }, SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction( c => CheckComplexity(c, a => a.Keyword.GetLocation(), "accessor"), SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKind.AddAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration); } private void CheckComplexity(SonarSyntaxNodeReportingContext context, Func getLocation, string declarationType, bool onlyGlobalStatements = false) where TSyntax : SyntaxNode => CheckComplexity(context, getLocation, n => n, declarationType, onlyGlobalStatements); private void CheckComplexity( SonarSyntaxNodeReportingContext context, Func getLocation, Func getNodeToCheck, string declarationType, bool onlyGlobalStatements = false) where TSyntax : SyntaxNode { var node = (TSyntax)context.Node; var nodeToCheck = getNodeToCheck(node); if (nodeToCheck == null) { return; } var complexityMetric = CSharpCyclomaticComplexityMetric.GetComplexity(nodeToCheck, onlyGlobalStatements); if (complexityMetric.Complexity > Maximum) { context.ReportIssue(Rule, getLocation(node), complexityMetric.Locations, Maximum.ToString(), complexityMetric.Complexity.ToString(), declarationType); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/FunctionNestingDepth.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class FunctionNestingDepth : FunctionNestingDepthBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var walker = new NestingDepthWalker(Maximum, token => c.ReportIssue(rule, token, Maximum.ToString())); walker.SafeVisit(c.Node); }, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.AddAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.GlobalStatement); private sealed class NestingDepthWalker : SafeCSharpSyntaxWalker { private readonly NestingDepthCounter counter; public NestingDepthWalker(int maximumNestingDepth, Action actionMaximumExceeded) => counter = new NestingDepthCounter(maximumNestingDepth, actionMaximumExceeded); public override void VisitIfStatement(IfStatementSyntax node) { var isPartOfChainedElseIfClause = node.Parent != null && node.Parent.IsKind(SyntaxKind.ElseClause); if (isPartOfChainedElseIfClause) { base.VisitIfStatement(node); } else { counter.CheckNesting(node.IfKeyword, () => base.VisitIfStatement(node)); } } public override void VisitForStatement(ForStatementSyntax node) => counter.CheckNesting(node.ForKeyword, () => base.VisitForStatement(node)); public override void VisitForEachStatement(ForEachStatementSyntax node) => counter.CheckNesting(node.ForEachKeyword, () => base.VisitForEachStatement(node)); public override void VisitWhileStatement(WhileStatementSyntax node) => counter.CheckNesting(node.WhileKeyword, () => base.VisitWhileStatement(node)); public override void VisitDoStatement(DoStatementSyntax node) => counter.CheckNesting(node.DoKeyword, () => base.VisitDoStatement(node)); public override void VisitSwitchStatement(SwitchStatementSyntax node) => counter.CheckNesting(node.SwitchKeyword, () => base.VisitSwitchStatement(node)); public override void VisitTryStatement(TryStatementSyntax node) => counter.CheckNesting(node.TryKeyword, () => base.VisitTryStatement(node)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericInheritanceShouldNotBeRecursive.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericInheritanceShouldNotBeRecursive : GenericInheritanceShouldNotBeRecursiveBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, }; protected override SyntaxToken GetKeyword(TypeDeclarationSyntax declaration) => declaration.Keyword; protected override Location GetLocation(TypeDeclarationSyntax declaration) => declaration.Identifier.GetLocation(); protected override INamedTypeSymbol GetNamedTypeSymbol(TypeDeclarationSyntax declaration, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(declaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericLoggerInjectionShouldMatchEnclosingType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericLoggerInjectionShouldMatchEnclosingType : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6672"; private const string MessageFormat = "Update this logger to use its enclosing type."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.References(KnownAssembly.MicrosoftExtensionsLoggingAbstractions)) { cc.RegisterNodeAction(c => { var constructor = (ConstructorDeclarationSyntax)c.Node; foreach (var invalidType in InvalidTypeParameters(constructor, c.Model)) { c.ReportIssue(Rule, invalidType); } }, SyntaxKind.ConstructorDeclaration); } }); // Returns T for [Constructor(ILogger logger)] where T is not Constructor private static IEnumerable InvalidTypeParameters(ConstructorDeclarationSyntax constructor, SemanticModel model) { var genericParameters = constructor.ParameterList.Parameters .Where(x => x.Type is GenericNameSyntax generic && generic.TypeArgumentList.Arguments.Count == 1) .Select(x => (GenericNameSyntax)x.Type) .ToArray(); if (genericParameters.Length == 0) { yield break; } var constructorType = model.GetDeclaredSymbol(constructor)?.ContainingType; foreach (var generic in genericParameters) { var genericArgument = generic.TypeArgumentList.Arguments[0]; if (IsGenericLogger(model.GetTypeInfo(generic).Type) // ILogger && !model.GetTypeInfo(genericArgument).Type.Equals(constructorType)) // T { yield return genericArgument; } } } private static bool IsGenericLogger(ITypeSymbol type) => type.Is(KnownType.Microsoft_Extensions_Logging_ILogger_TCategoryName) || type.Implements(KnownType.Microsoft_Extensions_Logging_ILogger_TCategoryName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericReadonlyFieldPropertyAssignment.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericReadonlyFieldPropertyAssignment : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2934"; private const string MessageFormat = "Restrict '{0}' to be a reference type or remove this assignment of '{1}'; it is useless if '{0}' is a value type."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var assignment = (AssignmentExpressionSyntax)c.Node; var expression = assignment.Left; ProcessPropertyChange(c, c.Model, expression); }, SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression); context.RegisterNodeAction(c => { var unary = (PrefixUnaryExpressionSyntax)c.Node; ProcessPropertyChange(c, c.Model, unary.Operand); }, SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression); context.RegisterNodeAction(c => { var unary = (PostfixUnaryExpressionSyntax)c.Node; ProcessPropertyChange(c, c.Model, unary.Operand); }, SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression); } private static void ProcessPropertyChange(SonarSyntaxNodeReportingContext context, SemanticModel semanticModel, ExpressionSyntax expression) { if (TupleExpressionSyntaxWrapper.IsInstance(expression)) { foreach (var tupleArgument in ((TupleExpressionSyntaxWrapper)expression).Arguments) { ProcessPropertyChange(context, semanticModel, tupleArgument.Expression); } } else if (SymbolFromMemberAccess(expression) is IFieldSymbol fieldSymbol && IsFieldReadonlyAndPossiblyValueType(fieldSymbol) && !IsInsideConstructorDeclaration(expression, fieldSymbol.ContainingType, semanticModel) && semanticModel.GetSymbolInfo(expression).Symbol is IPropertySymbol propertySymbol) { context.ReportIssue(Rule, expression, fieldSymbol.Name, propertySymbol.Name); } ISymbol SymbolFromMemberAccess(ExpressionSyntax expression) { var targetExpression = expression switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Expression, MemberBindingExpressionSyntax memberBinding when memberBinding.Parent?.Parent is ConditionalAccessExpressionSyntax conditionalAccess => conditionalAccess.Expression, _ => null }; return targetExpression is null ? null : semanticModel.GetSymbolInfo(targetExpression).Symbol; } } private static bool IsFieldReadonlyAndPossiblyValueType(IFieldSymbol fieldSymbol) => fieldSymbol is { IsReadOnly: true } && GenericParameterMightBeValueType(fieldSymbol.Type as ITypeParameterSymbol); private static bool IsInsideConstructorDeclaration(ExpressionSyntax expression, INamedTypeSymbol currentType, SemanticModel semanticModel) => semanticModel.GetEnclosingSymbol(expression.SpanStart) is IMethodSymbol { MethodKind: MethodKind.Constructor } constructorSymbol && constructorSymbol.ContainingType.Equals(currentType); private static bool GenericParameterMightBeValueType(ITypeParameterSymbol typeParameterSymbol) => typeParameterSymbol is { HasReferenceTypeConstraint: false, HasValueTypeConstraint: false, // CS1648 is raised, if constrained by 'struct'. ConstraintTypes: { } constraintTypes } && constraintTypes.All(MightBeValueType); private static bool MightBeValueType(ITypeSymbol type) => type.IsInterface() || GenericParameterMightBeValueType(type as ITypeParameterSymbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericReadonlyFieldPropertyAssignmentCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class GenericReadonlyFieldPropertyAssignmentCodeFix : SonarCodeFix { internal const string TitleRemove = "Remove assignment"; internal const string TitleAddClassConstraint = "Add reference type constraint"; private static readonly SyntaxAnnotation Annotation = new(nameof(GenericReadonlyFieldPropertyAssignmentCodeFix)); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GenericReadonlyFieldPropertyAssignment.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); var memberExpression = node switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Expression, MemberBindingExpressionSyntax { Parent.Parent: ConditionalAccessExpressionSyntax conditionalAccess } => conditionalAccess.Expression, _ => null }; if (memberExpression is null) { return; } var model = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var fieldSymbol = (IFieldSymbol)model.GetSymbolInfo(memberExpression).Symbol; var typeParameterSymbol = (ITypeParameterSymbol)fieldSymbol.Type; var genericType = typeParameterSymbol.ContainingType; var classDeclarationTasks = genericType.DeclaringSyntaxReferences .Select(x => x.GetSyntaxAsync(context.Cancel)) .ToList(); var taskResults = await Task.WhenAll(classDeclarationTasks).ConfigureAwait(false); var classDeclarations = taskResults.OfType().ToList(); if (classDeclarations.Any()) { context.RegisterCodeFix( TitleAddClassConstraint, async x => { var currentSolution = context.Document.Project.Solution; var mapping = DocumentIdClassDeclarationMapping(classDeclarations, currentSolution); foreach (var classes in mapping) { var document = currentSolution.GetDocument(classes.Key); var docRoot = await document.GetSyntaxRootAsync(context.Cancel).ConfigureAwait(false); var newDocRoot = NewDocumentRoot(docRoot, typeParameterSymbol, classes); currentSolution = currentSolution.WithDocumentSyntaxRoot(classes.Key, newDocRoot); } return currentSolution; }, context.Diagnostics); } if (node is { Parent: ExpressionSyntax { Parent: StatementSyntax statement } }) { context.RegisterCodeFix( TitleRemove, x => { var newRoot = root.RemoveNode(statement, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } } private static MultiValueDictionary DocumentIdClassDeclarationMapping(IEnumerable classDeclarations, Solution currentSolution) { var mapping = new MultiValueDictionary(); foreach (var classDeclaration in classDeclarations) { var documentId = currentSolution.GetDocument(classDeclaration.SyntaxTree).Id; mapping.AddWithKey(documentId, classDeclaration); } return mapping; } private static SyntaxNode NewDocumentRoot(SyntaxNode docRoot, ITypeParameterSymbol typeParameterSymbol, KeyValuePair> classes) { var newDocRoot = docRoot.ReplaceNodes(classes.Value, (_, rewritten) => rewritten.WithAdditionalAnnotations(Annotation)); var annotatedNodes = newDocRoot.GetAnnotatedNodes(Annotation).ToList(); while (annotatedNodes.Any()) { var classDeclaration = (ClassDeclarationSyntax)annotatedNodes[0]; var constraintClauses = NewConstraintClause(classDeclaration.ConstraintClauses, typeParameterSymbol.Name); newDocRoot = newDocRoot.ReplaceNode(classDeclaration, classDeclaration.WithConstraintClauses(constraintClauses).WithoutAnnotations(Annotation)); annotatedNodes = newDocRoot.GetAnnotatedNodes(Annotation).ToList(); } return newDocRoot; } private static SyntaxList NewConstraintClause(SyntaxList constraintClauses, string typeParameterName) { var constraintList = SyntaxFactory.List(); foreach (var constraint in constraintClauses) { var currentConstraint = constraint; if (currentConstraint.Name.Identifier.ValueText == typeParameterName && !currentConstraint.Constraints.AnyOfKind(SyntaxKind.ClassConstraint)) { currentConstraint = currentConstraint .WithConstraints(currentConstraint.Constraints.Insert(0, SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint))) .WithAdditionalAnnotations(Formatter.Annotation); } constraintList = constraintList.Add(currentConstraint); } return constraintList; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericTypeParameterEmptinessChecking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericTypeParameterEmptinessChecking : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2955"; private const string MessageFormat = "Use a comparison to 'default({0})' instead or add a constraint to '{0}' so that it can't be a value type."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var equalsExpression = (BinaryExpressionSyntax)c.Node; var leftIsNull = CSharpEquivalenceChecker.AreEquivalent(equalsExpression.Left, SyntaxConstants.NullLiteralExpression); var rightIsNull = CSharpEquivalenceChecker.AreEquivalent(equalsExpression.Right, SyntaxConstants.NullLiteralExpression); if (!(leftIsNull ^ rightIsNull)) { return; } var expressionToTypeCheck = leftIsNull ? equalsExpression.Right : equalsExpression.Left; if (c.Model.GetTypeInfo(expressionToTypeCheck).Type is ITypeParameterSymbol { HasReferenceTypeConstraint: false } typeInfo && !typeInfo.ConstraintTypes.OfType().Any() && !typeInfo.ConstraintTypes.Any(typeSymbol => typeSymbol.IsReferenceType && typeSymbol.IsClass())) { var expressionToReportOn = leftIsNull ? equalsExpression.Left : equalsExpression.Right; c.ReportIssue(Rule, expressionToReportOn, typeInfo.Name); } }, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericTypeParameterEmptinessCheckingCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class GenericTypeParameterEmptinessCheckingCodeFix : SonarCodeFix { internal const string Title = "Change null checking"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GenericTypeParameterEmptinessChecking.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); var binary = (BinaryExpressionSyntax)syntaxNode.Parent; var otherNode = binary.Left == syntaxNode ? binary.Right : binary.Left; var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var typeSymbol = (ITypeParameterSymbol)semanticModel.GetTypeInfo(otherNode).Type; var defaultExpression = SyntaxFactory.DefaultExpression(SyntaxFactory.ParseTypeName(typeSymbol.Name)); ExpressionSyntax newNode = SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), SyntaxFactory.IdentifierName("Equals")), SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(otherNode), SyntaxFactory.Argument(defaultExpression) }))); if (binary.IsKind(SyntaxKind.NotEqualsExpression)) { newNode = SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, newNode); } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode(binary, newNode.WithTriviaFrom(binary)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericTypeParameterInOut.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericTypeParameterInOut : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3246"; private const string MessageFormat = "Add the '{0}' keyword to parameter '{1}' to make it '{2}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => CheckInterfaceVariance(c, (InterfaceDeclarationSyntax)c.Node), SyntaxKind.InterfaceDeclaration); context.RegisterNodeAction(c => CheckDelegateVariance(c, (DelegateDeclarationSyntax)c.Node), SyntaxKind.DelegateDeclaration); } #region Top level private static void CheckInterfaceVariance(SonarSyntaxNodeReportingContext context, InterfaceDeclarationSyntax declaration) { var interfaceType = context.Model.GetDeclaredSymbol(declaration); if (interfaceType == null) { return; } foreach (var typeParameter in interfaceType.TypeParameters .Where(typeParameter => typeParameter.Variance == VarianceKind.None)) { var canBeIn = CheckTypeParameter(typeParameter, VarianceKind.In, interfaceType); var canBeOut = CheckTypeParameter(typeParameter, VarianceKind.Out, interfaceType); if (canBeIn ^ canBeOut) { ReportIssue(context, typeParameter, canBeIn ? VarianceKind.In : VarianceKind.Out); } } } private static void CheckDelegateVariance(SonarSyntaxNodeReportingContext context, DelegateDeclarationSyntax declaration) { var declaredSymbol = context.Model.GetDeclaredSymbol(declaration); if (declaredSymbol == null) { return; } var returnType = context.Model.GetTypeInfo(declaration.ReturnType).Type; if (returnType == null) { return; } var parameterSymbols = declaration.ParameterList == null ? ImmutableArray.Empty : declaration.ParameterList.Parameters .Select(p => context.Model.GetDeclaredSymbol(p)) .ToImmutableArray(); if (parameterSymbols.Any(parameter => parameter == null)) { return; } foreach (var typeParameter in declaredSymbol.TypeParameters .Where(typeParameter => typeParameter.Variance == VarianceKind.None)) { var canBeIn = CheckTypeParameter(typeParameter, VarianceKind.In, returnType, parameterSymbols); var canBeOut = CheckTypeParameter(typeParameter, VarianceKind.Out, returnType, parameterSymbols); if (canBeIn ^ canBeOut) { ReportIssue(context, typeParameter, canBeIn ? VarianceKind.In : VarianceKind.Out); } } } #endregion #region Top level per type parameter private static bool CheckTypeParameter(ITypeParameterSymbol typeParameter, VarianceKind variance, ITypeSymbol returnType, ImmutableArray parameters) { var canBe = CheckTypeParameterConstraintsInSymbol(typeParameter, variance); if (!canBe) { return false; } canBe = CanTypeParameterBeVariant(typeParameter, variance, returnType, true, false); if (!canBe) { return false; } canBe = CheckTypeParameterInParameters(typeParameter, variance, parameters); return canBe; } private static bool CheckTypeParameter(ITypeParameterSymbol typeParameter, VarianceKind variance, ITypeSymbol interfaceType) { if (typeParameter.Variance != VarianceKind.None) { return false; } foreach (var baseInterface in interfaceType.AllInterfaces) { var canBeVariant = CanTypeParameterBeVariant( typeParameter, variance, baseInterface, true, false); if (!canBeVariant) { return false; } } foreach (var member in interfaceType.GetMembers()) { bool canBeVariant; if (member.Kind == SymbolKind.Method) { canBeVariant = CheckTypeParameterInMethod(typeParameter, variance, (IMethodSymbol)member); if (!canBeVariant) { return false; } } else if (member.Kind == SymbolKind.Event) { canBeVariant = CheckTypeParameterInEvent(typeParameter, variance, (IEventSymbol)member); if (!canBeVariant) { return false; } } } return true; } #endregion private static void ReportIssue(SonarSyntaxNodeReportingContext context, ITypeParameterSymbol typeParameter, VarianceKind variance) { if (!typeParameter.DeclaringSyntaxReferences.Any()) { return; } var location = typeParameter.DeclaringSyntaxReferences.First().GetSyntax().GetLocation(); if (variance == VarianceKind.In) { context.ReportIssue(Rule, location, "in", typeParameter.Name, "contravariant"); return; } if (variance == VarianceKind.Out) { context.ReportIssue(Rule, location, "out", typeParameter.Name, "covariant"); } } #region Check type parameters method/event/parameters private static bool CheckTypeParameterInMethod(ITypeParameterSymbol typeParameter, VarianceKind variance, IMethodSymbol method) { var canBe = CheckTypeParameterConstraintsInSymbol(typeParameter, variance); if (!canBe) { return false; } canBe = CanTypeParameterBeVariant( typeParameter, variance, method.ReturnType, true, false); if (!canBe) { return false; } return CheckTypeParameterInParameters(typeParameter, variance, method.Parameters); } private static bool CheckTypeParameterInEvent(ITypeParameterSymbol typeParameter, VarianceKind variance, IEventSymbol @event) => CanTypeParameterBeVariant(typeParameter, variance, @event.Type, false, true); private static bool CheckTypeParameterInParameters(ITypeParameterSymbol typeParameter, VarianceKind variance, ImmutableArray parameters) { foreach (var param in parameters) { var canBe = CanTypeParameterBeVariant(typeParameter, variance, param.Type, param.RefKind != RefKind.None, true); if (!canBe) { return false; } } return true; } private static bool CheckTypeParameterConstraintsInSymbol(ITypeParameterSymbol typeParameter, VarianceKind variance) { foreach (var constraintType in typeParameter.ConstraintTypes) { var canBe = CanTypeParameterBeVariant(typeParameter, variance, constraintType, false, true); if (!canBe) { return false; } } return true; } #endregion #region Check type parameter variance low level private static bool CanTypeParameterBeVariant(ITypeParameterSymbol parameter, VarianceKind variance, ITypeSymbol type, bool requireOutputSafety, bool requireInputSafety) { switch (type.Kind) { case SymbolKind.TypeParameter: var typeParam = (ITypeParameterSymbol)type; if (!typeParam.Equals(parameter)) { return true; } return !((requireInputSafety && requireOutputSafety && variance != VarianceKind.None) || (requireOutputSafety && variance == VarianceKind.In) || (requireInputSafety && variance == VarianceKind.Out)); case SymbolKind.ArrayType: return CanTypeParameterBeVariant(parameter, variance, ((IArrayTypeSymbol)type).ElementType, requireOutputSafety, requireInputSafety); case SymbolKind.ErrorType: case SymbolKind.NamedType: return CanTypeParameterBeVariant(parameter, variance, (INamedTypeSymbol)type, requireOutputSafety, requireInputSafety); default: return true; } } private static bool CanTypeParameterBeVariant(ITypeParameterSymbol parameter, VarianceKind variance, INamedTypeSymbol namedType, bool requireOutputSafety, bool requireInputSafety) { switch (namedType.TypeKind) { case TypeKind.Class: case TypeKind.Struct: case TypeKind.Enum: case TypeKind.Interface: case TypeKind.Delegate: case TypeKind.Error: break; default: return true; } if (namedType.IsTupleType()) { return false; } var currentNamedType = namedType; while (currentNamedType != null) { for (var i = 0; i < currentNamedType.Arity; i++) { var typeParam = currentNamedType.TypeParameters[i]; var typeArg = currentNamedType.TypeArguments[i]; if (!typeArg.Equals(parameter)) { return false; } bool requireOut; bool requireIn; switch (typeParam.Variance) { case VarianceKind.Out: requireOut = requireOutputSafety; requireIn = requireInputSafety; break; case VarianceKind.In: requireOut = requireInputSafety; requireIn = requireOutputSafety; break; case VarianceKind.None: requireIn = true; requireOut = true; break; default: throw new NotSupportedException(); } if (!CanTypeParameterBeVariant(parameter, variance, typeArg, requireOut, requireIn)) { return false; } } currentNamedType = currentNamedType.ContainingType; } return true; } #endregion } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericTypeParameterUnused.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericTypeParameterUnused : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2326"; private const string MessageFormat = "'{0}' is not used in the {1}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly SyntaxKind[] MethodModifiersToSkip = { SyntaxKind.AbstractKeyword, SyntaxKind.VirtualKeyword, SyntaxKind.OverrideKeyword }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { if (c.Model.GetDeclaredSymbol(c.Node) is { } declarationSymbol) { CheckGenericTypeParameters(c, declarationSymbol); } }, SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction(c => { if (!c.IsRedundantPositionalRecordContext()) { CheckGenericTypeParameters(c, c.ContainingSymbol); } }, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); } private static void CheckGenericTypeParameters(SonarSyntaxNodeReportingContext c, ISymbol symbol) { var info = CreateParametersInfo(c); if (info?.Parameters is null || info.Parameters.Parameters.Count == 0) { return; } var typeParameterNames = info.Parameters.Parameters.Select(x => x.Identifier.Text).ToArray(); var usedTypeParameters = GetUsedTypeParameters(c, symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()), typeParameterNames).ToHashSet(); foreach (var typeParameter in typeParameterNames.Where(x => !usedTypeParameters.Contains(x))) { c.ReportIssue(Rule, info.Parameters.Parameters.First(x => x.Identifier.Text == typeParameter), typeParameter, info.ContainerName); } } private static ParametersInfo CreateParametersInfo(SonarSyntaxNodeReportingContext c) => c.Node switch { InterfaceDeclarationSyntax interfaceDeclaration => new ParametersInfo(interfaceDeclaration.TypeParameterList, "interface"), ClassDeclarationSyntax classDeclaration => new ParametersInfo(classDeclaration.TypeParameterList, "class"), StructDeclarationSyntax structDeclaration => new ParametersInfo(structDeclaration.TypeParameterList, "struct"), MethodDeclarationSyntax methodDeclaration when IsMethodCandidate(methodDeclaration, c.Model) => new ParametersInfo(methodDeclaration.TypeParameterList, "method"), var wrapper when LocalFunctionStatementSyntaxWrapper.IsInstance(wrapper) => new ParametersInfo(((LocalFunctionStatementSyntaxWrapper)c.Node).TypeParameterList, "local function"), var wrapper when RecordDeclarationSyntaxWrapper.IsInstance(wrapper) => new ParametersInfo(((RecordDeclarationSyntaxWrapper)c.Node).TypeParameterList, "record"), _ => null }; private static bool IsMethodCandidate(MethodDeclarationSyntax methodDeclaration, SemanticModel semanticModel) => !methodDeclaration.Modifiers.Any(x => MethodModifiersToSkip.Contains(x.Kind())) && methodDeclaration.ExplicitInterfaceSpecifier is null && methodDeclaration.HasBodyOrExpressionBody() && semanticModel.GetDeclaredSymbol(methodDeclaration) is { } methodSymbol && methodSymbol.IsChangeable(); private static List GetUsedTypeParameters(SonarSyntaxNodeReportingContext context, IEnumerable declarations, string[] typeParameterNames) => declarations.SelectMany(x => x.DescendantNodes()) .OfType() .Where(x => x.Parent is not TypeParameterConstraintClauseSyntax && typeParameterNames.Contains(x.Identifier.ValueText)) .Select(x => x.EnsureCorrectSemanticModelOrDefault(context.Model)?.GetSymbolInfo(x).Symbol) .Where(x => x is { Kind: SymbolKind.TypeParameter }) .Select(x => x.Name) .ToList(); private sealed record ParametersInfo(TypeParameterListSyntax Parameters, string ContainerName); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GenericTypeParametersRequired.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GenericTypeParametersRequired : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4018"; private const string MessageFormat = "Refactor this method to use all type parameters in the parameter list to enable type inference."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var typeParameters = methodDeclaration .TypeParameterList ?.Parameters .Select(p => c.Model.GetDeclaredSymbol(p)); if (typeParameters == null) { return; } var argumentTypes = methodDeclaration .ParameterList .Parameters .Select(p => c.Model.GetDeclaredSymbol(p)?.Type); var typeParametersInArguments = new HashSet(); foreach (var argumentType in argumentTypes) { AddTypeParameters(argumentType, typeParametersInArguments); } if (typeParameters.Except(typeParametersInArguments).Any()) { c.ReportIssue(Rule, methodDeclaration.Identifier); } }, SyntaxKind.MethodDeclaration); private static void AddTypeParameters(ITypeSymbol argumentSymbol, ISet set) { var localArgumentSymbol = argumentSymbol; if (localArgumentSymbol is IArrayTypeSymbol arrayTypeSymbol) { localArgumentSymbol = arrayTypeSymbol.ElementType; } if (localArgumentSymbol.Is(TypeKind.TypeParameter)) { set.Add(localArgumentSymbol as ITypeParameterSymbol); } if (localArgumentSymbol is INamedTypeSymbol namedSymbol) { foreach (var typeParam in namedSymbol.TypeArguments) { AddTypeParameters(typeParam, set); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GetHashCodeEqualsOverride.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GetHashCodeEqualsOverride : SonarDiagnosticAnalyzer { internal const string EqualsName = "Equals"; private const string DiagnosticId = "S3249"; private const string MessageFormat = "Remove this 'base' call to 'object.{0}', which is directly based on the object reference."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet MethodNames = new HashSet { "GetHashCode", EqualsName }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCodeBlockStartAction( cb => { if (!(cb.CodeBlock is MethodDeclarationSyntax methodDeclaration)) { return; } if (methodDeclaration.AttributeLists.Any() || !(cb.OwningSymbol is IMethodSymbol methodSymbol) || !MethodIsRelevant(methodSymbol, MethodNames) // this rule should not apply for records since Equals and GetHashCode are value-based || RecordDeclarationSyntaxWrapper.IsInstance(methodDeclaration.Ancestors().FirstOrDefault(node => node is TypeDeclarationSyntax))) { return; } var locations = new List(); cb.RegisterNodeAction( c => { if (TryGetLocationFromInvocationInsideMethod(c, methodSymbol, out var location)) { locations.Add(location); } }, SyntaxKind.InvocationExpression); cb.RegisterCodeBlockEndAction( c => { if (!locations.Any()) { return; } var firstPosition = locations.Select(loc => loc.SourceSpan.Start).Min(); var location = locations.First(loc => loc.SourceSpan.Start == firstPosition); c.ReportIssue(Rule, location, methodSymbol.Name); }); }); internal static bool IsEqualsCallInGuardCondition(InvocationExpressionSyntax invocation, IMethodSymbol invokedMethod) => invokedMethod.Name == EqualsName && invocation.Parent is IfStatementSyntax ifStatement && ifStatement.Condition == invocation && IfStatementWithSingleReturnTrue(ifStatement); internal static bool MethodIsRelevant(ISymbol symbol, ISet methodNames) => methodNames.Contains(symbol.Name) && symbol.IsOverride; private static bool TryGetLocationFromInvocationInsideMethod(SonarSyntaxNodeReportingContext context, ISymbol symbol, out Location location) { location = null; var invocation = (InvocationExpressionSyntax)context.Node; if (!(context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol invokedMethod) || invokedMethod.Name != symbol.Name || !invocation.IsOnBase()) { return false; } if (invokedMethod.IsInType(KnownType.System_Object) && !IsEqualsCallInGuardCondition(invocation, invokedMethod)) { location = invocation.GetLocation(); return true; } return false; } private static bool IfStatementWithSingleReturnTrue(IfStatementSyntax ifStatement) { var statement = ifStatement.Statement; var returnStatement = statement as ReturnStatementSyntax; if (statement is BlockSyntax block) { if (!block.Statements.Any()) { return false; } returnStatement = block.Statements.First() as ReturnStatementSyntax; } return returnStatement?.Expression != null && CSharpEquivalenceChecker.AreEquivalent(returnStatement.Expression, SyntaxConstants.TrueLiteralExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GetHashCodeMutable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GetHashCodeMutable : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2328"; private const string IssueMessage = "Refactor 'GetHashCode' to not reference mutable fields."; private const string SecondaryMessageFormat = "Remove this use of '{0}' or make it 'readonly'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, IssueMessage); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var methodSyntax = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodSyntax); if (methodSymbol.ContainingType.IsValueType || !methodSymbol.IsObjectGetHashCode()) { return; } ImmutableArray baseMembers; try { baseMembers = c.Model.LookupBaseMembers(methodSyntax.SpanStart); } catch (ArgumentException) { // this is expected on invalid code return; } var fieldsOfClass = baseMembers .Concat(methodSymbol.ContainingType.GetMembers()) .Select(symbol => symbol as IFieldSymbol) .WhereNotNull() .ToHashSet(); var identifiers = methodSyntax.DescendantNodes().OfType(); var secondaryLocations = GetAllFirstMutableFieldsUsed(c, fieldsOfClass, identifiers).Select(CreateSecondaryLocation).ToArray(); if (secondaryLocations.Any()) { c.ReportIssue(Rule, methodSyntax.Identifier, secondaryLocations); } }, SyntaxKind.MethodDeclaration); private static SecondaryLocation CreateSecondaryLocation(SimpleNameSyntax identifierSyntax) => new(identifierSyntax.GetLocation(), string.Format(SecondaryMessageFormat, identifierSyntax.Identifier.Text)); private static IEnumerable GetAllFirstMutableFieldsUsed(SonarSyntaxNodeReportingContext context, ICollection fieldsOfClass, IEnumerable identifiers) { var syntaxNodes = new Dictionary>(); foreach (var identifier in identifiers) { if (context.Model.GetSymbolInfo(identifier).Symbol is not IFieldSymbol identifierSymbol) { continue; } if (!syntaxNodes.ContainsKey(identifierSymbol)) { if (!IsFieldRelevant(identifierSymbol, fieldsOfClass)) { continue; } syntaxNodes.Add(identifierSymbol, []); } syntaxNodes[identifierSymbol].Add(identifier); } return syntaxNodes.Values .Select(identifierReferences => identifierReferences.OrderBy(id => id.SpanStart).FirstOrDefault()) .WhereNotNull(); } private static bool IsFieldRelevant(IFieldSymbol fieldSymbol, ICollection fieldsOfClass) => fieldSymbol is { IsConst: false, IsReadOnly: false } && fieldsOfClass.Contains(fieldSymbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GetHashCodeMutableCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Editing; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class GetHashCodeMutableCodeFix : SonarCodeFix { internal const string Title = "Make field 'readonly'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GetHashCodeMutable.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var identifiersToFix = diagnostic.AdditionalLocations .Select(location => location.SourceSpan) .Select(diagnosticSpan => root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) as IdentifierNameSyntax) .WhereNotNull() .ToList(); if (identifiersToFix.Count == 0) { return; } var semanticModel = await context.Document .GetSemanticModelAsync(context.Cancel) .ConfigureAwait(false); var allFieldDeclarationTasks = identifiersToFix.Select(identifier => GetFieldDeclarationSyntaxAsync(semanticModel, identifier, context.Cancel)); var allFieldDeclarations = await Task.WhenAll(allFieldDeclarationTasks).ConfigureAwait(false); allFieldDeclarations = allFieldDeclarations.WhereNotNull().ToArray(); context.RegisterCodeFix( Title, c => AddReadonlyToFieldDeclarationsAsync(context.Document, c, allFieldDeclarations), context.Diagnostics); } private static async Task GetFieldDeclarationSyntaxAsync(SemanticModel semanticModel, IdentifierNameSyntax identifierName, CancellationToken cancel) { if (!(semanticModel.GetSymbolInfo(identifierName).Symbol is IFieldSymbol fieldSymbol) || !fieldSymbol.DeclaringSyntaxReferences.Any()) { return null; } var reference = await fieldSymbol.DeclaringSyntaxReferences.First() .GetSyntaxAsync(cancel) .ConfigureAwait(false); var fieldDeclaration = (FieldDeclarationSyntax)reference.Parent.Parent; if (fieldDeclaration.Declaration.Variables.Count != 1) { return null; } return fieldDeclaration; } private static async Task AddReadonlyToFieldDeclarationsAsync(Document document, CancellationToken cancel, IEnumerable fieldDeclarations) { var editor = await DocumentEditor.CreateAsync(document, cancel); foreach (var fieldDeclaration in fieldDeclarations) { var readonlyToken = SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword).WithTrailingTrivia(SyntaxFactory.Space); var newFieldDeclaration = fieldDeclaration.AddModifiers(readonlyToken); editor.ReplaceNode(fieldDeclaration, newFieldDeclaration); } return editor.GetChangedDocument(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GetTypeWithIsAssignableFrom.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GetTypeWithIsAssignableFrom : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2219"; internal const string UseIsOperatorKey = "UseIsOperator"; internal const string ShouldRemoveGetTypeKey = "ShouldRemoveGetType"; private const string MessageFormat = "Use {0} instead."; private const string MessageIsOperator = "the 'is' operator"; private const string MessageIsInstanceOfType = "the 'IsInstanceOfType()' method"; private const string MessageNullCheck = "a 'null' check"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.Expression is MemberAccessExpressionSyntax memberAccess && invocation.HasExactlyNArguments(1) && memberAccess.Name.Identifier.ValueText is var methodName && (methodName == "IsInstanceOfType" || methodName == "IsAssignableFrom") && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsInType(KnownType.System_Type)) { CheckForIsAssignableFrom(c, memberAccess, methodSymbol, invocation.ArgumentList.Arguments.First().Expression); CheckForIsInstanceOfType(c, memberAccess, methodSymbol); } }, SyntaxKind.InvocationExpression); context.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; CheckGetTypeAndTypeOfEquality(c, binary.Left, binary.Right); CheckGetTypeAndTypeOfEquality(c, binary.Right, binary.Left); CheckAsOperatorComparedToNull(c, binary.Left, binary.Right); CheckAsOperatorComparedToNull(c, binary.Right, binary.Left); }, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); context.RegisterNodeAction(c => { var isExpression = (BinaryExpressionSyntax)c.Node; if (c.Model.GetTypeInfo(isExpression.Left).Type is var objectToCast && objectToCast.IsClass() && c.Model.GetSymbolInfo(isExpression.Right).Symbol is INamedTypeSymbol namedSymbol && namedSymbol.GetSymbolType() is var typeCastTo && typeCastTo.IsClass() && !typeCastTo.Is(KnownType.System_Object) && objectToCast.DerivesOrImplements(typeCastTo)) { ReportDiagnostic(c, MessageNullCheck); } }, SyntaxKind.IsExpression); context.RegisterNodeAction(c => { var isPattern = (IsPatternExpressionSyntaxWrapper)c.Node; if (ConstantPatternExpression(isPattern.Pattern) is { } constantExpression) { CheckAsOperatorComparedToNull(c, isPattern.Expression, constantExpression); } }, SyntaxKindEx.IsPatternExpression); } private static void CheckAsOperatorComparedToNull(SonarSyntaxNodeReportingContext context, ExpressionSyntax sideA, ExpressionSyntax sideB) { if (sideA.RemoveParentheses().IsKind(SyntaxKind.AsExpression) && sideB.RemoveParentheses().IsKind(SyntaxKind.NullLiteralExpression)) { ReportDiagnostic(context, MessageIsOperator); } } private static void CheckGetTypeAndTypeOfEquality(SonarSyntaxNodeReportingContext context, ExpressionSyntax sideA, ExpressionSyntax sideB) { if (sideA.ToStringContains("GetType") && sideB is TypeOfExpressionSyntax sideBTypeOf && (sideA as InvocationExpressionSyntax).IsGetTypeCall(context.Model) && context.Model.GetTypeInfo(sideBTypeOf.Type).Type is { } typeSymbol // Can be null for empty identifier from 'typeof' unfinished syntax && typeSymbol.IsSealed && !typeSymbol.OriginalDefinition.Is(KnownType.System_Nullable_T)) { ReportDiagnostic(context, MessageIsOperator); } } private static void CheckForIsInstanceOfType(SonarSyntaxNodeReportingContext context, MemberAccessExpressionSyntax memberAccess, IMethodSymbol methodSymbol) { if (methodSymbol.Name == nameof(Type.IsInstanceOfType) && memberAccess.Expression is TypeOfExpressionSyntax typeOf && !IsUnboundedGenericType(typeOf)) { ReportDiagnostic(context, MessageIsOperator, useIsOperator: true); } } private static void CheckForIsAssignableFrom(SonarSyntaxNodeReportingContext context, MemberAccessExpressionSyntax memberAccess, IMethodSymbol methodSymbol, ExpressionSyntax argument) { if (methodSymbol.Name == nameof(Type.IsAssignableFrom) && (argument as InvocationExpressionSyntax).IsGetTypeCall(context.Model)) { if (memberAccess.Expression is TypeOfExpressionSyntax typeOf) { if (!IsUnboundedGenericType(typeOf)) { ReportDiagnostic(context, MessageIsOperator, useIsOperator: true, shouldRemoveGetType: true); } } else { ReportDiagnostic(context, MessageIsInstanceOfType, shouldRemoveGetType: true); } } } private static bool IsUnboundedGenericType(TypeOfExpressionSyntax typeOf) => typeOf.Type switch { GenericNameSyntax { IsUnboundGenericName: true } => true, QualifiedNameSyntax { Right: GenericNameSyntax { IsUnboundGenericName: true } } => true, _ => false, }; private static ExpressionSyntax ConstantPatternExpression(SyntaxNode node) => node.Kind() switch { SyntaxKindEx.ConstantPattern => ((ConstantPatternSyntaxWrapper)node).Expression, SyntaxKindEx.NotPattern => ConstantPatternExpression(((UnaryPatternSyntaxWrapper)node).Pattern), _ => null }; private static void ReportDiagnostic(SonarSyntaxNodeReportingContext context, string messageArg, bool useIsOperator = false, bool shouldRemoveGetType = false) { var properties = ImmutableDictionary.Empty .Add(UseIsOperatorKey, useIsOperator.ToString()) .Add(ShouldRemoveGetTypeKey, shouldRemoveGetType.ToString()); context.ReportIssue(Rule, context.Node, properties, messageArg); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GetTypeWithIsAssignableFromCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class GetTypeWithIsAssignableFromCodeFix : SonarCodeFix { private const string Title = "Simplify type checking"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(GetTypeWithIsAssignableFrom.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); if (NewRoot(root, diagnostic, node) is { } newRoot) { context.RegisterCodeFix(Title, c => Task.FromResult(context.Document.WithSyntaxRoot(newRoot)), context.Diagnostics); } return Task.CompletedTask; } private static SyntaxNode NewRoot(SyntaxNode root, Diagnostic diagnostic, SyntaxNode node) => node switch { InvocationExpressionSyntax invocation => ChangeInvocation(root, diagnostic, invocation), BinaryExpressionSyntax binary => ChangeBinary(root, binary), var _ when node.IsKind(SyntaxKindEx.IsPatternExpression) => ChangeIsPattern(root, (IsPatternExpressionSyntaxWrapper)node), _ => null }; private static SyntaxNode ChangeInvocation(SyntaxNode root, Diagnostic diagnostic, InvocationExpressionSyntax invocation) { var useIsOperator = bool.Parse(diagnostic.Properties[GetTypeWithIsAssignableFrom.UseIsOperatorKey]); var shouldRemoveGetType = bool.Parse(diagnostic.Properties[GetTypeWithIsAssignableFrom.ShouldRemoveGetTypeKey]); var newNode = RefactoredExpression(invocation, useIsOperator, shouldRemoveGetType); return root.ReplaceNode(invocation, newNode.WithAdditionalAnnotations(Formatter.Annotation)); } private static SyntaxNode ChangeBinary(SyntaxNode root, BinaryExpressionSyntax binary) { if (binary.IsKind(SyntaxKind.IsExpression)) { return ChangeIsExpressionToNullCheck(root, binary); } else if (RefactoredExpression(binary) is { } expression) { return root.ReplaceNode(binary, expression.WithAdditionalAnnotations(Formatter.Annotation)); } else { return null; } } private static SyntaxNode ChangeIsPattern(SyntaxNode root, IsPatternExpressionSyntaxWrapper isPattern) { if (isPattern.Expression is BinaryExpressionSyntax binary) { var negationRequired = true; var current = isPattern.Pattern; while (current.SyntaxNode.IsKind(SyntaxKindEx.NotPattern)) { negationRequired = !negationRequired; current = ((UnaryPatternSyntaxWrapper)current).Pattern; } var newExpression = NegatedExpression(negationRequired, isPattern.SyntaxNode.Parent, GetIsExpression(binary)); return root.ReplaceNode(isPattern, newExpression.WithAdditionalAnnotations(Formatter.Annotation)); } else { return null; } } private static SyntaxNode ChangeIsExpressionToNullCheck(SyntaxNode root, BinaryExpressionSyntax binary) { var newNullCheck = NullCheck(binary); var newExpression = RefactoredExpression(newNullCheck) ?? newNullCheck; // Try to improve nested cases return root.ReplaceNode(binary, ExpressionWithParensIfNeeded(newExpression, binary.Parent).WithAdditionalAnnotations(Formatter.Annotation)); } private static BinaryExpressionSyntax NullCheck(BinaryExpressionSyntax binary) => SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, binary.Left.RemoveParentheses(), SyntaxConstants.NullLiteralExpression); private static ExpressionSyntax RefactoredExpression(BinaryExpressionSyntax binary) { ExpressionSyntax newExpression; var negationRequired = binary.IsKind(SyntaxKind.NotEqualsExpression); if (TryGetTypeOfComparison(binary, out var typeofExpression, out var getTypeSide)) { newExpression = CreateIsExpression(typeofExpression, getTypeSide, shouldRemoveGetType: true); } else if (AsOperatorComparisonToNull(binary) is { } asExpression) { newExpression = GetIsExpression(asExpression); negationRequired = !negationRequired; } else { return null; } return NegatedExpression(negationRequired, binary.Parent, newExpression); } private static ExpressionSyntax RefactoredExpression(InvocationExpressionSyntax invocation, bool useIsOperator, bool shouldRemoveGetType) { var typeInstance = ((MemberAccessExpressionSyntax)invocation.Expression).Expression; var getTypeCallInArgument = invocation.ArgumentList.Arguments.First(); return useIsOperator ? ExpressionWithParensIfNeeded(CreateIsExpression(typeInstance, getTypeCallInArgument.Expression, shouldRemoveGetType), invocation.Parent) : IsInstanceOfTypeCall(invocation, typeInstance, getTypeCallInArgument); } private static ExpressionSyntax NegatedExpression(bool negationRequired, SyntaxNode parent, ExpressionSyntax expression) => negationRequired ? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, SyntaxFactory.ParenthesizedExpression(expression)) : ExpressionWithParensIfNeeded(expression, parent); private static ExpressionSyntax GetIsExpression(BinaryExpressionSyntax asExpression) => SyntaxFactory.BinaryExpression(SyntaxKind.IsExpression, asExpression.Left, asExpression.Right).WithAdditionalAnnotations(Formatter.Annotation); private static BinaryExpressionSyntax AsOperatorComparisonToNull(BinaryExpressionSyntax binary) { var left = binary.Left.RemoveParentheses(); return left.IsKind(SyntaxKind.AsExpression) ? left as BinaryExpressionSyntax : binary.Right.RemoveParentheses() as BinaryExpressionSyntax; } private static bool TryGetTypeOfComparison(BinaryExpressionSyntax binary, out TypeOfExpressionSyntax typeofExpression, out ExpressionSyntax getTypeSide) { typeofExpression = binary.Left as TypeOfExpressionSyntax; getTypeSide = binary.Right; if (typeofExpression == null) { typeofExpression = binary.Right as TypeOfExpressionSyntax; getTypeSide = binary.Left; } return typeofExpression != null; } private static InvocationExpressionSyntax IsInstanceOfTypeCall(InvocationExpressionSyntax invocation, ExpressionSyntax typeInstance, ArgumentSyntax getTypeCallInArgument) => SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, typeInstance, SyntaxFactory.IdentifierName("IsInstanceOfType")).WithTriviaFrom(invocation.Expression), SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(ExpressionFromGetType(getTypeCallInArgument.Expression)).WithTriviaFrom(getTypeCallInArgument) })) .WithTriviaFrom(invocation.ArgumentList)) .WithTriviaFrom(invocation); private static ExpressionSyntax ExpressionWithParensIfNeeded(ExpressionSyntax expression, SyntaxNode parent) => (parent is ExpressionSyntax && !(parent is AssignmentExpressionSyntax) && !(parent is ParenthesizedExpressionSyntax)) ? SyntaxFactory.ParenthesizedExpression(expression) : expression; private static ExpressionSyntax CreateIsExpression(ExpressionSyntax typeInstance, ExpressionSyntax getTypeCall, bool shouldRemoveGetType) => SyntaxFactory.BinaryExpression(SyntaxKind.IsExpression, shouldRemoveGetType ? ExpressionFromGetType(getTypeCall) : getTypeCall, ((TypeOfExpressionSyntax)typeInstance).Type); private static ExpressionSyntax ExpressionFromGetType(ExpressionSyntax getTypeCall) => ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)getTypeCall).Expression).Expression; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GotoStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GotoStatement : GotoStatementBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] GotoSyntaxKinds => new[] { SyntaxKind.GotoStatement, SyntaxKind.GotoCaseStatement, SyntaxKind.GotoDefaultStatement }; protected override string GoToLabel => "goto"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/GuardConditionOnEqualsOverride.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class GuardConditionOnEqualsOverride : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3397"; private const string MessageFormat = "Change this guard condition to call 'object.ReferenceEquals'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ISet MethodNames = new HashSet { GetHashCodeEqualsOverride.EqualsName }; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCodeBlockStartAction( cb => { if (!(cb.OwningSymbol is IMethodSymbol methodSymbol) || !(cb.CodeBlock is MethodDeclarationSyntax) || !GetHashCodeEqualsOverride.MethodIsRelevant(methodSymbol, MethodNames)) { return; } cb.RegisterNodeAction(c => CheckInvocationInsideMethod(c, methodSymbol), SyntaxKind.InvocationExpression); }); private static void CheckInvocationInsideMethod(SonarSyntaxNodeReportingContext context, ISymbol symbol) { var invocation = (InvocationExpressionSyntax)context.Node; if (!(context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol invokedMethod) || invokedMethod.Name != symbol.Name || !invocation.IsOnBase()) { return; } if (!invokedMethod.ContainingType.Is(KnownType.System_Object) && GetHashCodeEqualsOverride.IsEqualsCallInGuardCondition(invocation, invokedMethod)) { context.ReportIssue(Rule, invocation); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/ClearTextProtocolsAreSensitive.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ClearTextProtocolsAreSensitive : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S5332"; private const string MessageFormat = "Using {0} protocol is insecure. Use {1} instead."; private const string EnableSslMessage = "EnableSsl should be set to true."; private const string TelnetKey = "telnet"; private const string EnableSslName = "EnableSsl"; private static readonly DiagnosticDescriptor DefaultRule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor EnableSslRule = DescriptorFactory.Create(DiagnosticId, EnableSslMessage); private static readonly Dictionary RecommendedProtocols = new() { {"telnet", "ssh"}, {"ftp", "sftp, scp or ftps"}, {"http", "https"}, {"clear-text SMTP", "SMTP over SSL/TLS or SMTP with STARTTLS" } }; private static readonly string[] CommonlyUsedXmlDomains = { "www.w3.org", "xml.apache.org", "maven.apache.org", "schemas.xmlsoap.org", "schemas.openxmlformats.org", "rdfs.org", "purl.org", "xmlns.com", "schemas.google.com", "schemas.microsoft.com", "collations.microsoft.com", "a9.com", "ns.adobe.com", "ltsc.ieee.org", "docbook.org", "graphml.graphdrawing.org", "json-schema.org", "www.sitemaps.org", "exslt.org", "docs.oasis-open.org", "ws-i.org", "schemas.android.com", "www.omg.org", "www.opengis.net", "www.itunes.com", }; private static readonly string[] CommonlyUsedExampleDomains = { "example.com", "example.org", "test.com" }; private static readonly string[] LocalhostAddresses = { "localhost", "127.0.0.1", "::1" }; private static readonly KnownType[] AttributesWithNamespaceParameter = new[] { KnownType.System_Windows_Markup_XmlnsPrefixAttribute, KnownType.System_Windows_Markup_XmlnsDefinitionAttribute, KnownType.System_Windows_Markup_XmlnsCompatibleWithAttribute, }; private static readonly CSharpObjectInitializationTracker ObjectInitializationTracker = new(constantValue => constantValue is bool value && value, ImmutableArray.Create(KnownType.System_Net_Mail_SmtpClient, KnownType.System_Net_FtpWebRequest), propertyName => propertyName == EnableSslName); private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(250); private static readonly Regex HttpRegex; private static readonly Regex FtpRegex; private static readonly Regex TelnetRegex; private static readonly Regex TelnetRegexForIdentifier; private static readonly Regex ValidServerRegex; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DefaultRule, EnableSslRule); public ClearTextProtocolsAreSensitive() : this(AnalyzerConfiguration.Hotspot) { } public ClearTextProtocolsAreSensitive(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } static ClearTextProtocolsAreSensitive() { const string allSubdomainsPattern = @"([^/?#]+\.)?"; var domainsList = LocalhostAddresses .Concat(CommonlyUsedXmlDomains) .Select(Regex.Escape) .Concat(CommonlyUsedExampleDomains.Select(x => allSubdomainsPattern + Regex.Escape(x))); var validServerPattern = domainsList.JoinStr("|"); HttpRegex = CompileRegex(@$"^http:\/\/(?!{validServerPattern})."); FtpRegex = CompileRegex(@$"^ftp:\/\/.*@(?!{validServerPattern})"); TelnetRegex = CompileRegex(@$"^telnet:\/\/.*@(?!{validServerPattern})"); TelnetRegexForIdentifier = CompileRegex(@"Telnet(?![a-z])", false); ValidServerRegex = CompileRegex($"^({validServerPattern})$"); } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (!IsEnabled(c.Options)) { return; } c.RegisterNodeAction( VisitStringExpressions, SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression, SyntaxKindEx.Utf8StringLiteralExpression); c.RegisterNodeAction(VisitObjectCreation, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); c.RegisterNodeAction(VisitInvocationExpression, SyntaxKind.InvocationExpression); c.RegisterNodeAction(VisitAssignments, SyntaxKind.SimpleAssignmentExpression); }); private static void VisitObjectCreation(SonarSyntaxNodeReportingContext context) { var objectCreation = ObjectCreationFactory.Create(context.Node); if (!IsServerSafe(objectCreation, context.Model) && ObjectInitializationTracker.ShouldBeReported(objectCreation, context.Model, false)) { context.ReportIssue(EnableSslRule, objectCreation.Expression); } else if (objectCreation.TypeAsString(context.Model) is { } typeAsString && TelnetRegexForIdentifier.SafeIsMatch(typeAsString)) { context.ReportIssue(DefaultRule, objectCreation.Expression, TelnetKey, RecommendedProtocols[TelnetKey]); } } private static void VisitInvocationExpression(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (TelnetRegexForIdentifier.SafeIsMatch(invocation.Expression.ToString())) { context.ReportIssue(DefaultRule, invocation, TelnetKey, RecommendedProtocols[TelnetKey]); } } private static void VisitAssignments(SonarSyntaxNodeReportingContext context) { var assignment = (AssignmentExpressionSyntax)context.Node; if (assignment.Left is MemberAccessExpressionSyntax memberAccess && memberAccess.IsMemberAccessOnKnownType(EnableSslName, KnownType.System_Net_FtpWebRequest, context.Model) && assignment.Right.FindConstantValue(context.Model) is bool enableSslValue && !enableSslValue) { context.ReportIssue(EnableSslRule, assignment); } } private static void VisitStringExpressions(SonarSyntaxNodeReportingContext c) { if (GetUnsafeProtocol(c.Node, c.Model) is { } unsafeProtocol) { c.ReportIssue(DefaultRule, c.Node, unsafeProtocol, RecommendedProtocols[unsafeProtocol]); } } private static bool IsServerSafe(IObjectCreation objectCreation, SemanticModel semanticModel) => objectCreation.ArgumentList?.Arguments.Count > 0 && ValidServerRegex.SafeIsMatch(GetText(objectCreation.ArgumentList.Arguments[0].Expression, semanticModel)); private static string GetUnsafeProtocol(SyntaxNode node, SemanticModel semanticModel) { var text = GetText(node, semanticModel); if (HttpRegex.SafeIsMatch(text) && !IsNamespace(semanticModel, node.Parent)) { return "http"; } else if (FtpRegex.SafeIsMatch(text)) { return "ftp"; } else if (TelnetRegex.SafeIsMatch(text)) { return "telnet"; } else { return null; } } private static string GetText(SyntaxNode node, SemanticModel model) { if (node is InterpolatedStringExpressionSyntax interpolatedStringExpression) { return interpolatedStringExpression.InterpolatedTextValue(model) ?? interpolatedStringExpression.ContentsText(); } else { return node is LiteralExpressionSyntax literalExpression ? literalExpression.Token.ValueText : string.Empty; } } private static bool IsNamespace(SemanticModel model, SyntaxNode node) => node switch { AttributeArgumentSyntax attributeArgument when attributeArgument.NameEquals is { } nameEquals && TokenContainsNamespace(nameEquals.Name.Identifier) => true, AttributeArgumentSyntax { Parent.Parent: AttributeSyntax attribute } => IsAttributeWithNamespaceParameter(model, attribute), EqualsValueClauseSyntax equalsValueClause => (equalsValueClause.Parent is VariableDeclaratorSyntax variableDeclarator && TokenContainsNamespace(variableDeclarator.Identifier)) || (equalsValueClause.Parent is ParameterSyntax parameter && TokenContainsNamespace(parameter.Identifier)), AssignmentExpressionSyntax assignmentExpression => assignmentExpression.Left.RemoveParentheses() is IdentifierNameSyntax identifierName && TokenContainsNamespace(identifierName.Identifier), ArgumentSyntax { Parent: ArgumentListSyntax { Parent: { } invocationOrCreation } } argument => CSharpFacade.Instance.MethodParameterLookup(invocationOrCreation, model).TryGetSymbol(argument, out var symbol) && symbol switch { { Name: "ns", ContainingNamespace: { } ns } when ns.Is("System.Xml.Serialization") => true, { Name: "ns" or "uri" or "namespaceURI", ContainingNamespace: { } ns } when ns.Is("System.Xml") => true, { Name: "xmlNamespace", ContainingType.Name: "XmlnsDictionary", ContainingNamespace: { } ns } when ns.Is("System.Windows.Markup") => true, { Name: "namespaceName", ContainingSymbol.Name: "Get", ContainingType.Name: "XNamespace", ContainingNamespace: { } ns } when ns.Is("System.Xml.Linq") => true, _ => false, }, _ => false }; private static bool IsAttributeWithNamespaceParameter(SemanticModel model, AttributeSyntax attribute) => model.GetSymbolInfo(attribute).Symbol is IMethodSymbol { ContainingType: { } attributeSymbol } && Array.Exists(AttributesWithNamespaceParameter, x => x.Matches(attributeSymbol)); private static bool TokenContainsNamespace(SyntaxToken token) => token.Text.IndexOf("Namespace", StringComparison.OrdinalIgnoreCase) != -1; private static Regex CompileRegex(string pattern, bool ignoreCase = true) => new(pattern, ignoreCase ? RegexOptions.Compiled | RegexOptions.IgnoreCase : RegexOptions.Compiled, RegexTimeout); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/CommandPath.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CommandPath : CommandPathBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public CommandPath() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ CommandPath(IAnalyzerConfiguration configuration) : base(configuration) { } protected override string FirstArgument(InvocationContext context) => ((InvocationExpressionSyntax)context.Node).ArgumentList.Get(0).FindStringConstant(context.Model); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/ConfiguringLoggers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ConfiguringLoggers : ConfiguringLoggersBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public ConfiguringLoggers() : this(AnalyzerConfiguration.Hotspot) { } // Set internal for testing internal ConfiguringLoggers(IAnalyzerConfiguration configuration) : base(configuration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/CookieShouldBeHttpOnly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CookieShouldBeHttpOnly : ObjectShouldBeInitializedCorrectlyBase { private const string DiagnosticId = "S3330"; private const string MessageFormat = "Make sure creating this cookie without the \"HttpOnly\" flag is safe."; private static readonly ImmutableArray TrackedTypes = ImmutableArray.Create( KnownType.System_Web_HttpCookie, KnownType.Microsoft_AspNetCore_Http_CookieOptions); protected override CSharpObjectInitializationTracker ObjectInitializationTracker { get; } = new( isAllowedConstantValue: constantValue => constantValue is true, trackedTypes: TrackedTypes, isTrackedPropertyName: propertyName => propertyName == "HttpOnly"); public CookieShouldBeHttpOnly() : this(AnalyzerConfiguration.Hotspot) { } internal CookieShouldBeHttpOnly(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration, DiagnosticId, MessageFormat) { } protected override bool IsDefaultConstructorSafe(SonarCompilationStartAnalysisContext context) => IsWebConfigCookieSet(context, "httpOnlyCookies"); protected override void Initialize(TrackerInput input) { var t = CSharpFacade.Instance.Tracker.ObjectCreation; t.Track(input, t.MatchConstructor(KnownType.Nancy_Cookies_NancyCookie), t.ExceptWhen(t.ArgumentIsBoolConstant("httpOnly", true))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/CookieShouldBeSecure.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CookieShouldBeSecure : ObjectShouldBeInitializedCorrectlyBase { private const string DiagnosticId = "S2092"; private const string MessageFormat = "Make sure creating this cookie without setting the 'Secure' property is safe here."; private static readonly ImmutableArray TrackedTypes = ImmutableArray.Create( KnownType.System_Web_HttpCookie, KnownType.Microsoft_AspNetCore_Http_CookieOptions); protected override CSharpObjectInitializationTracker ObjectInitializationTracker { get; } = new( isAllowedConstantValue: constantValue => constantValue is true, trackedTypes: TrackedTypes, isTrackedPropertyName: propertyName => propertyName == "Secure"); public CookieShouldBeSecure() : this(AnalyzerConfiguration.Hotspot) { } internal CookieShouldBeSecure(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override bool IsDefaultConstructorSafe(SonarCompilationStartAnalysisContext context) => IsWebConfigCookieSet(context, "requireSSL"); protected override void Initialize(TrackerInput input) { var t = CSharpFacade.Instance.Tracker.ObjectCreation; t.Track(input, t.MatchConstructor(KnownType.Nancy_Cookies_NancyCookie), t.ExceptWhen(t.ArgumentIsBoolConstant("secure", true))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/CreatingHashAlgorithms.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CreatingHashAlgorithms : CreatingHashAlgorithmsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public CreatingHashAlgorithms() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ CreatingHashAlgorithms(IAnalyzerConfiguration configuration) : base(configuration) { } protected override bool IsUnsafeAlgorithm(SyntaxNode argumentNode, SemanticModel model) => argumentNode as ArgumentSyntax is { } argument && argument.Expression as MemberAccessExpressionSyntax is { } memberAccess && memberAccess.Name.ToString() is "SHA1" or "MD5" && model.GetSymbolInfo(memberAccess.Expression).Symbol.GetSymbolType().Is(KnownType.System_Security_Cryptography_HashAlgorithmName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DeliveringDebugFeaturesInProduction : DeliveringDebugFeaturesInProductionBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public DeliveringDebugFeaturesInProduction() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ DeliveringDebugFeaturesInProduction(IAnalyzerConfiguration configuration) : base(configuration) { } // For simplicity we will avoid creating noise if the validation is invoked in the same method (https://github.com/SonarSource/sonar-dotnet/issues/5032) protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) => node.EnclosingScope() .DescendantNodes() .Any(x => IsDevelopmentCheck(x, semanticModel)); protected override bool IsInDevelopmentContext(SyntaxNode node) => node.Ancestors() .OfType() .Any(x => x.Identifier.Text == StartupDevelopment); private bool IsDevelopmentCheck(SyntaxNode node, SemanticModel semanticModel) => node is InvocationExpressionSyntax condition && IsValidationMethod(semanticModel, condition, condition.Expression.GetIdentifier()?.ValueText); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DisablingCsrfProtection.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisablingCsrfProtection : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S4502"; private const string MessageFormat = "Make sure disabling CSRF protection is safe here."; private const SyntaxKind ImplicitObjectCreationExpression = (SyntaxKind)8659; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public DisablingCsrfProtection() : base(AnalyzerConfiguration.Hotspot) { } public DisablingCsrfProtection(IAnalyzerConfiguration configuration) : base(configuration) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( c => { if (!IsEnabled(c.Options)) { return; } c.RegisterNodeAction( CheckIgnoreAntiforgeryTokenAttribute, SyntaxKind.Attribute, SyntaxKind.ObjectCreationExpression, ImplicitObjectCreationExpression); }); private static void CheckIgnoreAntiforgeryTokenAttribute(SonarSyntaxNodeReportingContext c) { var shouldReport = c.Node switch { AttributeSyntax attributeSyntax => attributeSyntax.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute, c.Model), ObjectCreationExpressionSyntax objectCreation => objectCreation.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute, c.Model), _ => c.Node.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute, c.Model) }; if (shouldReport) { ReportDiagnostic(c); } } private static void ReportDiagnostic(SonarSyntaxNodeReportingContext context) => context.ReportIssue(Rule, context.Node); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DisablingRequestValidation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DisablingRequestValidation : DisablingRequestValidationBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public DisablingRequestValidation() : this(AnalyzerConfiguration.Hotspot) { } public DisablingRequestValidation(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/DoNotUseRandom.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DoNotUseRandom : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S2245"; private const string MessageFormat = "Make sure that using this pseudorandom number generator is safe here."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public DoNotUseRandom() : base(AnalyzerConfiguration.Hotspot) { } public DoNotUseRandom(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( ccc => { if (!IsEnabled(ccc.Options)) { return; } ccc.RegisterNodeAction( c => { var objectCreationSyntax = (ObjectCreationExpressionSyntax)c.Node; var argumentsCount = objectCreationSyntax.ArgumentList?.Arguments.Count; if (argumentsCount <= 1 // Random has two ctors - with zero and one parameter && c.Model.GetSymbolInfo(objectCreationSyntax).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.Is(KnownType.System_Random)) { c.ReportIssue(Rule, objectCreationSyntax); } }, SyntaxKind.ObjectCreationExpression); }); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/ExecutingSqlQueries.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExecutingSqlQueries : ExecutingSqlQueriesBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; public ExecutingSqlQueries() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ ExecutingSqlQueries(IAnalyzerConfiguration configuration) : base(configuration) { } protected override ExpressionSyntax GetArgumentAtIndex(InvocationContext context, int index) => context.Node is InvocationExpressionSyntax invocation ? invocation.ArgumentList.Get(index) : null; protected override ExpressionSyntax GetArgumentAtIndex(ObjectCreationContext context, int index) => ObjectCreationFactory.Create(context.Node).ArgumentList.Get(index); protected override ExpressionSyntax GetSetValue(PropertyAccessContext context) => context.Node is MemberAccessExpressionSyntax setter && setter.IsLeftSideOfAssignment() ? ((AssignmentExpressionSyntax)setter.GetSelfOrTopParenthesizedExpression().Parent).Right.RemoveParentheses() : null; protected override bool IsTracked(ExpressionSyntax expression, SyntaxBaseContext context) => IsSensitiveExpression(expression, context.Model) || IsTrackedVariableDeclaration(expression, context); protected override bool IsSensitiveExpression(ExpressionSyntax expression, SemanticModel semanticModel) => expression is not null && (IsConcatenation(expression, semanticModel) || expression.IsKind(SyntaxKind.InterpolatedStringExpression) || (expression is InvocationExpressionSyntax invocation && IsInvocationOfInterest(invocation, semanticModel))); protected override Location SecondaryLocationForExpression(ExpressionSyntax node, string identifierNameToFind, out string identifierNameFound) { identifierNameFound = identifierNameToFind; if (node is null) { return Location.None; } if (node.Parent is EqualsValueClauseSyntax {Parent: VariableDeclaratorSyntax declarationSyntax}) { return declarationSyntax.Identifier.GetLocation(); } return node.Parent is AssignmentExpressionSyntax assignment ? assignment.Left.GetLocation() : Location.None; } private static bool IsInvocationOfInterest(InvocationExpressionSyntax invocation, SemanticModel model) => (invocation.IsMethodInvocation(KnownType.System_String, "Format", model) || invocation.IsMethodInvocation(KnownType.System_String, "Concat", model)) && !AllConstants(invocation.ArgumentList.Arguments.ToList(), model); private static bool IsConcatenation(ExpressionSyntax expression, SemanticModel model) => expression.IsKind(SyntaxKind.AddExpression) && expression is BinaryExpressionSyntax concatenation && !IsConcatenationOfConstants(concatenation, model); private static bool AllConstants(IEnumerable arguments, SemanticModel model) => arguments.All(x => x.Expression.HasConstantValue(model)); private static bool IsConcatenationOfConstants(BinaryExpressionSyntax binaryExpression, SemanticModel model) { System.Diagnostics.Debug.Assert(binaryExpression.IsKind(SyntaxKind.AddExpression), "Binary expression should be of syntax kind add expression."); if ((model.GetTypeInfo(binaryExpression).Type is not null) && binaryExpression.Right.HasConstantValue(model)) { var nestedLeft = binaryExpression.Left; var nestedBinary = nestedLeft as BinaryExpressionSyntax; while (nestedBinary is not null) { if (nestedBinary.Right.HasConstantValue(model) && (nestedBinary.IsKind(SyntaxKind.AddExpression) || nestedBinary.HasConstantValue(model))) { nestedLeft = nestedBinary.Left; nestedBinary = nestedLeft as BinaryExpressionSyntax; } else { return false; } } return nestedLeft.HasConstantValue(model); } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/ExpandingArchives.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ExpandingArchives : ExpandingArchivesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public ExpandingArchives() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ ExpandingArchives(IAnalyzerConfiguration configuration) : base(configuration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/HardcodedIpAddress.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class HardcodedIpAddress : HardcodedIpAddressBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public HardcodedIpAddress() : this(AnalyzerConfiguration.Hotspot) { } public HardcodedIpAddress(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override bool HasAttributes(SyntaxNode literalExpression) => literalExpression.HasAncestor(SyntaxKind.Attribute); protected override string GetAssignedVariableName(SyntaxNode stringLiteral) => stringLiteral.FirstAncestorOrSelf(IsVariableIdentifier)?.ToString(); private static bool IsVariableIdentifier(SyntaxNode syntaxNode) => syntaxNode is StatementSyntax || syntaxNode is VariableDeclaratorSyntax || syntaxNode is ParameterSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/InsecureDeserialization.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InsecureDeserialization : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S5766"; private const string MessageFormat = "Make sure not performing data validation after deserialization is safe here."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public InsecureDeserialization() : this(AnalyzerConfiguration.Hotspot) { } public InsecureDeserialization(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (TypeDeclarationSyntax)c.Node; if (!c.IsRedundantPositionalRecordContext() && IsEnabled(c.Options) && HasConstructorsWithParameters(declaration) // If there are no constructors, or if these don't have parameters, there is no validation done and the type is considered safe. && c.Model.GetDeclaredSymbol(declaration) is { } typeSymbol && HasSerializableAttribute(typeSymbol)) { ReportOnInsecureDeserializations(c, declaration, typeSymbol); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); private static void ReportOnInsecureDeserializations(SonarSyntaxNodeReportingContext context, TypeDeclarationSyntax declaration, ITypeSymbol typeSymbol) { var implementsISerializable = ImplementsISerializable(typeSymbol); var implementsIDeserializationCallback = ImplementsIDeserializationCallback(typeSymbol); var walker = new ConstructorDeclarationWalker(context.Model); walker.SafeVisit(declaration); if (!implementsISerializable && !implementsIDeserializationCallback) { foreach (var constructor in walker.GetConstructorsInfo(x => x.HasConditionalConstructs)) { ReportIssue(context, constructor); } } if (implementsISerializable && !walker.HasDeserializationCtorWithConditionalStatements()) { foreach (var constructor in walker.GetConstructorsInfo(x => !x.IsDeserializationConstructor && x.HasConditionalConstructs)) { ReportIssue(context, constructor); } } if (implementsIDeserializationCallback && !OnDeserializationHasConditions(declaration, context.Model)) { foreach (var constructor in walker.GetConstructorsInfo(x => x.HasConditionalConstructs)) { ReportIssue(context, constructor); } } static void ReportIssue(SonarSyntaxNodeReportingContext context, ConstructorInfo constructor) => context.ReportIssue(Rule, constructor.GetReportLocation()); } private static bool OnDeserializationHasConditions(TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel) => typeDeclaration .Members .OfType() .FirstOrDefault(methodDeclaration => IsOnDeserialization(methodDeclaration, semanticModel)) .ContainsConditionalConstructs(); private static bool IsOnDeserialization(MethodDeclarationSyntax methodDeclaration, SemanticModel semanticModel) => methodDeclaration.Identifier.Text == nameof(System.Runtime.Serialization.IDeserializationCallback.OnDeserialization) && methodDeclaration.ParameterList.Parameters.Count == 1 && methodDeclaration.ParameterList.Parameters[0].IsDeclarationKnownType(KnownType.System_Object, semanticModel); private static bool HasConstructorsWithParameters(TypeDeclarationSyntax typeDeclaration) => typeDeclaration .Members .OfType() .Any(constructorDeclaration => constructorDeclaration.ParameterList.Parameters.Count > 0); private static bool HasSerializableAttribute(ISymbol symbol) => symbol.HasAttribute(KnownType.System_SerializableAttribute); private static bool ImplementsISerializable(ITypeSymbol symbol) => symbol.Implements(KnownType.System_Runtime_Serialization_ISerializable); private static bool ImplementsIDeserializationCallback(ITypeSymbol symbol) => symbol.Implements(KnownType.System_Runtime_Serialization_IDeserializationCallback); /// /// This walker is responsible to visit all constructor declarations and check if parameters are used in a /// conditional structure or not. /// private sealed class ConstructorDeclarationWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel semanticModel; private readonly List constructorsInfo = new(); private bool visitedFirstLevel; public ConstructorDeclarationWalker(SemanticModel semanticModel) { this.semanticModel = semanticModel; } public IEnumerable GetConstructorsInfo(Func predicate) => constructorsInfo.Where(predicate); public bool HasDeserializationCtorWithConditionalStatements() => GetDeserializationConstructor() is { HasConditionalConstructs: true }; public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { var isDeserializationCtor = IsDeserializationConstructor(node); var hasConditionalStatements = isDeserializationCtor ? node.ContainsConditionalConstructs() : HasParametersUsedInConditionalConstructs(node); constructorsInfo.Add(new ConstructorInfo(node, hasConditionalStatements, isDeserializationCtor)); base.VisitConstructorDeclaration(node); } public override void VisitClassDeclaration(ClassDeclarationSyntax node) { if (visitedFirstLevel) { // Skip nested visits. The rule will be triggered for them also. return; } visitedFirstLevel = true; base.VisitClassDeclaration(node); } public override void Visit(SyntaxNode node) { if (node.Kind() is SyntaxKindEx.RecordDeclaration or SyntaxKindEx.RecordStructDeclaration) { if (visitedFirstLevel) { // Skip nested visits. The rule will be triggered for them also. return; } visitedFirstLevel = true; } base.Visit(node); } private bool HasParametersUsedInConditionalConstructs(BaseMethodDeclarationSyntax declaration) { var symbols = GetConstructorParameterSymbols(declaration, semanticModel); var conditionalsWalker = new ConditionalsWalker(semanticModel, symbols); conditionalsWalker.SafeVisit(declaration); return conditionalsWalker.HasParametersUsedInConditionalConstructs; } private ConstructorInfo GetDeserializationConstructor() => constructorsInfo.SingleOrDefault(info => info.IsDeserializationConstructor); private bool IsDeserializationConstructor(BaseMethodDeclarationSyntax declaration) => // A deserialization ctor has the following parameters: (SerializationInfo information, StreamingContext context) // See https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable?view=netcore-3.1#remarks declaration.ParameterList.Parameters.Count == 2 && declaration.ParameterList.Parameters[0].IsDeclarationKnownType(KnownType.System_Runtime_Serialization_SerializationInfo, semanticModel) && declaration.ParameterList.Parameters[1].IsDeclarationKnownType(KnownType.System_Runtime_Serialization_StreamingContext, semanticModel); private static ImmutableArray GetConstructorParameterSymbols(BaseMethodDeclarationSyntax node, SemanticModel semanticModel) => node.ParameterList.Parameters .Select(syntax => (ISymbol)semanticModel.GetDeclaredSymbol(syntax)) .ToImmutableArray(); } /// /// This walker is responsible to visit all conditional structures and check if a list of parameters /// are used or not. /// private sealed class ConditionalsWalker : SafeCSharpSyntaxWalker { private readonly SemanticModel semanticModel; private readonly ISet parameterNames; public ConditionalsWalker(SemanticModel semanticModel, ImmutableArray parameters) { this.semanticModel = semanticModel; parameterNames = parameters.Select(parameter => parameter.Name).ToHashSet(); } public bool HasParametersUsedInConditionalConstructs { get; private set; } public override void VisitIfStatement(IfStatementSyntax node) { UpdateParameterValidationStatus(node.Condition); base.VisitIfStatement(node); } public override void VisitConditionalExpression(ConditionalExpressionSyntax node) { UpdateParameterValidationStatus(node.Condition); base.VisitConditionalExpression(node); } public override void VisitSwitchStatement(SwitchStatementSyntax node) { UpdateParameterValidationStatus(node.Expression); base.VisitSwitchStatement(node); } public override void VisitBinaryExpression(BinaryExpressionSyntax node) { if (node.IsKind(SyntaxKind.CoalesceExpression)) { UpdateParameterValidationStatus(node.Left); } base.VisitBinaryExpression(node); } public override void VisitAssignmentExpression(AssignmentExpressionSyntax node) { if (node.IsKind(SyntaxKindEx.CoalesceAssignmentExpression)) { UpdateParameterValidationStatus(node.Left); } base.VisitAssignmentExpression(node); } public override void Visit(SyntaxNode node) { if (node.IsKind(SyntaxKindEx.SwitchExpression)) { UpdateParameterValidationStatus(((SwitchExpressionSyntaxWrapper)node).GoverningExpression); } if (node.IsKind(SyntaxKindEx.SwitchExpressionArm)) { var arm = (SwitchExpressionArmSyntaxWrapper)node; if (arm.Pattern.SyntaxNode != null) { UpdateParameterValidationStatus(arm.Pattern); } if (arm.WhenClause.SyntaxNode != null) { UpdateParameterValidationStatus(arm.WhenClause); } } base.Visit(node); } private void UpdateParameterValidationStatus(SyntaxNode node) => HasParametersUsedInConditionalConstructs |= node .DescendantNodesAndSelf() .OfType() .Where(identifier => parameterNames.Contains(identifier.Identifier.Text)) .Select(identifier => semanticModel.GetSymbolInfo(identifier).Symbol) .Any(symbol => symbol != null); } private sealed class ConstructorInfo { private readonly ConstructorDeclarationSyntax declarationSyntax; public ConstructorInfo(ConstructorDeclarationSyntax declaration, bool hasConditionalConstructs, bool isDeserializationConstructor) { declarationSyntax = declaration; HasConditionalConstructs = hasConditionalConstructs; IsDeserializationConstructor = isDeserializationConstructor; } public bool HasConditionalConstructs { get; } public bool IsDeserializationConstructor { get; } public Location GetReportLocation() => declarationSyntax.Identifier.GetLocation(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/PermissiveCors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PermissiveCors : TrackerHotspotDiagnosticAnalyzer { private const string DiagnosticId = "S5122"; private const string MessageFormat = "Make sure this permissive CORS policy is safe here."; private const string AccessControlAllowOriginHeader = "Access-Control-Allow-Origin"; private const string AccessControlAllowOriginPropertyName = "AccessControlAllowOrigin"; private const string StarConstant = "*"; protected override ILanguageFacade Language => CSharpFacade.Instance; public PermissiveCors() : base(AnalyzerConfiguration.Hotspot, DiagnosticId, MessageFormat) { } public PermissiveCors(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterCompilationStartAction(c => { if (IsEnabled(c.Options)) { c.RegisterNodeAction(VisitAttribute, SyntaxKind.Attribute); } }); } protected override void Initialize(TrackerInput input) { SetupInvocationTracker(Language.Tracker.Invocation, input); SetupObjectCreationTracker(Language.Tracker.ObjectCreation, input); } private static void SetupInvocationTracker(InvocationTracker tracker, TrackerInput input) { const int parameterCount = 2; tracker.Track( input, tracker.MatchMethod(new MemberDescriptor(KnownType.System_Collections_Generic_IDictionary_TKey_TValue, "Add")), tracker.MethodHasParameters(parameterCount), c => IsFirstArgumentAccessControlAllowOrigin((InvocationExpressionSyntax)c.Node, c.Model) && IsSecondArgumentStarString((InvocationExpressionSyntax)c.Node, c.Model), tracker.IsIHeadersDictionary()); tracker.Track( input, tracker.MatchMethod(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Http_HeaderDictionaryExtensions, "Append"), new MemberDescriptor(KnownType.System_Web_HttpResponse, "AppendHeader"), new MemberDescriptor(KnownType.System_Web_HttpResponseBase, "AddHeader"), new MemberDescriptor(KnownType.System_Collections_Specialized_NameValueCollection, "Add"), new MemberDescriptor(KnownType.System_Net_Http_Headers_HttpHeaders, "Add")), tracker.MethodHasParameters(parameterCount), c => IsFirstArgumentAccessControlAllowOrigin((InvocationExpressionSyntax)c.Node, c.Model) && IsSecondArgumentStarString((InvocationExpressionSyntax)c.Node, c.Model)); tracker.Track( input, tracker.MatchMethod(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Cors_Infrastructure_CorsPolicyBuilder, "WithOrigins")), c => ContainsStar(((InvocationExpressionSyntax)c.Node).ArgumentList.Arguments.Select(a => a.Expression), c.Model)); tracker.Track( input, tracker.MatchMethod(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Cors_Infrastructure_CorsPolicyBuilder, "AllowAnyOrigin"))); } private static void SetupObjectCreationTracker(ObjectCreationTracker tracker, TrackerInput input) => tracker.Track( input, tracker.MatchConstructor(KnownType.Microsoft_AspNetCore_Cors_Infrastructure_CorsPolicyBuilder), c => ContainsStar(ObjectCreationFactory.Create(c.Node), c.Model)); private void VisitAttribute(SonarSyntaxNodeReportingContext context) { var attribute = (AttributeSyntax)context.Node; if (attribute.IsKnownType(KnownType.System_Web_Http_Cors_EnableCorsAttribute, context.Model) && IsStar(attribute.ArgumentList.Arguments[0].Expression, context.Model)) { context.ReportIssue(Rule, attribute); } } private static bool IsFirstArgumentAccessControlAllowOrigin(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.ArgumentList.Arguments.First().Expression switch { InterpolatedStringExpressionSyntax interpolation => interpolation.FindStringConstant(semanticModel) == AccessControlAllowOriginHeader, LiteralExpressionSyntax literal => literal.Token.ValueText == AccessControlAllowOriginHeader, MemberAccessExpressionSyntax memberAccess => IsAccessControlAllowOriginProperty(memberAccess, semanticModel), _ => false }; private static bool IsAccessControlAllowOriginProperty(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) => memberAccess.Name.Identifier.Text == AccessControlAllowOriginPropertyName && memberAccess.Expression.IsKnownType(KnownType.Microsoft_Net_Http_Headers_HeaderNames, semanticModel); private static bool IsSecondArgumentStarString(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => IsStar(invocation.ArgumentList.Arguments[1].Expression, semanticModel); private static bool IsStar(ExpressionSyntax expressionSyntax, SemanticModel model) => expressionSyntax switch { InterpolatedStringExpressionSyntax interpolation => interpolation.FindStringConstant(model) == StarConstant, LiteralExpressionSyntax literal => ContainsStar(model.GetConstantValue(literal)), IdentifierNameSyntax identifier => ContainsStar(model.GetConstantValue(identifier)), ImplicitArrayCreationExpressionSyntax arrayCreation => ContainsStar(arrayCreation.Initializer.Expressions, model), { } objectCreation when objectCreation.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression => ContainsStar(ObjectCreationFactory.Create(objectCreation), model), _ => false }; private static bool ContainsStar(IEnumerable expressions, SemanticModel semanticModel) => expressions.Any(expression => ContainsStar(semanticModel.GetConstantValue(expression))); private static bool ContainsStar(Optional constantValue) => constantValue is {HasValue: true, Value: StarConstant}; private static bool ContainsStar(IObjectCreation objectCreation, SemanticModel semanticModel) => objectCreation.ArgumentList is { } argumentList && (objectCreation.IsKnownType(KnownType.Microsoft_Extensions_Primitives_StringValues, semanticModel) || objectCreation.IsKnownType(KnownType.Microsoft_AspNetCore_Cors_Infrastructure_CorsPolicyBuilder, semanticModel)) && argumentList.Arguments.Any(argument => IsStar(argument.Expression, semanticModel)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/PubliclyWritableDirectories.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PubliclyWritableDirectories : PubliclyWritableDirectoriesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public PubliclyWritableDirectories() : this(AnalyzerConfiguration.Hotspot) { } internal PubliclyWritableDirectories(IAnalyzerConfiguration configuration) : base(configuration) { } private protected override bool IsGetTempPathAssignment(InvocationExpressionSyntax invocationExpression, KnownType type, string methodName, SemanticModel semanticModel) => invocationExpression.IsMethodInvocation(type, methodName, semanticModel) && invocationExpression.Parent?.Kind() is SyntaxKind.EqualsValueClause or SyntaxKind.SimpleAssignmentExpression or SyntaxKind.ArrowExpressionClause or SyntaxKind.ReturnStatement; private protected override bool IsInsecureEnvironmentVariableRetrieval(InvocationExpressionSyntax invocation, KnownType type, string methodName, SemanticModel semanticModel) => invocation.IsMethodInvocation(type, methodName, semanticModel) && invocation.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && InsecureEnvironmentVariables.Any(x => x.Equals(firstArgument.Expression?.StringValue(semanticModel), StringComparison.OrdinalIgnoreCase)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/RequestsWithExcessiveLength.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RequestsWithExcessiveLength : RequestsWithExcessiveLengthBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public RequestsWithExcessiveLength() : this(SonarAnalyzer.Core.Common.AnalyzerConfiguration.Hotspot) { } internal RequestsWithExcessiveLength(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var body = methodDeclaration.GetBodyOrExpressionBody(); if (body is not null && body.DescendantNodes() .OfType() .Any(x => x.IsMethodInvocation(KnownType.Microsoft_AspNetCore_Components_Forms_IBrowserFile, "OpenReadStream", c.Model))) { var walker = new StreamReadSizeCheck(c.Model, FileUploadSizeLimit); if (walker.SafeVisit(body)) { foreach (var location in walker.Locations) { c.ReportIssue(Rule, location); } } } }, SyntaxKind.MethodDeclaration); base.Initialize(context); } protected override AttributeSyntax IsInvalidRequestFormLimits(AttributeSyntax attribute, SemanticModel semanticModel) => IsRequestFormLimits(attribute.Name.ToString()) && attribute.ArgumentList?.Arguments.FirstOrDefault(arg => IsMultipartBodyLengthLimit(arg)) is { } firstArgument && semanticModel.GetConstantValue(firstArgument.Expression) is { HasValue: true } constantValue && constantValue.Value is int intValue && intValue > FileUploadSizeLimit && attribute.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_RequestFormLimitsAttribute, semanticModel) ? attribute : null; protected override AttributeSyntax IsInvalidRequestSizeLimit(AttributeSyntax attribute, SemanticModel semanticModel) => IsRequestSizeLimit(attribute.Name.ToString()) && attribute.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && semanticModel.GetConstantValue(firstArgument.Expression) is { HasValue: true } constantValue && constantValue.Value is int intValue && intValue > FileUploadSizeLimit && attribute.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_RequestSizeLimitAttribute, semanticModel) ? attribute : null; protected override SyntaxNode GetMethodLocalFunctionOrClassDeclaration(AttributeSyntax attribute) => attribute.FirstAncestorOrSelf(node => node is MemberDeclarationSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(node)); protected override string AttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); private static bool IsMultipartBodyLengthLimit(AttributeArgumentSyntax argument) => argument.NameEquals is { } nameEquals && nameEquals.Name.Identifier.ValueText.Equals(MultipartBodyLengthLimit); private sealed class StreamReadSizeCheck(SemanticModel model, int fileUploadSizeLimit) : SafeCSharpSyntaxWalker { private const int GetMultipleFilesMaximumFileCount = 10; // Default value for `maximumFileCount` in `InputFileChangeEventArgs.GetMultipleFiles` is 10 private int numberOfFiles = 1; public List Locations { get; } = new(); public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.IsMethodInvocation(KnownType.Microsoft_AspNetCore_Components_Forms_InputFileChangeEventArgs, "GetMultipleFiles", model)) { numberOfFiles = node.ArgumentList.Arguments.FirstOrDefault() is { } firstArgument && model.GetConstantValue(firstArgument.Expression) is { HasValue: true } constantValue && Convert.ToInt32(constantValue.Value) is var count ? count : GetMultipleFilesMaximumFileCount; } if (node.IsMethodInvocation(KnownType.Microsoft_AspNetCore_Components_Forms_IBrowserFile, "OpenReadStream", model)) { var size = OpenReadStreamInvocationSize(node, model); if (numberOfFiles * size > fileUploadSizeLimit) { Locations.Add(node.GetLocation()); } } base.VisitInvocationExpression(node); } private static long OpenReadStreamInvocationSize(InvocationExpressionSyntax invocation, SemanticModel model) => invocation.ArgumentList.Arguments.FirstOrDefault() is { } firstArgument && model.GetConstantValue(firstArgument.Expression) is { HasValue: true } constantValue && Convert.ToInt64(constantValue.Value) is var size ? size : 500 * 1024; // Default `maxAllowedSize` in `IBrowserFile.OpenReadStream` is 500 KB } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/SpecifyTimeoutOnRegex.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SpecifyTimeoutOnRegex : SpecifyTimeoutOnRegexBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public SpecifyTimeoutOnRegex() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ SpecifyTimeoutOnRegex(IAnalyzerConfiguration config) : base(config) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UnsafeCodeBlocks.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnsafeCodeBlocks : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S6640"; private const string MessageFormat = """Make sure that using "unsafe" is safe here."""; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public UnsafeCodeBlocks() : this(AnalyzerConfiguration.Hotspot) { } public UnsafeCodeBlocks(IAnalyzerConfiguration configuration) : base(configuration) { } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => Report(c, ((UnsafeStatementSyntax)c.Node).UnsafeKeyword), SyntaxKind.UnsafeStatement); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((BaseTypeDeclarationSyntax)c.Node).Modifiers), SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((BaseMethodDeclarationSyntax)c.Node).Modifiers), SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.OperatorDeclaration); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((LocalFunctionStatementSyntaxWrapper)c.Node).Modifiers), SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((BaseFieldDeclarationSyntax)c.Node).Modifiers), SyntaxKind.FieldDeclaration, SyntaxKind.EventFieldDeclaration); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((BasePropertyDeclarationSyntax)c.Node).Modifiers), SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration); context.RegisterNodeAction( c => ReportIfUnsafe(c, ((DelegateDeclarationSyntax)c.Node).Modifiers), SyntaxKind.DelegateDeclaration); } private void ReportIfUnsafe(SonarSyntaxNodeReportingContext context, SyntaxTokenList modifiers) { if (modifiers.Find(SyntaxKind.UnsafeKeyword) is { } unsafeModifier) { Report(context, unsafeModifier); } } private void Report(SonarSyntaxNodeReportingContext context, SyntaxToken token) { if (IsEnabled(context.Options)) { context.ReportIssue(Rule, token); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Hotspots/UsingNonstandardCryptography.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UsingNonstandardCryptography : UsingNonstandardCryptographyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration }; public UsingNonstandardCryptography() : this(AnalyzerConfiguration.Hotspot) { } public UsingNonstandardCryptography(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override INamedTypeSymbol DeclaredSymbol(TypeDeclarationSyntax typeDeclarationSyntax, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(typeDeclarationSyntax); protected override Location Location(TypeDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax.Identifier.GetLocation(); protected override bool DerivesOrImplementsAny(TypeDeclarationSyntax typeDeclarationSyntax) => typeDeclarationSyntax.BaseList != null && typeDeclarationSyntax.BaseList.Types.Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IdentifiersNamedExtensionShouldBeEscaped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IdentifiersNamedExtensionShouldBeEscaped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S8368"; private const string MessageFormat = "'extension' is a contextual keyword in C# 14. Rename it or escape it as '@extension' to avoid ambiguity."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (compilationStart.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp14)) { return; } compilationStart.RegisterNodeAction( c => { if (c.Node.GetIdentifier() is { } id && IsExtensionToken(id)) { c.ReportIssue(Rule, id); } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.TypeParameter, SyntaxKind.ConstructorDeclaration, SyntaxKind.UsingDirective, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); compilationStart.RegisterNodeAction(c => { if (c.Node.TypeSyntax()?.Unwrap() is { } type and not (QualifiedNameSyntax or AliasQualifiedNameSyntax) && type.GetIdentifier() is { } id && IsExtensionToken(id)) { c.ReportIssue(Rule, id); } }, SyntaxKind.FieldDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.PropertyDeclaration); }); private static bool IsExtensionToken(SyntaxToken token) => token.ValueText == "extension" && !token.Text.StartsWith("@", StringComparison.Ordinal); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IdentifiersNamedFieldShouldBeEscaped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IdentifiersNamedFieldShouldBeEscaped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S8367"; private const string MessageFormat = "'field' is a contextual keyword in C# 14. Rename it, escape it as '@field', or qualify member access as 'this.field' to avoid ambiguity."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!compilationStart.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp14)) { compilationStart.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, cb => { if ((cb.CodeBlock is AccessorDeclarationSyntax { Parent.Parent: PropertyDeclarationSyntax } accessor && accessor.Kind() is SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration) || cb.CodeBlock is ArrowExpressionClauseSyntax { Parent: PropertyDeclarationSyntax }) { RegisterForPropertyBody(cb); } }); } }); private static void RegisterForPropertyBody(SonarCodeBlockStartAnalysisContext cb) { // Report local variable declarations, local functions, loop variables, catch variables, LINQ range variables, pattern/deconstruction variables, and parameters named 'field'. cb.RegisterNodeAction( c => { if (c.Node.GetIdentifier() is { } identifier && IsFieldToken(identifier)) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.VariableDeclarator, SyntaxKind.ForEachStatement, SyntaxKind.CatchDeclaration, SyntaxKind.FromClause, SyntaxKind.LetClause, SyntaxKind.JoinClause, SyntaxKind.JoinIntoClause, SyntaxKind.QueryContinuation, SyntaxKindEx.SingleVariableDesignation, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.Parameter); // Report unqualified references to a named symbol 'field' (class members, types, namespaces, …). // Exclude locally-declared symbols (locals, parameters, range variables) — reported at their declaration site above. cb.RegisterNodeAction( c => { if (c.Node is IdentifierNameSyntax { Identifier: var identifier, Parent: { } parent } identifierName && IsFieldToken(identifier) && parent is not MemberBindingExpressionSyntax && !(parent is MemberAccessExpressionSyntax mae && mae.Name == identifierName) && c.Model.GetSymbolInfo(identifierName).Symbol is not (null or ILocalSymbol or IParameterSymbol or IRangeVariableSymbol)) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.IdentifierName); } private static bool IsFieldToken(SyntaxToken token) => token.ValueText == "field" && !token.Text.StartsWith("@", StringComparison.Ordinal); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IfChainWithoutElse.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IfChainWithoutElse : IfChainWithoutElseBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind SyntaxKind => SyntaxKind.IfStatement; protected override string ElseClause => "else"; protected override bool IsElseIfWithoutElse(IfStatementSyntax ifSyntax) => ifSyntax.Parent.IsKind(SyntaxKind.ElseClause) && (ifSyntax.Else == null || IsEmptyBlock(ifSyntax.Else)); protected override Location IssueLocation(SonarSyntaxNodeReportingContext context, IfStatementSyntax ifSyntax) { var parentElse = (ElseClauseSyntax)ifSyntax.Parent; var diff = ifSyntax.IfKeyword.Span.End - parentElse.ElseKeyword.SpanStart; return Location.Create(context.Node.SyntaxTree, new TextSpan(parentElse.ElseKeyword.SpanStart, diff)); } private static bool IsEmptyBlock(ElseClauseSyntax elseClause) => elseClause.Statement is BlockSyntax blockSyntax && !(blockSyntax.Statements.Count > 0 || blockSyntax.DescendantTrivia().Any(x => x.IsComment() || x.IsKind(SyntaxKind.DisabledTextTrivia))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IfCollapsible.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IfCollapsible : IfCollapsibleBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var ifStatement = (IfStatementSyntax)c.Node; if (ifStatement.Else is not null) { return; } var parentIfStatement = ParentIfStatement(ifStatement); if (parentIfStatement is { Else: null } && !ContainsDynamicReference(ifStatement, c.Model)) { c.ReportIssue(Rule, ifStatement.IfKeyword, [parentIfStatement.IfKeyword.ToSecondaryLocation(SecondaryMessage)]); } }, SyntaxKind.IfStatement); private static bool ContainsDynamicReference(IfStatementSyntax ifStatement, SemanticModel model) => ifStatement.Condition.DescendantNodes().Any(x => x is ExpressionSyntax && x.IsDynamic(model)); private static IfStatementSyntax ParentIfStatement(IfStatementSyntax ifStatement) { var parent = ifStatement.Parent; while (parent.IsKind(SyntaxKind.Block)) { var block = (BlockSyntax)parent; if (block.Statements.Count != 1) { return null; } parent = parent.Parent; } return parent as IfStatementSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ImplementIDisposableCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ImplementIDisposableCorrectly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3881"; private const string MessageFormat = "Fix this implementation of 'IDisposable' to conform to the dispose pattern."; private static readonly ISet NotAllowedDisposeModifiers = new HashSet { SyntaxKind.VirtualKeyword, SyntaxKind.AbstractKeyword }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } var typeDeclarationSyntax = (TypeDeclarationSyntax)c.Node; var declarationIdentifier = typeDeclarationSyntax.Identifier; var checker = new DisposableChecker(typeDeclarationSyntax.BaseList, declarationIdentifier, c.Model.GetDeclaredSymbol(typeDeclarationSyntax), c.Node.GetDeclarationTypeName(), c.Model); var locations = checker.GetIssueLocations(typeDeclarationSyntax); if (locations.Any()) { c.ReportIssue(Rule, declarationIdentifier, locations); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private sealed class DisposableChecker { private readonly SemanticModel semanticModel; private readonly List secondaryLocations = new List(); private readonly BaseListSyntax baseTypes; private readonly SyntaxToken typeIdentifier; private readonly INamedTypeSymbol typeSymbol; private readonly string nodeType; public DisposableChecker(BaseListSyntax baseTypes, SyntaxToken typeIdentifier, INamedTypeSymbol typeSymbol, string nodeType, SemanticModel semanticModel) { this.baseTypes = baseTypes; this.typeIdentifier = typeIdentifier; this.typeSymbol = typeSymbol; this.nodeType = nodeType; this.semanticModel = semanticModel; } public List GetIssueLocations(TypeDeclarationSyntax typeDeclarationSyntax) { if (typeSymbol == null || typeSymbol.IsSealed) { return new List(); } if (typeSymbol.BaseType.Implements(KnownType.System_IDisposable)) { var iDisposableInterfaceSyntax = baseTypes?.Types.FirstOrDefault(IsOrImplementsIDisposable); if (iDisposableInterfaceSyntax != null) { AddSecondaryLocation(iDisposableInterfaceSyntax.GetLocation(), $"Remove 'IDisposable' from the list of interfaces implemented by '{typeSymbol.Name}'" + $" and override the base {nodeType} 'Dispose' implementation instead."); } if (HasVirtualDisposeBool(typeSymbol.BaseType)) { VerifyDisposeOverrideCallsBase(FindMethodImplementationOrAbstractDeclaration(typeSymbol, IsDisposeBool, typeDeclarationSyntax) .OfType() .FirstOrDefault()); } return secondaryLocations; } if (typeSymbol.Implements(KnownType.System_IDisposable)) { if (!FindMethodDeclarations(typeSymbol, IsDisposeBool).Any()) { AddSecondaryLocation(typeIdentifier.GetLocation(), $"Provide 'protected' overridable implementation of 'Dispose(bool)' on " + $"'{typeSymbol.Name}' or mark the type as 'sealed'."); } var destructor = FindMethodImplementationOrAbstractDeclaration(typeSymbol, x => x.IsDestructor(), typeDeclarationSyntax) .OfType() .FirstOrDefault(); VerifyDestructor(destructor); var disposeMethod = FindMethodImplementationOrAbstractDeclaration(typeSymbol, KnownMethods.IsIDisposableDispose, typeDeclarationSyntax) .OfType() .FirstOrDefault(); VerifyDispose(disposeMethod, typeSymbol.IsSealed); } return secondaryLocations; } private void AddSecondaryLocation(Location location, string message) => secondaryLocations.Add(new SecondaryLocation(location, message)); private void VerifyDestructor(DestructorDeclarationSyntax destructorSyntax) { if (!destructorSyntax.HasBodyOrExpressionBody()) { return; } if (!HasStatementsCount(destructorSyntax, 1) || !CallsVirtualDispose(destructorSyntax, argumentValue: a => IsLiteralArgument(a, SyntaxKind.FalseKeyword))) { AddSecondaryLocation(destructorSyntax.Identifier.GetLocation(), $"Modify '{typeSymbol.Name}.~{typeSymbol.Name}()' so that it calls 'Dispose(false)' and " + "then returns."); } } private void VerifyDisposeOverrideCallsBase(MethodDeclarationSyntax disposeMethod) { if (!disposeMethod.HasBodyOrExpressionBody()) { return; } var parameterName = disposeMethod.ParameterList.Parameters.Single().Identifier.Text; if (!CallsVirtualDispose(disposeMethod, argumentValue: a => a is { Expression: IdentifierNameSyntax { Identifier.Text: { } text } } && text == parameterName)) { AddSecondaryLocation(disposeMethod.Identifier.GetLocation(), $"Modify 'Dispose({parameterName})' so that it calls 'base.Dispose({parameterName})'."); } } private void VerifyDispose(MethodDeclarationSyntax disposeMethod, bool isSealedClass) { if (disposeMethod == null) { return; } if (disposeMethod.HasBodyOrExpressionBody() && !isSealedClass) { var missingVirtualDispose = !CallsVirtualDispose(disposeMethod, argumentValue: a => IsLiteralArgument(a, SyntaxKind.TrueKeyword)); var missingSuppressFinalize = !CallsSuppressFinalize(disposeMethod); string remediation = null; if (missingVirtualDispose && missingSuppressFinalize) { remediation = "should call 'Dispose(true)' and 'GC.SuppressFinalize(this)'."; } else if (missingVirtualDispose) { remediation = "should also call 'Dispose(true)'."; } else if (missingSuppressFinalize) { remediation = "should also call 'GC.SuppressFinalize(this)'."; } else if (!HasStatementsCount(disposeMethod, 2)) { remediation = "should call 'Dispose(true)', 'GC.SuppressFinalize(this)' and nothing else."; } if (remediation != null) { AddSecondaryLocation(disposeMethod.Identifier.GetLocation(), $"'{typeSymbol.Name}.Dispose()' {remediation}"); } } // Because of partial classes we cannot always rely on the current semantic model. // See issue: https://github.com/SonarSource/sonar-dotnet/issues/690 var disposeMethodSymbol = disposeMethod.SyntaxTree.SemanticModelOrDefault(semanticModel)?.GetDeclaredSymbol(disposeMethod); if (disposeMethodSymbol == null) { return; } if (disposeMethodSymbol.IsAbstract || disposeMethodSymbol.IsVirtual) { var modifier = disposeMethod.Modifiers .FirstOrDefault(m => m.IsAnyKind(NotAllowedDisposeModifiers)); AddSecondaryLocation(modifier.GetLocation(), $"'{typeSymbol.Name}.Dispose()' should not be 'virtual' or 'abstract'."); } if (disposeMethodSymbol.ExplicitInterfaceImplementations.Any()) { AddSecondaryLocation(disposeMethod.Identifier.GetLocation(), $"'{typeSymbol.Name}.Dispose()' should be 'public'."); } } private bool IsOrImplementsIDisposable(BaseTypeSyntax baseType) => (semanticModel.GetSymbolInfo(baseType.Type).Symbol as INamedTypeSymbol).Is(KnownType.System_IDisposable); private bool CallsSuppressFinalize(BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration.ContainsMethodInvocation(semanticModel, method => method.Expression.NameIs(nameof(GC.SuppressFinalize)) && method is { ArgumentList.Arguments: { Count: 1 } arguments } && arguments[0] is { Expression: ThisExpressionSyntax }, KnownMethods.IsGcSuppressFinalize); private bool CallsVirtualDispose(BaseMethodDeclarationSyntax methodDeclaration, Func argumentValue) => methodDeclaration.ContainsMethodInvocation(semanticModel, method => method.Expression.NameIs(nameof(IDisposable.Dispose)) && method is { ArgumentList.Arguments: { Count: 1 } arguments } && arguments[0] is var argument && argumentValue(argument), IsDisposeBool); private static bool IsDisposeBool(IMethodSymbol method) => method.Name == nameof(IDisposable.Dispose) && (method.IsVirtual || method.IsAbstract || method.IsOverride) && method.DeclaredAccessibility == Accessibility.Protected && method.Parameters.Length == 1 && method.Parameters.Any(p => p.Type.Is(KnownType.System_Boolean)); private static bool HasStatementsCount(BaseMethodDeclarationSyntax methodDeclaration, int expectedStatementsCount) => methodDeclaration.Body?.Statements.Count == expectedStatementsCount || (methodDeclaration.ExpressionBody() != null && expectedStatementsCount == 1); // Expression body has only one statement private static IEnumerable FindMethodDeclarations(INamedTypeSymbol typeSymbol, Func predicate) => typeSymbol.GetMembers().OfType().Where(predicate).Select(x => x.ImplementationSyntax()); private static IEnumerable FindMethodImplementationOrAbstractDeclaration(INamedTypeSymbol typeSymbol, Func predicate, TypeDeclarationSyntax typeDeclarationSyntax) => FindMethodDeclarations(typeSymbol, predicate) .OfType() // We want to skip the partial method declarations when reporting secondary issues since the messages are relevant only for implementation part. // We do want to include abstract methods though since the implementation is in another type which could be defined in a different assembly than the one analyzed. .Where(x => typeDeclarationSyntax.Contains(x) && (x.HasBodyOrExpressionBody() || x.Modifiers.AnyOfKind(SyntaxKind.AbstractKeyword))); private static bool HasVirtualDisposeBool(ITypeSymbol typeSymbol) => typeSymbol.GetSelfAndBaseTypes() .SelectMany(type => type.GetMembers()) .OfType() .Where(IsDisposeBool) .Any(symbol => !symbol.IsAbstract); private static bool IsLiteralArgument(ArgumentSyntax argument, SyntaxKind literalTokenKind) => argument is { Expression: LiteralExpressionSyntax { Token: var token } } && token.IsKind(literalTokenKind); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ImplementISerializableCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.Serialization; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ImplementISerializableCorrectly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3925"; private const string MessageFormat = "Update this implementation of 'ISerializable' to conform to the recommended serialization pattern. {0}"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } var typeDeclarationSyntax = (TypeDeclarationSyntax)c.Node; var typeSymbol = (INamedTypeSymbol)c.ContainingSymbol; if (!ImplementsISerializable(typeSymbol) || !OptsInForSerialization(typeSymbol)) { return; } var getObjectData = typeSymbol.GetMembers().OfType().FirstOrDefault(KnownMethods.IsGetObjectData); var implementationErrors = new List(); implementationErrors.AddRange(CheckSerializableAttribute(typeDeclarationSyntax.Keyword, typeSymbol)); implementationErrors.AddRange(CheckConstructor(typeDeclarationSyntax, typeSymbol)); implementationErrors.AddRange(CheckGetObjectDataAccessibility(typeDeclarationSyntax, typeSymbol, getObjectData)); implementationErrors.AddRange(CheckGetObjectData(typeDeclarationSyntax, typeSymbol, getObjectData)); if (implementationErrors.Any()) { c.ReportIssue(Rule, typeDeclarationSyntax.Identifier, implementationErrors, implementationErrors.JoinStr(" ", x => x.Message)); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); private static IEnumerable CheckSerializableAttribute(SyntaxToken typeKeyword, INamedTypeSymbol typeSymbol) { if (!typeSymbol.IsAbstract && !HasSerializableAttribute(typeSymbol)) { yield return new(typeKeyword.GetLocation(), $"Add 'System.SerializableAttribute' attribute on '{typeSymbol.Name}' because it implements 'ISerializable'."); } } // Symbol should be checked for null in the caller. private static IEnumerable DeclarationOrImplementation(TypeDeclarationSyntax typeDeclaration, IMethodSymbol symbol) => symbol.PartialImplementationPart is not null && symbol.PartialImplementationPart.DeclaringSyntaxReferences.First().GetSyntax() is var partialImplementation && typeDeclaration.DescendantNodes().Any(x => x.Equals(partialImplementation)) ? new[] { partialImplementation }.Cast() : symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).Cast(); private static IEnumerable CheckGetObjectData(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, IMethodSymbol getObjectData) { if (!ImplementsISerializable(typeSymbol.BaseType)) { yield break; } if (getObjectData == null) { var serializableFields = GetSerializableFieldNames(typeSymbol).ToList(); if (serializableFields.Any()) { yield return new(typeDeclaration.Keyword.GetLocation(), $"Override 'GetObjectData(SerializationInfo, StreamingContext)' and serialize '{serializableFields.JoinAnd()}'."); } } else if (getObjectData.IsOverride && !IsCallingBase(getObjectData)) { foreach (var declaration in DeclarationOrImplementation(typeDeclaration, getObjectData)) { yield return new(declaration.Identifier.GetLocation(), "Invoke 'base.GetObjectData(SerializationInfo, StreamingContext)' in 'GetObjectData'."); } } } private static IEnumerable CheckGetObjectDataAccessibility(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol, IMethodSymbol getObjectData) { if (getObjectData == null || typeSymbol.IsSealed || IsPublicVirtual(getObjectData) || IsExplicitImplementation(getObjectData)) { yield break; } foreach (var declaration in DeclarationOrImplementation(typeDeclaration, getObjectData)) { yield return new(declaration.Identifier.GetLocation(), $"Make 'GetObjectData' 'public' and 'virtual', or seal '{typeSymbol.Name}'."); } } private static IEnumerable GetSerializableFieldNames(INamedTypeSymbol typeSymbol) => typeSymbol.GetMembers().OfType() .Where(x => !x.IsStatic && ImplementsISerializable(x.Type)) .Select(x => x.Name); private static IEnumerable CheckConstructor(TypeDeclarationSyntax typeDeclaration, INamedTypeSymbol typeSymbol) { var accessibility = typeSymbol.IsSealed ? SyntaxConstants.Private : SyntaxConstants.Protected; if (typeSymbol.Constructors.FirstOrDefault(KnownMethods.IsSerializationConstructor) is { } serializationConstructor) { var constructorSyntax = DeclarationOrImplementation(typeDeclaration, serializationConstructor).First(); if ((typeSymbol.IsSealed && serializationConstructor.DeclaredAccessibility != Accessibility.Private) || (!typeSymbol.IsSealed && serializationConstructor.DeclaredAccessibility != Accessibility.Protected)) { yield return new(constructorSyntax.Identifier.GetLocation(), $"Make the serialization constructor '{accessibility}'."); } if (ImplementsISerializable(typeSymbol.BaseType) && !IsCallingBaseConstructor(serializationConstructor)) { yield return new(constructorSyntax.Identifier.GetLocation(), $"Call 'base(SerializationInfo, StreamingContext)' on the serialization constructor."); } } else { yield return new(typeDeclaration.Keyword.GetLocation(), $"Add a '{accessibility}' constructor '{typeSymbol.Name}(SerializationInfo, StreamingContext)'."); } } private static bool IsCallingBase(IMethodSymbol methodSymbol) => methodSymbol.ImplementationSyntax() is { } methodDeclaration && methodDeclaration.DescendantNodes() .OfType() .Select(x => x.Expression) .OfType() .Any(x => x.IsKind(SyntaxKind.SimpleMemberAccessExpression) && x.Expression.IsKind(SyntaxKind.BaseExpression) && x.Name.Identifier.ValueText == nameof(ISerializable.GetObjectData)); private static bool IsCallingBaseConstructor(IMethodSymbol constructorSymbol) => constructorSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is ConstructorDeclarationSyntax { Initializer: { ThisOrBaseKeyword: { RawKind: (int)SyntaxKind.BaseKeyword } } }; private static bool ImplementsISerializable(ITypeSymbol typeSymbol) => typeSymbol != null && typeSymbol.IsPubliclyAccessible() && typeSymbol.Implements(KnownType.System_Runtime_Serialization_ISerializable); private static bool OptsInForSerialization(INamedTypeSymbol typeSymbol) => typeSymbol.IsSerializable // [Serializable] is present at the types declaration || typeSymbol.Interfaces.Any(x => x.Is(KnownType.System_Runtime_Serialization_ISerializable)) // ISerializable is listed in the types declaration base type list || typeSymbol.Constructors.Any(KnownMethods.IsSerializationConstructor); // A serialization constructor is defined private static bool HasSerializableAttribute(ISymbol symbol) => symbol.HasAttribute(KnownType.System_SerializableAttribute); private static bool IsPublicVirtual(IMethodSymbol methodSymbol) => methodSymbol.DeclaredAccessibility == Accessibility.Public && (methodSymbol.IsVirtual || methodSymbol.IsOverride); private static bool IsExplicitImplementation(IMethodSymbol methodSymbol) => methodSymbol.ExplicitInterfaceImplementations.Any(KnownMethods.IsGetObjectData); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ImplementSerializationMethodsCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ImplementSerializationMethodsCorrectly : ImplementSerializationMethodsCorrectlyBase { private const string ProblemStatic = "non-static"; private const string ProblemReturnVoidText = "return 'void'"; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override string MethodStaticMessage => ProblemStatic; protected override string MethodReturnTypeShouldBeVoidMessage => ProblemReturnVoidText; protected override Location GetIdentifierLocation(IMethodSymbol methodSymbol) => methodSymbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()) .OfType() .FirstOrDefault() ?.Identifier .GetLocation(); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var wrapper = (LocalFunctionStatementSyntaxWrapper)c.Node; var attributes = GetSerializationAttributes(wrapper.AttributeLists, c.Model); ReportOnAttributes(c, attributes, "local functions"); }, SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction(c => { var lambda = (ParenthesizedLambdaExpressionSyntax)c.Node; var attributes = GetSerializationAttributes(lambda.AttributeLists, c.Model); ReportOnAttributes(c, attributes, "lambdas"); }, SyntaxKind.ParenthesizedLambdaExpression); base.Initialize(context); } private void ReportOnAttributes(SonarSyntaxNodeReportingContext context, IEnumerable attributes, string memberType) { foreach (var attribute in attributes) { context.ReportIssue(AttributeNotConsideredRule, attribute, memberType); } } private static IEnumerable GetSerializationAttributes(SyntaxList attributeList, SemanticModel model) => attributeList.SelectMany(x => x.Attributes) .Where(attribute => attribute.IsKnownType(KnownType.System_Runtime_Serialization_OnSerializingAttribute, model) || attribute.IsKnownType(KnownType.System_Runtime_Serialization_OnSerializedAttribute, model) || attribute.IsKnownType(KnownType.System_Runtime_Serialization_OnDeserializingAttribute, model) || attribute.IsKnownType(KnownType.System_Runtime_Serialization_OnDeserializedAttribute, model)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IndentSingleLineFollowingConditional.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { // Note: this rule only covers the indentation of the first line after a conditional. // Rule 2681 covers the misleading indentation of other lines of multiline blocks (https://jira.sonarsource.com/browse/RSPEC-2681) [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentSingleLineFollowingConditional : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3973"; private const string MessageFormat = "Use curly braces or indentation to denote the code conditionally executed by this '{0}'"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckWhile, SyntaxKind.WhileStatement); context.RegisterNodeAction(CheckDo, SyntaxKind.DoStatement); context.RegisterNodeAction(CheckFor, SyntaxKind.ForStatement); context.RegisterNodeAction(CheckForEach, SyntaxKind.ForEachStatement); context.RegisterNodeAction(CheckIf, SyntaxKind.IfStatement); context.RegisterNodeAction(CheckElse, SyntaxKind.ElseClause); } private static void CheckWhile(SonarSyntaxNodeReportingContext context) { var whileStatement = (WhileStatementSyntax)context.Node; if (!IsStatementIndentationOk(whileStatement, whileStatement.Statement)) { // Squiggle - "while (condition1 && condition2)" var primaryLocation = whileStatement.WhileKeyword.CreateLocation(whileStatement.CloseParenToken); ReportIssue(context, primaryLocation, whileStatement.Statement, "while"); } } private static void CheckDo(SonarSyntaxNodeReportingContext context) { var doStatement = (DoStatementSyntax)context.Node; if (!IsStatementIndentationOk(doStatement, doStatement.Statement)) { // Just highlight the "do" keyword ReportIssue(context, doStatement.DoKeyword.GetLocation(), doStatement.Statement, "do"); } } private static void CheckFor(SonarSyntaxNodeReportingContext context) { var forStatement = (ForStatementSyntax)context.Node; if (!IsStatementIndentationOk(forStatement, forStatement.Statement)) { // Squiggle - "for (...)" var primaryLocation = forStatement.ForKeyword.CreateLocation(forStatement.CloseParenToken); ReportIssue(context, primaryLocation, forStatement.Statement, "for"); } } private static void CheckForEach(SonarSyntaxNodeReportingContext context) { var forEachStatement = (ForEachStatementSyntax)context.Node; if (!IsStatementIndentationOk(forEachStatement, forEachStatement.Statement)) { // Squiggle - "foreach (...)" var primaryLocation = forEachStatement.ForEachKeyword.CreateLocation(forEachStatement.CloseParenToken); ReportIssue(context, primaryLocation, forEachStatement.Statement, "foreach"); } } private static void CheckIf(SonarSyntaxNodeReportingContext context) { var ifStatement = (IfStatementSyntax)context.Node; // Special case for "else if" on the same line. // In that case, we'll check that the statement is more indented then the "else", not the "if". // Highlighting: "if (...)", or "else if (...)" as appropriate SyntaxNode controlNode; SyntaxToken startToken; string conditionLabelText; if (ifStatement.Parent is ElseClauseSyntax elseClause && ifStatement.LineNumberToReport() == elseClause.LineNumberToReport()) { controlNode = elseClause; startToken = elseClause.ElseKeyword; conditionLabelText = "else if"; } else { controlNode = ifStatement; startToken = ifStatement.IfKeyword; conditionLabelText = "if"; } if (!IsStatementIndentationOk(controlNode, ifStatement.Statement)) { var primaryLocation = startToken.CreateLocation(ifStatement.CloseParenToken); ReportIssue(context, primaryLocation, ifStatement.Statement, conditionLabelText); } } private static void CheckElse(SonarSyntaxNodeReportingContext context) { var elseClause = (ElseClauseSyntax)context.Node; if (!IsStatementIndentationOk(elseClause, elseClause.Statement)) { // Just highlight the "else" keyword ReportIssue(context, elseClause.ElseKeyword.GetLocation(), elseClause.Statement, "else"); } } private static bool IsStatementIndentationOk(SyntaxNode controlNode, SyntaxNode conditionallyExecutedNode) => conditionallyExecutedNode is BlockSyntax || VisualIndentComparer.IsSecondIndentLonger(controlNode, conditionallyExecutedNode); private static void ReportIssue(SonarSyntaxNodeReportingContext context, Location primaryLocation, SyntaxNode secondaryLocationNode, string conditionLabelText) => context.ReportIssue(Rule, primaryLocation, [GetFirstLineOfNode(secondaryLocationNode).ToSecondary()], conditionLabelText); private static Location GetFirstLineOfNode(SyntaxNode node) { var lineNumber = node.GetLocation().StartLine(); var wholeLineSpan = node.SyntaxTree.GetText().Lines[lineNumber].Span; var secondaryLocationSpan = wholeLineSpan.Intersection(node.GetLocation().SourceSpan); return Location.Create(node.SyntaxTree, secondaryLocationSpan ?? wholeLineSpan); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IndexOfCheckAgainstZero.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndexOfCheckAgainstZero : IndexOfCheckAgainstZeroBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind LessThanExpression => SyntaxKind.LessThanExpression; protected override SyntaxKind GreaterThanExpression => SyntaxKind.GreaterThanExpression; protected override SyntaxNode Left(BinaryExpressionSyntax binaryExpression) => binaryExpression.Left; protected override SyntaxToken OperatorToken(BinaryExpressionSyntax binaryExpression) => binaryExpression.OperatorToken; protected override SyntaxNode Right(BinaryExpressionSyntax binaryExpression) => binaryExpression.Right; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; using CfgAllPathValidator = SonarAnalyzer.CFG.Roslyn.CfgAllPathValidator; namespace SonarAnalyzer.CSharp.Rules; public partial class InfiniteRecursion { private sealed class RoslynChecker : IChecker { public void CheckForNoExitProperty(SonarSyntaxNodeReportingContext c, PropertyDeclarationSyntax property, IPropertySymbol propertySymbol) => CheckForNoExit(c, propertySymbol, "property's recursion", "property accessor's recursion"); public void CheckForNoExitIndexer(SonarSyntaxNodeReportingContext c, IndexerDeclarationSyntax indexer, IPropertySymbol propertySymbol) => CheckForNoExit(c, propertySymbol, "indexer's recursion", "indexer accessor's recursion"); public void CheckForNoExitEvent(SonarSyntaxNodeReportingContext c, EventDeclarationSyntax eventDeclaration, IEventSymbol eventSymbol) { if (eventDeclaration.AccessorList is not null) { foreach (var accessor in eventDeclaration.AccessorList.Accessors.Where(x => x.HasBodyOrExpressionBody())) { var cfg = ControlFlowGraph.Create(accessor, c.Model, c.Cancel); var context = new RecursionContext(c, cfg, eventSymbol, accessor.Keyword.GetLocation(), "event accessor's recursion"); var walker = new RecursionSearcher(context); walker.CheckPaths(); } } } public void CheckForNoExitMethod(SonarSyntaxNodeReportingContext c, SyntaxNode body, SyntaxToken identifier, IMethodSymbol symbol) { if (body.CreateCfg(c.Model, c.Cancel) is { } cfg) { var context = new RecursionContext(c, cfg, symbol, identifier.GetLocation(), "method's recursion"); var walker = new RecursionSearcher(context); walker.CheckPaths(); } } private static void CheckForNoExit(SonarSyntaxNodeReportingContext c, IPropertySymbol propertySymbol, string arrowExpressionMessageArg, string accessorMessageArg) { ArrowExpressionClauseSyntax expressionBody = null; AccessorListSyntax accessorList = null; Location location = null; if (c.Node is PropertyDeclarationSyntax propertyDeclaration) { expressionBody = propertyDeclaration.ExpressionBody; accessorList = propertyDeclaration.AccessorList; location = propertyDeclaration.Identifier.GetLocation(); } else { var indexerDeclaration = (IndexerDeclarationSyntax)c.Node; expressionBody = indexerDeclaration.ExpressionBody; accessorList = indexerDeclaration.AccessorList; location = indexerDeclaration.ThisKeyword.GetLocation(); } if (expressionBody?.Expression is not null) { var cfg = ControlFlowGraph.Create(expressionBody, c.Model, c.Cancel); var walker = new RecursionSearcher(new RecursionContext(c, cfg, propertySymbol, location, arrowExpressionMessageArg)); walker.CheckPaths(); } else if (accessorList is not null) { foreach (var accessor in accessorList.Accessors.Where(x => x.HasBodyOrExpressionBody())) { var cfg = ControlFlowGraph.Create(accessor, c.Model, c.Cancel); var context = new RecursionContext(c, cfg, propertySymbol, accessor.Keyword.GetLocation(), accessorMessageArg); var walker = new RecursionSearcher(context, !accessor.Keyword.IsAnyKind(SyntaxKind.SetKeyword, SyntaxKindEx.InitKeyword)); walker.CheckPaths(); } } } private sealed class RecursionSearcher : CfgAllPathValidator { private readonly RecursionContext context; private readonly bool isGetAccesor; public RecursionSearcher(RecursionContext context, bool isGetAccesor = true) : base(context.ControlFlowGraph) { this.context = context; this.isGetAccesor = isGetAccesor; } public void CheckPaths() { if (!CfgCanExit() || CheckAllPaths()) { context.ReportIssue(); } } protected override bool IsValid(BasicBlock block) { if (block.OperationsAndBranchValue.ToReversedExecutionOrder().FirstOrDefault(x => context.AnalyzedSymbol.Equals(MemberSymbol(x.Instance))) is { Instance: { } } operation) { var isWrite = operation.Parent is { Kind: OperationKindEx.SimpleAssignment } parent && ISimpleAssignmentOperationWrapper.FromOperation(parent).Target == operation.Instance; return isGetAccesor ^ isWrite; } return false; static ISymbol MemberSymbol(IOperation operation) => operation.Kind switch { OperationKindEx.PropertyReference when IPropertyReferenceOperationWrapper.FromOperation(operation) is var propertyReference && InstanceReferencesThis(propertyReference.Instance) => propertyReference.Property, OperationKindEx.Invocation when IInvocationOperationWrapper.FromOperation(operation) is var invocation && (!invocation.IsVirtual || InstanceReferencesThis(invocation.Instance)) => invocation.TargetMethod, OperationKindEx.Binary => IBinaryOperationWrapper.FromOperation(operation).OperatorMethod, OperationKindEx.Decrement => IIncrementOrDecrementOperationWrapper.FromOperation(operation).OperatorMethod, OperationKindEx.Increment => IIncrementOrDecrementOperationWrapper.FromOperation(operation).OperatorMethod, OperationKindEx.Unary => IUnaryOperationWrapper.FromOperation(operation).OperatorMethod, OperationKindEx.Conversion => IConversionOperationWrapper.FromOperation(operation).OperatorMethod, OperationKindEx.EventReference => IEventReferenceOperationWrapper.FromOperation(operation).Member, _ => null }; static bool InstanceReferencesThis(IOperation instance) => instance is null || instance.IsAnyKind(OperationKindEx.FlowCaptureReference, OperationKindEx.InstanceReference); } protected override bool IsInvalid(BasicBlock block) => false; private bool CfgCanExit() => context.ControlFlowGraph.ExitBlock.IsReachable || context.ControlFlowGraph.Blocks.Any(x => x.FallThroughSuccessor?.Semantics == ControlFlowBranchSemantics.Throw && x.IsReachable); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.SonarCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CSharp.Rules { public partial class InfiniteRecursion { public class SonarChecker : IChecker { public void CheckForNoExitProperty(SonarSyntaxNodeReportingContext c, PropertyDeclarationSyntax property, IPropertySymbol propertySymbol) { IControlFlowGraph cfg; if (property.ExpressionBody?.Expression != null) { if (CSharpControlFlowGraph.TryGet(property, c.Model, out cfg)) { var walker = new RecursionSearcherForProperty( new RecursionContext(c, cfg, propertySymbol, property.Identifier.GetLocation(), "property's recursion"), isSetAccessor: false); walker.CheckPaths(); } return; } var accessors = property.AccessorList?.Accessors.Where(a => a.HasBodyOrExpressionBody()); if (accessors != null) { foreach (var accessor in accessors) { if (CSharpControlFlowGraph.TryGet(accessor, c.Model, out cfg)) { var walker = new RecursionSearcherForProperty( new RecursionContext(c, cfg, propertySymbol, accessor.Keyword.GetLocation(), "property accessor's recursion"), isSetAccessor: accessor.Keyword.IsKind(SyntaxKind.SetKeyword)); walker.CheckPaths(); CheckInfiniteJumpLoop(c, accessor, cfg, "property accessor"); } } } } public void CheckForNoExitIndexer(SonarSyntaxNodeReportingContext c, IndexerDeclarationSyntax indexer, IPropertySymbol propertySymbol) { // SonarCFG is out of support } public void CheckForNoExitEvent(SonarSyntaxNodeReportingContext c, EventDeclarationSyntax eventDeclaration, IEventSymbol eventSymbol) { // SonarCFG is out of support } public void CheckForNoExitMethod(SonarSyntaxNodeReportingContext c, SyntaxNode body, SyntaxToken identifier, IMethodSymbol symbol) { if (CSharpControlFlowGraph.TryGet(body, c.Model, out var cfg)) { var walker = new RecursionSearcherForMethod(new RecursionContext(c, cfg, symbol, identifier.GetLocation(), "method's recursion")); walker.CheckPaths(); CheckInfiniteJumpLoop(c, body, cfg, "method"); } } private static void CheckInfiniteJumpLoop(SonarSyntaxNodeReportingContext context, SyntaxNode body, IControlFlowGraph cfg, string declarationType) { if (body is null) { return; } var reachableFromBlock = cfg.Blocks.Except(new[] { cfg.ExitBlock }).ToDictionary( b => b, b => b.AllSuccessorBlocks); var alreadyProcessed = new HashSet(); foreach (var reachable in reachableFromBlock) { if (!reachable.Key.AllPredecessorBlocks.Contains(cfg.EntryBlock) || alreadyProcessed.Contains(reachable.Key) || reachable.Value.Contains(cfg.ExitBlock)) { continue; } alreadyProcessed.UnionWith(reachable.Value); alreadyProcessed.Add(reachable.Key); var reportOnOptions = reachable.Value.OfType() .Where(jb => jb.JumpNode is GotoStatementSyntax) .ToList(); if (!reportOnOptions.Any()) { continue; } // Calculate stable report location: var lastJumpLocation = reportOnOptions.Max(b => b.JumpNode.SpanStart); var reportOn = reportOnOptions.First(b => b.JumpNode.SpanStart == lastJumpLocation); context.ReportIssue(Rule, reportOn.JumpNode, declarationType); } } private class RecursionSearcherForMethod : RecursionSearcher { public RecursionSearcherForMethod(RecursionContext context) : base(context) { } protected override bool HasReferenceToDeclaringSymbol(Block block) => block.Instructions.Any(x => x is InvocationExpressionSyntax invocation && IsInstructionOnThisAndMatchesDeclaringSymbol(invocation.Expression, context.AnalyzedSymbol, context.Model)); } private class RecursionSearcherForProperty : RecursionSearcher { private readonly bool isSet; public RecursionSearcherForProperty(RecursionContext context, bool isSetAccessor) : base(context) => isSet = isSetAccessor; private static readonly ISet TypesForReference = new HashSet { typeof(IdentifierNameSyntax), typeof(MemberAccessExpressionSyntax) }; protected override bool HasReferenceToDeclaringSymbol(Block block) => block.Instructions.Any(x => TypesForReference.Contains(x.GetType()) && MatchesAccessor(x) && IsInstructionOnThisAndMatchesDeclaringSymbol(x, context.AnalyzedSymbol, context.Model)); private bool MatchesAccessor(SyntaxNode node) { var propertyAccess = ((ExpressionSyntax)node).GetSelfOrTopParenthesizedExpression(); var isNodeASet = propertyAccess.Parent is AssignmentExpressionSyntax assignment && assignment.Left == propertyAccess; return isNodeASet == isSet; } } private abstract class RecursionSearcher : CfgAllPathValidator { protected readonly RecursionContext context; protected abstract bool HasReferenceToDeclaringSymbol(Block block); protected RecursionSearcher(RecursionContext context) : base(context.ControlFlowGraph) => this.context = context; public void CheckPaths() { if (CheckAllPaths()) { context.ReportIssue(); } } protected override bool IsBlockValid(Block block) => HasReferenceToDeclaringSymbol(block); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public partial class InfiniteRecursion : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2190"; private const string MessageFormat = "Add a way to break out of this {0}."; private readonly IChecker checker; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static DiagnosticDescriptor Rule => DescriptorFactory.Create(DiagnosticId, MessageFormat); public InfiniteRecursion() : this(AnalyzerConfiguration.AlwaysEnabled) { } internal /* for testing */ InfiniteRecursion(IAnalyzerConfiguration configuration) => checker = configuration.UseSonarCfg() ? new SonarChecker() : new RoslynChecker(); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; CheckForNoExitMethod(c, method.Identifier); }, SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => { var function = (LocalFunctionStatementSyntaxWrapper)c.Node; CheckForNoExitMethod(c, function.Identifier); }, SyntaxKindEx.LocalFunctionStatement); context.RegisterNodeAction( c => { var @operator = (OperatorDeclarationSyntax)c.Node; CheckForNoExitMethod(c, @operator.OperatorToken); }, SyntaxKind.OperatorDeclaration); context.RegisterNodeAction( c => { var conversionOperator = (ConversionOperatorDeclarationSyntax)c.Node; CheckForNoExitMethod(c, conversionOperator.OperatorKeyword); }, SyntaxKind.ConversionOperatorDeclaration); context.RegisterNodeAction( c => { var property = (PropertyDeclarationSyntax)c.Node; checker.CheckForNoExitProperty(c, property, c.Model.GetDeclaredSymbol(property)); }, SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => { var indexer = (IndexerDeclarationSyntax)c.Node; checker.CheckForNoExitIndexer(c, indexer, c.Model.GetDeclaredSymbol(indexer)); }, SyntaxKind.IndexerDeclaration); context.RegisterNodeAction( c => { var eventDeclaration = (EventDeclarationSyntax)c.Node; checker.CheckForNoExitEvent(c, eventDeclaration, c.Model.GetDeclaredSymbol(eventDeclaration)); }, SyntaxKind.EventDeclaration); } private void CheckForNoExitMethod(SonarSyntaxNodeReportingContext c, SyntaxToken identifier) { if (c.Model.GetDeclaredSymbol(c.Node) is IMethodSymbol symbol) { checker.CheckForNoExitMethod(c, c.Node, identifier, symbol); } } private static bool IsInstructionOnThisAndMatchesDeclaringSymbol(SyntaxNode node, ISymbol declaringSymbol, SemanticModel semanticModel) { var name = node is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression.IsKind(SyntaxKind.ThisExpression) ? memberAccess.Name : node as NameSyntax; return name is not null && semanticModel.GetSymbolInfo(name).Symbol is { } assignedSymbol && declaringSymbol.Equals(assignedSymbol); } private sealed class RecursionContext { private readonly SonarSyntaxNodeReportingContext analysisContext; private readonly string messageArg; private readonly Location issueLocation; public TControlFlowGraph ControlFlowGraph { get; } public ISymbol AnalyzedSymbol { get; } public SemanticModel Model => analysisContext.Model; public RecursionContext(SonarSyntaxNodeReportingContext analysisContext, TControlFlowGraph controlFlowGraph, ISymbol analyzedSymbol, Location issueLocation, string messageArg) { this.analysisContext = analysisContext; this.messageArg = messageArg; this.issueLocation = issueLocation; ControlFlowGraph = controlFlowGraph; AnalyzedSymbol = analyzedSymbol; } public void ReportIssue() => analysisContext.ReportIssue(Rule, issueLocation, messageArg); } private interface IChecker { void CheckForNoExitProperty(SonarSyntaxNodeReportingContext c, PropertyDeclarationSyntax property, IPropertySymbol propertySymbol); void CheckForNoExitIndexer(SonarSyntaxNodeReportingContext c, IndexerDeclarationSyntax indexer, IPropertySymbol propertySymbol); void CheckForNoExitEvent(SonarSyntaxNodeReportingContext c, EventDeclarationSyntax eventDeclaration, IEventSymbol eventSymbol); void CheckForNoExitMethod(SonarSyntaxNodeReportingContext c, SyntaxNode body, SyntaxToken identifier, IMethodSymbol symbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InheritedCollidingInterfaceMembers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InheritedCollidingInterfaceMembers : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3444"; private const string MessageFormat = "Rename or add member{1} {0} to this interface to resolve ambiguities."; private const string SecondaryMessageFormat = "This member collides with '{0}'"; private const int MaxMemberDisplayCount = 2; private const int MinBaseListTypes = 2; private static readonly ISet PartKindsToStartWith = new HashSet { SymbolDisplayPartKind.MethodName, SymbolDisplayPartKind.PropertyName, SymbolDisplayPartKind.EventName }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var interfaceDeclaration = (InterfaceDeclarationSyntax)c.Node; if (interfaceDeclaration.BaseList is null || interfaceDeclaration.BaseList.Types.Count < MinBaseListTypes) { return; } var interfaceSymbol = c.Model.GetDeclaredSymbol(interfaceDeclaration); if (interfaceSymbol is null) { return; } var collidingMembers = GetCollidingMembers(interfaceSymbol).Take(MaxMemberDisplayCount + 1).ToList(); if (collidingMembers.Any()) { var membersText = GetIssueMessageText(collidingMembers, c.Model, interfaceDeclaration.SpanStart); var pluralize = collidingMembers.Count > 1 ? "s" : string.Empty; c.ReportIssue(Rule, interfaceDeclaration.Identifier, SecondaryLocations(collidingMembers, c.Model), membersText, pluralize); } }, SyntaxKind.InterfaceDeclaration); private static IEnumerable SecondaryLocations(List collidingMembers, SemanticModel model) { return collidingMembers .SelectMany(x => x.Member.Locations.Select(l => new { Location = l, x.CollideWith, CollideWithSymbolName = CollideWithSymbolName(x) })) .Where(x => x.Location.IsInSource) .Select(x => x.Location.ToSecondary(x.CollideWithSymbolName is { Length: > 0 } ? SecondaryMessageFormat : null, x.CollideWithSymbolName)); string CollideWithSymbolName(CollidingMember collidingMember) { return collidingMember.CollideWith.Locations.FirstOrDefault() is { } location && location.SourceTree.SemanticModelOrDefault(model) is { } semanticModel ? GetMemberDisplayName(collidingMember.CollideWith, location.SourceSpan.Start, semanticModel, [SymbolDisplayPartKind.ClassName, SymbolDisplayPartKind.InterfaceName]) : string.Empty; } } private static IEnumerable GetCollidingMembers(ITypeSymbol interfaceSymbol) { var implementedInterfaces = interfaceSymbol.Interfaces; var membersFromDerivedInterface = interfaceSymbol.GetMembers().OfType().ToList(); for (var i = 0; i < implementedInterfaces.Length; i++) { var notRedefinedMembersFromInterface = implementedInterfaces[i] .GetMembers() .OfType() .Where(x => x.DeclaredAccessibility != Accessibility.Private && !membersFromDerivedInterface.Any(redefinedMember => AreCollidingMethods(x, redefinedMember))); var collidingMembers = notRedefinedMembersFromInterface.SelectMany(x => GetCollidingMembersForMember(x, implementedInterfaces.Skip(i + 1))); foreach (var collidingMember in collidingMembers) { yield return collidingMember; } IEnumerable GetCollidingMembersForMember(IMethodSymbol member, IEnumerable interfaces) => interfaces.SelectMany(x => GetCollidingMembersForMemberAndInterface(member, x)); IEnumerable GetCollidingMembersForMemberAndInterface(IMethodSymbol member, INamedTypeSymbol interfaceToCheck) => interfaceToCheck .GetMembers(member.Name) .OfType() .Where(IsNotEventRemoveAccessor) .Where(x => AreCollidingMethods(member, x)) .Select(x => new CollidingMember(x, member)); } } private static bool IsNotEventRemoveAccessor(IMethodSymbol methodSymbol) => // we only want to report on events once, so we are not collecting the "remove" accessors, // and handle the "add" accessor reporting separately in methodSymbol.MethodKind != MethodKind.EventRemove; private static string GetIssueMessageText(IEnumerable collidingMembers, SemanticModel model, int spanStart) { var names = collidingMembers.Take(MaxMemberDisplayCount) .Select(x => $"'{GetMemberDisplayName(x.Member, spanStart, model)}'") .Distinct() .ToList(); return names.Count switch { 1 => names[0], 2 => $"{names[0]} and {names[1]}", _ => names.JoinStr(", ") + ", ..." }; } private static string GetMemberDisplayName(IMethodSymbol method, int spanStart, SemanticModel model, HashSet additionalPartKindsToStartWith = null) { if (method.AssociatedSymbol is IPropertySymbol { IsIndexer: true } property) { var text = property.ToMinimalDisplayString(model, spanStart, SymbolDisplayFormat.CSharpShortErrorMessageFormat); return $"{text}"; } var parts = method.ToMinimalDisplayParts(model, spanStart, SymbolDisplayFormat.CSharpShortErrorMessageFormat) .SkipWhile(x => (additionalPartKindsToStartWith is null || !additionalPartKindsToStartWith.Contains(x.Kind)) && !PartKindsToStartWith.Contains(x.Kind)) .ToList(); if (method.MethodKind == MethodKind.EventAdd) { parts = parts.Take(parts.Count - 2).ToList(); } return $"{string.Join(string.Empty, parts)}"; } private static bool AreCollidingMethods(IMethodSymbol methodSymbol1, IMethodSymbol methodSymbol2) { if (methodSymbol1.Name != methodSymbol2.Name || methodSymbol1.MethodKind != methodSymbol2.MethodKind || methodSymbol1.Parameters.Length != methodSymbol2.Parameters.Length || methodSymbol1.Arity != methodSymbol2.Arity) { return false; } for (var i = 0; i < methodSymbol1.Parameters.Length; i++) { var param1 = methodSymbol1.Parameters[i]; var param2 = methodSymbol2.Parameters[i]; if (param1.RefKind != param2.RefKind || !Equals(param1.Type, param2.Type)) { return false; } } return true; } private sealed record CollidingMember(IMethodSymbol Member, IMethodSymbol CollideWith); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InitializeStaticFieldsInline.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InitializeStaticFieldsInline : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3963"; private const string MessageFormat = "Initialize all 'static fields' inline and remove the 'static constructor'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var constructor = (ConstructorDeclarationSyntax)c.Node; if (!constructor.Modifiers.Any(SyntaxKind.StaticKeyword) || (constructor.Body is null && constructor.ExpressionBody() is null)) { return; } if (c.Model.GetDeclaredSymbol(constructor).ContainingType is { } currentType) { var bodyDescendantNodes = constructor.Body?.DescendantNodes().ToArray() ?? constructor.ExpressionBody()?.DescendantNodes().ToArray() ?? []; var assignedFieldCount = bodyDescendantNodes .OfType() .SelectMany(x => FieldSymbolsFromLeftSide(x, c.Model, currentType)) .Select(x => x.Name) .Distinct() .Count(); var hasIfOrSwitch = Array.Exists(bodyDescendantNodes, x => x.Kind() is SyntaxKind.IfStatement or SyntaxKind.SwitchStatement); if (((hasIfOrSwitch && assignedFieldCount == 1) || (!hasIfOrSwitch && assignedFieldCount > 0)) && !HasTupleAssignmentForMultipleFields(bodyDescendantNodes, c.Model, currentType)) { c.ReportIssue(Rule, constructor.Identifier); } } }, SyntaxKind.ConstructorDeclaration); private static bool HasTupleAssignmentForMultipleFields(SyntaxNode[] nodes, SemanticModel model, INamedTypeSymbol currentType) => nodes.OfType() .Where(x => x.Left.Kind() is SyntaxKindEx.TupleExpression) .Select(x => FieldSymbolsFromLeftSide(x, model, currentType)) .Any(x => x.Count() > 1); // if more than one field is assigned in a tuple then we assume that the static constructor is needed private static IEnumerable FieldSymbolsFromLeftSide(AssignmentExpressionSyntax assignment, SemanticModel model, INamedTypeSymbol currentType) => ExtractSymbols(assignment.Left, model) .OfType() .Distinct() .Where(x => x.ContainingType.Equals(currentType)); private static ISymbol[] ExtractSymbols(SyntaxNode node, SemanticModel model) => TupleExpressionSyntaxWrapper.IsInstance(node) ? ((TupleExpressionSyntaxWrapper)node).Arguments .SelectMany(x => ExtractSymbols(x.Expression, model)) .ToArray() : [model.GetSymbolInfo(node).Symbol]; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InsecureContentSecurityPolicy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InsecureContentSecurityPolicy : TrackerHotspotDiagnosticAnalyzer { private const string DiagnosticId = "S7039"; private const string MessageFormat = "Content Security Policies should be restrictive to mitigate the risk of content injection attacks."; protected override ILanguageFacade Language => CSharpFacade.Instance; public InsecureContentSecurityPolicy() : base(AnalyzerConfiguration.AlwaysEnabled, DiagnosticId, MessageFormat) { } public InsecureContentSecurityPolicy(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var propertyTracker = Language.Tracker.PropertyAccess; propertyTracker.Track( input, propertyTracker.MatchProperty(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Http_IHeaderDictionary, "ContentSecurityPolicy")), x => IsInsecureContentSecurityPolicyValue((string)propertyTracker.AssignedValue(x))); var elementAccessTracker = Language.Tracker.ElementAccess; elementAccessTracker.Track( input, elementAccessTracker.MatchProperty(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Http_HttpResponse, "Headers")), elementAccessTracker.ArgumentAtIndexEquals(0, "Content-Security-Policy"), x => IsInsecureContentSecurityPolicyValue((string)elementAccessTracker.AssignedValue(x))); var invocationTracker = Language.Tracker.Invocation; invocationTracker.Track( input, invocationTracker.MatchMethod( new MemberDescriptor(KnownType.System_Collections_Generic_IDictionary_TKey_TValue, "Add"), new MemberDescriptor(KnownType.Microsoft_AspNetCore_Http_HeaderDictionaryExtensions, "Append")), invocationTracker.ArgumentAtIndexIsAny(0, "Content-Security-Policy"), invocationTracker.ArgumentAtIndexIs(1, IsInsecureValue)); } private static bool IsInsecureValue(SyntaxNode argumentNode, SemanticModel model) => IsInsecureContentSecurityPolicyValue(((ArgumentSyntax)argumentNode).Expression, model); private static bool IsInsecureContentSecurityPolicyValue(SyntaxNode node, SemanticModel model) => node switch { LiteralExpressionSyntax literal => IsInsecureContentSecurityPolicyValue(literal.Token.ValueText), InterpolatedStringExpressionSyntax interpolatedString => interpolatedString.InterpolatedTextValue(model) is { } value && IsInsecureContentSecurityPolicyValue(value), _ when node.FindConstantValue(model) is string constantValue => IsInsecureContentSecurityPolicyValue(constantValue), _ => false, }; private static bool IsInsecureContentSecurityPolicyValue(string value) => value is not null && (value.Contains('*') || value.Contains("'unsafe-inline'") || value.Contains("'unsafe-hashes'") || value.Contains("'unsafe-eval'")); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InsecureEncryptionAlgorithm.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InsecureEncryptionAlgorithm : InsecureEncryptionAlgorithmBase { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override ILanguageFacade Language => CSharpFacade.Instance; protected override ArgumentListSyntax ArgumentList(InvocationExpressionSyntax invocationExpression) => invocationExpression.ArgumentList; protected override SeparatedSyntaxList Arguments(ArgumentListSyntax argumentList) => argumentList.Arguments; protected override bool IsStringLiteralArgument(ArgumentSyntax argument) => argument.Expression.IsKind(SyntaxKind.StringLiteralExpression); protected override SyntaxNode Expression(ArgumentSyntax argument) => argument.Expression; protected override Location Location(SyntaxNode objectCreation) => objectCreation is ObjectCreationExpressionSyntax objectCreationExpression ? objectCreationExpression.Type.GetLocation() : objectCreation.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InsecureTemporaryFilesCreation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InsecureTemporaryFilesCreation : InsecureTemporaryFilesCreationBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InsteadOfAny.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InsteadOfAny : InsteadOfAnyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsSimpleEqualityCheck(InvocationExpressionSyntax node, SemanticModel model) => GetArgumentExpression(node, 0) is SimpleLambdaExpressionSyntax lambda && lambda.Parameter.Identifier.ValueText is var lambdaVariableName && lambda.Body switch { BinaryExpressionSyntax binary when binary.OperatorToken.IsKind(SyntaxKind.EqualsEqualsToken) => HasValidBinaryOperands(lambdaVariableName, binary.Left, binary.Right, model), InvocationExpressionSyntax invocation => HasValidInvocationOperands(invocation, lambdaVariableName, model), _ => false }; private bool HasValidBinaryOperands(string lambdaVariableName, SyntaxNode first, SyntaxNode second, SemanticModel model) => (AreValidOperands(lambdaVariableName, first, second) && IsNullOrValueTypeOrString(second, model)) || (AreValidOperands(lambdaVariableName, second, first) && IsNullOrValueTypeOrString(first, model)); private static bool IsNullOrValueTypeOrString(SyntaxNode node, SemanticModel model) => node.IsKind(SyntaxKind.NullLiteralExpression) || IsValueTypeOrString(node, model); protected override bool AreValidOperands(string lambdaVariable, SyntaxNode first, SyntaxNode second) => first is IdentifierNameSyntax && IsNameEqualTo(first, lambdaVariable) && second switch { LiteralExpressionSyntax => true, IdentifierNameSyntax => !IsNameEqualTo(first, second.GetName()), _ => false, }; protected override SyntaxNode GetArgumentExpression(InvocationExpressionSyntax invocation, int index) => invocation.ArgumentList.Arguments[index].Expression; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InterfaceMethodsShouldBeCallableByChildTypes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InterfaceMethodsShouldBeCallableByChildTypes : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4039"; private const string MessageFormat = "Make '{0}' sealed, change to a non-explicit declaration or provide a " + "new method exposing the functionality of '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => ReportOnIssue(c, m => m.ExplicitInterfaceSpecifier, m => m.Identifier, AreMethodsEquivalent), SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => ReportOnIssue(c, m => m.ExplicitInterfaceSpecifier, m => m.Identifier, ArePropertiesEquivalent), SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => ReportOnIssue(c, m => m.ExplicitInterfaceSpecifier, m => m.Identifier, AreEventsEquivalent), SyntaxKind.EventDeclaration); } private static void ReportOnIssue(SonarSyntaxNodeReportingContext analysisContext, Func getExplicitInterfaceSpecifier, Func getIdentifierName, Func areMembersEquivalent) where TMemberSyntax : MemberDeclarationSyntax { var memberDeclaration = (TMemberSyntax)analysisContext.Node; var explicitInterfaceSpecifier = getExplicitInterfaceSpecifier(memberDeclaration); if (explicitInterfaceSpecifier == null) { return; } var declaration = (TypeDeclarationSyntax)memberDeclaration.FirstAncestorOrSelf(node => node is TypeDeclarationSyntax); if (declaration == null || declaration.Identifier.IsMissing || !IsDeclarationTracked(declaration, analysisContext.Model)) { return; } var hasPublicEquivalentMethod = declaration.Members .OfType() .Any(member => areMembersEquivalent(member, memberDeclaration)); if (!hasPublicEquivalentMethod) { var identifierName = getIdentifierName(memberDeclaration); analysisContext.ReportIssue(Rule, identifierName, declaration.Identifier.ValueText, string.Concat(explicitInterfaceSpecifier.Name, ".", identifierName.ValueText)); } } private static bool IsDeclarationTracked(BaseTypeDeclarationSyntax declaration, SemanticModel semanticModel) { var symbol = semanticModel.GetDeclaredSymbol(declaration); return symbol is { IsSealed: false } && symbol.IsPubliclyAccessible(); } private static bool AreMethodsEquivalent(MethodDeclarationSyntax currentMethod, MethodDeclarationSyntax targetedMethod) => currentMethod != targetedMethod && currentMethod.Modifiers.Any(IsPublicOrProtected) && (currentMethod.Identifier.ValueText == targetedMethod.Identifier.ValueText || (targetedMethod.Identifier.ValueText == nameof(IDisposable.Dispose) && currentMethod.Identifier.ValueText == "Close")); // Allows to replace IDisposable.Dispose() with Close() private static bool ArePropertiesEquivalent(PropertyDeclarationSyntax currentProperty, PropertyDeclarationSyntax targetedProperty) => currentProperty != targetedProperty && currentProperty.Identifier.ValueText == targetedProperty.Identifier.ValueText && currentProperty.Modifiers.Any(IsPublicOrProtected); private static bool AreEventsEquivalent(EventDeclarationSyntax currentEvent, EventDeclarationSyntax targetedEvent) => currentEvent != targetedEvent && currentEvent.Identifier.ValueText == targetedEvent.Identifier.ValueText && currentEvent.Modifiers.Any(IsPublicOrProtected); private static bool IsPublicOrProtected(SyntaxToken modifier) => modifier.IsKind(SyntaxKind.PublicKeyword) || modifier.IsKind(SyntaxKind.ProtectedKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InterfacesShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InterfacesShouldNotBeEmpty : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4023"; private const string MessageFormat = "Remove this interface or add members to it."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var interfaceDeclaration = (InterfaceDeclarationSyntax)c.Node; if (interfaceDeclaration.Identifier.IsMissing || interfaceDeclaration.Members.Count > 0) { return; } var interfaceSymbol = c.Model.GetDeclaredSymbol(interfaceDeclaration); if (interfaceSymbol is { DeclaredAccessibility: Accessibility.Public } && !IsAggregatingOtherInterfaces(interfaceSymbol) && !IsSpecializedGeneric(interfaceSymbol) && !HasEnhancingAttribute(interfaceSymbol)) { c.ReportIssue(Rule, interfaceDeclaration.Identifier); } }, SyntaxKind.InterfaceDeclaration); private static bool IsAggregatingOtherInterfaces(ITypeSymbol interfaceSymbol) => interfaceSymbol.Interfaces.Length > 1; private static bool IsSpecializedGeneric(INamedTypeSymbol interfaceSymbol) => IsImplementingInterface(interfaceSymbol) && (IsBoundGeneric(interfaceSymbol) || IsConstraintGeneric(interfaceSymbol)); private static bool IsConstraintGeneric(INamedTypeSymbol interfaceSymbol) => interfaceSymbol.TypeParameters.Any(x => x.HasAnyConstraint()); private static bool IsBoundGeneric(INamedTypeSymbol interfaceSymbol) => interfaceSymbol.Interfaces.Any(i => i.TypeArguments.Any(a => a is INamedTypeSymbol { IsUnboundGenericType: false })); private static bool HasEnhancingAttribute(INamedTypeSymbol interfaceSymbol) => IsImplementingInterface(interfaceSymbol) // Attributes on interfaces without base interfaces do not make sense. // Implementing types do not get the attribute applied even with AttributeUsageAttribute.Inherited = true && interfaceSymbol.GetAttributes().Any(); private static bool IsImplementingInterface(INamedTypeSymbol interfaceSymbol) => !interfaceSymbol.Interfaces.IsEmpty; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InvalidCastToInterface.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InvalidCastToInterface : InvalidCastToInterfaceBase { public static readonly DiagnosticDescriptor S1944 = DescriptorFactory.Create(DiagnosticId, MessageFormat); // This indirection is needed only because of the old SE engine, see base class. protected override ILanguageFacade Language => CSharpFacade.Instance; protected override DiagnosticDescriptor Rule => S1944; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/InvocationResolvesToOverrideWithParams.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InvocationResolvesToOverrideWithParams : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3220"; private const string MessageFormat = "Review this call, which partially matches an overload without 'params'. The partial match is '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var node = c.Node; var argumentList = (node as InvocationExpressionSyntax)?.ArgumentList ?? ((ObjectCreationExpressionSyntax)node).ArgumentList; CheckCall(c, node, argumentList); }, SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression); private static void CheckCall(SonarSyntaxNodeReportingContext context, SyntaxNode node, ArgumentListSyntax argumentList) { if (argumentList is { Arguments.Count: > 0 } && context.Model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.Parameters.LastOrDefault() is { IsParams: true } && !IsInvocationWithExplicitArray(argumentList, method, context.Model) && ArgumentTypes(context, argumentList) is var argumentTypes && Array.TrueForAll(argumentTypes, x => x is not IErrorTypeSymbol) && OtherOverloadsOf(method).FirstOrDefault(IsPossibleMatch) is { } otherMethod && method.IsGenericMethod == otherMethod.IsGenericMethod) { context.ReportIssue(Rule, node, otherMethod.ToMinimalDisplayString(context.Model, node.SpanStart)); } bool IsPossibleMatch(IMethodSymbol method) => ArgumentsMatchParameters(argumentList, argumentTypes, method, context.Model) && MethodAccessibleWithinType(method, context.ContainingSymbol.ContainingType); } private static ITypeSymbol[] ArgumentTypes(SonarSyntaxNodeReportingContext context, ArgumentListSyntax argumentList) => argumentList.Arguments .Select(x => context.Model.GetTypeInfo(x.Expression)) .Select(x => x.Type ?? x.ConvertedType) // Action and Func won't always resolve properly with Type .ToArray(); private static IEnumerable OtherOverloadsOf(IMethodSymbol method) => method.ContainingType .GetMembers(method.Name) .OfType() .Where(x => !x.IsVararg && x.MethodKind == method.MethodKind && !x.Equals(method) && x.Parameters.Any() && !x.Parameters.Last().IsParams); private static bool IsInvocationWithExplicitArray(ArgumentListSyntax argumentList, IMethodSymbol invokedMethodSymbol, SemanticModel semanticModel) { var lookup = new CSharpMethodParameterLookup(argumentList, invokedMethodSymbol); var parameters = argumentList.Arguments.Select(Valid).ToArray(); return Array.TrueForAll(parameters, x => x is not null) && parameters.Count(x => x.IsParams) == 1; IParameterSymbol Valid(ArgumentSyntax argument) => lookup.TryGetSymbol(argument, out var parameter) && (!parameter.IsParams || semanticModel.GetTypeInfo(argument.Expression).Type is IArrayTypeSymbol) ? parameter : null; } private static bool ArgumentsMatchParameters(ArgumentListSyntax argumentList, ITypeSymbol[] argumentTypes, IMethodSymbol possibleOtherMethod, SemanticModel semanticModel) { var lookup = new CSharpMethodParameterLookup(argumentList, possibleOtherMethod); var parameters = argumentList.Arguments.Select((argument, index) => Valid(argument, argumentTypes[index])).ToArray(); return Array.TrueForAll(parameters, x => x is not null) && possibleOtherMethod.Parameters.Except(parameters).All(x => x.HasExplicitDefaultValue); IParameterSymbol Valid(ArgumentSyntax argument, ITypeSymbol type) => lookup.TryGetSymbol(argument, out var parameter) && ((type is INamedTypeSymbol && semanticModel.ClassifyConversion(argument.Expression, parameter.Type).IsImplicit) || (type is not INamedTypeSymbol && parameter.Type.IsReferenceType)) ? parameter : null; } private static bool MethodAccessibleWithinType(IMethodSymbol method, ITypeSymbol type) => IsInTypeOrNested(method, type) || method.DeclaredAccessibility switch { Accessibility.Private => false, // ProtectedAndInternal corresponds to `private protected`. Accessibility.ProtectedAndInternal => type.DerivesFrom(method.ContainingType) && method.IsInSameAssembly(type), // ProtectedOrInternal corresponds to `protected internal`. Accessibility.ProtectedOrInternal => type.DerivesFrom(method.ContainingType) || method.IsInSameAssembly(type), Accessibility.Protected => type.DerivesFrom(method.ContainingType), Accessibility.Internal => method.IsInSameAssembly(type), Accessibility.Public => true, _ => false, }; private static bool IsInTypeOrNested(IMethodSymbol method, ITypeSymbol type) => type is not null && (method.IsInType(type) || IsInTypeOrNested(method, type.ContainingType)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/IssueSuppression.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IssueSuppression : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1309"; private const string MessageFormat = "Do not suppress issues."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var attribute = (AttributeSyntax)c.Node; if (!(c.Model.GetSymbolInfo(attribute).Symbol is IMethodSymbol attributeConstructor) || !attributeConstructor.ContainingType.Is(KnownType.System_Diagnostics_CodeAnalysis_SuppressMessageAttribute)) { return; } if (!(attribute.Name is IdentifierNameSyntax identifier)) { identifier = (attribute.Name as QualifiedNameSyntax)?.Right as IdentifierNameSyntax; } if (identifier != null) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.Attribute); context.RegisterTreeAction( c => { foreach (var token in c.Tree.GetRoot().DescendantTokens()) { CheckTrivias(c, token.LeadingTrivia); CheckTrivias(c, token.TrailingTrivia); } }); } private static void CheckTrivias(SonarSyntaxTreeReportingContext c, SyntaxTriviaList triviaList) { var pragmaWarnings = triviaList .Where(t => t.HasStructure) .Select(t => t.GetStructure()) .OfType() .Where(t => t.DisableOrRestoreKeyword.IsKind(SyntaxKind.DisableKeyword)); foreach (var pragmaWarning in pragmaWarnings) { c.ReportIssue(Rule, pragmaWarning.CreateLocation(pragmaWarning.DisableOrRestoreKeyword)); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/JSInvokableMethodsShouldBePublic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class JSInvokableMethodsShouldBePublic : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6798"; private const string MessageFormat = "Methods marked as 'JSInvokable' should be 'public'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (c.Compilation.GetTypeByMetadataName(KnownType.Microsoft_JSInterop_JSInvokable) is not null) { c.RegisterNodeAction(CheckMethod, SyntaxKind.MethodDeclaration); } }); private static void CheckMethod(SonarSyntaxNodeReportingContext context) { var method = (MethodDeclarationSyntax)context.Node; if (!method.Modifiers.AnyOfKind(SyntaxKind.PublicKeyword) && method.AttributeLists.SelectMany(x => x.Attributes).Any(x => x.IsKnownType(KnownType.Microsoft_JSInterop_JSInvokable, context.Model))) { context.ReportIssue(Rule, method.Identifier); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/JwtSigned.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class JwtSigned : JwtSignedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; public JwtSigned() : base(AnalyzerConfiguration.AlwaysEnabled) { } protected override BuilderPatternCondition CreateBuilderPatternCondition() => new CSharpBuilderPatternCondition(JwtBuilderConstructorIsSafe, JwtBuilderDescriptors( invocation => invocation.ArgumentList?.Arguments.Count != 1 || !invocation.ArgumentList.Arguments.Single().Expression.RemoveParentheses().IsKind(SyntaxKind.FalseLiteralExpression))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LdapConnectionShouldBeSecure.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LdapConnectionShouldBeSecure : ObjectShouldBeInitializedCorrectlyBase { private const string DiagnosticId = "S4433"; private const string MessageFormat = "Set the 'AuthenticationType' property of this DirectoryEntry to 'AuthenticationTypes.Secure'."; private const int AuthenticationTypesNone = 0; private const int AuthenticationTypesAnonymous = 16; protected override CSharpObjectInitializationTracker ObjectInitializationTracker { get; } = new CSharpObjectInitializationTracker( isAllowedConstantValue: x => x is int integerValue && !IsUnsafe(integerValue), trackedTypes: ImmutableArray.Create(KnownType.System_DirectoryServices_DirectoryEntry), isTrackedPropertyName: x => x == "AuthenticationType", isAllowedObject: IsAllowedObject, trackedConstructorArgumentIndex: 3); public LdapConnectionShouldBeSecure() : base(AnalyzerConfiguration.AlwaysEnabled, DiagnosticId, MessageFormat) { } private static bool IsAllowedObject(ISymbol authTypeSymbol, SyntaxNode authTypeExpression, SemanticModel model) => authTypeSymbol.GetSymbolType().Is(KnownType.System_DirectoryServices_AuthenticationTypes) && !(authTypeExpression.FindConstantValue(model) is int authType && IsUnsafe(authType)); private static bool IsUnsafe(int authType) => authType is AuthenticationTypesNone or AuthenticationTypesAnonymous; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LineLength.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LineLength : LineLengthBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LinkedListPropertiesInsteadOfMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LinkedListPropertiesInsteadOfMethods : LinkedListPropertiesInsteadOfMethodsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsRelevantCallAndType(InvocationExpressionSyntax invocation, SemanticModel model) => invocation.HasExactlyNArguments(0) && invocation.Operands() is { Left: { } left, Right: { } right } && IsRelevantType(right, model) && IsCorrectType(left, model); private static bool IsCorrectType(SyntaxNode left, SemanticModel model) => model.GetTypeInfo(left).Type is { } type && type.DerivesFrom(KnownType.System_Collections_Generic_LinkedList_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LinkedListPropertiesInsteadOfMethodsCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class LinkedListPropertiesInsteadOfMethodsCodeFix : SonarCodeFix { private const string Title = "Replace extension method call with property"; public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(LinkedListPropertiesInsteadOfMethods.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var identifierSyntax = (IdentifierNameSyntax)root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); if (identifierSyntax is { Parent: ExpressionSyntax { Parent: InvocationExpressionSyntax invocationExpression } expression }) { var newMember = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expression, SyntaxFactory.IdentifierName("Value")); context.RegisterCodeFix( Title, _ => { var newRoot = root.ReplaceNode(invocationExpression, newMember); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LiteralSuffixUpperCase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LiteralSuffixUpperCase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S818"; private const string MessageFormat = "Upper case this literal suffix."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( static c => { var literal = (LiteralExpressionSyntax)c.Node; var text = literal.Token.Text; if (text[text.Length - 1] == 'l' && !ShouldIgnore(text)) { c.ReportIssue(Rule, Location.Create(literal.SyntaxTree, new TextSpan(literal.Span.End - 1, 1))); } }, SyntaxKind.NumericLiteralExpression); // We know that @text is a number that ends with 'l'. Being a number, it has at least one digit (thus 2 characters). // If it has 3 characters or more, it could be `2ul` or `2Ul` and we ignore this, because 'l' is easier to read. private static bool ShouldIgnore(string text) => text.Length > 2 && text[text.Length - 2] is 'U' or 'u'; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LiteralSuffixUpperCaseCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class LiteralSuffixUpperCaseCodeFix : SonarCodeFix { private const string Title = "Make literal suffix upper case"; private const string LowercaseEllSuffix = "CS0078"; // The 'l' suffix is easily confused with the digit '1' -- use 'L' for clarity: 25l -> 25L public override ImmutableArray FixableDiagnosticIds { get { return ImmutableArray.Create(LiteralSuffixUpperCase.DiagnosticId, LowercaseEllSuffix); } } protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is LiteralExpressionSyntax literal)) { return Task.CompletedTask; } var newLiteral = SyntaxFactory.Literal( literal.Token.Text.ToUpperInvariant(), (long)literal.Token.Value); if (!newLiteral.IsKind(SyntaxKind.None)) { context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode(literal, literal.WithToken(newLiteral).WithTriviaFrom(literal)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LiteralsShouldNotBePassedAsLocalizedParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LiteralsShouldNotBePassedAsLocalizedParameters : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4055"; private const string MessageFormat = "Replace this string literal with a string retrieved through an instance of the 'ResourceManager' class."; private static readonly HashSet LocalizableSymbolNames = new(StringComparer.OrdinalIgnoreCase) { "TEXT", "CAPTION", "MESSAGE" }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(AnalyzeInvocations, SyntaxKind.InvocationExpression); context.RegisterNodeAction(AnalyzeAssignments, SyntaxKind.SimpleAssignmentExpression); } private static void AnalyzeInvocations(SonarSyntaxNodeReportingContext context) { var invocationSyntax = (InvocationExpressionSyntax)context.Node; if (!(context.Model.GetSymbolInfo(invocationSyntax).Symbol is IMethodSymbol methodSymbol) || invocationSyntax.ArgumentList is null) { return; } // Calling to/from debug-only code if (methodSymbol.IsDiagnosticDebugMethod() || methodSymbol.IsConditionalDebugMethod() || invocationSyntax.IsInConditionalDebug(context.Model)) { return; } if (methodSymbol.IsConsoleWrite() || methodSymbol.IsConsoleWriteLine()) { var firstArgument = invocationSyntax.ArgumentList.Arguments.FirstOrDefault(); if (IsStringLiteral(firstArgument?.Expression, context.Model)) { context.ReportIssue(Rule, firstArgument); } return; } var nonCompliantParameters = methodSymbol.Parameters .Merge(invocationSyntax.ArgumentList.Arguments, (parameter, syntax) => new { parameter, syntax }) .Where(x => IsLocalizableStringLiteral(x.parameter, x.syntax, context.Model)); foreach (var nonCompliantParameter in nonCompliantParameters) { context.ReportIssue(Rule, nonCompliantParameter.syntax); } } private static void AnalyzeAssignments(SonarSyntaxNodeReportingContext context) { var assignmentSyntax = (AssignmentExpressionSyntax)context.Node; if (assignmentSyntax.IsInConditionalDebug(context.Model)) { return; } var assignmentMappings = assignmentSyntax.MapAssignmentArguments(); foreach (var assignmentMapping in assignmentMappings) { if (context.Model.GetSymbolInfo(assignmentMapping.Left).Symbol is IPropertySymbol propertySymbol && IsLocalizable(propertySymbol) && IsStringLiteral(assignmentMapping.Right, context.Model)) { context.ReportIssue(Rule, assignmentMapping.Right); } } } private static bool IsStringLiteral(SyntaxNode expression, SemanticModel model) => expression is not null && model.GetConstantValue(expression) is { HasValue: true, Value: string _ }; private static bool IsLocalizable(ISymbol symbol) => symbol?.Name is not null && symbol.GetAttributes(KnownType.System_ComponentModel_LocalizableAttribute) is var localizableAttributes && IsLocalizable(symbol.Name, new List(localizableAttributes)); private static bool IsLocalizable(string symbolName, IReadOnlyCollection localizableAttributes) => localizableAttributes.Any(x => HasConstructorWitValue(x, true)) || (symbolName.SplitCamelCaseToWords().Any(LocalizableSymbolNames.Contains) && (!localizableAttributes.Any(x => HasConstructorWitValue(x, false)))); private static bool IsLocalizableStringLiteral(ISymbol symbol, ArgumentSyntax argumentSyntax, SemanticModel model) => symbol is not null && argumentSyntax is not null && IsLocalizable(symbol) && IsStringLiteral(argumentSyntax.Expression, model); private static bool HasConstructorWitValue(AttributeData attribute, bool expectedValue) => attribute.ConstructorArguments.Any(x => x.Value is bool boolValue && boolValue == expectedValue); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LockedFieldShouldBeReadonly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LockedFieldShouldBeReadonly : SonarDiagnosticAnalyzer { private const string LockedFieldDiagnosticId = "S2445"; private const string LocalVariableDiagnosticId = "S6507"; private const string MessageFormat = "Do not lock on {0}, use a readonly field instead."; private static readonly DiagnosticDescriptor LockedFieldRule = DescriptorFactory.Create(LockedFieldDiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor LocalVariableRule = DescriptorFactory.Create(LocalVariableDiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(LockedFieldRule, LocalVariableRule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(CheckLockStatement, SyntaxKind.LockStatement); private static void CheckLockStatement(SonarSyntaxNodeReportingContext context) { var expression = ((LockStatementSyntax)context.Node).Expression?.RemoveParentheses(); if (IsCreation(expression)) { context.ReportIssue(LockedFieldRule, expression, "a new instance because is a no-op"); } else { var lazySymbol = new Lazy(() => context.Model.GetSymbolInfo(expression).Symbol); if (IsOfTypeString(expression, lazySymbol)) { context.ReportIssue(LockedFieldRule, expression, "strings as they can be interned"); } else if (expression is IdentifierNameSyntax && lazySymbol.Value is ILocalSymbol localSymbol) { context.ReportIssue(LocalVariableRule, expression, $"local variable '{localSymbol.Name}'"); } else if (FieldWritable(expression, lazySymbol) is { } field) { context.ReportIssue(LockedFieldRule, expression, $"writable field '{field.Name}'"); } } } private static bool IsCreation(ExpressionSyntax expression) => expression?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKind.AnonymousObjectCreationExpression or SyntaxKind.ArrayCreationExpression or SyntaxKind.ImplicitArrayCreationExpression or SyntaxKind.QueryExpression; private static bool IsOfTypeString(ExpressionSyntax expression, Lazy lazySymbol) => expression?.Kind() is SyntaxKind.StringLiteralExpression or SyntaxKind.InterpolatedStringExpression || lazySymbol.Value.GetSymbolType().Is(KnownType.System_String); private static IFieldSymbol FieldWritable(ExpressionSyntax expression, Lazy lazySymbol) => expression?.Kind() is SyntaxKind.IdentifierName or SyntaxKind.SimpleMemberAccessExpression && lazySymbol.Value is IFieldSymbol lockedField && !lockedField.IsReadOnly ? lockedField : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LoggerFieldsShouldBePrivateStaticReadonly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LoggerFieldsShouldBePrivateStaticReadonly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1312"; private const string MessageFormat = "Make the logger '{0}' private static readonly."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); private static readonly KnownAssembly[] LoggingFrameworks = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.NLog, KnownAssembly.Serilog, KnownAssembly.Log4Net, KnownAssembly.CastleCore, ]; private static readonly ImmutableArray Loggers = ImmutableArray.Create( KnownType.Microsoft_Extensions_Logging_ILogger, KnownType.Microsoft_Extensions_Logging_ILogger_TCategoryName, KnownType.NLog_ILogger, KnownType.NLog_ILoggerBase, KnownType.NLog_Logger, KnownType.Serilog_ILogger, KnownType.log4net_ILog, KnownType.log4net_Core_ILogger, KnownType.Castle_Core_Logging_ILogger); private static readonly HashSet InvalidAccessModifiers = [ SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword, SyntaxKind.PublicKeyword ]; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(LoggingFrameworks)) { cc.RegisterNodeAction(c => { foreach (var invalid in InvalidFields((FieldDeclarationSyntax)c.Node, c.Model)) { c.ReportIssue(Rule, invalid, invalid.ValueText); } }, SyntaxKind.FieldDeclaration); } }); private static IEnumerable InvalidFields(BaseFieldDeclarationSyntax field, SemanticModel model) { if (field.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword)) && field.Modifiers.Any(x => x.IsKind(SyntaxKind.ReadOnlyKeyword)) && field.Modifiers.All(x => !x.IsAnyKind(InvalidAccessModifiers))) { yield break; } foreach (var variable in field.Declaration.Variables.Where(ShouldRaise)) { yield return variable.Identifier; } bool ShouldRaise(VariableDeclaratorSyntax variable) => model.GetDeclaredSymbol(variable) is { } symbol && !symbol.ContainingType.IsInterface() // exclude default interface implementation fields && symbol.GetSymbolType().DerivesOrImplementsAny(Loggers); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LoggerMembersNamesShouldComply.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LoggerMembersNamesShouldComply : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S6669"; private const string MessageFormat = "Rename this {0} '{1}' to match the regular expression '{2}'."; private const string DefaultFormat = "^_?[Ll]og(ger)?$"; // unused unless the user changes the regex private static readonly ImmutableHashSet DefaultAllowedNames = ImmutableHashSet.Create( "log", "Log", "_log", "_Log", "logger", "Logger", "_logger", "_Logger", "instance", "Instance"); // "Instance" is a common name for singleton pattern private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.RegularExpression, "Regular expression used to check the field or property names against", DefaultFormat)] public string Format { get; set; } = DefaultFormat; private bool UsesDefaultFormat => Format == DefaultFormat; private Regex NameRegex { get; set; } private static readonly ImmutableArray Loggers = ImmutableArray.Create( KnownType.Microsoft_Extensions_Logging_ILogger, KnownType.Microsoft_Extensions_Logging_ILogger_TCategoryName, KnownType.Serilog_ILogger, KnownType.NLog_ILogger, KnownType.NLog_ILoggerBase, KnownType.NLog_Logger, KnownType.log4net_ILog, KnownType.log4net_Core_ILogger, KnownType.Castle_Core_Logging_ILogger); private static readonly KnownAssembly[] Assemblies = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.Serilog, KnownAssembly.NLog, KnownAssembly.Log4Net, KnownAssembly.CastleCore ]; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(Assemblies)) { NameRegex = UsesDefaultFormat ? null : new(Format, RegexOptions.Compiled, Constants.DefaultRegexTimeout); cc.RegisterNodeAction(c => { foreach (var memberData in Declarations(c.Node)) { if (!MatchesFormat(memberData.Name) && c.Model.GetDeclaredSymbol(memberData.Member).GetSymbolType() is { } type && type.DerivesOrImplementsAny(Loggers)) { c.ReportIssue(Rule, memberData.Location, memberData.MemberType, memberData.Name, Format); } } }, SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration); } }); private bool MatchesFormat(string name) => UsesDefaultFormat ? DefaultAllowedNames.Contains(name) // for performance, if the user doesn't change the regex, we can use a hashtable lookup : NameRegex.SafeIsMatch(name); private static IEnumerable Declarations(SyntaxNode node) { if (node is FieldDeclarationSyntax field) { // can be multiple variables in a single declaration foreach (var variable in field.Declaration.Variables) { yield return new(variable, variable.Identifier.GetLocation(), variable.Identifier.ValueText, false); } } else if (node is PropertyDeclarationSyntax property) { yield return new(property, property.Identifier.GetLocation(), property.Identifier.ValueText, true); } } private readonly record struct MemberData(SyntaxNode Member, Location Location, string Name, bool IsProperty) { public readonly string MemberType => IsProperty ? "property" : "field"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LoggersShouldBeNamedForEnclosingType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LoggersShouldBeNamedForEnclosingType : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3416"; private const string MessageFormat = "Update this logger to use its enclosing type."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); private static readonly KnownAssembly[] SupportedFrameworks = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.NLog, KnownAssembly.Log4Net, ]; private static readonly ImmutableArray Loggers = ImmutableArray.Create( KnownType.Microsoft_Extensions_Logging_ILogger, KnownType.Microsoft_Extensions_Logging_ILogger_TCategoryName, KnownType.NLog_Logger, KnownType.NLog_ILogger, KnownType.NLog_ILoggerBase, KnownType.log4net_ILog); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(SupportedFrameworks)) { cc.RegisterNodeAction(Process, SyntaxKind.InvocationExpression); } }); private static void Process(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (invocation.GetName() is "GetLogger" or "CreateLogger" && EnclosingTypeNode(invocation) is { } enclosingType // filter out top-level statements, choose new() first and then enclosing type && ExtractArgument(invocation) is { } argument && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol method && IsValidMethod(method) && !MatchesEnclosingType(argument, enclosingType, context.Model)) { context.ReportIssue(Rule, argument); } } private static SyntaxNode EnclosingTypeNode(InvocationExpressionSyntax invocation) { var ancestors = invocation.Ancestors(); return (SyntaxNode)ancestors.OfType().FirstOrDefault() // prioritize new() over enclosing type ?? ancestors.OfType().FirstOrDefault(); } // Extracts T for generic argument, nameof or typeof expressions private static SyntaxNode ExtractArgument(InvocationExpressionSyntax invocation) { // CreateLogger if (ExtractGeneric(invocation) is { } generic) { return generic; } else if (invocation.ArgumentList?.Arguments.Count == 1) { return invocation.ArgumentList.Arguments[0].Expression switch { TypeOfExpressionSyntax typeOf => typeOf.Type, // CreateLogger(typeof(T)) MemberAccessExpressionSyntax memberAccess => ExtractTypeOfName(memberAccess), // CreateLogger(typeof(T).Name) InvocationExpressionSyntax innerInvocation => ExtractNameOf(innerInvocation), // CreateLogger(nameof(T)) _ => null }; } else { return null; } } private static bool IsValidMethod(IMethodSymbol method) { return Matches(KnownType.Microsoft_Extensions_Logging_ILoggerFactory, true) || Matches(KnownType.Microsoft_Extensions_Logging_LoggerFactoryExtensions, false) || Matches(KnownType.NLog_LogManager, false) || Matches(KnownType.NLog_LogFactory, true) || Matches(KnownType.log4net_LogManager, false) || MatchesGeneric(); bool Matches(KnownType containingType, bool checkDerived) => method.HasContainingType(containingType, checkDerived) && method.Parameters.Length == 1 && method.Parameters[0].Type.IsAny(KnownType.System_String, KnownType.System_Type); bool MatchesGeneric() => method.ContainingType.Is(KnownType.Microsoft_Extensions_Logging_LoggerFactoryExtensions) && method.TypeParameters.Length == 1; } private static bool MatchesEnclosingType(SyntaxNode argument, SyntaxNode enclosingNode, SemanticModel model) => model.GetTypeInfo(argument).Type is { } argumentType && EnclosingTypeSymbol(model, enclosingNode) is { } enclosingType && (enclosingType.Equals(argumentType) || argumentType.TypeKind is TypeKind.TypeParameter // Do not raise on CreateLogger if T is not concrete || enclosingType.DerivesOrImplementsAny(Loggers)); // Do not raise on Decorator pattern private static ITypeSymbol EnclosingTypeSymbol(SemanticModel model, SyntaxNode enclosingNode) => (model.GetDeclaredSymbol(enclosingNode) ?? model.GetSymbolInfo(enclosingNode).Symbol).GetSymbolType(); private static SyntaxNode ExtractGeneric(InvocationExpressionSyntax invocation) { var genericName = invocation.Expression switch { GenericNameSyntax g => g, // CreateLogger MemberAccessExpressionSyntax memberAccess => memberAccess.Name as GenericNameSyntax, // A..B.CreateLogger _ => null }; return genericName?.TypeArgumentList?.Arguments.Count == 1 ? genericName.TypeArgumentList.Arguments[0] : null; } private static SyntaxNode ExtractTypeOfName(MemberAccessExpressionSyntax memberAccess) => memberAccess.Expression is TypeOfExpressionSyntax typeOf && memberAccess.GetName() is "Name" or "FullName" or "AssemblyQualifiedName" ? typeOf.Type : null; private static SyntaxNode ExtractNameOf(InvocationExpressionSyntax invocation) => invocation.NameIs("nameof") && invocation.ArgumentList?.Arguments.Count == 1 ? invocation.ArgumentList?.Arguments[0].Expression : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LoggingArgumentsShouldBePassedCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Roslyn.Utilities.SonarAnalyzer.Shared.LoggingFrameworkMethods; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LoggingArgumentsShouldBePassedCorrectly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6668"; private const string MessageFormat = "Logging arguments should be passed to the correct parameter."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray MicrosoftLoggingExtensionsInvalidTypes = ImmutableArray.Create(KnownType.System_Exception, KnownType.Microsoft_Extensions_Logging_LogLevel, KnownType.Microsoft_Extensions_Logging_EventId); private static readonly ImmutableArray CastleCoreInvalidTypes = ImmutableArray.Create(KnownType.System_Exception); private static readonly ImmutableArray NLogAndSerilogInvalidTypes = ImmutableArray.Create(KnownType.System_Exception, KnownType.Serilog_Events_LogEventLevel, KnownType.NLog_LogLevel); private static readonly HashSet LoggingMethodNames = MicrosoftExtensionsLogging .Concat(NLogLoggingMethods) .Concat(Serilog) .Concat(CastleCoreOrCommonCore) .ToHashSet(); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (!LoggingMethodNames.Contains(invocation.GetName()) || c.Model.GetSymbolInfo(invocation).Symbol is not IMethodSymbol invocationSymbol) { return; } if (invocationSymbol.HasContainingType(KnownType.Microsoft_Extensions_Logging_LoggerExtensions, false)) { CheckInvalidParams(invocation, invocationSymbol, c, Filter(invocationSymbol, MicrosoftLoggingExtensionsInvalidTypes)); } else if (invocationSymbol.HasContainingType(KnownType.Castle_Core_Logging_ILogger, true)) { CheckInvalidParams(invocation, invocationSymbol, c, Filter(invocationSymbol, CastleCoreInvalidTypes)); } else if (invocationSymbol.HasContainingType(KnownType.Serilog_ILogger, true) || invocationSymbol.HasContainingType(KnownType.Serilog_Log, false) || invocationSymbol.HasContainingType(KnownType.NLog_ILoggerBase, true) || invocationSymbol.HasContainingType(KnownType.NLog_ILoggerExtensions, false)) { var knownTypes = Filter(invocationSymbol, NLogAndSerilogInvalidTypes); CheckInvalidParams(invocation, invocationSymbol, c, knownTypes); CheckInvalidTypeParams(invocation, invocationSymbol, c, knownTypes); } }, SyntaxKind.InvocationExpression); private static void CheckInvalidParams(InvocationExpressionSyntax invocation, IMethodSymbol invocationSymbol, SonarSyntaxNodeReportingContext c, ImmutableArray knownTypes) { var paramsParameter = invocationSymbol.Parameters.FirstOrDefault(x => x.IsParams); if (paramsParameter is null || knownTypes.IsEmpty) { return; } var paramsIndex = invocationSymbol.Parameters.IndexOf(paramsParameter); var invalidArguments = invocation.ArgumentList.Arguments .Where(x => x.GetArgumentIndex() >= paramsIndex && IsInvalidArgument(x, c.Model, knownTypes)) .ToSecondaryLocations() .ToArray(); if (invalidArguments.Length > 0) { c.ReportIssue(Rule, invocation.Expression, invalidArguments); } } private static void CheckInvalidTypeParams(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol, SonarSyntaxNodeReportingContext c, ImmutableArray knownTypes) { if (!knownTypes.IsEmpty && !IsNLogIgnoredOverload(methodSymbol) && methodSymbol.TypeArguments.Any(x => x.DerivesFromAny(knownTypes))) { var typeParameterNames = methodSymbol.TypeParameters.Select(x => x.MetadataName).ToArray(); var positions = methodSymbol.ConstructedFrom.Parameters.Where(x => typeParameterNames.Contains(x.Type.MetadataName)).Select(x => methodSymbol.ConstructedFrom.Parameters.IndexOf(x)); var invalidArguments = InvalidArguments(invocation, c.Model, positions, knownTypes).ToSecondaryLocations(); c.ReportIssue(Rule, invocation.Expression, invalidArguments); } } private static bool IsNLogIgnoredOverload(IMethodSymbol methodSymbol) => // These overloads are ignored since they will try to convert the T value to an exception. MatchesParams(methodSymbol, KnownType.System_Exception) || MatchesParams(methodSymbol, KnownType.System_IFormatProvider, KnownType.System_Exception) || MatchesParams(methodSymbol, KnownType.NLog_ILogger, KnownType.System_Exception) || MatchesParams(methodSymbol, KnownType.NLog_ILogger, KnownType.System_IFormatProvider, KnownType.System_Exception) || MatchesParams(methodSymbol, KnownType.NLog_LogLevel, KnownType.System_Exception) || MatchesParams(methodSymbol, KnownType.NLog_LogLevel, KnownType.System_IFormatProvider, KnownType.System_Exception); private static bool MatchesParams(IMethodSymbol methodSymbol, params KnownType[] knownTypes) => methodSymbol.Parameters.Length == knownTypes.Length && !methodSymbol.Parameters.Where((x, index) => !x.Type.DerivesFrom(knownTypes[index])).Any(); private static IEnumerable InvalidArguments(InvocationExpressionSyntax invocation, SemanticModel model, IEnumerable positionsToCheck, ImmutableArray knownTypes) => positionsToCheck .Select(x => invocation.ArgumentList.Arguments[x]) .Where(x => IsInvalidArgument(x, model, knownTypes)); private static bool IsInvalidArgument(ArgumentSyntax argumentSyntax, SemanticModel model, ImmutableArray knownTypes) => model.GetTypeInfo(argumentSyntax.Expression).Type?.DerivesFromAny(knownTypes) is true; // This method filters out the types that the method accepts strongly: // logger.Debug(exception, "template", exception) // ^^^^^^^^^ valid // ^^^^^^^^^ do not raise private static ImmutableArray Filter(IMethodSymbol methodSymbol, ImmutableArray knownTypes) => knownTypes.Where(knownType => !methodSymbol.ConstructedFrom.Parameters.Any(x => x.Type.DerivesFrom(knownType))).ToImmutableArray(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LoopsAndLinq.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Roslyn.Utilities; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LoopsAndLinq : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3267"; private const string MessageFormat = "{0}"; private const string WhereMessageFormat = @"Loops should be simplified using the ""Where"" LINQ method"; private const string SelectMessageFormat = "Loop should be simplified by calling Select({0} => {0}.{1}))"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var forEachStatementSyntax = (ForEachStatementSyntax)c.Node; if (!IsOrImplementsIEnumerable(c.Model, forEachStatementSyntax) || IsInPerformanceSensitiveContext(forEachStatementSyntax, c.Model)) { return; } if (CanBeSimplifiedUsingWhere(forEachStatementSyntax.Statement, c, out var ifConditionLocation)) { c.ReportIssue(Rule, forEachStatementSyntax.Expression, [ifConditionLocation], WhereMessageFormat); } else { CheckIfCanBeSimplifiedUsingSelect(c, forEachStatementSyntax); } }, SyntaxKind.ForEachStatement); private static bool IsInPerformanceSensitiveContext(ForEachStatementSyntax node, SemanticModel model) => node.PerformanceSensitiveAttribute(model) is { } attribute // If AllowGenericEnumeration property is configured to true, in the context of the rule, we are not in a performance sensitive context && (!attribute.TryGetAttributeValue(nameof(PerformanceSensitiveAttribute.AllowGenericEnumeration), out var allow) || allow); private static bool CanBeSimplifiedUsingWhere(SyntaxNode statement, SonarSyntaxNodeReportingContext context, out SecondaryLocation ifConditionLocation) { if (IfStatement(statement) is { } ifStatementSyntax && CanIfStatementBeMoved(ifStatementSyntax) && !ContainsRefStructReference(ifStatementSyntax, context.Model)) { ifConditionLocation = ifStatementSyntax.Condition.ToSecondaryLocation(); // If the 'if' block contains a single return or assignment with a break, // we cannot simplify the loop using LINQ if the return or assignment is a nullable conversion. // also see https://sonarsource.atlassian.net/browse/NET-1222 return SingleReturnOrBreakingAssignment(ifStatementSyntax) is not { } returnOrAssignment || !RequiresNullableConversion(returnOrAssignment, context); } ifConditionLocation = null; return false; } private static bool ContainsRefStructReference(IfStatementSyntax ifStatement, SemanticModel model) => ifStatement.DescendantNodes() .OfType() .Any(x => model.GetTypeInfo(x).Type?.IsRefStruct() == true); private static bool RequiresNullableConversion(SyntaxNode returnOrAssignment, SonarSyntaxNodeReportingContext context) { var expression = returnOrAssignment switch { ReturnStatementSyntax returnStatement => returnStatement.Expression, AssignmentExpressionSyntax assignment => assignment.Right, _ => throw new InvalidOperationException("Unreachable") }; // expression can be null if the return statement is empty return expression is not null && context.Model.GetTypeInfo(expression) is { Type: { } type, ConvertedType: { } convertedType } && context.Compilation.ClassifyConversion(type, convertedType).IsNullable; } private static SyntaxNode SingleReturnOrBreakingAssignment(IfStatementSyntax ifStatementSyntax) { // Check if the first statement of the block is a return if (ifStatementSyntax.Statement is { FirstNonBlockStatement: ReturnStatementSyntax returnStatement }) { return returnStatement; } // Check if the statement is a block with a single assignment followed by a break if (ifStatementSyntax.Statement is BlockSyntax { Statements: { Count: 2 } statements } && statements[0] is ExpressionStatementSyntax { Expression: AssignmentExpressionSyntax assignment } && statements[1] is BreakStatementSyntax) { return assignment; } return null; } private static IfStatementSyntax IfStatement(SyntaxNode node) => node switch { IfStatementSyntax ifStatementSyntax => ifStatementSyntax, BlockSyntax blockSyntax when blockSyntax.ChildNodes().Count() == 1 => IfStatement(blockSyntax.ChildNodes().Single()), _ => null }; private static bool CanIfStatementBeMoved(IfStatementSyntax ifStatementSyntax) => ifStatementSyntax.Else is null && IsValidCondition(ifStatementSyntax.Condition); private static bool IsValidCondition(ExpressionSyntax condition) => condition switch { _ when condition.Kind() is SyntaxKind.IsExpression or SyntaxKindEx.IsPatternExpression => IsValidIsPattern(condition), InvocationExpressionSyntax invocation => IsValidInvocation(invocation), BinaryExpressionSyntax binary => IsValidBinaryExpression(binary), PrefixUnaryExpressionSyntax unary => IsValidCondition(unary.Operand), IdentifierNameSyntax => true, LiteralExpressionSyntax => true, _ => false }; private static bool IsValidBinaryExpression(BinaryExpressionSyntax expression) => IsValidCondition(expression.Left) && IsValidCondition(expression.Right); private static bool IsValidInvocation(InvocationExpressionSyntax invocationExpressionSyntax) => !invocationExpressionSyntax.DescendantNodes() .OfType() .Any(x => x.RefOrOutKeyword.Kind() is SyntaxKind.OutKeyword or SyntaxKind.RefKeyword); private static bool IsValidIsPattern(SyntaxNode isPattern) => !isPattern.DescendantNodes() .Any(x => x.Kind() is SyntaxKindEx.VarPattern or SyntaxKindEx.SingleVariableDesignation or SyntaxKindEx.ParenthesizedVariableDesignation); /// /// There are multiple scenarios where the code can be simplified using LINQ. /// For simplicity, we consider that Select() can be used /// only when a single property from the foreach variable is used. /// We skip checking method invocations since depending on the method being called, moving it can make the code harder to read. /// The issue is raised if: /// - the property is used more than once /// - the property is the right side of a variable declaration. /// private static void CheckIfCanBeSimplifiedUsingSelect(SonarSyntaxNodeReportingContext c, ForEachStatementSyntax forEachStatementSyntax) { var declaredSymbol = new Lazy(() => c.Model.GetDeclaredSymbol(forEachStatementSyntax)); var accessedProperties = new Dictionary(); foreach (var identifierSyntax in GetStatementIdentifiers(forEachStatementSyntax)) { if (identifierSyntax.Parent is MemberAccessExpressionSyntax { Parent: not InvocationExpressionSyntax } memberAccessExpressionSyntax && IsNotLeftSideOfAssignment(memberAccessExpressionSyntax) && c.Model.GetSymbolInfo(identifierSyntax).Symbol is { } identifierSymbol && identifierSymbol.Equals(declaredSymbol.Value) && c.Model.GetSymbolInfo(memberAccessExpressionSyntax.Name).Symbol is { } symbol && !symbol.GetSymbolType().IsRefStruct()) { var usageStats = accessedProperties.GetOrAdd(symbol, _ => new UsageStats()); usageStats.IsInVarDeclarator = memberAccessExpressionSyntax.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax }; usageStats.Count++; } else { return; } } if (accessedProperties.Count == 1 && accessedProperties.First().Value is var stats && (stats.IsInVarDeclarator || stats.Count > 1)) { c.ReportIssue(Rule, forEachStatementSyntax.Expression, string.Format(SelectMessageFormat, forEachStatementSyntax.Identifier.ValueText, accessedProperties.Single().Key.Name)); } static IEnumerable GetStatementIdentifiers(ForEachStatementSyntax forEachStatementSyntax) => forEachStatementSyntax.Statement .DescendantNodes() .OfType() .Where(x => x.Identifier.ValueText == forEachStatementSyntax.Identifier.ValueText); static bool IsNotLeftSideOfAssignment(MemberAccessExpressionSyntax memberAccess) => !(memberAccess.Parent is AssignmentExpressionSyntax assignment && assignment.Left == memberAccess); } private static bool IsOrImplementsIEnumerable(SemanticModel model, ForEachStatementSyntax forEachStatementSyntax) => model.GetTypeInfo(forEachStatementSyntax.Expression).Type is var expressionType && (expressionType.Is(KnownType.System_Collections_Generic_IEnumerable_T) || expressionType.Implements(KnownType.System_Collections_Generic_IEnumerable_T) || expressionType.Is(KnownType.System_Collections_Generic_IAsyncEnumerable_T) || expressionType.Implements(KnownType.System_Collections_Generic_IAsyncEnumerable_T)); private sealed class UsageStats { public int Count { get; set; } public bool IsInVarDeclarator { get; set; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LooseFilePermissions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LooseFilePermissions : LooseFilePermissionsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void VisitInvocations(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if ((IsSetAccessRule(invocation, context.Model) || IsAddAccessRule(invocation, context.Model)) && (ObjectCreation(invocation, context.Model) is { } objectCreation)) { var invocationLocation = invocation.GetLocation(); var secondaryLocation = objectCreation.Expression.GetLocation(); context.ReportIssue(rule, invocationLocation, invocationLocation.StartLine() == secondaryLocation.StartLine() ? [] : [secondaryLocation.ToSecondary(MessageFormat)]); } } private static IObjectCreation ObjectCreation(InvocationExpressionSyntax invocation, SemanticModel model) { if (VulnerableFileSystemAccessRule(invocation.DescendantNodes()) is { } accessRuleSyntaxNode) { return accessRuleSyntaxNode; } else if (invocation.GetArgumentSymbolsOfKnownType(KnownType.System_Security_AccessControl_FileSystemAccessRule, model).FirstOrDefault() is { } accessRuleSymbol && accessRuleSymbol is not IMethodSymbol) { return VulnerableFileSystemAccessRule(accessRuleSymbol.LocationNodes(invocation)); } else { return null; } IObjectCreation VulnerableFileSystemAccessRule(IEnumerable nodes) => FilterObjectCreations(nodes).FirstOrDefault(x => IsFileSystemAccessRuleForEveryoneWithAllow(x, model)); } private static bool IsFileSystemAccessRuleForEveryoneWithAllow(IObjectCreation objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_AccessControl_FileSystemAccessRule, model) && objectCreation.ArgumentList is { } argumentList && IsEveryone(argumentList.Arguments.First().Expression, model) && model.GetConstantValue(argumentList.Arguments.Last().Expression) is { HasValue: true, Value: 0 }; private static bool IsEveryone(SyntaxNode node, SemanticModel model) => model.GetConstantValue(node) is { HasValue: true, Value: Everyone } || FilterObjectCreations(node.DescendantNodesAndSelf()).Any(x => IsNTAccountWithEveryone(x, model) || IsSecurityIdentifierWithEveryone(x, model)); private static bool IsNTAccountWithEveryone(IObjectCreation objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_Principal_NTAccount, model) && objectCreation.ArgumentList is { } argumentList && model.GetConstantValue(argumentList.Arguments.Last().Expression) is { HasValue: true, Value: Everyone }; private static bool IsSecurityIdentifierWithEveryone(IObjectCreation objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_Principal_SecurityIdentifier, model) && objectCreation.ArgumentList is { } argumentList && model.GetConstantValue(argumentList.Arguments.First().Expression) is { HasValue: true, Value: 1 }; private static IEnumerable FilterObjectCreations(IEnumerable nodes) => nodes.Where(x => x.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression).Select(ObjectCreationFactory.Create); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/LossOfFractionInDivision.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LossOfFractionInDivision : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2184"; private const string MessageFormat = "Cast one of the operands of this division to '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var division = (BinaryExpressionSyntax)c.Node; if (c.Model.GetSymbolInfo(division).Symbol as IMethodSymbol is not { } symbol || !symbol.ContainingType.IsAny(KnownType.IntegralNumbersIncludingNative)) { return; } if (DivisionIsInAssignmentAndTypeIsNonIntegral(division, c.Model, out var divisionResultType) || DivisionIsArgumentAndTypeIsNonIntegral(division, c.Model, out divisionResultType) || DivisionIsInReturnAndTypeIsNonIntegral(division, c.Model, out divisionResultType)) { c.ReportIssue(Rule, division, divisionResultType.ToMinimalDisplayString(c.Model, division.SpanStart)); } }, SyntaxKind.DivideExpression); private static bool DivisionIsInReturnAndTypeIsNonIntegral(SyntaxNode division, SemanticModel semanticModel, out ITypeSymbol divisionResultType) { if (division.Parent is ReturnStatementSyntax || division.Parent is LambdaExpressionSyntax) { divisionResultType = (semanticModel.GetEnclosingSymbol(division.SpanStart) as IMethodSymbol)?.ReturnType; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } divisionResultType = null; return false; } private static bool DivisionIsArgumentAndTypeIsNonIntegral(SyntaxNode division, SemanticModel semanticModel, out ITypeSymbol divisionResultType) { if (division.Parent is not ArgumentSyntax argument) { divisionResultType = null; return false; } if (argument.Parent.Parent is not InvocationExpressionSyntax invocation) { divisionResultType = null; return false; } var lookup = new CSharpMethodParameterLookup(invocation, semanticModel); if (!lookup.TryGetSymbol(argument, out var parameter)) { divisionResultType = null; return false; } divisionResultType = parameter.Type; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } private static bool DivisionIsInAssignmentAndTypeIsNonIntegral(SyntaxNode division, SemanticModel semanticModel, out ITypeSymbol divisionResultType) { if (division.Parent is AssignmentExpressionSyntax assignment) { divisionResultType = semanticModel.GetTypeInfo(assignment.Left).Type; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } if (division is { Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax variableDecl } } }) { divisionResultType = semanticModel.GetTypeInfo(variableDecl.Type).Type; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } if (DivisionIsInTupleTypeIsNonIntegral(division, semanticModel, out divisionResultType)) { return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } divisionResultType = null; return false; } private static bool DivisionIsInTupleTypeIsNonIntegral(SyntaxNode division, SemanticModel semanticModel, out ITypeSymbol divisionResultType) { var outerTuple = GetMostOuterTuple(division); if (outerTuple is { Parent: AssignmentExpressionSyntax assignmentSyntax } && assignmentSyntax.MapAssignmentArguments() is { } assignmentMappings) { var divisionResult = assignmentMappings.FirstOrDefault(x => x.Right.Equals(division)).Left; if (divisionResult is { }) { divisionResultType = semanticModel.GetTypeInfo(divisionResult).Type; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } } // var (a, b) = (1, 1 / 3) else if (outerTuple is { Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax variableDeclaration } } }) { var tupleArguments = ((TupleExpressionSyntaxWrapper)outerTuple).AllArguments(); var declarationType = semanticModel.GetTypeInfo(variableDeclaration.Type).Type; var flattenTupleTypes = AllTupleElements(declarationType); var divisionArgumentIndex = DivisionArgumentIndex(tupleArguments, division); if (divisionArgumentIndex != -1) { divisionResultType = flattenTupleTypes[divisionArgumentIndex]; return divisionResultType.IsAny(KnownType.NonIntegralNumbers); } } divisionResultType = null; return false; static SyntaxNode GetMostOuterTuple(SyntaxNode node) => node.Ancestors() .TakeWhile(x => TupleExpressionSyntaxWrapper.IsInstance(x) || x.IsKind(SyntaxKind.Argument)) .LastOrDefault(x => TupleExpressionSyntaxWrapper.IsInstance(x)); static int DivisionArgumentIndex(ImmutableArray arguments, SyntaxNode division) => arguments.IndexOf(x => x.Expression.Equals(division)); static List AllTupleElements(ITypeSymbol typeSymbol) { List flattenTupleTypes = new(); CollectTupleTypes(flattenTupleTypes, typeSymbol); return flattenTupleTypes; static void CollectTupleTypes(List symbolList, ITypeSymbol tupleTypeSymbol) { if (tupleTypeSymbol.IsTupleType()) { var elements = ((INamedTypeSymbol)tupleTypeSymbol).TupleElements; foreach (var element in elements) { CollectTupleTypes(symbolList, element.Type); } } else { symbolList.Add(tupleTypeSymbol.GetSymbolType()); } } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MagicNumberShouldNotBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MagicNumberShouldNotBeUsed : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S109"; private const string MessageFormat = "Assign this magic number '{0}' to a well-named variable or constant, and use that instead."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet NotConsideredAsMagicNumbers = new HashSet { "-1", "0", "1" }; private static readonly string[] AcceptedCollectionMembersForSingleDigitComparison = { "Size", "Count", "Length" }; private static readonly HashSet AllowedSingleDigitComparisons = [ SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var literalExpression = (LiteralExpressionSyntax)c.Node; if (!IsExceptionToTheRule(literalExpression)) { c.ReportIssue(Rule, literalExpression, literalExpression.Token.ValueText); } }, SyntaxKind.NumericLiteralExpression); private static bool IsExceptionToTheRule(LiteralExpressionSyntax literalExpression) => NotConsideredAsMagicNumbers.Contains(literalExpression.Token.ValueText) || literalExpression.FirstAncestorOrSelf() != null || literalExpression.FirstAncestorOrSelf() != null || literalExpression.FirstAncestorOrSelf() != null || literalExpression.FirstAncestorOrSelf()?.Identifier.ValueText == nameof(object.GetHashCode) || literalExpression.FirstAncestorOrSelf() != null || IsInsideProperty(literalExpression) || IsSingleDigitInToleratedComparisons(literalExpression) || IsToleratedArgument(literalExpression); // Inside property we consider magic numbers as exceptions in the following cases: // - A {get; set;} = MAGIC_NUMBER // - A { get { return MAGIC_NUMBER; } } private static bool IsInsideProperty(SyntaxNode node) { if (node.FirstAncestorOrSelf() == null) { return false; } var parent = node.Parent; return parent is ReturnStatementSyntax || parent is EqualsValueClauseSyntax; } private static bool IsSingleDigitInToleratedComparisons(LiteralExpressionSyntax literalExpression) => literalExpression.Parent is BinaryExpressionSyntax binaryExpression && IsSingleDigit(literalExpression.Token.ValueText) && binaryExpression.IsAnyKind(AllowedSingleDigitComparisons) && IsComparingCollectionSize(binaryExpression); private static bool IsToleratedArgument(LiteralExpressionSyntax literalExpression) => IsToleratedMethodArgument(literalExpression) || IsSingleOrNamedAttributeArgument(literalExpression); // Named argument or constructor argument. private static bool IsToleratedMethodArgument(LiteralExpressionSyntax literalExpression) => literalExpression.Parent is ArgumentSyntax arg && (arg.NameColon is not null || arg.Parent.Parent is ObjectCreationExpressionSyntax || LooksLikeTimeApi(arg.Parent.Parent)); private static bool LooksLikeTimeApi(SyntaxNode node) => node is InvocationExpressionSyntax invocationExpression && invocationExpression.Expression.GetIdentifier() is { } identifier && identifier.ValueText.StartsWith("From"); private static bool IsSingleOrNamedAttributeArgument(LiteralExpressionSyntax literalExpression) => literalExpression.Parent is AttributeArgumentSyntax arg && (arg.NameColon is not null || arg.NameEquals is not null || (arg.Parent is AttributeArgumentListSyntax argList && argList.Arguments.Count == 1)); private static bool IsSingleDigit(string text) => byte.TryParse(text, out var result) && result <= 9; // We allow single-digit comparisons when checking the size of a collection, which is usually done to access the first elements. private static bool IsComparingCollectionSize(BinaryExpressionSyntax binaryComparisonToLiteral) { var comparedToLiteral = binaryComparisonToLiteral.Left is LiteralExpressionSyntax ? binaryComparisonToLiteral.Right : binaryComparisonToLiteral.Left; return GetMemberName(comparedToLiteral) is { } name && AcceptedCollectionMembersForSingleDigitComparison.Contains(name); // we also allow LINQ Count() - the implementation is kept simple to avoid expensive SemanticModel calls static string GetMemberName(SyntaxNode node) => node switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.ValueText, InvocationExpressionSyntax invocationExpressionSyntax => GetMemberName(invocationExpressionSyntax.Expression), _ => null }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MarkAssemblyWithAssemblyVersionAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MarkAssemblyWithAssemblyVersionAttribute : MarkAssemblyWithAssemblyVersionAttributeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MarkAssemblyWithClsCompliantAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MarkAssemblyWithClsCompliantAttribute : MarkAssemblyWithClsCompliantAttributeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MarkAssemblyWithComVisibleAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MarkAssemblyWithComVisibleAttribute : MarkAssemblyWithComVisibleAttributeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MarkAssemblyWithNeutralResourcesLanguageAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MarkAssemblyWithNeutralResourcesLanguageAttribute : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4026"; private const string MessageFormat = "Provide a 'System.Resources.NeutralResourcesLanguageAttribute' attribute for assembly '{0}'."; private const string StronglyTypedResourceBuilder = "System.Resources.Tools.StronglyTypedResourceBuilder"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isCompilationEnd: true); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { var hasResx = false; c.RegisterNodeActionInAllFiles(cc => hasResx = hasResx || IsResxGeneratedFile(cc.Model, (ClassDeclarationSyntax)cc.Node), SyntaxKind.ClassDeclaration); c.RegisterCompilationEndAction(cc => { if (hasResx && !HasNeutralResourcesLanguageAttribute(cc.Compilation.Assembly)) { cc.ReportIssue(Rule, (Location)null, cc.Compilation.AssemblyName); } }); }); private static bool IsDesignerFile(SyntaxTree tree) => tree.FilePath?.IndexOf(".Designer.", StringComparison.OrdinalIgnoreCase) > 0; private static bool HasGeneratedCodeAttributeWithStronglyTypedResourceBuilderValue(SemanticModel semanticModel, ClassDeclarationSyntax classSyntax) => classSyntax.AttributeLists .GetAttributes(KnownType.System_CodeDom_Compiler_GeneratedCodeAttribute, semanticModel) .Where(x => x.ArgumentList.Arguments.Count > 0) .Select(x => semanticModel.GetConstantValue(x.ArgumentList.Arguments[0].Expression)) .Any(constant => string.Equals(constant.Value as string, StronglyTypedResourceBuilder, StringComparison.OrdinalIgnoreCase)); private static bool IsResxGeneratedFile(SemanticModel semanticModel, ClassDeclarationSyntax classSyntax) => IsDesignerFile(semanticModel.SyntaxTree) && HasGeneratedCodeAttributeWithStronglyTypedResourceBuilderValue(semanticModel, classSyntax); private static bool HasNeutralResourcesLanguageAttribute(IAssemblySymbol assemblySymbol) => assemblySymbol.GetAttributes(KnownType.System_Resources_NeutralResourcesLanguageAttribute) .Any(attribute => attribute.ConstructorArguments.Any(arg => arg.Type.Is(KnownType.System_String) && !string.IsNullOrWhiteSpace((string)arg.Value))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MarkWindowsFormsMainWithStaThread.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MarkWindowsFormsMainWithStaThread : MarkWindowsFormsMainWithStaThreadBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.MethodDeclaration }; protected override Location GetLocation(MethodDeclarationSyntax method) => method.FindIdentifierLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberInitializedToDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberInitializedToDefault : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3052"; private const string MessageFormat = "Remove this initialization to '{0}', the compiler will do that for you."; private const string Zero = "Zero"; private static readonly CSharpExpressionNumericConverter ExpressionNumericConverter = new CSharpExpressionNumericConverter(); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckField, SyntaxKind.FieldDeclaration); context.RegisterNodeAction(CheckEvent, SyntaxKind.EventFieldDeclaration); context.RegisterNodeAction(CheckAutoProperty, SyntaxKind.PropertyDeclaration); } private static void CheckAutoProperty(SonarSyntaxNodeReportingContext context) { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; if (propertyDeclaration.Initializer == null || !propertyDeclaration.IsAutoProperty()) { return; } var propertySymbol = context.Model.GetDeclaredSymbol(propertyDeclaration); if (propertySymbol != null && IsDefaultValueInitializer(propertyDeclaration.Initializer, propertySymbol.Type)) { context.ReportIssue(Rule, propertyDeclaration.Initializer, propertySymbol.Name); } } private static void CheckEvent(SonarSyntaxNodeReportingContext context) { var field = (EventFieldDeclarationSyntax)context.Node; foreach (var eventDeclaration in field.Declaration.Variables.Where(v => v.Initializer != null)) { if (!(context.Model.GetDeclaredSymbol(eventDeclaration) is IEventSymbol eventSymbol)) { continue; } if (IsDefaultValueInitializer(eventDeclaration.Initializer, eventSymbol.Type)) { context.ReportIssue(Rule, eventDeclaration.Initializer, eventSymbol.Name); return; } } } private static void CheckField(SonarSyntaxNodeReportingContext context) { var field = (FieldDeclarationSyntax)context.Node; foreach (var variableDeclarator in field.Declaration.Variables.Where(v => v.Initializer != null)) { if (context.Model.GetDeclaredSymbol(variableDeclarator) is IFieldSymbol {IsConst: false} fieldSymbol && IsDefaultValueInitializer(variableDeclarator.Initializer, fieldSymbol.Type)) { context.ReportIssue(Rule, variableDeclarator.Initializer, fieldSymbol.Name); } } } internal static bool IsDefaultValueInitializer(EqualsValueClauseSyntax initializer, ITypeSymbol type) => IsDefaultExpressionInitializer(initializer) || IsReferenceTypeNullInitializer(initializer, type) || IsValueTypeDefaultValueInitializer(initializer, type); private static bool IsDefaultExpressionInitializer(EqualsValueClauseSyntax initializer) => initializer.Value is DefaultExpressionSyntax; private static bool IsReferenceTypeNullInitializer(EqualsValueClauseSyntax initializer, ITypeSymbol type) => type.IsReferenceType && CSharpEquivalenceChecker.AreEquivalent(SyntaxConstants.NullLiteralExpression, initializer.Value); private static bool IsValueTypeDefaultValueInitializer(EqualsValueClauseSyntax initializer, ITypeSymbol type) { if (!type.IsValueType) { return false; } switch (type.SpecialType) { case SpecialType.System_Boolean: return CSharpEquivalenceChecker.AreEquivalent(initializer.Value, SyntaxConstants.FalseLiteralExpression); case SpecialType.System_Decimal: case SpecialType.System_Double: case SpecialType.System_Single: return ExpressionNumericConverter.ConstantDoubleValue(initializer.Value) is { } constantValue && Math.Abs(constantValue - default(double)) < double.Epsilon; case SpecialType.System_Char: case SpecialType.System_Byte: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_SByte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: case SpecialType.System_IntPtr: case SpecialType.System_UIntPtr: { if (initializer.Value is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.Text == Zero) { return true; } else if (ObjectCreationFactory.TryCreate(initializer.Value) is { } objectCreation) { var argCount = objectCreation.ArgumentList?.Arguments.Count; if (argCount is null || argCount == 0) { return true; } return ExpressionNumericConverter.ConstantIntValue(objectCreation.ArgumentList.Arguments.First().Expression) is 0; } else { return ExpressionNumericConverter.ConstantIntValue(initializer.Value) is 0; } } default: return false; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberInitializedToDefaultCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MemberInitializedToDefaultCodeFix : SonarCodeFix { private const string Title = "Remove redundant initializer"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MemberInitializedToDefault.DiagnosticId, MemberInitializerRedundant.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is EqualsValueClauseSyntax initializer)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var parent = initializer.Parent; SyntaxNode newParent; if (!(parent is PropertyDeclarationSyntax propDecl)) { newParent = parent.RemoveNode(initializer, SyntaxRemoveOptions.KeepNoTrivia); } else { var newPropDecl = propDecl .WithInitializer(null) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) .WithTriviaFrom(propDecl); newParent = newPropDecl; } var newRoot = root.ReplaceNode( parent, newParent.WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberInitializerRedundant.RoslynCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.CSharp.Rules { public sealed partial class MemberInitializerRedundant { private class RoslynChecker : CfgAllPathValidator { private readonly ISymbol memberToCheck; private readonly CancellationToken cancel; private readonly ControlFlowGraph cfg; public RoslynChecker(ControlFlowGraph cfg, ISymbol memberToCheck, CancellationToken cancel) : base(cfg) { this.cfg = cfg; this.memberToCheck = memberToCheck; this.cancel = cancel; } // Returns true if the block contains assignment before access protected override bool IsValid(BasicBlock block) => ProcessBlock(block, cfg, false); // Returns true if the block contains access before assignment protected override bool IsInvalid(BasicBlock block) => ProcessBlock(block, cfg, true); private bool ProcessBlock(BasicBlock block, ControlFlowGraph controlFlowGraph, bool checkReadBeforeWrite) { foreach (var operation in block.OperationsAndBranchValue.Reverse().ToReversedExecutionOrder()) { if (checkReadBeforeWrite && operation.Instance.Kind == OperationKindEx.FlowAnonymousFunction) { var anonymousFunction = IFlowAnonymousFunctionOperationWrapper.FromOperation(operation.Instance); var anonymousFunctionCfg = controlFlowGraph.GetAnonymousFunctionControlFlowGraph(anonymousFunction, cancel); if (anonymousFunctionCfg.Blocks.Any(x => ProcessBlock(x, anonymousFunctionCfg, checkReadBeforeWrite))) { return true; } } else if (memberToCheck.Equals(MemberSymbol(operation.Instance))) { return IsReadOrWrite(operation, checkReadBeforeWrite); } } return false; } private bool IsReadOrWrite(IOperationWrapperSonar child, bool checkReadBeforeWrite) { if (child.Instance.IsOutArgumentReference()) { // it is out argument - that means that this is write return !checkReadBeforeWrite; } var isWrite = child.Parent is { Kind: OperationKindEx.SimpleAssignment } parent && ISimpleAssignmentOperationWrapper.FromOperation(parent).Target == child.Instance; return checkReadBeforeWrite ^ isWrite; } private static ISymbol MemberSymbol(IOperation operation) => operation.Kind switch { OperationKindEx.FieldReference when IFieldReferenceOperationWrapper.FromOperation(operation) is var fieldReference && InstanceReferencesThis(fieldReference.Instance) => fieldReference.Field, OperationKindEx.PropertyReference when IPropertyReferenceOperationWrapper.FromOperation(operation) is var propertyReference && InstanceReferencesThis(propertyReference.Instance) => propertyReference.Property, OperationKindEx.EventReference when IEventReferenceOperationWrapper.FromOperation(operation) is var eventReference && InstanceReferencesThis(eventReference.Instance) => eventReference.Member, _ => null }; private static bool InstanceReferencesThis(IOperation instance) => instance == null || instance.IsAnyKind(OperationKindEx.FlowCaptureReference, OperationKindEx.InstanceReference); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberInitializerRedundant.SonarCfg.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CSharp.Rules { public sealed partial class MemberInitializerRedundant { private class SonarChecker : CfgAllPathValidator { private readonly RedundancyChecker redundancyChecker; public SonarChecker(IControlFlowGraph cfg, ISymbol memberToCheck, SemanticModel semanticModel) : base(cfg) => redundancyChecker = new RedundancyChecker(memberToCheck, semanticModel); // Returns true if the block contains assignment before access protected override bool IsBlockValid(Block block) { foreach (var instruction in block.Instructions) { switch (instruction.Kind()) { case SyntaxKind.IdentifierName: case SyntaxKind.SimpleMemberAccessExpression: { if (RedundancyChecker.PossibleMemberAccessParent(instruction) is { } memberAccess && redundancyChecker.TryGetReadWriteFromMemberAccess(memberAccess, out var isRead)) { return !isRead; } } break; case SyntaxKind.SimpleAssignmentExpression: { var assignment = (AssignmentExpressionSyntax)instruction; if (redundancyChecker.IsMatchingMember(assignment.Left.RemoveParentheses())) { return true; } } break; } } return false; } // Returns true if the block contains access before assignment protected override bool IsBlockInvalid(Block block) { foreach (var instruction in block.Instructions) { switch (instruction.Kind()) { case SyntaxKind.IdentifierName: case SyntaxKind.SimpleMemberAccessExpression: { var memberAccess = RedundancyChecker.PossibleMemberAccessParent(instruction); if (memberAccess != null && redundancyChecker.TryGetReadWriteFromMemberAccess(memberAccess, out var isRead)) { return isRead; } } break; case SyntaxKind.SimpleAssignmentExpression: { var assignment = (AssignmentExpressionSyntax)instruction; if (redundancyChecker.IsMatchingMember(assignment.Left)) { return false; } } break; case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.QueryExpression: { if (redundancyChecker.IsMemberUsedInsideLambda(instruction)) { return true; } } break; } } return false; } private class RedundancyChecker { private readonly ISymbol memberToCheck; private readonly SemanticModel semanticModel; public RedundancyChecker(ISymbol memberToCheck, SemanticModel semanticModel) { this.memberToCheck = memberToCheck; this.semanticModel = semanticModel; } public bool IsMemberUsedInsideLambda(SyntaxNode instruction) => instruction.DescendantNodes() .OfType() .Select(PossibleMemberAccessParent) .Any(IsMatchingMember); public bool IsMatchingMember(ExpressionSyntax expression) { return ExtractIdentifier(expression) is { } identifier && semanticModel.GetSymbolInfo(identifier).Symbol is { } assignedSymbol && memberToCheck.Equals(assignedSymbol); IdentifierNameSyntax ExtractIdentifier(ExpressionSyntax expressionSyntax) { if (expressionSyntax.IsKind(SyntaxKind.IdentifierName)) { return (IdentifierNameSyntax)expressionSyntax; } else if (expressionSyntax is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression.IsKind(SyntaxKind.ThisExpression)) { return memberAccess.Name as IdentifierNameSyntax; } else if (expressionSyntax is ConditionalAccessExpressionSyntax conditionalAccess && conditionalAccess.Expression.IsKind(SyntaxKind.ThisExpression)) { return (conditionalAccess.WhenNotNull as MemberBindingExpressionSyntax)?.Name as IdentifierNameSyntax; } else { return null; } } } public static ExpressionSyntax PossibleMemberAccessParent(SyntaxNode node) => node is MemberAccessExpressionSyntax memberAccess ? memberAccess : PossibleMemberAccessParent(node as IdentifierNameSyntax); private static ExpressionSyntax PossibleMemberAccessParent(IdentifierNameSyntax identifier) { if (identifier.Parent is MemberAccessExpressionSyntax memberAccess) { return memberAccess; } if (identifier.Parent is MemberBindingExpressionSyntax memberBinding) { return (ExpressionSyntax)memberBinding.Parent; } return identifier; } public bool TryGetReadWriteFromMemberAccess(ExpressionSyntax expression, out bool isRead) { isRead = false; var parenthesized = expression.GetSelfOrTopParenthesizedExpression(); if (!IsMatchingMember(expression)) { return false; } if (IsOutArgument(parenthesized)) { isRead = false; return true; } if (IsReadAccess(parenthesized, this.semanticModel)) { isRead = true; return true; } return false; } private static bool IsBeingAssigned(ExpressionSyntax expression) => expression.Parent is AssignmentExpressionSyntax assignment && assignment.IsKind(SyntaxKind.SimpleAssignmentExpression) && assignment.Left == expression; private static bool IsOutArgument(ExpressionSyntax parenthesized) => parenthesized.Parent is ArgumentSyntax argument && argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword); private static bool IsReadAccess(ExpressionSyntax parenthesized, SemanticModel semanticModel) => !IsBeingAssigned(parenthesized) && !parenthesized.IsInNameOfArgument(semanticModel); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberInitializerRedundant.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Sonar; using SymbolWithInitializer = System.Collections.Generic.KeyValuePair; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed partial class MemberInitializerRedundant : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3604"; private const string InstanceMemberMessage = "Remove the member initializer, all constructors set an initial value for the member."; private const string StaticMemberMessage = "Remove the static member initializer, a static constructor or module initializer sets an initial value for the member."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "{0}"); private readonly bool useSonarCfg; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public MemberInitializerRedundant() : this(AnalyzerConfiguration.AlwaysEnabled) { } internal MemberInitializerRedundant(IAnalyzerConfiguration configuration) => useSonarCfg = configuration.UseSonarCfg(); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var declaration = (TypeDeclarationSyntax)c.Node; if (c.Model.GetDeclaredSymbol(declaration)?.GetMembers() is { Length: > 0 } members) { // structs cannot initialize fields/properties at declaration time // interfaces cannot have instance fields and instance properties cannot have initializers if (declaration is ClassDeclarationSyntax) { CheckInstanceMembers(c, declaration, members); } CheckStaticMembers(c, declaration, members); } }, // For record support, see details in https://github.com/SonarSource/sonar-dotnet/pull/4756 // it is difficult to work with instance record constructors w/o raising FPs SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration); private void CheckInstanceMembers(SonarSyntaxNodeReportingContext c, TypeDeclarationSyntax declaration, IEnumerable typeMembers) { var constructors = typeMembers.OfType().Where(x => x is { MethodKind: MethodKind.Constructor, IsStatic: false }).ToList(); if (constructors.Exists(x => x.IsImplicitlyDeclared || x.IsPrimaryConstructor)) { // Implicit parameterless constructors and primary constructors can be considered as having an // empty body and they do not initialize any members. If any of these is present, the rule does not apply. return; } // only retrieve the member symbols (an expensive call) if there are explicit class initializers var initializedMembers = InitializedMembers(c.Model, declaration, IsNotStaticOrConst); if (initializedMembers.Count == 0) { return; } var constructorDeclarations = ConstructorDeclarations(c, constructors); foreach (var kvp in initializedMembers) { // the instance member should be initialized in ALL instance constructors // otherwise, initializing it inline makes sense and the rule should not report if (constructorDeclarations.TrueForAll(x => // Calls another ctor, which is also checked: x is { Node.Initializer.ThisOrBaseKeyword.RawKind: (int)SyntaxKind.ThisKeyword } || IsSymbolFirstSetInCfg(kvp.Key, x.Node, x.Model, c.Cancel))) { c.ReportIssue(Rule, kvp.Value, InstanceMemberMessage); } } } private void CheckStaticMembers(SonarSyntaxNodeReportingContext c, TypeDeclarationSyntax declaration, IEnumerable typeMembers) { var typeInitializers = typeMembers.OfType().Where(x => x.MethodKind == MethodKind.StaticConstructor || x.IsModuleInitializer()).ToList(); if (typeInitializers.Count == 0) { return; } // only retrieve the member symbols (an expensive call) if there are explicit class initializers var initializedMembers = InitializedMembers(c.Model, declaration, IsStatic); if (initializedMembers.Count == 0) { return; } var initializerDeclarations = ConstructorDeclarations(c, typeInitializers); foreach (var memberSymbol in initializedMembers.Keys) { // there can be only one static constructor // all module initializers are executed when the type is created, so it is enough if ANY initializes the member if (initializerDeclarations.Any(x => IsSymbolFirstSetInCfg(memberSymbol, x.Node, x.Model, c.Cancel))) { c.ReportIssue(Rule, initializedMembers[memberSymbol], StaticMemberMessage); } } } /// /// Returns true if the member is overwritten without being read in the instance constructor. /// Returns false if the member is not set in the constructor, or if it is read before being set. /// private bool IsSymbolFirstSetInCfg(ISymbol classMember, BaseMethodDeclarationSyntax constructorOrInitializer, SemanticModel model, CancellationToken cancel) { if (useSonarCfg) { if (!CSharpControlFlowGraph.TryGet(constructorOrInitializer, model, out var cfg)) { return false; } var checker = new SonarChecker(cfg, classMember, model); return checker.CheckAllPaths(); } else if (ControlFlowGraph.Create(constructorOrInitializer, model, cancel) is { } cfg) { var checker = new RoslynChecker(cfg, classMember, cancel); return checker.CheckAllPaths(); } else { return false; } } private static bool IsNotStaticOrConst(SyntaxTokenList tokenList) => !tokenList.Any(x => x.Kind() is SyntaxKind.StaticKeyword or SyntaxKind.ConstKeyword); private static bool IsStatic(SyntaxTokenList tokenList) => tokenList.Any(x => x.IsKind(SyntaxKind.StaticKeyword)); // Retrieves the class members which are initialized - instance or static ones, depending on the given modifiers. private static Dictionary InitializedMembers(SemanticModel model, TypeDeclarationSyntax declaration, Func filterModifiers) { var candidateFields = InitializedFieldLikeDeclarations(declaration, filterModifiers, model, x => x.Type); var candidateEvents = InitializedFieldLikeDeclarations(declaration, filterModifiers, model, x => x.Type); var candidateProperties = InitializedPropertyDeclarations(declaration, filterModifiers, model); var allMembers = candidateFields.Select(x => new SymbolWithInitializer(x.Symbol, x.Initializer)) .Concat(candidateEvents.Select(x => new SymbolWithInitializer(x.Symbol, x.Initializer))) .Concat(candidateProperties.Select(x => new SymbolWithInitializer(x.Symbol, x.Initializer))) .ToDictionary(x => x.Key, x => x.Value); return allMembers; } private static List> ConstructorDeclarations(SonarSyntaxNodeReportingContext context, List constructorSymbols) where TSyntax : SyntaxNode => constructorSymbols.SelectMany(x => x.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Select(declarationNode => new { declarationNode, constructorSymbol = x })) .Select(x => new { x.declarationNode, x.constructorSymbol, model = x.declarationNode.EnsureCorrectSemanticModelOrDefault(context.Model) }) .Where(x => x.model is not null) .Select(x => new NodeSymbolAndModel(x.declarationNode, x.constructorSymbol, x.model)) .ToList(); private static IEnumerable> InitializedPropertyDeclarations(TypeDeclarationSyntax declaration, Func filterModifiers, SemanticModel model) => declaration.Members .OfType() .Where(x => filterModifiers(x.Modifiers) && x.Initializer is not null && x.IsAutoProperty()) .Select(x => new DeclarationTuple(x.Initializer, model.GetDeclaredSymbol(x))) .Where(x => x.Symbol is not null && !MemberInitializedToDefault.IsDefaultValueInitializer(x.Initializer, x.Symbol.Type)); private static IEnumerable> InitializedFieldLikeDeclarations(TypeDeclarationSyntax declaration, Func filterModifiers, SemanticModel model, Func typeSelector) where TDeclarationType : BaseFieldDeclarationSyntax where TSymbol : class, ISymbol => declaration.Members .OfType() .Where(x => filterModifiers(x.Modifiers)) .SelectMany(x => x.Declaration.Variables .Where(v => v.Initializer is not null) .Select(v => new DeclarationTuple(v.Initializer, model.GetDeclaredSymbol(v) as TSymbol))) .Where(x => x.Symbol is not null && !MemberInitializedToDefault.IsDefaultValueInitializer(x.Initializer, typeSelector(x.Symbol))); private sealed class DeclarationTuple where TSymbol : ISymbol { public EqualsValueClauseSyntax Initializer { get; } public TSymbol Symbol { get; } public DeclarationTuple(EqualsValueClauseSyntax initializer, TSymbol symbol) { Initializer = initializer; Symbol = symbol; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberOverrideCallsBaseMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberOverrideCallsBaseMember : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1185"; private const string MessageFormat = "Remove this {1} '{0}' to simply inherit its behavior."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly string[] IgnoredMethodNames = { "Equals", "GetHashCode" }; private static readonly string[] IgnoredRecordMethodNames = { "ToString", "PrintMembers" }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; if (IsMethodCandidate(method, c.Model)) { c.ReportIssue(Rule, method, method.Identifier.ValueText, "method"); } }, SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => { var property = (PropertyDeclarationSyntax)c.Node; if (IsPropertyCandidate(property, c.Model)) { c.ReportIssue(Rule, property, property.Identifier.ValueText, "property"); } }, SyntaxKind.PropertyDeclaration); } private static bool IsPropertyCandidate(PropertyDeclarationSyntax propertySyntax, SemanticModel semanticModel) { if (HasDocumentationComment(propertySyntax)) { return false; } var propertySymbol = semanticModel.GetDeclaredSymbol(propertySyntax); if (propertySymbol == null || !propertySymbol.IsOverride || propertySymbol.IsSealed || propertySymbol.OverriddenProperty == null || (propertySymbol.GetMethod != null && propertySymbol.OverriddenProperty.GetMethod == null) || (propertySymbol.SetMethod != null && propertySymbol.OverriddenProperty.SetMethod == null) || propertySymbol.IsAnyAttributeInOverridingChain()) { return false; } return CheckGetAccessorIfAny(propertySyntax, propertySymbol, semanticModel) && CheckSetAccessorIfAny(propertySyntax, propertySymbol, semanticModel); } private static bool CheckGetAccessorIfAny(PropertyDeclarationSyntax propertySyntax, IPropertySymbol propertySymbol, SemanticModel semanticModel) { var getAccessor = propertySyntax.AccessorList?.Accessors.FirstOrDefault(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)); if (getAccessor == null && propertySyntax.ExpressionBody == null) { // no getter return true; } var expression = propertySyntax.ExpressionBody?.Expression ?? getAccessor?.ExpressionBody?.Expression ?? GetSingleStatementExpression(getAccessor?.Body, isVoid: false); return expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is BaseExpressionSyntax && IsBaseProperty(propertySymbol, semanticModel, memberAccess); } private static bool IsBaseProperty(IPropertySymbol propertySymbol, SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccess) => semanticModel.GetSymbolInfo(memberAccess).Symbol is IPropertySymbol invokedPropertySymbol && invokedPropertySymbol.Equals(propertySymbol.OverriddenProperty); private static bool CheckSetAccessorIfAny(PropertyDeclarationSyntax propertySyntax, IPropertySymbol propertySymbol, SemanticModel semanticModel) { var setAccessor = propertySyntax.AccessorList?.Accessors.FirstOrDefault(x => x.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration); if (setAccessor is null) { return true; } var expression = setAccessor?.ExpressionBody?.Expression ?? GetSingleStatementExpression(setAccessor?.Body, isVoid: true); return expression is AssignmentExpressionSyntax expressionToCheck && expressionToCheck.IsKind(SyntaxKind.SimpleAssignmentExpression) && expressionToCheck.Left is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is BaseExpressionSyntax && expressionToCheck.Right is IdentifierNameSyntax { Identifier: { ValueText: "value" } } && semanticModel.GetSymbolInfo(expressionToCheck.Right).Symbol is IParameterSymbol { IsImplicitlyDeclared: true } && IsBaseProperty(propertySymbol, semanticModel, memberAccess); } private static bool IsMethodCandidate(MethodDeclarationSyntax methodSyntax, SemanticModel semanticModel) { if (HasDocumentationComment(methodSyntax)) { return false; } var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax); if (IsMethodSymbolExcluded(methodSymbol)) { return false; } var expression = methodSyntax.ExpressionBody?.Expression ?? GetSingleStatementExpression(methodSyntax.Body, isVoid: methodSymbol.ReturnsVoid); var invocationExpression = expression as InvocationExpressionSyntax; return invocationExpression?.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is BaseExpressionSyntax && semanticModel.GetSymbolInfo(invocationExpression).Symbol is IMethodSymbol invokedMethod && invokedMethod.Equals(methodSymbol.OverriddenMethod) && AreArgumentsMatchParameters(methodSymbol, semanticModel, invocationExpression, invokedMethod); } private static bool IsMethodSymbolExcluded(IMethodSymbol methodSymbol) => methodSymbol == null || !methodSymbol.IsOverride || methodSymbol.IsSealed || methodSymbol.OverriddenMethod == null || IgnoredMethodNames.Contains(methodSymbol.Name) || methodSymbol.Parameters.Any(p => p.HasExplicitDefaultValue) || methodSymbol.OverriddenMethod.Parameters.Any(p => p.HasExplicitDefaultValue) || methodSymbol.IsAnyAttributeInOverridingChain() || IsRecordCompilerGenerated(methodSymbol); private static bool IsRecordCompilerGenerated(IMethodSymbol methodSymbol) => IgnoredRecordMethodNames.Contains(methodSymbol.Name) && methodSymbol.ContainingSymbol is ITypeSymbol type && type.IsRecord(); private static bool HasDocumentationComment(SyntaxNode node) => node.GetLeadingTrivia() .Any(t => t.Kind() is SyntaxKind.SingleLineDocumentationCommentTrivia or SyntaxKind.MultiLineDocumentationCommentTrivia); private static bool AreArgumentsMatchParameters(IMethodSymbol methodSymbol, SemanticModel semanticModel, InvocationExpressionSyntax expressionToCheck, IMethodSymbol invokedMethod) { if (!invokedMethod.Parameters.Any()) { return true; } if (expressionToCheck.ArgumentList == null || invokedMethod.Parameters.Length != expressionToCheck.ArgumentList.Arguments.Count) { return false; } var argumentExpressions = expressionToCheck.ArgumentList.Arguments.Select(a => a.Expression as IdentifierNameSyntax).ToList(); for (var i = 0; i < argumentExpressions.Count; i++) { if (argumentExpressions[i] == null || !(semanticModel.GetSymbolInfo(argumentExpressions[i]).Symbol is IParameterSymbol parameterSymbol) || !parameterSymbol.Equals(methodSymbol.Parameters[i]) || parameterSymbol.Name != methodSymbol.OverriddenMethod.Parameters[i].Name) { return false; } } return true; } private static ExpressionSyntax GetSingleStatementExpression(BlockSyntax block, bool isVoid) { if (block == null || block.Statements.Count != 1) { return null; } return isVoid ? (block.Statements[0] as ExpressionStatementSyntax)?.Expression : (block.Statements[0] as ReturnStatementSyntax)?.Expression; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberOverrideCallsBaseMemberCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MemberOverrideCallsBaseMemberCodeFix : SonarCodeFix { private const string Title = "Remove redundant override"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MemberOverrideCallsBaseMember.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnosticSpan = context.Diagnostics.First().Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is MemberDeclarationSyntax member)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(member, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberShadowsOuterStaticMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberShadowsOuterStaticMember : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3218"; private const string MessageFormat = "Rename this {0} to not shadow the outer class' member with the same name."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction(c => { var innerClassSymbol = (INamedTypeSymbol)c.Symbol; var containerClassSymbol = innerClassSymbol.ContainingType; if (!IsValidType(innerClassSymbol) || !IsValidType(containerClassSymbol) || (innerClassSymbol.GetMembers().Where(x => !x.IsImplicitlyDeclared && !IsStaticAndVirtualOrAbstract(x)).ToList() is var members && !members.Any())) { return; } var selfAndOuterNamedTypes = SelfAndOuterNamedTypes(containerClassSymbol); foreach (var innerMember in members) { var outerMembersOfSameName = selfAndOuterNamedTypes.SelectMany(x => x.GetMembers(innerMember.Name)).ToList(); switch (innerMember) { case IPropertySymbol: case IFieldSymbol: case IEventSymbol: case IMethodSymbol { MethodKind: MethodKind.DeclareMethod or MethodKind.Ordinary }: CheckMember(c, outerMembersOfSameName, innerMember); break; case INamedTypeSymbol namedType: CheckNamedType(c, outerMembersOfSameName, namedType); break; } } }, SymbolKind.NamedType); private static void CheckNamedType(SonarSymbolReportingContext context, IReadOnlyList outerMembersOfSameName, INamedTypeSymbol namedType) { if (outerMembersOfSameName.Any(x => x is INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct or TypeKind.Delegate or TypeKind.Enum or TypeKind.Interface })) { foreach (var identifier in namedType.DeclaringReferenceIdentifiers) { context.ReportIssue(Rule, identifier, namedType.GetClassification()); } } } private static void CheckMember(SonarSymbolReportingContext context, IReadOnlyList outerMembersOfSameName, ISymbol member) { if (outerMembersOfSameName.Any(x => (x.IsStatic && !x.IsAbstract && !x.IsVirtual) || x is IFieldSymbol { IsConst: true }) && member.FirstDeclaringReferenceIdentifier?.GetLocation() is { Kind: LocationKind.SourceFile } location) { context.ReportIssue(Rule, location, member.GetClassification()); } } private static IReadOnlyList SelfAndOuterNamedTypes(INamedTypeSymbol symbol) { var namedTypes = new List(); var current = symbol; while (current is not null) { namedTypes.Add(current); current = current.ContainingType; } return namedTypes; } private static bool IsValidType(INamedTypeSymbol symbol) => symbol.IsClassOrStruct() || symbol.IsInterface(); private static bool IsStaticAndVirtualOrAbstract(ISymbol symbol) => symbol.IsStatic && (symbol.IsVirtual || symbol.IsAbstract); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberShouldBeStatic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberShouldBeStatic : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2325"; private const string MessageFormat = "Make '{0}' a static {1}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableHashSet MethodNameWhitelist = ImmutableHashSet.Create( "Application_AuthenticateRequest", "Application_BeginRequest", "Application_End", "Application_EndRequest", "Application_Error", "Application_Init", "Application_Start", "Session_End", "Session_Start"); private static readonly ImmutableHashSet InstanceSymbolKinds = ImmutableHashSet.Create( SymbolKind.Field, SymbolKind.Property, SymbolKind.Event, SymbolKind.Method); private static readonly ImmutableArray WebControllerTypes = ImmutableArray.Create( KnownType.System_Web_Mvc_Controller, KnownType.System_Web_Http_ApiController, KnownType.Microsoft_AspNetCore_Mvc_Controller, KnownType.System_Web_HttpApplication); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckIssue(c, PropertyDescendants, d => d.Identifier, "property"), SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( c => CheckIssue(c, MethodDescendants, d => d.Identifier, "method"), SyntaxKind.MethodDeclaration); } private static IEnumerable PropertyDescendants(PropertyDeclarationSyntax propertyDeclaration) => propertyDeclaration.ExpressionBody is null ? propertyDeclaration.AccessorList.Accessors.SelectMany(x => x.DescendantNodes()) : propertyDeclaration.ExpressionBody.DescendantNodes(); private static IEnumerable MethodDescendants(MethodDeclarationSyntax methodDeclaration) => methodDeclaration.ExpressionBody is null ? methodDeclaration.Body?.DescendantNodes() : methodDeclaration.ExpressionBody.DescendantNodes(); private static void CheckIssue(SonarSyntaxNodeReportingContext context, Func> getDescendants, Func getIdentifier, string memberKind) where TDeclarationSyntax : MemberDeclarationSyntax { var declaration = (TDeclarationSyntax)context.Node; if (IsEmptyMethod(declaration) || CSharpFacade.Instance.Syntax.ModifierKinds(declaration).Contains(SyntaxKind.PartialKeyword)) { return; } if (context.Model.GetDeclaredSymbol(declaration) is not { } methodOrPropertySymbol || IsStaticVirtualAbstractOrOverride() || MethodNameWhitelist.Contains(methodOrPropertySymbol.Name) || IsOverrideInterfaceOrNew() || IsExcludedByEnclosingType() || methodOrPropertySymbol.GetAttributes().Any(IsIgnoredAttribute) || IsAutoProperty(methodOrPropertySymbol) || IsPublicControllerMethod(methodOrPropertySymbol) || IsWindowsDesktopEventHandler(methodOrPropertySymbol)) { return; } var descendants = getDescendants(declaration); if (descendants is null || HasInstanceReferences(descendants, context.Model)) { return; } var identifier = getIdentifier(declaration); context.ReportIssue(Rule, identifier, identifier.Text, memberKind); bool IsStaticVirtualAbstractOrOverride() => methodOrPropertySymbol.IsStatic || methodOrPropertySymbol.IsVirtual || methodOrPropertySymbol.IsAbstract || methodOrPropertySymbol.IsOverride; bool IsOverrideInterfaceOrNew() => methodOrPropertySymbol.InterfaceMembers().Any() || IsNewMethod(methodOrPropertySymbol) || IsNewProperty(methodOrPropertySymbol); bool IsExcludedByEnclosingType() => methodOrPropertySymbol.ContainingType.IsInterface() // Any generic type in nesting chain with member accessible from outside (through the whole nesting chain) is excluded. || (methodOrPropertySymbol.ContainingType.IsGenericType && methodOrPropertySymbol.GetEffectiveAccessibility().IsAccessibleOutsideTheType()) // Any nested private generic type with member accessible from outside that type (not the whole nesting chain) is also excluded. || (methodOrPropertySymbol.ContainingType.TypeArguments.Any() && methodOrPropertySymbol.DeclaredAccessibility.IsAccessibleOutsideTheType()); } private static bool IsIgnoredAttribute(AttributeData attribute) => !attribute.AttributeClass.Is(KnownType.System_Diagnostics_CodeAnalysis_SuppressMessageAttribute); private static bool IsEmptyMethod(MemberDeclarationSyntax node) => node is MethodDeclarationSyntax { Body.Statements.Count: 0, ExpressionBody: null }; private static bool IsNewMethod(ISymbol symbol) => symbol.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Any(x => x.Modifiers.Any(SyntaxKind.NewKeyword)); private static bool IsNewProperty(ISymbol symbol) => symbol.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Any(x => x.Modifiers.Any(SyntaxKind.NewKeyword)); private static bool IsAutoProperty(ISymbol symbol) => symbol.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Any(x => x.AccessorList is not null && x.AccessorList.Accessors.All(a => a.Body is null && a.ExpressionBody is null)); private static bool IsPublicControllerMethod(ISymbol symbol) => symbol is IMethodSymbol methodSymbol && methodSymbol.GetEffectiveAccessibility() == Accessibility.Public && methodSymbol.ContainingType.DerivesFromAny(WebControllerTypes); private static bool IsWindowsDesktopEventHandler(ISymbol symbol) => symbol is IMethodSymbol { Parameters.Length: 2 } methodSymbol && methodSymbol.Parameters[0].Type.Is(KnownType.System_Object) && methodSymbol.Parameters[1].Type.DerivesFrom(KnownType.System_EventArgs) && (IsContainingTypeWindowsForm(methodSymbol) || IsContainingTypeWpf(methodSymbol)); private static bool IsContainingTypeWindowsForm(IMethodSymbol methodSymbol) => methodSymbol.ContainingType.Implements(KnownType.System_Windows_Forms_IContainerControl); private static bool IsContainingTypeWpf(IMethodSymbol methodSymbol) => methodSymbol.ContainingType.DerivesFrom(KnownType.System_Windows_FrameworkElement); private static bool HasInstanceReferences(IEnumerable nodes, SemanticModel model) => nodes.OfType() .Where(IsLeftmostIdentifierName) .Where(x => !x.IsInNameOfArgument(model)) .Any(x => IsInstanceMember(x, model)); private static bool IsLeftmostIdentifierName(ExpressionSyntax node) { if (node is InstanceExpressionSyntax || node.IsKind(SyntaxKindEx.FieldExpression)) { return true; } if (node is not SimpleNameSyntax) { return false; } var memberAccess = node.Parent as MemberAccessExpressionSyntax; var conditional = node.Parent as ConditionalAccessExpressionSyntax; var memberBinding = node.Parent as MemberBindingExpressionSyntax; return (memberAccess is null && conditional is null && memberBinding is null) || memberAccess?.Expression == node || conditional?.Expression == node; } private static bool IsInstanceMember(ExpressionSyntax node, SemanticModel model) { if (node is InstanceExpressionSyntax || node.IsKind(SyntaxKindEx.FieldExpression)) { return true; } // For ctor(foo: bar), 'IsConstructorParameter(foo)' returns true. This check prevents that case. else if (node.Parent is NameColonSyntax) { return false; } return model.GetSymbolInfo(node).Symbol is { IsStatic: false } symbol && (InstanceSymbolKinds.Contains(symbol.Kind) || IsConstructorParameter(symbol) || IsExtensionParameter(symbol)); // Checking for primary constructor parameters static bool IsConstructorParameter(ISymbol symbol) => symbol is IParameterSymbol { ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.Constructor } }; static bool IsExtensionParameter(ISymbol symbol) => symbol is IParameterSymbol { ContainingSymbol: ITypeSymbol { TypeKind: TypeKindEx.Extension } }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MemberShouldNotHaveConflictingTransparencyAttributes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberShouldNotHaveConflictingTransparencyAttributes : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4211"; private const string MessageFormat = "Change or remove this attribute to be consistent with its container."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override bool EnableConcurrentExecution => false; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( csac => { var nodesWithSecuritySafeCritical = new Dictionary(); var nodesWithSecurityCritical = new Dictionary(); csac.RegisterNodeAction(snac => CollectSecurityAttributes(snac, nodesWithSecuritySafeCritical, nodesWithSecurityCritical), SyntaxKind.Attribute); csac.RegisterCompilationEndAction(cac => ReportOnConflictingTransparencyAttributes(cac, nodesWithSecuritySafeCritical, nodesWithSecurityCritical)); }); private static void CollectSecurityAttributes(SonarSyntaxNodeReportingContext syntaxNodeAnalysisContext, Dictionary nodesWithSecuritySafeCritical, Dictionary nodesWithSecurityCritical) { var attribute = (AttributeSyntax)syntaxNodeAnalysisContext.Node; if (!(syntaxNodeAnalysisContext.Model.GetSymbolInfo(attribute).Symbol is IMethodSymbol attributeConstructor)) { return; } if (attributeConstructor.ContainingType.Is(KnownType.System_Security_SecuritySafeCriticalAttribute)) { nodesWithSecuritySafeCritical.Add(attribute.Parent.Parent, attribute); } else if (attributeConstructor.ContainingType.Is(KnownType.System_Security_SecurityCriticalAttribute)) { nodesWithSecurityCritical.Add(attribute.Parent.Parent, attribute); } else { // nothing } } private static void ReportOnConflictingTransparencyAttributes(SonarCompilationReportingContext compilationContext, Dictionary nodesWithSecuritySafeCritical, Dictionary nodesWithSecurityCritical) { var assemblySecurityCriticalAttribute = compilationContext.Compilation.Assembly .GetAttributes(KnownType.System_Security_SecurityCriticalAttribute) .FirstOrDefault(); if (assemblySecurityCriticalAttribute is not null) { var assemblySecurityLocation = assemblySecurityCriticalAttribute.ApplicationSyntaxReference.GetSyntax().ToSecondaryLocation(); // All parts declaring the 'SecuritySafeCriticalAttribute' are incorrect since the assembly // itself is marked as 'SecurityCritical'. foreach (var item in nodesWithSecuritySafeCritical) { compilationContext.ReportIssue(Rule, item.Value.GetLocation(), [assemblySecurityLocation]); } } else { foreach (var item in nodesWithSecuritySafeCritical) { var current = item.Key.Parent; while (current is not null) { if (nodesWithSecurityCritical.ContainsKey(current)) { compilationContext.ReportIssue(Rule, item.Value.GetLocation(), [nodesWithSecurityCritical[current].ToSecondaryLocation()]); break; } current = current.Parent; } } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/IMessageTemplateCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules.MessageTemplates; public interface IMessageTemplateCheck { DiagnosticDescriptor Rule { get; } void Execute(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, ArgumentSyntax templateArgument, MessageTemplatesParser.Placeholder[] placeholders); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/LoggingTemplatePlaceHoldersShouldBeInOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using SonarAnalyzer.CSharp.Core.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules.MessageTemplates; public sealed class LoggingTemplatePlaceHoldersShouldBeInOrder : IMessageTemplateCheck { private const string DiagnosticId = "S6673"; private const string MessageFormat = "Template placeholders should be in the right order: placeholder '{0}' does not match with argument '{1}'."; private const string SecondaryMessageFormat = "The argument should be '{0}' to match placeholder '{1}'."; internal static readonly DiagnosticDescriptor S6673 = DescriptorFactory.Create(DiagnosticId, MessageFormat); public DiagnosticDescriptor Rule => S6673; public void Execute(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, ArgumentSyntax templateArgument, MessageTemplatesParser.Placeholder[] placeholders) { var methodSymbol = (IMethodSymbol)context.Model.GetSymbolInfo(invocation).Symbol; var placeholderValues = PlaceholderValues(invocation, methodSymbol).ToImmutableArray(); for (var i = 0; i < placeholders.Length; i++) { var placeholder = placeholders[i]; if (placeholder.Name != "_" && !int.TryParse(placeholder.Name, out _) && Array.FindIndex(placeholders, x => x.Name == placeholder.Name) == i // don't raise for duplicate placeholders && OutOfOrderPlaceholderValue(placeholder, i, placeholderValues, out var betterFitArgument) is { } outOfOrderArgument) { var templateStart = templateArgument.Expression.GetLocation().SourceSpan.Start; var primaryLocation = Location.Create(context.Tree, new(templateStart + placeholder.Start, placeholder.Length)); context.ReportIssue(Rule, primaryLocation, SecondaryLocations(outOfOrderArgument, betterFitArgument, placeholder), placeholder.Name, outOfOrderArgument.ToString()); return; // only raise on the first out-of-order placeholder to make the rule less noisy } } } private static IEnumerable SecondaryLocations(SyntaxNode node, SyntaxNode betterFitArgument, MessageTemplatesParser.Placeholder placeholder) => [node.ToSecondaryLocation(SecondaryMessageFormat, BetterFitName(betterFitArgument), placeholder.Name)]; private static string BetterFitName(SyntaxNode node) => node is MemberAccessExpressionSyntax or CastExpressionSyntax ? node.ToString() : node.GetName(); private static IEnumerable PlaceholderValues(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) { var parameters = methodSymbol.Parameters.Where(x => x.Name == "args" || x.Name.StartsWith("argument") || x.Name.StartsWith("propertyValue")) .ToArray(); if (parameters.Length == 0) { yield break; } var parameterLookup = CSharpFacade.Instance.MethodParameterLookup(invocation, methodSymbol); foreach (var parameter in parameters) { if (parameterLookup.TryGetSyntax(parameter, out var expressions)) { foreach (var item in expressions) { yield return item; } } } } private static SyntaxNode OutOfOrderPlaceholderValue( MessageTemplatesParser.Placeholder placeholder, int placeholderIndex, ImmutableArray placeholderValues, out SyntaxNode betterFitArgument) { betterFitArgument = null; if (placeholderIndex < placeholderValues.Length && MatchesName(placeholder.Name, placeholderValues[placeholderIndex], isStrict: false) is not false) { return null; } else { for (var i = 0; i < placeholderValues.Length; i++) { if (i != placeholderIndex && placeholderIndex < placeholderValues.Length && MatchesName(placeholder.Name, placeholderValues[i], isStrict: true) is true) { betterFitArgument = placeholderValues.Skip(i).FirstOrDefault(x => MatchesName(placeholder.Name, x, isStrict: true) is true); return placeholderValues[placeholderIndex]; } } } return null; } private static bool? MatchesName(string placeholderName, SyntaxNode placeholderValue, bool isStrict) => placeholderValue switch { MemberAccessExpressionSyntax memberAccess => MatchesName(placeholderName, memberAccess.Name, isStrict), CastExpressionSyntax cast => MatchesName(placeholderName, cast.Expression, isStrict), NameSyntax name => SimpleStringMatches(placeholderName, name.GetName(), isStrict), _ => null }; private static bool SimpleStringMatches(string placeholderName, string argumentName, bool isStrict) { if (isStrict) { return OnlyLetters(placeholderName).Equals(OnlyLetters(argumentName), StringComparison.OrdinalIgnoreCase); } else { var placeholderComponents = SplitByCamelCase(placeholderName); var argumentComponents = SplitByCamelCase(argumentName); return placeholderComponents.Intersect(argumentComponents, StringComparer.OrdinalIgnoreCase).Any(); } } private static IEnumerable SplitByCamelCase(string text) { var builder = new StringBuilder(text.Length); foreach (var ch in text) { if (char.IsUpper(ch) && builder.Length > 0) { yield return builder.ToString(); builder.Clear(); } if (char.IsLetter(ch)) { builder.Append(ch); } } if (builder.Length > 0) { yield return builder.ToString(); } } private static string OnlyLetters(string text) => new(text.Where(char.IsLetter).ToArray()); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/MessageTemplateAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.RegularExpressions; using SonarAnalyzer.CSharp.Rules.MessageTemplates; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MessageTemplateAnalyzer : SonarDiagnosticAnalyzer { private static readonly ImmutableHashSet Checks = ImmutableHashSet.Create( new LoggingTemplatePlaceHoldersShouldBeInOrder(), new NamedPlaceholdersShouldBeUnique(), new UsePascalCaseForNamedPlaceHolders()); private static readonly HashSet StringExpressionSyntaxKinds = [ SyntaxKind.StringLiteralExpression, SyntaxKind.AddExpression, SyntaxKind.InterpolatedStringExpression, SyntaxKind.InterpolatedStringText ]; public override ImmutableArray SupportedDiagnostics => Checks.Select(x => x.Rule).ToImmutableArray(); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.Serilog, KnownAssembly.NLog)) { cc.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; var enabledChecks = Checks.Where(x => x.Rule.IsEnabled(c)).ToArray(); if (enabledChecks.Length > 0 && MessageTemplateExtractor.TemplateArgument(invocation, c.Model) is { } argument && HasValidExpression(argument) && MessageTemplatesParser.Parse(argument.Expression) is { Success: true } result) { foreach (var check in enabledChecks) { check.Execute(c, invocation, argument, result.Placeholders); } } }, SyntaxKind.InvocationExpression); } }); // Allow: // "regular string" // "concatenated " + "string" // condition ? "ternary" : "scenarios" // $"interpolated {string}" // Do not allow: // "complex" + $"interpolated {scenarios}" // condition ? "complex : $"interpolated {scenarios}" private static bool HasValidExpression(ArgumentSyntax argument) => argument.Expression.IsKind(SyntaxKind.InterpolatedStringExpression) || argument.Expression.DescendantNodes().All(x => x.IsAnyKind(StringExpressionSyntaxKinds)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/MessageTemplateExtractor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Roslyn.Utilities.SonarAnalyzer.Shared.LoggingFrameworkMethods; namespace SonarAnalyzer.CSharp.Rules.MessageTemplates; internal static class MessageTemplateExtractor { public static ArgumentSyntax TemplateArgument(InvocationExpressionSyntax invocation, SemanticModel model) => TemplateArgument(invocation, model, KnownType.Microsoft_Extensions_Logging_LoggerExtensions, MicrosoftExtensionsLogging, "message") ?? TemplateArgument(invocation, model, KnownType.Serilog_Log, Serilog, "messageTemplate") ?? TemplateArgument(invocation, model, KnownType.Serilog_ILogger, Serilog, "messageTemplate", checkDerivedTypes: true) ?? TemplateArgument(invocation, model, KnownType.NLog_ILoggerExtensions, NLogLoggingMethods, "message") ?? TemplateArgument(invocation, model, KnownType.NLog_ILogger, NLogLoggingMethods, "message", checkDerivedTypes: true) ?? TemplateArgument(invocation, model, KnownType.NLog_ILoggerBase, NLogILoggerBase, "message", checkDerivedTypes: true); private static ArgumentSyntax TemplateArgument(InvocationExpressionSyntax invocation, SemanticModel model, KnownType type, ICollection methods, string template, bool checkDerivedTypes = false) => methods.Contains(invocation.GetIdentifier().ToString()) && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol method && method.HasContainingType(type, checkDerivedTypes) && CSharpFacade.Instance.MethodParameterLookup(invocation, method) is { } lookup && lookup.TryGetSyntax(template, out var argumentsFound) // Fetch Argument.Expression with IParameterSymbol.Name == templateName && argumentsFound.Length == 1 ? (ArgumentSyntax)argumentsFound[0].Parent : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/MessageTemplatesShouldBeCorrect.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.CSharp.Core.RegularExpressions; using SonarAnalyzer.CSharp.Rules.MessageTemplates; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MessageTemplatesShouldBeCorrect : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6674"; private const string MessageFormat = "Log message template {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.Serilog, KnownAssembly.NLog)) { cc.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (MessageTemplateExtractor.TemplateArgument(invocation, c.Model) is { } argument && argument.Expression.IsKind(SyntaxKind.StringLiteralExpression) && TemplateValidator.ContainsErrors(argument.Expression.ToString(), out var errors)) { var templateStart = argument.Expression.GetLocation().SourceSpan.Start; foreach (var error in errors) { var location = Location.Create(c.Tree, new(templateStart + error.Start, error.Length)); c.ReportIssue(Rule, location, error.Message); } } }, SyntaxKind.InvocationExpression); } }); private static class TemplateValidator { private const int EmptyPlaceholderSize = 2; // "{}" private const string TextPattern = @"([^\{]|\{\{|\}\})+"; private const string HolePattern = @"{(?[^\}]*)}"; private const string TemplatePattern = $"^({TextPattern}|{HolePattern})*$"; // This is similar to the regex used for MessageTemplatesAnalyzer, but it is far more permissive. // The goal is to manually parse the placeholders, so that we can report more specific issues than just "malformed template". private static readonly Regex TemplateRegex = new(TemplatePattern, RegexOptions.Compiled, TimeSpan.FromMilliseconds(300)); private static readonly Regex PlaceholderNameRegex = new("^[0-9a-zA-Z_]+$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); private static readonly Regex PlaceholderAlignmentRegex = new("^-?[0-9]+$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); public static bool ContainsErrors(string template, out List errors) { var result = MessageTemplatesParser.Parse(template, TemplateRegex); errors = result.Success ? result.Placeholders.Select(ParsePlaceholder).Where(x => x is not null).ToList() : [new("should be syntactically correct", 0, template.Length)]; return errors.Count > 0; } private static ParsingError ParsePlaceholder(MessageTemplatesParser.Placeholder placeholder) { if (placeholder.Length == 0) { return new("should not contain empty placeholder", placeholder.Start - 1, EmptyPlaceholderSize); } var parts = Split(placeholder.Name); return "🔥" switch { _ when !PlaceholderNameRegex.SafeIsMatch(parts.Name) => new($"placeholder '{parts.Name}' should only contain letters, numbers, and underscore", placeholder), _ when parts.Alignment is not null && !PlaceholderAlignmentRegex.SafeIsMatch(parts.Alignment) => new($"placeholder '{parts.Name}' should have numeric alignment instead of '{parts.Alignment}'", placeholder), _ when parts.Format == string.Empty => new($"placeholder '{parts.Name}' should not have empty format", placeholder), _ => null, }; } // pattern is: name[,alignment][:format] private static Parts Split(string placeholder) { string alignment = null; string format = null; var formatIndex = placeholder.IndexOf(':'); var alignmentIndex = placeholder.IndexOf(','); if (formatIndex >= 0 && alignmentIndex > formatIndex) { // example {name:format,alignment} // ^^^^^^^^^^^^^^^^ all of this is format, need to reset alignment alignmentIndex = -1; } if (formatIndex == -1) { formatIndex = placeholder.Length; } else { format = placeholder.Substring(formatIndex + 1); } if (alignmentIndex == -1) { alignmentIndex = formatIndex; } else { alignment = placeholder.Substring(alignmentIndex + 1, formatIndex - alignmentIndex - 1); } var start = placeholder[0] is '@' or '$' ? 1 : 0; // skip prefix var name = placeholder.Substring(start, alignmentIndex - start); return new(name, alignment, format); } private sealed record Parts(string Name, string Alignment, string Format); public sealed record ParsingError(string Message, int Start, int Length) { public ParsingError(string message, MessageTemplatesParser.Placeholder placeholder) : this(message, placeholder.Start, placeholder.Length) { } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/NamedPlaceholdersShouldBeUnique.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.RegularExpressions; namespace SonarAnalyzer.CSharp.Rules.MessageTemplates; public sealed class NamedPlaceholdersShouldBeUnique : IMessageTemplateCheck { private const string DiagnosticId = "S6677"; private const string MessageFormat = "Message template placeholder '{0}' is not unique."; internal static readonly DiagnosticDescriptor S6677 = DescriptorFactory.Create(DiagnosticId, MessageFormat); public DiagnosticDescriptor Rule => S6677; public void Execute(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, ArgumentSyntax templateArgument, MessageTemplatesParser.Placeholder[] placeholders) { var duplicatedGroups = placeholders .Where(x => x.Name != "_" && !int.TryParse(x.Name, out _)) // exclude wildcard "_" and index placeholders like {42} .GroupBy(x => x.Name) .Where(x => x.Count() > 1); foreach (var group in duplicatedGroups) { var templateStart = templateArgument.Expression.GetLocation().SourceSpan.Start; var locations = group.Select(x => Location.Create(context.Tree, new(templateStart + x.Start, x.Length))); context.ReportIssue(Rule, locations.First(), locations.Skip(1).ToSecondary(MessageFormat, group.First().Name), group.First().Name); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MessageTemplates/UsePascalCaseForNamedPlaceHolders.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static SonarAnalyzer.CSharp.Core.RegularExpressions.MessageTemplatesParser; namespace SonarAnalyzer.CSharp.Rules.MessageTemplates; public sealed class UsePascalCaseForNamedPlaceHolders : IMessageTemplateCheck { private const string DiagnosticId = "S6678"; private const string MessageFormat = "Use PascalCase for named placeholders."; internal static readonly DiagnosticDescriptor S6678 = DescriptorFactory.Create(DiagnosticId, MessageFormat); public DiagnosticDescriptor Rule => S6678; public void Execute(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, ArgumentSyntax templateArgument, Placeholder[] placeholders) { var nonPascalCasePlaceholders = placeholders.Where(x => char.IsLower(x.Name[0])).ToArray(); if (nonPascalCasePlaceholders.Length > 0) { context.ReportIssue(Rule, templateArgument, nonPascalCasePlaceholders.Select(CreateLocation)); } SecondaryLocation CreateLocation(Placeholder placeholder) => Location.Create(context.Tree, new(templateArgument.Expression.GetLocation().SourceSpan.Start + placeholder.Start, placeholder.Length)).ToSecondary(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverloadOptionalParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodOverloadOptionalParameter : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3427"; private const string MessageFormat = "This method signature overlaps the one defined on line {0}{1}, the default parameter value {2}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { if (c.Symbol is not IMethodSymbol methodSymbol || ShouldSkip(methodSymbol)) { return; } foreach (var info in GetParameterHidingInfo(methodSymbol)) { ReportIssue(c, info); } }, SymbolKind.Method); private static void ReportIssue(SonarSymbolReportingContext c, ParameterHidingMethodInfo hidingInfo) { var syntax = hidingInfo.ParameterToReportOn.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); if (syntax == null || hidingInfo.HidingMethod.ImplementationSyntax() is not { } hidingMethodSyntax) { return; } var defaultCanBeUsed = IsMoreParameterAvailableInConflicting(hidingInfo) || !MethodsUsingSameParameterNames(hidingInfo); var isOtherFile = syntax.SyntaxTree.FilePath != hidingMethodSyntax.SyntaxTree.FilePath; c.ReportIssue( Rule, syntax, (hidingMethodSyntax.GetLocation().GetMappedLineSpan().StartLinePosition.Line + 1).ToString(), isOtherFile ? $" in file '{new FileInfo(hidingMethodSyntax.SyntaxTree.FilePath).Name}'" : string.Empty, defaultCanBeUsed ? "can only be used with named arguments" : "can't be used"); } private static List GetParameterHidingInfo(IMethodSymbol methodSymbol) => methodSymbol.ContainingType .GetMembers(methodSymbol.Name) .OfType() .Where(m => m.TypeParameters.Length == methodSymbol.TypeParameters.Length) .Where(m => m.Parameters.Length < methodSymbol.Parameters.Length) .Where(m => !m.Parameters.Any(p => p.IsParams)) .Where(candidateHidingMethod => IsMethodHidingOriginal(candidateHidingMethod, methodSymbol)) .Where(candidateHidingMethod => methodSymbol.Parameters[candidateHidingMethod.Parameters.Length].IsOptional) .Select(candidateHidingMethod => new ParameterHidingMethodInfo { ParameterToReportOn = methodSymbol.Parameters[candidateHidingMethod.Parameters.Length], HiddenMethod = methodSymbol, HidingMethod = candidateHidingMethod }) .ToList(); private static bool MethodsUsingSameParameterNames(ParameterHidingMethodInfo hidingInfo) { for (var i = 0; i < hidingInfo.HidingMethod.Parameters.Length; i++) { if (hidingInfo.HidingMethod.Parameters[i].Name != hidingInfo.HiddenMethod.Parameters[i].Name) { return false; } } return true; } private static bool IsMoreParameterAvailableInConflicting(ParameterHidingMethodInfo hidingInfo) => hidingInfo.HiddenMethod.Parameters.IndexOf(hidingInfo.ParameterToReportOn) < hidingInfo.HiddenMethod.Parameters.Length - 1; private static bool IsMethodHidingOriginal(IMethodSymbol candidateHidingMethod, IMethodSymbol method) => candidateHidingMethod.Parameters .Zip(method.Parameters, (param1, param2) => new { param1, param2 }) .All(p => AreTypesEqual(p.param1.Type, p.param2.Type) && p.param1.IsOptional == p.param2.IsOptional); private static bool AreTypesEqual(ITypeSymbol t1, ITypeSymbol t2) => Equals(t1, t2) || (t1.Is(TypeKind.TypeParameter) && t2.Is(TypeKind.TypeParameter)) || AreGenericInstancesTypesEqual(t1, t2); private static bool AreGenericInstancesTypesEqual(ITypeSymbol t1, ITypeSymbol t2) { if (!t1.OriginalDefinition.Equals(t2.OriginalDefinition)) { return false; } if (t1 is INamedTypeSymbol named1 && t2 is INamedTypeSymbol named2) { return named1.TypeArguments.SequenceEqual(named2.TypeArguments, AreTypesEqual); } return false; } private static bool ShouldSkip(IMethodSymbol methodSymbol) => methodSymbol.InterfaceMembers().Any() || methodSymbol.GetOverriddenMember() is not null || !methodSymbol.Parameters.Any(p => p.IsOptional); private sealed class ParameterHidingMethodInfo { public IParameterSymbol ParameterToReportOn { get; init; } public IMethodSymbol HidingMethod { get; init; } public IMethodSymbol HiddenMethod { get; init; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverloadsShouldBeGrouped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodOverloadsShouldBeGrouped : MethodOverloadsShouldBeGroupedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = [ SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKindEx.ExtensionBlockDeclaration ]; protected override MemberInfo CreateMemberInfo(SonarSyntaxNodeReportingContext c, MemberDeclarationSyntax member) => member switch { ConstructorDeclarationSyntax constructor => new MemberInfo(c, member, constructor.Identifier, constructor.IsStatic(), false, true), MethodDeclarationSyntax { ExplicitInterfaceSpecifier: { } } => null, // Skip explicit interface implementations MethodDeclarationSyntax method => new MemberInfo(c, member, method.Identifier, method.IsStatic(), method.Modifiers.Any(SyntaxKind.AbstractKeyword), true), _ => null, }; protected override IEnumerable MemberDeclarations(SyntaxNode node) => ((TypeDeclarationSyntax)node).Members; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideAddsParams.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodOverrideAddsParams : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3600"; private const string MessageFormat = "'params' should be removed from this override."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(method); if (methodSymbol is not { IsOverride: true } || methodSymbol.OverriddenMethod == null) { return; } var lastParameter = method.ParameterList.Parameters.LastOrDefault(); if (lastParameter == null) { return; } var paramsKeyword = lastParameter.Modifiers.FirstOrDefault(modifier => modifier.IsKind(SyntaxKind.ParamsKeyword)); if (paramsKeyword != default && IsNotSemanticallyParams(lastParameter, c.Model)) { c.ReportIssue(Rule, paramsKeyword); } }, SyntaxKind.MethodDeclaration); private static bool IsNotSemanticallyParams(ParameterSyntax parameter, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(parameter) is { IsParams: false }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideAddsParamsCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MethodOverrideAddsParamsCodeFix : SonarCodeFix { private const string Title = "Remove the 'params' modifier"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodOverrideAddsParams.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var paramsToken = root.FindToken(diagnosticSpan.Start); if (!paramsToken.IsKind(SyntaxKind.ParamsKeyword)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var node = paramsToken.Parent; var newNode = node.ReplaceToken( paramsToken, SyntaxFactory.Token(SyntaxKind.None)); newNode = newNode.WithTriviaFrom(node); var newRoot = root.ReplaceNode(node, newNode); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideChangedDefaultValue.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodOverrideChangedDefaultValue : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1006"; private const string MessageFormat = "{0} the default parameter value {1}."; internal const string MessageAdd = "defined in the overridden method"; internal const string MessageRemove = "to match the signature of overridden method"; internal const string MessageUseSame = "defined in the overridden method"; internal const string MessageRemoveExplicit = "from this explicit interface implementation"; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(method); var overriddenMember = methodSymbol.GetOverriddenMember() ?? methodSymbol.InterfaceMembers().FirstOrDefault(); if (methodSymbol == null || overriddenMember == null) { return; } for (var i = 0; i < methodSymbol.Parameters.Length; i++) { var overridingParameter = methodSymbol.Parameters[i]; var overriddenParameter = overriddenMember.Parameters[i]; var parameterSyntax = method.ParameterList.Parameters[i]; ReportParameterIfNeeded(c, overridingParameter, overriddenParameter, parameterSyntax, methodSymbol.ExplicitInterfaceImplementations.Any()); } }, SyntaxKind.MethodDeclaration); } private static void ReportParameterIfNeeded(SonarSyntaxNodeReportingContext context, IParameterSymbol overridingParameter, IParameterSymbol overriddenParameter, ParameterSyntax parameterSyntax, bool isExplicitImplementation) { if (isExplicitImplementation) { if (overridingParameter.HasExplicitDefaultValue) { context.ReportIssue(rule, parameterSyntax.Default, "Remove", MessageRemoveExplicit); } return; } if (overridingParameter.HasExplicitDefaultValue && !overriddenParameter.HasExplicitDefaultValue) { context.ReportIssue(rule, parameterSyntax.Default, "Remove", MessageRemove); return; } if (!overridingParameter.HasExplicitDefaultValue && overriddenParameter.HasExplicitDefaultValue) { context.ReportIssue(rule, parameterSyntax.Identifier, "Add", MessageAdd); return; } if (overridingParameter.HasExplicitDefaultValue && overriddenParameter.HasExplicitDefaultValue && !Equals(overridingParameter.ExplicitDefaultValue, overriddenParameter.ExplicitDefaultValue)) { context.ReportIssue(rule, parameterSyntax.Default.Value, "Use", MessageUseSame); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideChangedDefaultValueCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MethodOverrideChangedDefaultValueCodeFix : SonarCodeFix { internal const string TitleGeneral = "Synchronize default parameter value"; internal const string TitleExplicitInterface = "Remove default parameter value from explicit interface implementation"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodOverrideChangedDefaultValue.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); var parameter = syntaxNode?.FirstAncestorOrSelf(); if (parameter == null) { return; } var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var parameterSymbol = semanticModel.GetDeclaredSymbol(parameter); if (!(parameterSymbol?.ContainingSymbol is IMethodSymbol methodSymbol)) { return; } ParameterSyntax newParameter; string title; if (methodSymbol.ExplicitInterfaceImplementations.Any()) { newParameter = parameter.WithDefault(null); title = TitleExplicitInterface; } else { var index = methodSymbol.Parameters.IndexOf(parameterSymbol); var overriddenMember = methodSymbol.GetOverriddenMember() ?? methodSymbol.InterfaceMembers().FirstOrDefault(); if (index == -1 || overriddenMember == null) { return; } var overriddenParameter = overriddenMember.Parameters[index]; if (!TryGetNewParameterSyntax(parameter, overriddenParameter, out newParameter)) { return; } title = TitleGeneral; } RegisterCodeFix(context, root, parameter, newParameter, title); } private static void RegisterCodeFix(SonarCodeFixContext context, SyntaxNode root, ParameterSyntax parameter, ParameterSyntax newParameter, string codeFixTitle) => context.RegisterCodeFix( codeFixTitle, c => { var newRoot = root.ReplaceNode( parameter, newParameter.WithTriviaFrom(parameter)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static bool TryGetNewParameterSyntax(ParameterSyntax parameter, IParameterSymbol overriddenParameter, out ParameterSyntax newParameterSyntax) { if (!overriddenParameter.HasExplicitDefaultValue) { newParameterSyntax = parameter.WithDefault(null).WithAdditionalAnnotations(Formatter.Annotation); return true; } var defaultSyntax = (overriddenParameter.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as ParameterSyntax)?.Default; if (defaultSyntax != null) { newParameterSyntax = parameter.WithDefault(defaultSyntax.WithoutTrivia().WithAdditionalAnnotations(Formatter.Annotation)); return true; } newParameterSyntax = null; return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideNoParams.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodOverrideNoParams : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3262"; private const string MessageFormat = "'params' should not be removed from an override."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(method); if (methodSymbol is not null && methodSymbol.IsOverride && methodSymbol.OverriddenMethod is not null && methodSymbol.OverriddenMethod.Parameters.Any(p => p.IsParams) && !method.ParameterList.Parameters.Last().Modifiers.Any(SyntaxKind.ParamsKeyword)) { c.ReportIssue(Rule, method.ParameterList.Parameters.Last()); } }, SyntaxKind.MethodDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodOverrideNoParamsCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MethodOverrideNoParamsCodeFix : SonarCodeFix { internal const string Title = "Add the 'params' modifier"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodOverrideNoParams.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var parameter = (ParameterSyntax)root.FindNode(diagnosticSpan); context.RegisterCodeFix( Title, c => { var newParameter = parameter.WithModifiers( parameter.Modifiers.Add( SyntaxFactory.Token(SyntaxKind.ParamsKeyword))); var newRoot = root.ReplaceNode( parameter, newParameter.WithTriviaFrom(parameter)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodParameterMissingOptional.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodParameterMissingOptional : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3450"; private const string MessageFormat = "Add the 'Optional' attribute to this parameter."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var parameter = (ParameterSyntax)c.Node; if (!parameter.AttributeLists.Any()) { return; } var attributes = AttributeSyntaxSymbolMapping.GetAttributesForParameter(parameter, c.Model).ToList(); var defaultParameterValueAttribute = attributes.FirstOrDefault(a => a.Symbol.IsInType(KnownType.System_Runtime_InteropServices_DefaultParameterValueAttribute)); if (defaultParameterValueAttribute == null) { return; } var optionalAttribute = attributes.FirstOrDefault(a => a.Symbol.IsInType(KnownType.System_Runtime_InteropServices_OptionalAttribute)); if (optionalAttribute == null) { c.ReportIssue(Rule, defaultParameterValueAttribute.Node); } }, SyntaxKind.Parameter); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodParameterMissingOptionalCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MethodParameterMissingOptionalCodeFix : SonarCodeFix { private const string Title = "Add missing 'Optional' attribute"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodParameterMissingOptional.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var attribute = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) as AttributeSyntax; var attributeList = attribute?.Parent as AttributeListSyntax; if (attribute == null || attributeList == null) { return; } var semanticModel = await context.Document.GetSemanticModelAsync().ConfigureAwait(false); var optionalAttribute = semanticModel?.Compilation.GetTypeByMetadataName(KnownType.System_Runtime_InteropServices_OptionalAttribute); if (optionalAttribute == null) { return; } context.RegisterCodeFix( Title, _ => { var newRoot = root.ReplaceNode(attributeList, GetNewAttributeList(attributeList, optionalAttribute, semanticModel)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static AttributeListSyntax GetNewAttributeList(AttributeListSyntax attributeList, ISymbol optionalAttribute, SemanticModel semanticModel) { var attributeName = optionalAttribute.ToMinimalDisplayString(semanticModel, attributeList.SpanStart); attributeName = attributeName.Remove(attributeName.IndexOf("Attribute", System.StringComparison.Ordinal)); return attributeList .AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName(attributeName))) .WithAdditionalAnnotations(Formatter.Annotation); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodParameterUnused.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Sonar; using SonarAnalyzer.CSharp.Core.LiveVariableAnalysis; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodParameterUnused : MethodParameterUnusedBase { internal const string IsRemovableKey = "IsRemovable"; private const string MessageUnused = "unused method parameter '{0}'"; private const string MessageDead = "parameter '{0}', whose value is ignored in the method"; private const string MessageFormat = "Remove this {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private readonly bool useSonarCfg; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public MethodParameterUnused() : this(AnalyzerConfiguration.AlwaysEnabled) { } internal /* for testing */ MethodParameterUnused(IAnalyzerConfiguration configuration) => useSonarCfg = configuration.UseSonarCfg(); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = CreateContext(c); if ((declaration.Body is null && declaration.ExpressionBody is null) || declaration.Body?.Statements.Count == 0 // Don't report on empty methods || declaration.Modifiers.AnyOfKind(SyntaxKind.PartialKeyword) || declaration.Symbol is null || !declaration.Symbol.ContainingType.IsClassOrStruct() || declaration.Symbol.IsMainMethod() || OnlyThrowsNotImplementedException(declaration)) { return; } ReportUnusedParametersOnMethod(declaration); }, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKindEx.LocalFunctionStatement); private static MethodContext CreateContext(SonarSyntaxNodeReportingContext c) { if (c.Node is BaseMethodDeclarationSyntax method) { return new MethodContext(c, method); } else if (c.Node.IsKind(SyntaxKindEx.LocalFunctionStatement)) { return new MethodContext(c, (LocalFunctionStatementSyntaxWrapper)c.Node); } else { throw new InvalidOperationException("Unexpected Node: " + c.Node); } } private static bool OnlyThrowsNotImplementedException(MethodContext declaration) { if (declaration.Body is not null && declaration.Body.Statements.Count != 1) { return false; } var throwExpressions = Enumerable.Empty(); if (declaration.ExpressionBody is not null) { if (ThrowExpressionSyntaxWrapper.IsInstance(declaration.ExpressionBody.Expression)) { throwExpressions = [((ThrowExpressionSyntaxWrapper)declaration.ExpressionBody.Expression).Expression]; } } else { throwExpressions = declaration.Body.Statements.OfType().Select(x => x.Expression); } return throwExpressions .OfType() .Select(x => declaration.Context.Model.GetSymbolInfo(x).Symbol) .OfType() .Any(x => x is not null && x.ContainingType.Is(KnownType.System_NotImplementedException)); } private void ReportUnusedParametersOnMethod(MethodContext declaration) { if (!MethodCanBeSafelyChanged(declaration.Symbol)) { return; } var unusedParameters = GetUnusedParameters(declaration); if (unusedParameters.Any() && !IsUsedAsEventHandlerFunctionOrAction(declaration) && !IsCandidateSerializableConstructor(unusedParameters, declaration.Symbol)) { ReportOnUnusedParameters(declaration, unusedParameters, MessageUnused); } ReportOnDeadParametersAtEntry(declaration, unusedParameters); } private void ReportOnDeadParametersAtEntry(MethodContext declaration, IImmutableList noReportOnParameters) { if (declaration.Context.Node.IsKind(SyntaxKind.ConstructorDeclaration)) { return; } var excludedParameters = noReportOnParameters; if (declaration.Symbol.IsExtensionMethod) { excludedParameters = excludedParameters.Add(declaration.Symbol.Parameters.First()); } excludedParameters = excludedParameters.AddRange(declaration.Symbol.Parameters.Where(x => x.RefKind != RefKind.None)); var candidateParameters = declaration.Symbol.Parameters.Except(excludedParameters); if (candidateParameters.Any() && ComputeLva(declaration) is { } lva) { ReportOnUnusedParameters(declaration, candidateParameters.Except(lva.LiveInEntryBlock).Except(lva.CapturedVariables), MessageDead, isRemovable: false); } } private LvaResult ComputeLva(MethodContext declaration) { if (useSonarCfg) { return CSharpControlFlowGraph.TryGet(declaration.Context.Node, declaration.Context.Model, out var cfg) ? new LvaResult(declaration, cfg) : null; } else { return declaration.Context.Node.CreateCfg(declaration.Context.Model, declaration.Context.Cancel) is { } cfg ? new LvaResult(cfg, declaration.Context.Cancel) : null; } } private static void ReportOnUnusedParameters(MethodContext declaration, IEnumerable parametersToReportOn, string messagePattern, bool isRemovable = true) { if (declaration.ParameterList is null) { return; } var parameters = declaration.ParameterList.Parameters .Select(x => new NodeAndSymbol(x, declaration.Context.Model.GetDeclaredSymbol(x))) .Where(x => x.Symbol is not null); foreach (var parameter in parameters.Where(x => parametersToReportOn.Contains(x.Symbol))) { declaration.Context.ReportIssue( Rule, parameter.Node, ImmutableDictionary.Empty.Add(IsRemovableKey, isRemovable.ToString()), string.Format(messagePattern, parameter.Symbol.Name)); } } private static bool MethodCanBeSafelyChanged(IMethodSymbol methodSymbol) => methodSymbol.GetEffectiveAccessibility() == Accessibility.Private && !methodSymbol.GetAttributes().Any() && methodSymbol.IsChangeable() && !methodSymbol.IsEventHandler(); private static IImmutableList GetUnusedParameters(MethodContext declaration) { var usedParameters = new HashSet(); var bodies = declaration.Context.Node.IsKind(SyntaxKind.ConstructorDeclaration) ? new SyntaxNode[] { declaration.Body, declaration.ExpressionBody, ((ConstructorDeclarationSyntax)declaration.Context.Node).Initializer } : new SyntaxNode[] { declaration.Body, declaration.ExpressionBody }; foreach (var body in bodies.WhereNotNull()) { usedParameters.UnionWith(GetUsedParameters(declaration.Symbol.Parameters, body, declaration.Context.Model)); } var unusedParameter = declaration.Symbol.Parameters.Except(usedParameters); if (declaration.Symbol.IsExtensionMethod) { unusedParameter = unusedParameter.Except([declaration.Symbol.Parameters.First()]); } return unusedParameter.Except(usedParameters).ToImmutableArray(); } private static ISet GetUsedParameters(ImmutableArray parameters, SyntaxNode body, SemanticModel model) => body.DescendantNodes() .Where(x => x.IsKind(SyntaxKind.IdentifierName)) .Select(x => model.GetSymbolInfo(x).Symbol as IParameterSymbol) .Where(x => x is not null && parameters.Contains(x)) .ToHashSet(); private static bool IsUsedAsEventHandlerFunctionOrAction(MethodContext declaration) => declaration.Symbol.ContainingType.DeclaringSyntaxReferences.Select(x => x.GetSyntax()) .Any(x => IsMethodUsedAsEventHandlerFunctionOrActionWithinNode(declaration.Symbol, x, x.EnsureCorrectSemanticModelOrDefault(declaration.Context.Model))); private static bool IsMethodUsedAsEventHandlerFunctionOrActionWithinNode(IMethodSymbol methodSymbol, SyntaxNode typeDeclaration, SemanticModel model) => typeDeclaration.DescendantNodes() .OfType() .Any(x => IsMethodUsedAsEventHandlerFunctionOrActionInExpression(methodSymbol, x, model)); private static bool IsMethodUsedAsEventHandlerFunctionOrActionInExpression(IMethodSymbol methodSymbol, ExpressionSyntax expression, SemanticModel model) => !expression.IsKind(SyntaxKind.InvocationExpression) && model is not null && IsStandaloneExpression(expression) && methodSymbol.Equals(model.GetSymbolInfo(expression).Symbol?.OriginalDefinition); private static bool IsStandaloneExpression(ExpressionSyntax expression) { var parentAsAssignment = expression.Parent as AssignmentExpressionSyntax; return expression.Parent is not ExpressionSyntax || (parentAsAssignment is not null && ReferenceEquals(expression, parentAsAssignment.Right)); } private static bool IsCandidateSerializableConstructor(IImmutableList unusedParameters, IMethodSymbol methodSymbol) => unusedParameters.Count == 1 && methodSymbol.MethodKind == MethodKind.Constructor && methodSymbol.Parameters.Length == 2 && methodSymbol.Parameters.All(x => !x.IsOptional) && unusedParameters[0].Equals(methodSymbol.Parameters[1]) && methodSymbol.ContainingType.Implements(KnownType.System_Runtime_Serialization_ISerializable) && methodSymbol.Parameters[0].IsType(KnownType.System_Runtime_Serialization_SerializationInfo) && methodSymbol.Parameters[1].IsType(KnownType.System_Runtime_Serialization_StreamingContext); private sealed class MethodContext { public readonly SonarSyntaxNodeReportingContext Context; public readonly IMethodSymbol Symbol; public readonly SyntaxTokenList Modifiers; public readonly ParameterListSyntax ParameterList; public readonly BlockSyntax Body; public readonly ArrowExpressionClauseSyntax ExpressionBody; public MethodContext(SonarSyntaxNodeReportingContext context, BaseMethodDeclarationSyntax declaration) : this(context, declaration.Modifiers, declaration.ParameterList, declaration.Body, declaration.ExpressionBody()) { } public MethodContext(SonarSyntaxNodeReportingContext context, LocalFunctionStatementSyntaxWrapper declaration) : this(context, declaration.Modifiers, declaration.ParameterList, declaration.Body, declaration.ExpressionBody) { } public MethodContext(SonarSyntaxNodeReportingContext context, SyntaxTokenList modifiers, ParameterListSyntax parameterList, BlockSyntax body, ArrowExpressionClauseSyntax expressionBody) { Context = context; Symbol = context.Model.GetDeclaredSymbol(context.Node) as IMethodSymbol; Modifiers = modifiers; ParameterList = parameterList; Body = body; ExpressionBody = expressionBody; } } private sealed class LvaResult { public readonly IReadOnlyCollection LiveInEntryBlock; public readonly IReadOnlyCollection CapturedVariables; public LvaResult(MethodContext declaration, IControlFlowGraph cfg) { var lva = new SonarCSharpLiveVariableAnalysis(cfg, declaration.Symbol, declaration.Context.Model, declaration.Context.Cancel); LiveInEntryBlock = lva.LiveIn(cfg.EntryBlock).OfType().ToImmutableArray(); CapturedVariables = lva.CapturedVariables; } public LvaResult(ControlFlowGraph cfg, CancellationToken cancel) { var lva = new RoslynLiveVariableAnalysis(cfg, CSharpSyntaxClassifier.Instance, cancel); LiveInEntryBlock = lva.LiveIn(cfg.EntryBlock).OfType().ToImmutableArray(); CapturedVariables = lva.CapturedVariables; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodParameterUnusedCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class MethodParameterUnusedCodeFix : SonarCodeFix { private const string Title = "Remove unused parameter"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodParameterUnused.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var parameter = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) as ParameterSyntax; if (!bool.Parse(diagnostic.Properties[MethodParameterUnused.IsRemovableKey])) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode( parameter, SyntaxRemoveOptions.KeepLeadingTrivia | SyntaxRemoveOptions.AddElasticMarker); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodShouldBeNamedAccordingToSynchronicity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodShouldBeNamedAccordingToSynchronicity : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4261"; private const string MessageFormat = "{0}"; private const string AddAsyncSuffixMessage = "Add the 'Async' suffix to the name of this method."; private const string RemoveAsyncSuffixMessage = "Remove the 'Async' suffix to the name of this method."; private static readonly ImmutableArray AsyncReturnTypes = ImmutableArray.Create( KnownType.System_Threading_Tasks_Task, KnownType.System_Threading_Tasks_Task_T, KnownType.System_Threading_Tasks_ValueTask, // NetCore 2.2+ KnownType.System_Threading_Tasks_ValueTask_TResult); private static readonly ImmutableArray AsyncReturnInterfaces = ImmutableArray.Create(KnownType.System_Collections_Generic_IAsyncEnumerable_T); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; if (methodDeclaration.Identifier.IsMissing) { return; } var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol == null || methodSymbol.IsMainMethod() || methodSymbol.InterfaceMembers().Any() || methodSymbol.GetOverriddenMember() != null || methodSymbol.IsTestMethod() || methodSymbol.IsControllerActionMethod() || IsSignalRHubMethod(methodSymbol)) { return; } var hasAsyncReturnType = HasAsyncReturnType(methodSymbol); var hasAsyncSuffix = HasAsyncSuffix(methodDeclaration); if (hasAsyncSuffix && !hasAsyncReturnType) { c.ReportIssue(Rule, methodDeclaration.Identifier, RemoveAsyncSuffixMessage); } else if (!hasAsyncSuffix && hasAsyncReturnType) { c.ReportIssue(Rule, methodDeclaration.Identifier, AddAsyncSuffixMessage); } }, SyntaxKind.MethodDeclaration); private static bool HasAsyncReturnType(IMethodSymbol methodSymbol) => methodSymbol.ReturnType is ITypeParameterSymbol typeParameter ? typeParameter.ConstraintTypes.Any(IsAsyncType) : (methodSymbol.ReturnType as INamedTypeSymbol)?.ConstructedFrom is { } returnSymbol && !returnSymbol.Is(KnownType.Void) && IsAsyncType(returnSymbol); private static bool IsAsyncType(ITypeSymbol typeSymbol) => typeSymbol.DerivesFromAny(AsyncReturnTypes) || typeSymbol.IsAny(AsyncReturnInterfaces) || typeSymbol.ImplementsAny(AsyncReturnInterfaces); private static bool HasAsyncSuffix(MethodDeclarationSyntax methodDeclaration) => methodDeclaration.Identifier.ValueText.EndsWith("async", StringComparison.OrdinalIgnoreCase); private static bool IsSignalRHubMethod(ISymbol methodSymbol) => methodSymbol.GetEffectiveAccessibility() == Accessibility.Public && IsSignalRHubMethod(methodSymbol.ContainingType); private static bool IsSignalRHubMethod(ITypeSymbol typeSymbol) => typeSymbol.DerivesFrom(KnownType.Microsoft_AspNet_SignalR_Hub); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodShouldNotOnlyReturnConstant.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodShouldNotOnlyReturnConstant : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3400"; private const string MessageFormat = "Remove this method and declare a constant for this value."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; if (method.ParameterList?.Parameters.Count is 0 && !IsVirtual(method) && IsConstantExpression(SingleExpressionOrDefault(method), c.Model) && !ContainsConditionalCompilation((SyntaxNode)method.ExpressionBody ?? method.Body) && c.Model.GetDeclaredSymbol(method) is { } methodSymbol && !methodSymbol.ContainingType.IsInterface() && methodSymbol.InterfaceMembers().IsEmpty() && methodSymbol.GetOverriddenMember() is null) { c.ReportIssue(Rule, method.Identifier); } }, SyntaxKind.MethodDeclaration); private static bool IsVirtual(BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.VirtualKeyword)); private static ExpressionSyntax SingleExpressionOrDefault(MethodDeclarationSyntax method) => method switch { { ExpressionBody: { } body } => body.Expression, { Body.Statements: { Count: 1 } statements } when statements.Single() is ReturnStatementSyntax returnStatement => returnStatement.Expression, _ => null }; private static bool IsConstantExpression(ExpressionSyntax expression, SemanticModel model) => expression.RemoveParentheses() is LiteralExpressionSyntax literal && !literal.IsNullLiteral() && model.GetConstantValue(literal).HasValue; private static bool ContainsConditionalCompilation(SyntaxNode node) => node.DescendantNodes(descendIntoTrivia: true).Any(x => x.IsKind(SyntaxKind.IfDirectiveTrivia)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodsShouldNotHaveIdenticalImplementations.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodsShouldNotHaveIdenticalImplementations : MethodsShouldNotHaveIdenticalImplementationsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds => [ SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.CompilationUnit ]; protected override IEnumerable GetMethodDeclarations(SyntaxNode node) => node.IsKind(SyntaxKind.CompilationUnit) ? ((CompilationUnitSyntax)node).GetMethodDeclarations() : ((TypeDeclarationSyntax)node).GetMethodDeclarations(); protected override bool AreDuplicates(SemanticModel model, IMethodDeclaration firstMethod, IMethodDeclaration secondMethod) => firstMethod is { Body.Statements.Count: > 1 } && firstMethod.Identifier.ValueText != secondMethod.Identifier.ValueText && HaveSameParameters(firstMethod.ParameterList?.Parameters, secondMethod.ParameterList?.Parameters) && HaveSameTypeParameters(model, firstMethod.TypeParameterList?.Parameters, secondMethod.TypeParameterList?.Parameters) && AreTheSameType(model, firstMethod.ReturnType, secondMethod.ReturnType) && firstMethod.Body.IsEquivalentTo(secondMethod.Body, false); protected override SyntaxToken GetMethodIdentifier(IMethodDeclaration method) => method.Identifier; protected override bool IsExcludedFromBeingExamined(SonarSyntaxNodeReportingContext context) => base.IsExcludedFromBeingExamined(context) && !context.IsTopLevelMain; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodsShouldNotHaveTooManyLines.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodsShouldNotHaveTooManyLines : MethodsShouldNotHaveTooManyLinesBase { private const string LocalFunctionMessageFormat = "{0} local function has {1} lines, which is greater than the {2} lines authorized."; private static readonly DiagnosticDescriptor DefaultRule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); private static readonly DiagnosticDescriptor LocalFunctionRule = DescriptorFactory.Create(DiagnosticId, LocalFunctionMessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(DefaultRule, LocalFunctionRule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration }; protected override string MethodKeyword => "methods"; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction(c => { var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)c.Node; if (localFunctionStatement.IsTopLevel() || localFunctionStatement.Modifiers.Any(SyntaxKind.StaticKeyword)) { var wrapper = (LocalFunctionStatementSyntaxWrapper)c.Node; var linesCount = CountLines(wrapper); if (linesCount > Max) { var modifierPrefix = wrapper.Modifiers.Any(SyntaxKind.StaticKeyword) ? "This static" : "This"; c.ReportIssue(LocalFunctionRule, wrapper.Identifier, modifierPrefix, linesCount.ToString(), Max.ToString(), MethodKeyword); } } }, SyntaxKindEx.LocalFunctionStatement); base.Initialize(context); } protected override IEnumerable GetMethodTokens(BaseMethodDeclarationSyntax baseMethodDeclaration) => baseMethodDeclaration.ExpressionBody()?.Expression?.DescendantTokens() ?? baseMethodDeclaration.Body?.Statements.Where(s => !IsStaticLocalFunction(s)).SelectMany(s => s.DescendantTokens()) ?? Enumerable.Empty(); protected override SyntaxToken? GetMethodIdentifierToken(BaseMethodDeclarationSyntax baseMethodDeclaration) => baseMethodDeclaration.GetIdentifierOrDefault(); protected override string GetMethodKindAndName(SyntaxToken identifierToken) { var identifierName = identifierToken.ValueText; if (string.IsNullOrEmpty(identifierName)) { return "method"; } var declaration = identifierToken.Parent; if (declaration.IsKind(SyntaxKind.ConstructorDeclaration)) { return $"constructor '{identifierName}'"; } if (declaration.IsKind(SyntaxKind.DestructorDeclaration)) { return $"finalizer '~{identifierName}'"; } if (declaration is MethodDeclarationSyntax) { return $"method '{identifierName}'"; } return "method"; } private static IEnumerable GetMethodTokens(LocalFunctionStatementSyntaxWrapper wrapper) => wrapper.ExpressionBody?.Expression.DescendantTokens() ?? wrapper.Body?.Statements.SelectMany(s => s.DescendantTokens()) ?? Enumerable.Empty(); private static long CountLines(LocalFunctionStatementSyntaxWrapper wrapper) => GetMethodTokens(wrapper).SelectMany(x => x.LineNumbers()) .Distinct() .LongCount(); private static bool IsStaticLocalFunction(SyntaxNode node) => node.IsKind(SyntaxKindEx.LocalFunctionStatement) && ((LocalFunctionStatementSyntaxWrapper)node).Modifiers.Any(SyntaxKind.StaticKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MethodsShouldUseBaseTypes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodsShouldUseBaseTypes : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3242"; private const string MessageFormat = "Consider using more general type '{0}' instead of '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => FindViolations((BaseMethodDeclarationSyntax)c.Node, c.Model).ForEach(d => c.ReportIssue(d)), SyntaxKind.MethodDeclaration); private static List FindViolations(BaseMethodDeclarationSyntax methodDeclaration, SemanticModel semanticModel) { if (semanticModel.GetDeclaredSymbol(methodDeclaration) is not { } methodSymbol || methodSymbol.Parameters.Length == 0 || methodSymbol.IsOverride || methodSymbol.IsVirtual || methodSymbol.IsControllerActionMethod() || methodSymbol.InterfaceMembers().Any() || methodSymbol.IsEventHandler()) { return Enumerable.Empty().ToList(); } var methodAccessibility = methodSymbol.GetEffectiveAccessibility(); // The GroupBy is useless in most of the cases but safe-guard in case of 2+ parameters with same name (invalid code). // In this case we analyze only the first parameter (a new analysis will be triggered after fixing the names). var parametersToCheck = methodSymbol.Parameters .Where(IsTrackedParameter) .GroupBy(p => p.Name) .ToDictionary(p => p.Key, p => new ParameterData(p.First(), methodAccessibility)); var parameterUsesInMethod = methodDeclaration .DescendantNodes() .OfType() .Where(id => parametersToCheck.Values.Any(p => p.MatchesIdentifier(id, semanticModel))); foreach (var identifierReference in parameterUsesInMethod) { var key = identifierReference.Identifier.ValueText ?? string.Empty; if (!parametersToCheck.TryGetValue(key, out var paramData) || !paramData.ShouldReportOn) { continue; } if (identifierReference.Parent is EqualsValueClauseSyntax or AssignmentExpressionSyntax) { paramData.ShouldReportOn = false; continue; } var symbolUsedAs = FindParameterUseAsType(identifierReference, semanticModel); if (symbolUsedAs != null && !IsNestedGeneric(symbolUsedAs)) // In order to avoid triggering "S4017: Refactor this method to remove the nested type argument." { paramData.AddUsage(symbolUsedAs); } } return parametersToCheck.Values .Select(p => p.GetRuleViolation()) .WhereNotNull() .ToList(); } private static bool IsNestedGeneric(ISymbol symbol) => symbol is INamedTypeSymbol { IsGenericType: true } namedTypeSymbol && namedTypeSymbol.TypeArguments.Any(argument => argument is INamedTypeSymbol { IsGenericType: true }); private static bool IsTrackedParameter(IParameterSymbol parameterSymbol) { var type = parameterSymbol.Type; return !type.DerivesFrom(KnownType.System_Array) && !type.IsValueType && !type.Is(KnownType.System_String); } private static SyntaxNode GetFirstNonParenthesizedParent(SyntaxNode node) => node is ExpressionSyntax expression ? expression.GetFirstNonParenthesizedParent() : node; private static ITypeSymbol FindParameterUseAsType(SyntaxNode identifier, SemanticModel semanticModel) { var callSite = semanticModel.GetEnclosingSymbol(identifier.SpanStart)?.ContainingAssembly; var identifierParent = GetFirstNonParenthesizedParent(identifier); return identifierParent switch { ConditionalAccessExpressionSyntax conditionalAccess => HandleConditionalAccess(conditionalAccess, identifier, semanticModel, callSite), MemberAccessExpressionSyntax => GetFirstNonParenthesizedParent(identifierParent) is InvocationExpressionSyntax invocationExpression ? HandleInvocation(identifier, semanticModel.GetSymbolInfo(invocationExpression).Symbol, semanticModel, callSite) : HandlePropertyOrField(identifier, semanticModel.GetSymbolInfo(identifierParent).Symbol, callSite), ArgumentSyntax => semanticModel.GetTypeInfo(identifier).ConvertedType, ElementAccessExpressionSyntax => HandlePropertyOrField(identifier, semanticModel.GetSymbolInfo(identifierParent).Symbol, callSite), _ => null }; } private static ITypeSymbol HandleConditionalAccess(ConditionalAccessExpressionSyntax conditionalAccess, SyntaxNode identifier, SemanticModel semanticModel, IAssemblySymbol callSite) { var conditionalAccessExpression = conditionalAccess.WhenNotNull is ConditionalAccessExpressionSyntax subsequentConditionalAccess ? subsequentConditionalAccess.Expression : conditionalAccess.WhenNotNull; return conditionalAccessExpression switch { MemberBindingExpressionSyntax { Name: { } } binding => HandlePropertyOrField(identifier, semanticModel.GetSymbolInfo(binding.Name).Symbol, callSite), InvocationExpressionSyntax { Expression: MemberBindingExpressionSyntax memberBinding } => HandleInvocation(identifier, semanticModel.GetSymbolInfo(memberBinding).Symbol, semanticModel, callSite), _ => null }; } private static ITypeSymbol HandlePropertyOrField(SyntaxNode identifier, ISymbol symbol, IAssemblySymbol callSite) { if (symbol is not IPropertySymbol propertySymbol) { return FindOriginatingSymbol(symbol, callSite); } var parent = GetFirstNonParenthesizedParent(identifier); var grandParent = GetFirstNonParenthesizedParent(parent); var propertyAccessor = grandParent is AssignmentExpressionSyntax ? propertySymbol.SetMethod : propertySymbol.GetMethod; return FindOriginatingSymbol(propertyAccessor, callSite); } private static ITypeSymbol HandleInvocation(SyntaxNode invokedOn, ISymbol invocationSymbol, SemanticModel semanticModel, IAssemblySymbol callSite) { if (invocationSymbol is not IMethodSymbol methodSymbol) { return null; } return methodSymbol.IsExtensionMethod ? semanticModel.GetTypeInfo(invokedOn).ConvertedType : FindOriginatingSymbol(invocationSymbol, callSite); } private static INamedTypeSymbol FindOriginatingSymbol(ISymbol accessedMember, ISymbol usageSite) { if (accessedMember == null) { return null; } var originatingInterface = accessedMember.InterfaceMembers().FirstOrDefault()?.ContainingType; if (originatingInterface != null && IsNotInternalOrSameAssembly(originatingInterface) && !originatingInterface.Is(KnownType.System_Runtime_InteropServices_Exception)) { return originatingInterface; } var originatingType = accessedMember.GetOverriddenMember()?.ContainingType; return originatingType != null && IsNotInternalOrSameAssembly(originatingType) ? originatingType : accessedMember.ContainingType; // Do not suggest internal types that are declared in an assembly different than // the one that's declaring the parameter. Such types should not be suggested at // all if there is no InternalsVisibleTo attribute present in the compilation. // Since the check for the attribute must be done in CompilationEnd thus making // the rule unusable in Visual Studio, we will not suggest such classes and will // generate some False Negatives. bool IsNotInternalOrSameAssembly(ISymbol namedTypeSymbol) => namedTypeSymbol.ContainingAssembly.Equals(usageSite) || namedTypeSymbol.GetEffectiveAccessibility() != Accessibility.Internal; } private sealed class ParameterData { public bool ShouldReportOn { get; set; } = true; private readonly IParameterSymbol parameterSymbol; private readonly Accessibility methodAccessibility; private readonly Dictionary usedAs = new(); public ParameterData(IParameterSymbol parameterSymbol, Accessibility methodAccessibility) { this.parameterSymbol = parameterSymbol; this.methodAccessibility = methodAccessibility; } public void AddUsage(ITypeSymbol symbolUsedAs) { if (usedAs.ContainsKey(symbolUsedAs)) { usedAs[symbolUsedAs]++; } else { usedAs[symbolUsedAs] = 1; } } public bool MatchesIdentifier(ExpressionSyntax identifier, SemanticModel semanticModel) { var symbol = semanticModel.GetSymbolInfo(identifier).Symbol; return Equals(parameterSymbol, symbol); } public Diagnostic GetRuleViolation() { if (!ShouldReportOn) { return null; } var mostGeneralType = FindMostGeneralType(); return Equals(mostGeneralType, parameterSymbol.Type) || IsIgnoredBaseType(mostGeneralType.GetSymbolType()) ? null : Diagnostic.Create(Rule, parameterSymbol.Locations.First(), mostGeneralType.ToDisplayString(), parameterSymbol.Type.ToDisplayString()); } private static bool IsIgnoredBaseType(ITypeSymbol typeSymbol) => typeSymbol.IsAny(KnownType.System_Object, KnownType.System_ValueType, KnownType.System_Enum) || typeSymbol.Name.StartsWith("_", StringComparison.Ordinal) || IsCollectionOfKeyValuePair(typeSymbol); private static bool IsCollectionOfKeyValuePair(ITypeSymbol typeSymbol) => typeSymbol is INamedTypeSymbol namedType && namedType.TypeArguments.FirstOrDefault() is INamedTypeSymbol firstGenericType && namedType.ConstructedFrom.Is(KnownType.System_Collections_Generic_ICollection_T) && firstGenericType.ConstructedFrom.Is(KnownType.System_Collections_Generic_KeyValuePair_TKey_TValue); private ISymbol FindMostGeneralType() { var mostGeneralType = parameterSymbol.Type; var multipleEnumerableCalls = usedAs.Where(HasMultipleUseOfIEnumerable).ToList(); foreach (var v in multipleEnumerableCalls) { usedAs.Remove(v.Key); } if (usedAs.Count == 0) { return mostGeneralType; } mostGeneralType = FindMostGeneralAccessibleClassOrSelf(mostGeneralType); mostGeneralType = FindMostGeneralAccessibleInterfaceOrSelf(mostGeneralType); return mostGeneralType; static bool HasMultipleUseOfIEnumerable(KeyValuePair kvp) => kvp.Value > 1 && (kvp.Key.OriginalDefinition.Is(KnownType.System_Collections_Generic_IEnumerable_T) || kvp.Key.Is(KnownType.System_Collections_IEnumerable)); ITypeSymbol FindMostGeneralAccessibleClassOrSelf(ITypeSymbol typeSymbol) { var currentSymbol = typeSymbol.BaseType; while (currentSymbol != null) { if (DerivesOrImplementsAll(currentSymbol)) { typeSymbol = currentSymbol; } currentSymbol = currentSymbol?.BaseType; } return typeSymbol; } ITypeSymbol FindMostGeneralAccessibleInterfaceOrSelf(ITypeSymbol typeSymbol) => typeSymbol.Interfaces.FirstOrDefault(DerivesOrImplementsAll) is { } @interface ? FindMostGeneralAccessibleInterfaceOrSelf(@interface) : typeSymbol; } private bool DerivesOrImplementsAll(ITypeSymbol type) { return usedAs.Keys.All(type.DerivesOrImplements) && IsConsistentAccessibility(type.GetEffectiveAccessibility()); bool IsConsistentAccessibility(Accessibility baseTypeAccessibility) => methodAccessibility switch { Accessibility.Private => true, // ProtectedAndInternal corresponds to `private protected`. Accessibility.ProtectedAndInternal => baseTypeAccessibility is not Accessibility.Private, // ProtectedOrInternal corresponds to `protected internal`. Accessibility.ProtectedOrInternal => baseTypeAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal, Accessibility.Protected => baseTypeAccessibility == Accessibility.Public || baseTypeAccessibility == methodAccessibility, Accessibility.Internal => baseTypeAccessibility == Accessibility.Public || baseTypeAccessibility == methodAccessibility, Accessibility.Public => baseTypeAccessibility == Accessibility.Public, _ => false }; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MultilineBlocksWithoutBrace.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MultilineBlocksWithoutBrace : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2681"; private const string MessageFormat = "This line will not be executed {0}; only the first line of this {2}-line block will be. The rest will execute {1}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckLoop(c, ((WhileStatementSyntax)c.Node).Statement), SyntaxKind.WhileStatement); context.RegisterNodeAction( c => CheckLoop(c, ((ForStatementSyntax)c.Node).Statement), SyntaxKind.ForStatement); context.RegisterNodeAction( c => CheckLoop(c, ((ForEachStatementSyntax)c.Node).Statement), SyntaxKind.ForEachStatement); context.RegisterNodeAction( c => CheckIf(c, (IfStatementSyntax)c.Node), SyntaxKind.IfStatement); } private static void CheckLoop(SonarSyntaxNodeReportingContext context, StatementSyntax statement) { if (!IsNestedStatement(statement)) { CheckStatement(context, statement, "in a loop", "only once"); } } private static void CheckIf(SonarSyntaxNodeReportingContext context, IfStatementSyntax ifStatement) { if (!ifStatement.PrecedingIfsInConditionChain().Any() && !IsNestedStatement(ifStatement.Statement) && LastStatementInIfChain(ifStatement) is { } lastStatementInIfChain && !IsStatementCandidateLoop(lastStatementInIfChain)) { CheckStatement(context, lastStatementInIfChain, "conditionally", "unconditionally"); } } private static StatementSyntax LastStatementInIfChain(IfStatementSyntax ifStatement) { var statement = ifStatement.Statement; while (ifStatement is { }) { if (ifStatement.Else is null) { return ifStatement.Statement; } statement = ifStatement.Else.Statement; ifStatement = statement as IfStatementSyntax; } return statement; } private static void CheckStatement(SonarSyntaxNodeReportingContext context, StatementSyntax first, string executed, string execute) { if (IsNotEmpty(first) && SecondStatement(context.Node, first) is { } second && IsNotEmpty(second) && MisleadingtIndenting(first, second)) { var secondLine = StartPosition(second).Line; var lineSpan = context.Node.SyntaxTree.GetText().Lines[secondLine].Span; var location = Location.Create(context.Node.SyntaxTree, TextSpan.FromBounds(second.SpanStart, lineSpan.End)); var blockSize = secondLine - StartPosition(first).Line + 1; context.ReportIssue(Rule, location, [first.ToSecondaryLocation()], executed, execute, blockSize.ToString()); } } private static bool IsNotEmpty(SyntaxNode node) => node is not EmptyStatementSyntax; private static bool MisleadingtIndenting(SyntaxNode first, SyntaxNode second) { var firstPosition = StartPosition(first); var secondPosition = StartPosition(second); var ancestor = first.AncestorsAndSelf().Select(x => StartPosition(x)).Last(x => x.Line == firstPosition.Line); // If the first node is not at the same line as its parent return firstPosition.Character == ancestor.Character ? secondPosition.Character >= firstPosition.Character : secondPosition.Character > ancestor.Character; } private static LinePosition StartPosition(SyntaxNode node) => node.GetLocation().GetLineSpan().StartLinePosition; private static SyntaxNode SecondStatement(SyntaxNode root, SyntaxNode first) => !first.IsKind(SyntaxKind.Block) // This algorithm to get the next statement can sometimes return a parent statement (for example a BlockSyntax) // so we need to filter this case by returning if the nextStatement happens to be one ancestor of statement. && root.GetLastToken().GetNextToken().Parent is { } second && !first.Ancestors().Contains(second) && second is not ElseClauseSyntax ? second : null; private static bool IsNestedStatement(StatementSyntax statement) => statement?.Kind() is SyntaxKind.IfStatement or SyntaxKind.ForStatement or SyntaxKind.ForEachStatement or SyntaxKind.WhileStatement; private static bool IsStatementCandidateLoop(StatementSyntax statement) => statement?.Kind() is SyntaxKind.ForEachStatement or SyntaxKind.ForStatement or SyntaxKind.WhileStatement; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MultipleVariableDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MultipleVariableDeclaration : MultipleVariableDeclarationBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MultipleVariableDeclarationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public class MultipleVariableDeclarationCodeFix : MultipleVariableDeclarationCodeFixBase { protected override SyntaxNode CalculateNewRoot(SyntaxNode root, SyntaxNode node) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax declaration } ? root.ReplaceNode(declaration.Parent, CreateNewNodes(declaration)) : root; private static IEnumerable CreateNewNodes(VariableDeclarationSyntax declaration) { var newDeclarations = declaration.Variables.Select(x => CreateNewDeclaration(x, declaration)); return declaration.Parent switch { FieldDeclarationSyntax fieldDeclaration => newDeclarations.Select(x => SyntaxFactory.FieldDeclaration(fieldDeclaration.AttributeLists, fieldDeclaration.Modifiers, x)), LocalDeclarationStatementSyntax localDeclaration => newDeclarations.Select(x => SyntaxFactory.LocalDeclarationStatement(localDeclaration.Modifiers, x)), _ => new[] { declaration.Parent } }; } private static VariableDeclarationSyntax CreateNewDeclaration(VariableDeclaratorSyntax variable, VariableDeclarationSyntax declaration) => SyntaxFactory.VariableDeclaration( declaration.Type.WithoutTrailingTrivia(), SyntaxFactory.SeparatedList(new[] { variable.WithLeadingTrivia(GetLeadingTriviaFor(variable)) })); private static IEnumerable GetLeadingTriviaFor(VariableDeclaratorSyntax variable) => variable.GetFirstToken().GetPreviousToken().TrailingTrivia.Concat(variable.GetLeadingTrivia()); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MutableFieldsShouldNotBe.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; public abstract class MutableFieldsShouldNotBe : SonarDiagnosticAnalyzer { private static readonly ImmutableArray MutableBaseTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_ICollection_T, KnownType.System_Array); private static readonly ImmutableArray ImmutableBaseTypes = ImmutableArray.Create( KnownType.System_Collections_ObjectModel_ReadOnlyCollection_T, KnownType.System_Collections_ObjectModel_ReadOnlyDictionary_TKey_TValue, KnownType.System_Collections_ObjectModel_ReadOnlySet_T, KnownType.System_Collections_Frozen_FrozenDictionary_TKey_TValue, KnownType.System_Collections_Frozen_FrozenSet_T, KnownType.System_Collections_Immutable_ImmutableArray_T, KnownType.System_Collections_Immutable_IImmutableDictionary_TKey_TValue, KnownType.System_Collections_Immutable_IImmutableList_T, KnownType.System_Collections_Immutable_IImmutableSet_T, KnownType.System_Collections_Immutable_IImmutableStack_T, KnownType.System_Collections_Immutable_IImmutableQueue_T); private readonly DiagnosticDescriptor rule; protected abstract ISet InvalidModifiers { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected MutableFieldsShouldNotBe(string diagnosticId, string messageFormat) => rule = DescriptorFactory.Create(diagnosticId, messageFormat); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } var typeDeclaration = (TypeDeclarationSyntax)c.Node; var fieldDeclarations = typeDeclaration.Members.OfType(); var assignmentsImmutability = FieldAssignmentImmutability(typeDeclaration, fieldDeclarations, c.Model); foreach (var fieldDeclaration in fieldDeclarations) { if (HasAllInvalidModifiers(fieldDeclaration) && fieldDeclaration.Declaration.Variables.Count > 0 && c.Model.GetDeclaredSymbol(fieldDeclaration.Declaration.Variables[0]) is IFieldSymbol { Type: not null } fieldSymbol && fieldSymbol.GetEffectiveAccessibility() == Accessibility.Public && !IsImmutableOrValidMutableType(fieldSymbol.Type) // The field seems to be violating the rule but we should exclude the cases where the field is read-only // and all initializations to this field are immutable && CollectInvalidFieldVariables(fieldDeclaration, assignmentsImmutability, c.Model).ToList() is { Count: > 0 } incorrectFieldVariables) { var pluralizeSuffix = incorrectFieldVariables.Count > 1 ? "s" : string.Empty; c.ReportIssue(rule, fieldDeclaration.Declaration.Type, pluralizeSuffix, incorrectFieldVariables.ToSentence(quoteWords: true)); } } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private bool HasAllInvalidModifiers(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.Modifiers.Count(m => InvalidModifiers.Contains(m.Kind())) == InvalidModifiers.Count; private static Dictionary FieldAssignmentImmutability(TypeDeclarationSyntax typeDeclaration, IEnumerable fieldDeclarations, SemanticModel semanticModel) { var variableNames = fieldDeclarations.SelectMany(x => x.Declaration.Variables) .Select(x => x.Identifier.ValueText) .ToHashSet(); var ctorAssignments = typeDeclaration.Members.OfType() .SelectMany(x => x.DescendantNodes()) .OfType(); var variableToImmutability = variableNames.ToDictionary(x => x, x => (bool?)null); foreach (var assignment in ctorAssignments) { if (assignment.Left is not IdentifierNameSyntax identifierName || !variableNames.Contains(identifierName.Identifier.ValueText) || variableToImmutability[identifierName.Identifier.ValueText] == false) { continue; } variableToImmutability[identifierName.Identifier.ValueText] = IsImmutableOrValidMutableType(semanticModel.GetTypeInfo(assignment.Right).Type, assignment.Right); } return variableToImmutability; } private static IEnumerable CollectInvalidFieldVariables(FieldDeclarationSyntax fieldDeclaration, Dictionary assignmentsInCtors, SemanticModel semanticModel) => fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) ? CollectReadonlyInvalidFieldVariables(fieldDeclaration, assignmentsInCtors, semanticModel) : fieldDeclaration.Declaration.Variables.Select(x => x.Identifier.ValueText); private static IEnumerable CollectReadonlyInvalidFieldVariables(FieldDeclarationSyntax fieldDeclaration, Dictionary assignmentsInCtors, SemanticModel semanticModel) { foreach (var variable in fieldDeclaration.Declaration.Variables) { var onlyInitializedWithImmutablesInCtor = assignmentsInCtors[variable.Identifier.ValueText]; if (onlyInitializedWithImmutablesInCtor == false) { yield return variable.Identifier.ValueText; } if (variable.Initializer is null || semanticModel.GetSymbolInfo(variable.Initializer.Value).Symbol is not IMethodSymbol methodSymbol) { continue; } var typeSymbol = methodSymbol.MethodKind == MethodKind.Constructor ? methodSymbol.ContainingType : methodSymbol.ReturnType; if (!IsImmutableOrValidMutableType(typeSymbol, variable.Initializer.Value)) { yield return variable.Identifier.ValueText; } } } private static bool IsImmutableOrValidMutableType(ITypeSymbol typeSymbol, ExpressionSyntax value = null) { if (value.IsNullLiteral()) { return true; } if (typeSymbol is INamedTypeSymbol namedTypeSymbol) { typeSymbol = namedTypeSymbol.ConstructedFrom; } return !typeSymbol.DerivesOrImplementsAny(MutableBaseTypes) || typeSymbol.DerivesOrImplementsAny(ImmutableBaseTypes); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MutableFieldsShouldNotBePublicReadonly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MutableFieldsShouldNotBePublicReadonly : MutableFieldsShouldNotBe { private const string DiagnosticId = "S3887"; private const string MessageFormat = "Use an immutable collection or reduce the accessibility of the non-private readonly field{0} {1}."; protected override ISet InvalidModifiers { get; } = new HashSet { SyntaxKind.PublicKeyword, SyntaxKind.ReadOnlyKeyword }; public MutableFieldsShouldNotBePublicReadonly() : base(DiagnosticId, MessageFormat) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/MutableFieldsShouldNotBePublicStatic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MutableFieldsShouldNotBePublicStatic : MutableFieldsShouldNotBe { private const string DiagnosticId = "S2386"; private const string MessageFormat = "Use an immutable collection or reduce the accessibility of the public static field{0} {1}."; protected override ISet InvalidModifiers { get; } = new HashSet { SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword }; public MutableFieldsShouldNotBePublicStatic() : base(DiagnosticId, MessageFormat) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NameOfShouldBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NameOfShouldBeUsed : NameOfShouldBeUsedBase { private static readonly HashSet StringTokenTypes = new HashSet { SyntaxKind.InterpolatedStringTextToken, SyntaxKind.StringLiteralToken, SyntaxKindEx.SingleLineRawStringLiteralToken, SyntaxKindEx.MultiLineRawStringLiteralToken }; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override string NameOf => "nameof"; protected override BaseMethodDeclarationSyntax MethodSyntax(SyntaxNode node) => (BaseMethodDeclarationSyntax)node; protected override bool IsStringLiteral(SyntaxToken t) => t.IsAnyKind(StringTokenTypes); protected override IEnumerable GetParameterNames(BaseMethodDeclarationSyntax method) { var paramGroups = method.ParameterList?.Parameters.GroupBy(p => p.Identifier.ValueText); return paramGroups == null || paramGroups.Any(g => g.Count() != 1) ? Enumerable.Empty() : paramGroups.Select(g => g.First().Identifier.ValueText); } protected override bool LeastLanguageVersionMatches(SonarSyntaxNodeReportingContext context) => context.Compilation.IsAtLeastLanguageVersion(LanguageVersion.CSharp6); protected override bool IsArgumentExceptionCallingNameOf(SyntaxNode node, IEnumerable arguments) => ((ThrowStatementSyntax)node).Expression is ObjectCreationExpressionSyntax objectCreation && ArgumentExceptionNameOfPosition(objectCreation.Type.ToString()) is var idx && objectCreation.ArgumentList?.Arguments is { } creationArguments && creationArguments.Count >= idx + 1 && creationArguments[idx].Expression is InvocationExpressionSyntax invocation && invocation.Expression.ToString() == "nameof" && invocation.ArgumentList.Arguments.Count == 1 && arguments.Contains(invocation.ArgumentList.Arguments[0].Expression.ToString()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NativeMethodsShouldBeWrapped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NativeMethodsShouldBeWrapped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4200"; private const string MessageFormat = "{0}"; private const string MakeThisMethodPrivateMessage = "Make this native method private and provide a wrapper."; private const string MakeThisWrapperLessTrivialMessage = "Make this wrapper for native method '{0}' less trivial."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterSymbolAction(ReportPublicExternalMethods, SymbolKind.Method); context.RegisterNodeAction(ReportTrivialWrappers, SyntaxKind.MethodDeclaration); } private static void ReportPublicExternalMethods(SonarSymbolReportingContext c) { var methodSymbol = (IMethodSymbol)c.Symbol; if (IsExternMethod(methodSymbol) && methodSymbol.IsPubliclyAccessible()) { foreach (var methodDeclaration in methodSymbol.DeclaringSyntaxReferences .Where(x => !x.SyntaxTree.IsConsideredGenerated(CSharpGeneratedCodeRecognizer.Instance, c.IsRazorAnalysisEnabled())) .Select(x => x.GetSyntax()) .OfType()) { c.ReportIssue(Rule, methodDeclaration.Identifier, MakeThisMethodPrivateMessage); } } } private static bool IsExternMethod(IMethodSymbol methodSymbol) => methodSymbol.IsExtern || methodSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_LibraryImportAttribute); private static void ReportTrivialWrappers(SonarSyntaxNodeReportingContext c) { var methodDeclaration = (MethodDeclarationSyntax)c.Node; if (methodDeclaration.ParameterList.Parameters.Count == 0) { return; } var descendants = GetBodyDescendants(methodDeclaration); if (HasAtLeastTwo(descendants.OfType()) || HasAtLeastTwo(descendants.OfType())) { return; } var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol == null || (methodSymbol.IsExtern && methodDeclaration.ParameterList == null)) { return; } var externalMethodSymbols = GetExternalMethods(methodSymbol); descendants.OfType() .Where(ParametersMatchContainingMethodDeclaration) .Select(i => c.Model.GetSymbolInfo(i).Symbol) .OfType() .Where(externalMethodSymbols.Contains) .ToList() .ForEach(Report); void Report(IMethodSymbol externMethod) => c.ReportIssue(Rule, methodDeclaration.Identifier, string.Format(MakeThisWrapperLessTrivialMessage, externMethod.Name)); bool ParametersMatchContainingMethodDeclaration(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.All(IsDeclaredParameterOrLiteral); bool IsDeclaredParameterOrLiteral(ArgumentSyntax a) => a.Expression is LiteralExpressionSyntax || (a.Expression is IdentifierNameSyntax i && methodDeclaration.ParameterList.Parameters.Any(p => p.Identifier.Text == i.Identifier.Text)); } private static ISet GetExternalMethods(IMethodSymbol methodSymbol) => methodSymbol.ContainingType.GetMembers() .OfType() .Where(IsExternMethod) .ToHashSet(); private static IEnumerable GetBodyDescendants(MethodDeclarationSyntax methodDeclaration) => methodDeclaration.Body?.DescendantNodes() ?? methodDeclaration.ExpressionBody?.DescendantNodes() ?? Enumerable.Empty(); private static bool HasAtLeastTwo(IEnumerable collection) => collection.Take(2).Count() == 2; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NestedCodeBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NestedCodeBlock : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1199"; private const string MessageFormat = "Extract this nested code block into a separate method."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var block = (BlockSyntax)c.Node; if (block.Parent?.Kind() is SyntaxKind.Block or SyntaxKind.GlobalStatement) { c.ReportIssue(Rule, block.OpenBraceToken); } }, SyntaxKind.Block); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NoExceptionsInFinally.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NoExceptionsInFinally : NoExceptionsInFinallyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => new ThrowInFinallyWalker(c, Rule).SafeVisit(((FinallyClauseSyntax)c.Node).Block), SyntaxKind.FinallyClause); private sealed class ThrowInFinallyWalker : SafeCSharpSyntaxWalker { private readonly SonarSyntaxNodeReportingContext context; private readonly DiagnosticDescriptor rule; public ThrowInFinallyWalker(SonarSyntaxNodeReportingContext context, DiagnosticDescriptor rule) { this.context = context; this.rule = rule; } public override void VisitThrowStatement(ThrowStatementSyntax node) => context.ReportIssue(rule, node); public override void VisitFinallyClause(FinallyClauseSyntax node) { // Do not call base to force the walker to stop. Another walker will take care of this finally clause. } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NonAsyncTaskShouldNotReturnNull.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonAsyncTaskShouldNotReturnNull : NonAsyncTaskShouldNotReturnNullBase { private const string MessageFormat = "Do not return null from this method, instead return 'Task.FromResult(null)', " + "'Task.CompletedTask' or 'Task.Delay(0)'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ISet TrackedNullLiteralLocations = new HashSet { SyntaxKind.ArrowExpressionClause, SyntaxKind.ReturnStatement }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var nullLiteral = (LiteralExpressionSyntax)c.Node; if (!nullLiteral.GetFirstNonParenthesizedParent().IsAnyKind(TrackedNullLiteralLocations)) { return; } var enclosingMember = GetEnclosingMember(nullLiteral); if (enclosingMember != null && !enclosingMember.IsKind(SyntaxKind.VariableDeclaration) && IsInvalidEnclosingSymbolContext(enclosingMember, c.Model)) { c.ReportIssue(rule, nullLiteral); } }, SyntaxKind.NullLiteralExpression); } private static SyntaxNode GetEnclosingMember(LiteralExpressionSyntax literal) { foreach (var ancestor in literal.Ancestors()) { switch (ancestor.Kind()) { case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.VariableDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKindEx.LocalFunctionStatement: return ancestor; } } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NonDerivedPrivateClassesShouldBeSealed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonDerivedPrivateClassesShouldBeSealed : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3260"; private const string MessageFormat = "{0} {1} which are not derived in the current {2} should be marked as 'sealed'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); private static readonly ImmutableHashSet KindsToBeDescended = ImmutableHashSet.Create( SyntaxKind.CompilationUnit, SyntaxKind.NamespaceDeclaration, SyntaxKindEx.FileScopedNamespaceDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static readonly ImmutableHashSet PossiblyVirtualKinds = ImmutableHashSet.Create( SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration, SyntaxKind.EventDeclaration, SyntaxKind.IndexerDeclaration); protected override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction(c => { var declarations = c.Tree.GetRoot() .DescendantNodes(x => x.IsAnyKind(KindsToBeDescended)) .Where(x => x.Kind() is SyntaxKind.ClassDeclaration or SyntaxKindEx.RecordDeclaration) .Select(x => (TypeDeclarationSyntax)x); var model = new Lazy(() => c.Compilation.GetSemanticModel(c.Tree)); var symbols = new Lazy>(() => declarations.Select(x => model.Value.GetDeclaredSymbol(x)).ToList()); foreach (var declaration in declarations) { if (!IsSealed(declaration) && !HasVirtualMembers(declaration) && !IsPossiblyDerived(declaration, model, symbols, out var modifier, out var inheritanceScope)) { var type = declaration.IsKind(SyntaxKind.ClassDeclaration) ? "classes" : "record classes"; c.ReportIssue(Rule, declaration.Identifier, modifier, type, inheritanceScope); } } }); private static bool HasVirtualMembers(TypeDeclarationSyntax typeDeclaration) => typeDeclaration.Members .Where(member => member.IsAnyKind(PossiblyVirtualKinds)) .Any(member => member.Modifiers.Any(SyntaxKind.VirtualKeyword)); private static bool IsSealed(TypeDeclarationSyntax typeDeclaration) => typeDeclaration.Modifiers.Any(SyntaxKind.SealedKeyword) || typeDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword) || typeDeclaration.Modifiers.Any(SyntaxKind.AbstractKeyword); private static bool IsPossiblyDerived( TypeDeclarationSyntax declaration, Lazy model, Lazy> otherSymbols, out string modifierDescription, out string scopeDescription) { if (declaration.Modifiers.Any(SyntaxKind.PrivateKeyword)) { modifierDescription = "Private"; scopeDescription = "assembly"; var symbol = model.Value.GetDeclaredSymbol(declaration); return symbol.ContainingType.GetAllNamedTypes().Any(other => !other.MetadataName.Equals(symbol.MetadataName) && other.DerivesFrom(symbol)); } if (declaration.Modifiers.Any(SyntaxKindEx.FileKeyword)) { modifierDescription = "File-scoped"; scopeDescription = "file"; var symbol = model.Value.GetDeclaredSymbol(declaration); return otherSymbols.Value.Exists(other => !other.MetadataName.Equals(symbol.MetadataName) && other.DerivesFrom(symbol)); } modifierDescription = scopeDescription = string.Empty; return true; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NonFlagsEnumInBitwiseOperation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonFlagsEnumInBitwiseOperation : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3265"; private const string MessageFormat = "{0}"; private const string MessageRemove = "Remove this bitwise operation; the enum '{0}' is not marked with 'Flags' attribute."; private const string MessageChangeOrRemove = "Mark enum '{0}' with 'Flags' attribute or remove this bitwise operation."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckExpressionWithOperator(c, b => b.OperatorToken), SyntaxKind.BitwiseOrExpression, SyntaxKind.BitwiseAndExpression, SyntaxKind.ExclusiveOrExpression); context.RegisterNodeAction( c => CheckExpressionWithOperator(c, a => a.OperatorToken), SyntaxKind.AndAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression); } private static void CheckExpressionWithOperator(SonarSyntaxNodeReportingContext context, Func operatorSelector) where T : SyntaxNode { if (context.Model.GetSymbolInfo(context.Node).Symbol is not IMethodSymbol { MethodKind: MethodKind.BuiltinOperator, ReturnType.TypeKind: TypeKind.Enum } operation || operation.ReturnType.HasAttribute(KnownType.System_FlagsAttribute) || IsIgnored(operation.ReturnType)) { return; } var friendlyTypeName = operation.ReturnType.ToMinimalDisplayString(context.Model, context.Node.SpanStart); var messageFormat = operation.ReturnType.DeclaringSyntaxReferences.Any() ? MessageChangeOrRemove : MessageRemove; var message = string.Format(messageFormat, friendlyTypeName); var op = operatorSelector((T)context.Node); context.ReportIssue(Rule, op, message); } private static bool IsIgnored(ITypeSymbol enumType) => // https://stackoverflow.com/questions/38689649/why-is-methodimplattributes-not-marked-with-flagsattribute#comment64809864_38689649 enumType.Is(KnownType.System_Reflection_MethodImplAttributes); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NonFlagsEnumInBitwiseOperationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class NonFlagsEnumInBitwiseOperationCodeFix : SonarCodeFix { private const string Title = "Add [Flags] to enum declaration"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(NonFlagsEnumInBitwiseOperation.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var operation = semanticModel.GetSymbolInfo(node).Symbol as IMethodSymbol; if (!(operation?.ReturnType?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(context.Cancel) is EnumDeclarationSyntax enumDeclaration)) { return; } if (enumDeclaration.AttributeLists.GetAttributes(KnownType.System_FlagsAttribute, semanticModel).Any()) // FixAllProvider already added it from another issue { return; } var flagsAttributeType = semanticModel.Compilation.GetTypeByMetadataName(KnownType.System_FlagsAttribute); if (flagsAttributeType == null) { return; } var currentSolution = context.Document.Project.Solution; var documentId = currentSolution.GetDocumentId(enumDeclaration.SyntaxTree); if (documentId == null) { return; } context.RegisterCodeFix( Title, async c => { var enumDeclarationRoot = await currentSolution.GetDocument(documentId).GetSyntaxRootAsync(c).ConfigureAwait(false); var flagsAttributeName = flagsAttributeType.ToMinimalDisplayString(semanticModel, enumDeclaration.SpanStart); flagsAttributeName = flagsAttributeName.Remove(flagsAttributeName.IndexOf("Attribute", System.StringComparison.Ordinal)); var attributes = enumDeclaration.AttributeLists.Add( SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Attribute(SyntaxFactory.ParseName(flagsAttributeName)) }))); var newDeclaration = enumDeclaration.WithAttributeLists(attributes); var newRoot = enumDeclarationRoot.ReplaceNode( enumDeclaration, newDeclaration); return currentSolution.WithDocumentSyntaxRoot(documentId, newRoot); }, context.Diagnostics); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NormalizeStringsToUppercase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NormalizeStringsToUppercase : DoNotCallMethodsCSharpBase { private const string DiagnosticId = "S4040"; protected override string MessageFormat => "Change this normalization to 'ToUpperInvariant()'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Char, "ToLower"), new(KnownType.System_String, "ToLower"), new(KnownType.System_Char, "ToLowerInvariant"), new(KnownType.System_String, "ToLowerInvariant"), }; public NormalizeStringsToUppercase() : base(DiagnosticId) { } protected override bool ShouldReportOnMethodCall(InvocationExpressionSyntax invocation, SemanticModel semanticModel, MemberDescriptor memberDescriptor) { var identifier = invocation.GetMethodCallIdentifier().Value.ValueText; // never null when we get here if (identifier == "ToLowerInvariant") { return true; } // ToLower and ToLowerInvariant are extension methods for string but not for char var isExtensionMethod = memberDescriptor.ContainingType == KnownType.System_String; return invocation.ArgumentList != null && invocation.ArgumentList.Arguments.Count == (isExtensionMethod ? 1 : 2) && invocation.ArgumentList.Arguments[isExtensionMethod ? 0 : 1].Expression.ToString() == "CultureInfo.InvariantCulture"; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NotAssignedPrivateMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using MemberUsage = SonarAnalyzer.Core.Common.NodeSymbolAndModel; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NotAssignedPrivateMember : SonarDiagnosticAnalyzer { /* CS0649 reports the same on internal fields. So that's wider in scope, but that's not a live Roslyn analyzer, the issue only shows up at build time and not during editing. */ private const string DiagnosticId = "S3459"; private const string MessageFormat = "Remove unassigned {0} '{1}', or set its value."; private const Accessibility MaxAccessibility = Accessibility.Private; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet PreOrPostfixOpSyntaxKinds = [ SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression, SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression, ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var namedType = (INamedTypeSymbol)c.Symbol; if (TypeDefinitionShouldBeAnalyzed(namedType)) { var removableDeclarationCollector = new CSharpRemovableDeclarationCollector(namedType, c.Compilation); var allCandidateMembers = CandidateDeclarations(removableDeclarationCollector); if (allCandidateMembers.Any()) { var usedMembers = MemberUsages(removableDeclarationCollector, allCandidateMembers.Select(t => t.Symbol).ToHashSet()); var usedMemberSymbols = usedMembers.Select(x => x.Symbol).ToHashSet(); var unassignedUsedMemberSymbols = allCandidateMembers.Where(x => usedMemberSymbols.Contains(x.Symbol) && !AssignedMemberSymbols(usedMembers).Contains(x.Symbol)); foreach (var candidateMember in unassignedUsedMemberSymbols) { c.ReportIssue( Rule, candidateMember.Node.GetIdentifier().Value.GetLocation(), candidateMember.Node is VariableDeclaratorSyntax ? "field" : "auto-property", candidateMember.Symbol.Name); } } } }, SymbolKind.NamedType); private static List> CandidateDeclarations(CSharpRemovableDeclarationCollector removableDeclarationCollector) { var candidateFields = removableDeclarationCollector.RemovableFieldLikeDeclarations(new HashSet { SyntaxKind.FieldDeclaration }, MaxAccessibility) .Where(x => !IsInitializedOrFixed((VariableDeclaratorSyntax)x.Node) && !HasStructLayoutAttribute(x.Symbol.ContainingType)); var candidateProperties = removableDeclarationCollector.RemovableDeclarations(new HashSet { SyntaxKind.PropertyDeclaration }, MaxAccessibility) .Where(x => IsAutoPropertyWithNoInitializer((PropertyDeclarationSyntax)x.Node) && !HasStructLayoutAttribute(x.Symbol.ContainingType)); return candidateFields.Concat(candidateProperties).ToList(); } private static bool TypeDefinitionShouldBeAnalyzed(ITypeSymbol namedType) => namedType.IsClassOrStruct() && !HasStructLayoutAttribute(namedType) && namedType.ContainingType is null && !namedType.HasAttribute(KnownType.System_SerializableAttribute); private static bool HasStructLayoutAttribute(ISymbol namedTypeSymbol) => namedTypeSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_StructLayoutAttribute); private static bool IsInitializedOrFixed(VariableDeclaratorSyntax declarator) => declarator.Initializer is not null || (declarator.Parent.Parent is BaseFieldDeclarationSyntax fieldDeclaration && fieldDeclaration.Modifiers.Any(SyntaxKind.FixedKeyword)); private static bool IsAutoPropertyWithNoInitializer(PropertyDeclarationSyntax declaration) => declaration.Initializer is null && declaration.AccessorList is not null && declaration.AccessorList.Accessors.All(x => x.Body is null && x.ExpressionBody is null); private static IList MemberUsages(CSharpRemovableDeclarationCollector removableDeclarationCollector, HashSet declaredPrivateSymbols) { var symbolNames = declaredPrivateSymbols.Select(x => x.Name).ToHashSet(); var usages = removableDeclarationCollector.TypeDeclarations .SelectMany(x => x.Node.DescendantNodes().Select(SimpleName).WhereNotNull() .Where(x => symbolNames.Contains(x.Identifier.ValueText)) .Select(node => new MemberUsage(node, x.Model.GetSymbolInfo(node).Symbol, x.Model))); return usages.Where(x => x.Symbol is IFieldSymbol or IPropertySymbol).ToList(); static SimpleNameSyntax SimpleName(SyntaxNode node) => node switch { IdentifierNameSyntax identifierName => identifierName, GenericNameSyntax genericName => genericName, _ => null }; } private static ISet AssignedMemberSymbols(IList memberUsages) { var assignedMembers = new HashSet(); foreach (var memberUsage in memberUsages) { var memberSymbol = memberUsage.Symbol; var node = RelevantNode(memberUsage.Node, memberSymbol); var parentNode = node.Parent; if (PreOrPostfixOpSyntaxKinds.Contains(parentNode.Kind()) || (parentNode is AssignmentExpressionSyntax assignment && assignment.Left == node) || (parentNode is ArgumentSyntax argument && (!argument.RefOrOutKeyword.IsKind(SyntaxKind.None) || TupleExpressionSyntaxWrapper.IsInstance(argument.Parent))) || RefExpressionSyntaxWrapper.IsInstance(parentNode)) { assignedMembers.Add(memberSymbol); assignedMembers.Add(memberSymbol.OriginalDefinition); } } return assignedMembers; } private static SyntaxNode RelevantNode(ExpressionSyntax node, ISymbol memberSymbol) { // Handle "expr.FieldName" if (node.Parent is MemberAccessExpressionSyntax simpleMemberAccess && simpleMemberAccess.Name == node) { node = simpleMemberAccess; } // Handle "expr?.FieldName" else if (node.Parent is MemberBindingExpressionSyntax memberBinding && memberBinding.Name == node) { node = memberBinding; } // Handle "((expr.FieldName))" node = node.GetSelfOrTopParenthesizedExpression(); if (IsValueType(memberSymbol)) { // Handle (((exp.FieldName)).Member1).Member2 var parentMemberAccess = node.Parent as MemberAccessExpressionSyntax; while (IsParentMemberAccess(parentMemberAccess, node)) { node = parentMemberAccess.GetSelfOrTopParenthesizedExpression(); parentMemberAccess = node.Parent as MemberAccessExpressionSyntax; } node = node.GetSelfOrTopParenthesizedExpression(); } return node; } private static bool IsParentMemberAccess(MemberAccessExpressionSyntax parent, ExpressionSyntax node) => parent?.Expression == node; private static bool IsValueType(ISymbol symbol) => symbol switch { IFieldSymbol field => field.Type.IsValueType, IPropertySymbol property => property.Type.IsValueType, _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/NumberPatternShouldBeRegular.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NumberPatternShouldBeRegular : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3937"; private const string MessageFormat = "Review this number; its irregular pattern indicates an error."; private const char Underscore = '_'; private const char Dot = '.'; private const int NotFound = -1; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { if (!c.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp7)) { return; } var literal = (LiteralExpressionSyntax)c.Node; if (HasIrregularPattern(literal.Token.Text)) { c.ReportIssue(rule, literal); } }, SyntaxKind.NumericLiteralExpression); } /// internal for test purposes. internal static bool HasIrregularPattern(string numericToken) { var split = StripNumericPreAndSuffix(numericToken).Split(Dot); // ignore multiple dots. if (split.Length > 2) { return false; } var groupLengthsLeftFromDot = split[0].Split(Underscore).Select(g => g.Length).ToArray(); if (HasIrregularGroupLengths(groupLengthsLeftFromDot)) { return true; } // no dot, so done. if (split.Length == 1) { return false; } // reverse, as for right from the dot, the last (instead of the first) // group length is allowed to be shorter than the group length. var groupLengthsRightFromDot = split[1].Split(Underscore).Select(g => g.Length).Reverse().ToArray(); return HasIrregularGroupLengths(groupLengthsRightFromDot); } private static string StripNumericPreAndSuffix(string numericToken) { var length = numericToken.Length; // hexadecimal and binary prefixes (0xFFFF_23_AB, 0b1110_1101) if (numericToken.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase) || numericToken.StartsWith("0b", StringComparison.InvariantCultureIgnoreCase)) { return numericToken.Substring(2, length - 2); } // Scientific notation (1.23E8) var exponentMarker = numericToken.IndexOf("E", StringComparison.InvariantCultureIgnoreCase); if (exponentMarker != NotFound) { return numericToken.Substring(0, exponentMarker); } // UL and LU suffix. if (numericToken.EndsWith("UL", StringComparison.OrdinalIgnoreCase) || numericToken.EndsWith("LU", StringComparison.OrdinalIgnoreCase)) { return numericToken.Substring(0, length - 2); } // single suffixes if ("LDFUMldfum".IndexOf(numericToken[numericToken.Length - 1]) != NotFound) { return numericToken.Substring(0, length - 1); } return numericToken; } private static bool HasIrregularGroupLengths(int[] groupLengths) { if (groupLengths.Length < 2) { return false; } // the first group is allowed to contain less digits that the other ones, // so take the expected length from the second group. var groupLength = groupLengths[1]; // we consider groups of 1 digit irregular. // first should not be bigger. if (groupLength < 2 || groupLengths[0] > groupLength) { return true; } return groupLengths.Skip(1).Any(l => l != groupLength); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ObjectCreatedDropped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ObjectCreatedDropped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1848"; private const string MessageFormat = "Either remove this useless object instantiation of class '{0}' or use it."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var creation = (ObjectCreationExpressionSyntax)c.Node; if (creation.Parent is ExpressionStatementSyntax) { c.ReportIssue(Rule, creation, creation.Type.ToString()); } }, SyntaxKind.ObjectCreationExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ObjectShouldBeInitializedCorrectlyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Xml.XPath; using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { public abstract class ObjectShouldBeInitializedCorrectlyBase : TrackerHotspotDiagnosticAnalyzer { protected abstract CSharpObjectInitializationTracker ObjectInitializationTracker { get; } protected override ILanguageFacade Language => CSharpFacade.Instance; protected ObjectShouldBeInitializedCorrectlyBase(IAnalyzerConfiguration configuration, string diagnosticId, string messageFormat) : base(configuration, diagnosticId, messageFormat) { } protected virtual bool IsDefaultConstructorSafe(SonarCompilationStartAnalysisContext context) => false; protected override void Initialize(TrackerInput input) { // This should be overriden by inheriting class that uses trackers } protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterCompilationStartAction( compilationStartContext => { if (!IsEnabled(compilationStartContext.Options)) { return; } var isDefaultConstructorSafe = IsDefaultConstructorSafe(compilationStartContext); compilationStartContext.RegisterNodeAction( c => { var objectCreation = ObjectCreationFactory.Create(c.Node); if (ObjectInitializationTracker.ShouldBeReported(objectCreation, c.Model, isDefaultConstructorSafe )) { c.ReportIssue(SupportedDiagnostics[0], objectCreation.Expression); } }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); compilationStartContext.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; if (ObjectInitializationTracker.ShouldBeReported(assignment, c.Model)) { c.ReportIssue(SupportedDiagnostics[0], assignment); } }, SyntaxKind.SimpleAssignmentExpression); }); } protected static bool IsWebConfigCookieSet(SonarCompilationStartAnalysisContext context, string attribute) { foreach (var fullPath in context.ProjectConfiguration().FilesToAnalyze.FindFiles("web.config")) { var webConfig = File.ReadAllText(fullPath); if (webConfig.Contains("") && webConfig.ParseXDocument() is { } doc && doc.XPathSelectElements("configuration/system.web/httpCookies").Any(x => x.GetAttributeIfBoolValueIs(attribute, true) != null)) { return true; } } return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ObsoleteAttributes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ObsoleteAttributes : ObsoleteAttributesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode GetExplanationExpression(SyntaxNode node) => node is AttributeSyntax { ArgumentList.Arguments: { Count: >= 1 } arguments } && arguments[0] is { Expression: { } expression } ? expression : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OperatorOverloadsShouldHaveNamedAlternatives.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OperatorOverloadsShouldHaveNamedAlternatives : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4069"; private const string MessageFormat = "Implement alternative method '{0}' for the operator '{1}'."; private static readonly Dictionary operatorAlternatives = new Dictionary { ["op_Addition"] = "Add", ["op_BitwiseAnd"] = "BitwiseAnd", ["op_BitwiseOr"] = "BitwiseOr", ["op_Division"] = "Divide", ["op_ExclusiveOr"] = "Xor", ["op_Equality"] = "Equals", ["op_Inequality"] = "Equals", ["op_GreaterThan"] = "Compare", ["op_LessThan"] = "Compare", ["op_GreaterThanOrEqual"] = "Compare", ["op_LessThanOrEqual"] = "Compare", ["op_Decrement"] = "Decrement", ["op_Increment"] = "Increment", ["op_LeftShift"] = "LeftShift", ["op_RightShift"] = "RightShift", ["op_LogicalNot"] = "LogicalNot", ["op_Modulus"] = "Mod", ["op_Multiply"] = "Multiply", ["op_OnesComplement"] = "OnesComplement", ["op_Subtraction"] = "Subtract", ["op_UnaryNegation"] = "Negate", ["op_UnaryPlus"] = "Plus" }; private static readonly Dictionary otherOperatorAlternatives = new Dictionary { ["op_GreaterThan"] = "CompareTo", ["op_LessThan"] = "CompareTo", ["op_GreaterThanOrEqual"] = "CompareTo", ["op_LessThanOrEqual"] = "CompareTo", }; private static readonly Dictionary operatorNames = new Dictionary { ["op_Addition"] = "+", ["op_BitwiseAnd"] = "&", ["op_BitwiseOr"] = "|", ["op_Division"] = "/", ["op_ExclusiveOr"] = "^", ["op_Equality"] = "==", ["op_Inequality"] = "!=", ["op_GreaterThan"] = ">", ["op_LessThan"] = "<", ["op_GreaterThanOrEqual"] = ">=", ["op_LessThanOrEqual"] = "<=", ["op_Decrement"] = "--", ["op_Increment"] = "++", ["op_LeftShift"] = "<<", ["op_RightShift"] = ">>", ["op_LogicalNot"] = "!", ["op_Modulus"] = "%", ["op_Multiply"] = "*", ["op_OnesComplement"] = "~", ["op_Subtraction"] = "-", ["op_UnaryNegation"] = "-", ["op_UnaryPlus"] = "+" }; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var operatorDeclaration = (OperatorDeclarationSyntax)c.Node; var operatorSymbol = c.Model.GetDeclaredSymbol(operatorDeclaration); if (operatorSymbol == null || operatorSymbol.MethodKind != MethodKind.UserDefinedOperator) { return; } var operatorName = operatorNames.GetValueOrDefault(operatorSymbol.Name); if (operatorName != null && !HasAlternativeMethod(operatorSymbol, out var operatorAlternativeMethodName)) { c.ReportIssue(rule, operatorDeclaration.OperatorToken, operatorAlternativeMethodName, operatorName); } }, SyntaxKind.OperatorDeclaration); } /// /// Checks if the class containing the given operator overload contains a method with an alternative name for /// the operator. Will return false if no methods with alternative name are present, or when the operator has /// no alternative names. /// /// /// True when the class contains at least one alternative method for the given operator, otherwise false. The /// returns the name of the method to be added as an alternative to /// the operator of it does not exist. /// private static bool HasAlternativeMethod(IMethodSymbol operatorSymbol, out string operatorAlternativeMethodName) { operatorAlternativeMethodName = operatorAlternatives.GetValueOrDefault(operatorSymbol.Name); if (operatorAlternativeMethodName == null || HasMethodWithName(operatorAlternativeMethodName)) { return true; } // Suggest only the "main" alternatives, the "other" alternatives are to loosen the rule var otherOperatorAlternativeMethodName = otherOperatorAlternatives.GetValueOrDefault(operatorSymbol.Name); return otherOperatorAlternativeMethodName != null && HasMethodWithName(otherOperatorAlternativeMethodName); bool HasMethodWithName(string name) => operatorSymbol.ContainingType .GetMembers(name) .OfType() .Any(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OperatorsShouldBeOverloadedConsistently.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OperatorsShouldBeOverloadedConsistently : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4050"; private const string MessageFormat = "Provide an implementation for: {0}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; var classSymbol = (INamedTypeSymbol)c.ContainingSymbol; if (classDeclaration.Identifier.IsMissing || !classSymbol.IsPubliclyAccessible()) { return; } var missingMethods = FindMissingMethods(classSymbol).ToList(); if (missingMethods.Count > 0) { c.ReportIssue(Rule, classDeclaration.Identifier, missingMethods.ToSentence(quoteWords: true)); } }, // This rule is not applicable for records, as for records it is not possible to override the == operator. SyntaxKind.ClassDeclaration); private static IEnumerable FindMissingMethods(INamedTypeSymbol classSymbol) { var implementedMethods = GetImplementedMethods(classSymbol).ToHashSet(); var requiredMethods = new HashSet(); if (implementedMethods.Contains(MethodName.OperatorPlus) || implementedMethods.Contains(MethodName.OperatorMinus) || implementedMethods.Contains(MethodName.OperatorMultiply) || implementedMethods.Contains(MethodName.OperatorDivide) || implementedMethods.Contains(MethodName.OperatorRemainder)) { requiredMethods.Add(MethodName.OperatorEquals); requiredMethods.Add(MethodName.OperatorNotEquals); requiredMethods.Add(MethodName.ObjectEquals); requiredMethods.Add(MethodName.ObjectGetHashCode); } if (implementedMethods.Contains(MethodName.OperatorEquals)) { requiredMethods.Add(MethodName.ObjectEquals); requiredMethods.Add(MethodName.ObjectGetHashCode); } if (implementedMethods.Contains(MethodName.OperatorNotEquals)) { requiredMethods.Add(MethodName.ObjectEquals); requiredMethods.Add(MethodName.ObjectGetHashCode); } return requiredMethods.Except(implementedMethods); } private static IEnumerable GetImplementedMethods(INamedTypeSymbol classSymbol) { foreach (var member in classSymbol.GetMembers().OfType().Where(x => !x.IsConstructor())) { if (ImplementedOperator(member) is { } name) { yield return name; } else if (KnownMethods.IsObjectEquals(member)) { yield return MethodName.ObjectEquals; } else if (KnownMethods.IsObjectGetHashCode(member)) { yield return MethodName.ObjectGetHashCode; } } } private static string ImplementedOperator(IMethodSymbol member) => member switch { { MethodKind: not MethodKind.UserDefinedOperator } => null, _ when KnownMethods.IsOperatorBinaryPlus(member) => MethodName.OperatorPlus, _ when KnownMethods.IsOperatorBinaryMinus(member) => MethodName.OperatorMinus, _ when KnownMethods.IsOperatorBinaryMultiply(member) => MethodName.OperatorMultiply, _ when KnownMethods.IsOperatorBinaryDivide(member) => MethodName.OperatorDivide, _ when KnownMethods.IsOperatorBinaryModulus(member) => MethodName.OperatorRemainder, _ when KnownMethods.IsOperatorEquals(member) => MethodName.OperatorEquals, _ when KnownMethods.IsOperatorNotEquals(member) => MethodName.OperatorNotEquals, _ => null }; private static class MethodName { public const string OperatorPlus = "operator+"; public const string OperatorMinus = "operator-"; public const string OperatorMultiply = "operator*"; public const string OperatorDivide = "operator/"; public const string OperatorRemainder = "operator%"; public const string OperatorEquals = "operator=="; public const string OperatorNotEquals = "operator!="; public const string ObjectEquals = "Object.Equals"; public const string ObjectGetHashCode = "Object.GetHashCode"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OptionalParameter : OptionalParameterBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray kindsOfInterest = ImmutableArray.Create(SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration); public override ImmutableArray SyntaxKindsOfInterest => kindsOfInterest; protected override IEnumerable GetParameters(BaseMethodDeclarationSyntax method) => method.ParameterList?.Parameters ?? Enumerable.Empty(); protected override bool IsOptional(ParameterSyntax parameter) => parameter.Default != null && parameter.Default.Value != null; protected override Location GetReportLocation(ParameterSyntax parameter) => parameter.Default.GetLocation(); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalParameterNotPassedToBaseCall.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OptionalParameterNotPassedToBaseCall : OptionalParameterNotPassedToBaseCallBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override DiagnosticDescriptor Rule => rule; protected override int GetArgumentCount(InvocationExpressionSyntax invocation) => invocation.ArgumentList == null ? 0 : invocation.ArgumentList.Arguments.Count; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (!invocation.IsOnBase()) { return; } ReportOptionalParameterNotPassedToBase(c, invocation); }, SyntaxKind.InvocationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalParameterWithDefaultValue.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OptionalParameterWithDefaultValue : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3451"; private const string MessageFormat = "Use '[DefaultParameterValue]' instead."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var parameter = (ParameterSyntax)c.Node; if (!parameter.AttributeLists.Any()) { return; } var attributes = AttributeSyntaxSymbolMapping.GetAttributesForParameter(parameter, c.Model).ToList(); var hasNoOptional = attributes.All(attr => !attr.Symbol.IsInType(KnownType.System_Runtime_InteropServices_OptionalAttribute)); if (hasNoOptional) { return; } var hasDefaultParameterValue = attributes.Any(attr => attr.Symbol.IsInType(KnownType.System_Runtime_InteropServices_DefaultParameterValueAttribute)); if (hasDefaultParameterValue) { return; } var defaultValueAttribute = attributes.FirstOrDefault(a => a.Symbol.IsInType(KnownType.System_ComponentModel_DefaultValueAttribute)); if (defaultValueAttribute != null) { c.ReportIssue(Rule, defaultValueAttribute.Node); } }, SyntaxKind.Parameter); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalParameterWithDefaultValueCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class OptionalParameterWithDefaultValueCodeFix : SonarCodeFix { private const string Title = "Change to '[DefaultParameterValue]'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(OptionalParameterWithDefaultValue.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var attribute = root.FindNode(diagnosticSpan) as AttributeSyntax; if (attribute?.ArgumentList == null || attribute.ArgumentList.Arguments.Count != 1) { return; } var semanticModel = await context.Document.GetSemanticModelAsync().ConfigureAwait(false); var defaultParameterValueAttributeType = semanticModel?.Compilation.GetTypeByMetadataName(KnownType.System_Runtime_InteropServices_DefaultParameterValueAttribute); if (defaultParameterValueAttributeType == null) { return; } context.RegisterCodeFix( Title, _ => { var attributeName = defaultParameterValueAttributeType.ToMinimalDisplayString(semanticModel, attribute.SpanStart); attributeName = attributeName.Remove(attributeName.IndexOf("Attribute", System.StringComparison.Ordinal)); var newAttribute = attribute.WithName(SyntaxFactory.ParseName(attributeName)); var newRoot = root.ReplaceNode(attribute, newAttribute); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalRefOutParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OptionalRefOutParameter : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3447"; private const string MessageFormat = "Remove the 'Optional' attribute, it cannot be used with '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var parameter = (ParameterSyntax)c.Node; if (!parameter.AttributeLists.Any() || !parameter.Modifiers.Any(m => m.IsKind(SyntaxKind.RefKeyword) || m.IsKind(SyntaxKind.OutKeyword))) { return; } var optionalAttribute = AttributeSyntaxSymbolMapping.GetAttributesForParameter(parameter, c.Model) .FirstOrDefault(a => a.Symbol.IsInType(KnownType.System_Runtime_InteropServices_OptionalAttribute)); if (optionalAttribute != null) { var refKind = parameter.Modifiers.Any(SyntaxKind.OutKeyword) ? "out" : "ref"; c.ReportIssue(Rule, optionalAttribute.Node, refKind); } }, SyntaxKind.Parameter); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OptionalRefOutParameterCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class OptionalRefOutParameterCodeFix : SonarCodeFix { internal const string Title = "Remove 'Optional' attribute"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(OptionalRefOutParameter.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var nodeToRemove = root.FindNode(diagnosticSpan); if (nodeToRemove.Parent is AttributeListSyntax attributeList && attributeList.Attributes.Count == 1) { nodeToRemove = attributeList; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(nodeToRemove, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OrderByRepeated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OrderByRepeated : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3169"; private const string MessageFormat = "Use 'ThenBy' instead."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var outerInvocation = (InvocationExpressionSyntax)c.Node; if (outerInvocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is InvocationExpressionSyntax innerInvocation && IsMethodOrderByExtension(outerInvocation, c.Model) && IsOrderByOrThenBy(innerInvocation, c.Model)) { c.ReportIssue(rule, memberAccess.Name); } static bool IsOrderByOrThenBy(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => IsMethodOrderByExtension(invocation, semanticModel) || IsMethodThenByExtension(invocation, semanticModel); }, SyntaxKind.InvocationExpression); } private static bool IsMethodOrderByExtension(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.Expression.ToStringContains("OrderBy") && semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.Name == "OrderBy" && methodSymbol.MethodKind == MethodKind.ReducedExtension && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); private static bool IsMethodThenByExtension(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.Expression.ToStringContains("ThenBy") && semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.Name == "ThenBy" && methodSymbol.MethodKind == MethodKind.ReducedExtension && MethodIsOnIOrderedEnumerable(methodSymbol); private static bool MethodIsOnIOrderedEnumerable(IMethodSymbol methodSymbol) => methodSymbol.ReceiverType is INamedTypeSymbol receiverType && receiverType.ConstructedFrom.ContainingNamespace.ToString() == "System.Linq" && receiverType.ConstructedFrom.MetadataName == "IOrderedEnumerable`1"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OrderByRepeatedCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class OrderByRepeatedCodeFix : SonarCodeFix { internal const string Title = "Change 'OrderBy' to 'ThenBy'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(OrderByRepeated.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); context.RegisterCodeFix( Title, c => ChangeToThenByAsync(context.Document, syntaxNode, c), context.Diagnostics); return Task.CompletedTask; } private static async Task ChangeToThenByAsync(Document document, SyntaxNode syntaxNode, CancellationToken cancel) { var root = await document.GetSyntaxRootAsync(cancel).ConfigureAwait(false); var newRoot = root.ReplaceNode(syntaxNode, SyntaxFactory.IdentifierName("ThenBy")); return document.WithSyntaxRoot(newRoot); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/OverrideGetHashCodeOnOverridingEquals.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class OverrideGetHashCodeOnOverridingEquals : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1206"; private const string MessageFormat = "This {0} overrides '{1}' and should therefore also override '{2}'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var declaration = (TypeDeclarationSyntax)c.Node; var typeSymbol = c.Model.GetDeclaredSymbol(declaration); if (typeSymbol == null) { return; } var overridenMethods = typeSymbol.GetMembers() .OfType() .Where(method => method.IsObjectEquals() || method.IsObjectGetHashCode()) .Select(method => method.Name) .ToList(); if (overridenMethods.Count == 0 || overridenMethods.Count == 2) { return; } c.ReportIssue(rule, declaration.Identifier, declaration.Keyword.ValueText, overridenMethods[0], GetMissingMethodName(overridenMethods[0])); }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration); } private static string GetMissingMethodName(string overridenMethodName) { return overridenMethodName == nameof(object.Equals) ? nameof(object.GetHashCode) : nameof(object.Equals); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PInvokesShouldNotBeVisible.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PInvokesShouldNotBeVisible : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4214"; private const string MessageFormat = "Make this 'P/Invoke' method private or internal."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol != null && methodSymbol.IsExtern && methodSymbol.IsStatic && methodSymbol.IsPubliclyAccessible() && methodSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_DllImportAttribute)) { c.ReportIssue(rule, methodDeclaration.Identifier); } }, SyntaxKind.MethodDeclaration); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterAssignedTo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterAssignedTo : ParameterAssignedToBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsAssignmentToCatchVariable(ISymbol symbol, SyntaxNode node) => symbol is ILocalSymbol localSymbol && localSymbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).Any(x => x.Parent is CatchClauseSyntax catchClause && catchClause.Declaration == x); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterNameMatchesOriginal.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterNameMatchesOriginal : ParameterNameMatchesOriginalBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = new[] { SyntaxKind.MethodDeclaration }; protected override IEnumerable ParameterIdentifiers(MethodDeclarationSyntax method) => method.ParameterList.Parameters.Select(x => x.Identifier); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterNamesShouldNotDuplicateMethodNames.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterNamesShouldNotDuplicateMethodNames : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3872"; private const string MessageFormat = "Rename the parameter '{0}' so that it does not duplicate the method name."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var method = (MethodDeclarationSyntax)c.Node; CheckMethodParameters(c, method.Identifier, method.ParameterList); }, SyntaxKind.MethodDeclaration); context.RegisterNodeAction(c => { var localFunction = (LocalFunctionStatementSyntaxWrapper)c.Node; CheckMethodParameters(c, localFunction.Identifier, localFunction.ParameterList); }, SyntaxKindEx.LocalFunctionStatement); } private static void CheckMethodParameters(SonarSyntaxNodeReportingContext context, SyntaxToken identifier, ParameterListSyntax parameterList) { var methodName = identifier.ToString(); foreach (var parameter in parameterList.Parameters.Select(p => p.Identifier)) { var parameterName = parameter.ToString(); if (string.Equals(parameterName, methodName, StringComparison.OrdinalIgnoreCase)) { context.ReportIssue(rule, parameter, [identifier.ToSecondaryLocation()], parameterName); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterTypeShouldMatchRouteTypeConstraint.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterTypeShouldMatchRouteTypeConstraint : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6800"; private const string MessageFormat = "{0}"; private const string ImplicitStringPrimaryMessageFormat = "Parameter type '{0}' does not match route parameter implicit type constraint 'string' in route '{1}'."; private const string PrimaryMessageFormat = "Parameter type '{0}' does not match route parameter type constraint '{1}' in route '{2}'."; private const string MessageWithSecondaryLocationFormat = "Parameter type '{0}' does not match route parameter type constraint."; private const string SecondaryMessageFormat = "This route parameter has a '{0}' type constraint."; private const string ImplicitStringSecondaryMessage = "This route parameter has an implicit 'string' type constraint."; private const string ImplicitStringConstraint = "string"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); // https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing?view=aspnetcore-7.0#route-constraints private static readonly Dictionary ConstraintMapping = new(StringComparer.InvariantCultureIgnoreCase) { { "bool", new SupportedType(KnownType.System_Boolean) }, { "datetime", new SupportedType(KnownType.System_DateTime) }, { "decimal", new SupportedType(KnownType.System_Decimal) }, { "double", new SupportedType(KnownType.System_Double) }, { "float", new SupportedType(KnownType.System_Single) }, { "guid", new SupportedType(KnownType.System_Guid) }, { "int", new SupportedType(KnownType.System_Int32) }, { "long", new SupportedType(KnownType.System_Int64) }, // Implicit string constraint { ImplicitStringConstraint, new SupportedType(KnownType.System_String) } }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(cc => { // If we are not in a Blazor project, we don't need to go further. if (cc.Compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Components_RouteAttribute) is null) { return; } cc.RegisterSymbolAction(c => { foreach (var property in GetPropertyTypeMismatches((INamedTypeSymbol)c.Symbol, c.Compilation)) { c.ReportIssue(Rule, property.Type.GetLocation(), property.ToSecondaryLocations(), property.ToPrimaryMessage()); } }, SymbolKind.NamedType); }); private static IReadOnlyList GetPropertyTypeMismatches(INamedTypeSymbol classSymbol, Compilation compilation) { var routeParams = GetRouteParametersWithValidConstraint(classSymbol); if (routeParams.Count == 0) { return Array.Empty(); } return classSymbol .GetMembers() .Where(IsPropertyWithParameterAttributeInRoute) .SelectMany(property => routeParams[property.Name].Select(routeParam => new { RouteParam = routeParam, Property = property })) .Where(x => !IsTypeMatchRouteConstraint(x.Property.GetSymbolType(), x.RouteParam.Constraint, compilation)) .SelectMany(x => x.Property.DeclaringSyntaxReferences .Where(r => r.GetSyntax() is PropertyDeclarationSyntax) .Select(r => new PropertyTypeMismatch(((PropertyDeclarationSyntax)r.GetSyntax()).Type, x.RouteParam.Constraint, x.RouteParam.FromRoute, x.RouteParam.RouteParamLocation))) .ToList(); bool IsPropertyWithParameterAttributeInRoute(ISymbol member) => member.Kind is SymbolKind.Property && member.HasAttribute(KnownType.Microsoft_AspNetCore_Components_ParameterAttribute) && routeParams.ContainsKey(member.Name); } private static bool IsTypeMatchRouteConstraint(ITypeSymbol type, string routeConstraintType, Compilation compilation) { if (type.IsNullableValueType()) { type = ((INamedTypeSymbol)type).TypeArguments[0]; } return ConstraintMapping.ContainsKey(routeConstraintType) && ConstraintMapping[routeConstraintType].Matches(type, compilation); } private static Dictionary> GetRouteParametersWithValidConstraint(INamedTypeSymbol classDeclaration) { var routeParameters = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); var routeAttributeDataList = classDeclaration.GetAttributes() .Where(x => KnownType.Microsoft_AspNetCore_Components_RouteAttribute.Matches(x.AttributeClass)); foreach (var routeAttributeData in routeAttributeDataList) { var routeNode = routeAttributeData.ApplicationSyntaxReference.GetSyntax() as AttributeSyntax; if (!routeAttributeData.TryGetAttributeValue("template", out var route) || routeNode is null) { continue; } var routeParams = route .Split('/') .Where(segment => segment.StartsWith("{") && segment.EndsWith("}")) .Select(x => new { Param = x, Parts = x.TrimStart('{').TrimEnd('}', '?').Split(':') }) .Where(x => x.Parts.Length == 1 || (x.Parts.Length == 2 && ConstraintMapping.ContainsKey(x.Parts[1]))); foreach (var routeParam in routeParams) { routeParameters .GetOrAdd(routeParam.Parts[0], _ => new List(1)) .Add(new(routeParam.Parts.Length == 2 ? routeParam.Parts[1] : ImplicitStringConstraint, route, CalculateRouteParamLocation(routeNode.GetLocation(), routeNode, routeParam.Param))); } } return routeParameters; } private static Location CalculateRouteParamLocation(Location attributeLocation, AttributeSyntax routeNode, string routeParam) { if (GeneratedCodeRecognizer.IsRazorGeneratedFile(attributeLocation.SourceTree)) { return null; } return routeNode.ArgumentList.Arguments[0].Expression.RemoveParentheses() switch { var expression when expression.ToString() is var route && route.IndexOf(routeParam, StringComparison.InvariantCulture) is >= 0 and var index => CreateLocationFromRoute(expression.GetLocation(), index, routeParam.Length), var expression => expression.GetLocation() }; Location CreateLocationFromRoute(Location routeLocation, int index, int lenght) => Location.Create(routeLocation.SourceTree, new TextSpan(routeLocation.SourceSpan.Start + index, lenght)); } private sealed class PropertyTypeMismatch { public TypeSyntax Type { get; } public string ConstraintType { get; } public string Route { get; } public Location RouteParamLocation { get; } private bool IsImplicitStringConstraint => ConstraintType.Equals("string", StringComparison.OrdinalIgnoreCase); private bool CanReportSecondaryLocation => RouteParamLocation is not null; public PropertyTypeMismatch(TypeSyntax type, string constraintType, string route, Location routeParamLocation) { Type = type; ConstraintType = constraintType; Route = route; RouteParamLocation = routeParamLocation; } public string ToPrimaryMessage() { if (CanReportSecondaryLocation) { return string.Format(MessageWithSecondaryLocationFormat, GetTypeName(Type)); } else if (IsImplicitStringConstraint) { return string.Format(ImplicitStringPrimaryMessageFormat, GetTypeName(Type), Route); } else { return string.Format(PrimaryMessageFormat, GetTypeName(Type), ConstraintType.ToLower(), Route); } } private static string GetTypeName(TypeSyntax type) => type switch { ArrayTypeSyntax _ => type.ToString(), PointerTypeSyntax _ => type.ToString(), _ => type.GetName() }; public IEnumerable ToSecondaryLocations() => CanReportSecondaryLocation ? [new(RouteParamLocation, ToSecondaryMessage())] : []; private string ToSecondaryMessage() => IsImplicitStringConstraint ? ImplicitStringSecondaryMessage : string.Format(SecondaryMessageFormat, ConstraintType.ToLower()); } private readonly record struct RouteParameter(string Constraint, string FromRoute, Location RouteParamLocation); private readonly record struct SupportedType(KnownType ConstraintKnownType) { public bool Matches(ITypeSymbol propertyType, Compilation compilation) { if (propertyType.Is(ConstraintKnownType)) { return true; } var constraintTypeSymbol = compilation.GetTypeByMetadataName(ConstraintKnownType); var conversion = compilation.ClassifyConversion(constraintTypeSymbol, propertyType); return conversion.IsBoxing || conversion.IsEnumeration; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Walkers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterValidationInAsyncShouldBeWrapped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4457"; private const string MessageFormat = "Split this method into two, one handling parameters check and the other handling the asynchronous code."; private const string SecondaryLocationMessage = "This ArgumentException will be raised only after observing the task."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var method = (MethodDeclarationSyntax)c.Node; if (!method.Modifiers.Any(SyntaxKind.AsyncKeyword) || method.HasReturnTypeVoid() || (method.Identifier.ValueText == "Main" && c.Model.GetDeclaredSymbol(method).IsMainMethod())) { return; } var walker = new ParameterValidationInAsyncWalker(c.Model); walker.SafeVisit(method); if (walker.ArgumentExceptionLocations.Any()) { c.ReportIssue(Rule, method.Identifier, walker.ArgumentExceptionLocations); } }, SyntaxKind.MethodDeclaration); private sealed class ParameterValidationInAsyncWalker : ParameterValidationInMethodWalker { protected override string SecondaryMessage => SecondaryLocationMessage; public ParameterValidationInAsyncWalker(SemanticModel model) : base(model) { } public override void VisitAwaitExpression(AwaitExpressionSyntax node) => keepWalking = false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Walkers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParameterValidationInYieldShouldBeWrapped : SonarDiagnosticAnalyzer { 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); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var walker = new ParameterValidationInYieldWalker(c.Model); walker.SafeVisit(methodDeclaration); if (walker.HasYieldStatement && walker.ArgumentExceptionLocations.Any()) { c.ReportIssue(Rule, methodDeclaration.Identifier, walker.ArgumentExceptionLocations); } }, SyntaxKind.MethodDeclaration); private sealed class ParameterValidationInYieldWalker : ParameterValidationInMethodWalker { public bool HasYieldStatement { get; private set; } public ParameterValidationInYieldWalker(SemanticModel model) : base(model) { } public override void VisitYieldStatement(YieldStatementSyntax node) { HasYieldStatement = true; base.VisitYieldStatement(node); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ParametersCorrectOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ParametersCorrectOrder : ParametersCorrectOrderBase { protected override SyntaxKind[] InvocationKinds => new[] { SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression, SyntaxKind.ThisConstructorInitializer, SyntaxKind.BaseConstructorInitializer, SyntaxKindEx.PrimaryConstructorBaseType, SyntaxKindEx.ImplicitObjectCreationExpression, }; protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PartCreationPolicyShouldBeUsedWithExportAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PartCreationPolicyShouldBeUsedWithExportAttribute : PartCreationPolicyShouldBeUsedWithExportAttributeBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override TypeDeclarationSyntax GetTypeDeclaration(AttributeSyntax attribute) { var declaration = attribute.FirstAncestorOrSelf(); if (declaration is ClassDeclarationSyntax classDeclaration) { return classDeclaration; } else if (RecordDeclarationSyntaxWrapper.IsInstance(declaration)) { return (RecordDeclarationSyntaxWrapper)declaration; } else { return null; } } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(AnalyzeNode, SyntaxKind.Attribute); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PartialMethodNoImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PartialMethodNoImplementation : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3251"; private const string MessageFormat = "Supply an implementation for {0} partial method{1}."; private const string MessageAdditional = ", otherwise this call will be ignored"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckForCandidatePartialDeclaration, SyntaxKind.MethodDeclaration); context.RegisterNodeAction(CheckForCandidatePartialInvocation, SyntaxKind.InvocationExpression); } private static void CheckForCandidatePartialDeclaration(SonarSyntaxNodeReportingContext context) { var declaration = (MethodDeclarationSyntax)context.Node; var partialKeyword = declaration.Modifiers.FirstOrDefault(m => m.IsKind(SyntaxKind.PartialKeyword)); if (partialKeyword != default && !declaration.HasBodyOrExpressionBody() && !declaration.Modifiers.Any(HasAccessModifier) && context.Model.GetDeclaredSymbol(declaration) is { } methodSymbol && methodSymbol.PartialImplementationPart == null) { context.ReportIssue(Rule, partialKeyword, "this", string.Empty); } } private static void CheckForCandidatePartialInvocation(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (invocation.Parent is StatementSyntax statement && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.PartialImplementationPart == null && PartialMethodsWithoutAccessModifier(methodSymbol).Any()) { context.ReportIssue(Rule, statement, "the", MessageAdditional); } } // from the method symbol it's not possible to tell if it's a partial method or not. // https://github.com/dotnet/roslyn/issues/48 private static IEnumerable PartialMethodsWithoutAccessModifier(IMethodSymbol methodSymbol) => methodSymbol.DeclaringSyntaxReferences .Select(r => r.GetSyntax()) .OfType() .Where(method => method.Modifiers.Any(SyntaxKind.PartialKeyword) && !method.Modifiers.Any(HasAccessModifier) && method.Body == null && method.ExpressionBody == null); private static bool HasAccessModifier(SyntaxToken token) => token.IsAnyKind(SyntaxKind.PublicKeyword, SyntaxKind.InternalKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PasswordsShouldBeStoredCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PasswordsShouldBeStoredCorrectly : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S5344"; private const string MessageFormat = "{0}"; private const string UseMoreIterationsMessageFormat = "Use at least 100,000 iterations here."; private const int IterationCountThreshold = 100_000; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { AspNetCore(context); AspNetFramework(context); Rfc2898DeriveBytes(context); BouncyCastle(context); } private static void AspNetCore(SonarAnalysisContext context) { var propertyTracker = CSharpFacade.Instance.Tracker.PropertyAccess; Track( propertyTracker, context, UseMoreIterationsMessageFormat, propertyTracker.MatchSetter(), propertyTracker.MatchProperty(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Identity_PasswordHasherOptions, "IterationCount")), x => HasFewIterations(x, propertyTracker)); Track( propertyTracker, context, "Identity v2 uses only 1000 iterations. Consider changing to identity V3.", propertyTracker.MatchSetter(), propertyTracker.MatchProperty(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Identity_PasswordHasherOptions, "CompatibilityMode")), x => propertyTracker.AssignedValue(x) is 0); // PasswordHasherCompatibilityMode.IdentityV2 results to 0 var argumentTracker = CSharpFacade.Instance.Tracker.Argument; Track( argumentTracker, context, UseMoreIterationsMessageFormat, argumentTracker.MatchArgument(ArgumentDescriptor.MethodInvocation(KnownType.Microsoft_AspNetCore_Cryptography_KeyDerivation_KeyDerivation, "Pbkdf2", "iterationCount", 3)), x => ArgumentLessThan(x, IterationCountThreshold)); } private static void AspNetFramework(SonarAnalysisContext context) { var tracker = CSharpFacade.Instance.Tracker.ObjectCreation; Track( tracker, context, "PasswordHasher does not support state-of-the-art parameters. Use Rfc2898DeriveBytes instead.", tracker.WhenDerivesFrom(KnownType.Microsoft_AspNet_Identity_PasswordHasherOptions)); } private static void Rfc2898DeriveBytes(SonarAnalysisContext context) { // Raise when hashAlgorithm is present var argumentTracker = CSharpFacade.Instance.Tracker.Argument; // Exclude the constructors that have a hashAlgorithm parameter var constructorArgument = ArgumentDescriptor.ConstructorInvocation( ctor => ctor.ContainingType.Is(KnownType.System_Security_Cryptography_Rfc2898DeriveBytes) && ctor.Parameters.Any(x => x.Name == "hashAlgorithm"), (methodName, comparison) => string.Compare(methodName, "Rfc2898DeriveBytes", comparison) == 0, null, x => x.Name == "iterations", null, null); var invocationArgument = ArgumentDescriptor.MethodInvocation(KnownType.System_Security_Cryptography_Rfc2898DeriveBytes, "Pbkdf2", "iterations", x => x is 2 or 3); Track( argumentTracker, context, UseMoreIterationsMessageFormat, argumentTracker.Or( argumentTracker.MatchArgument(constructorArgument), argumentTracker.MatchArgument(invocationArgument)), x => ArgumentLessThan(x, IterationCountThreshold)); // Raise when hashAlgorithm is NOT present var objectCreationTracker = CSharpFacade.Instance.Tracker.ObjectCreation; Track( objectCreationTracker, context, "Use at least 100,000 iterations and a state-of-the-art digest algorithm here.", objectCreationTracker.MatchConstructor(KnownType.System_Security_Cryptography_Rfc2898DeriveBytes), x => x.InvokedConstructorSymbol.Value.Parameters.All(x => x.Name != "hashAlgorithm")); var propertyTracker = CSharpFacade.Instance.Tracker.PropertyAccess; Track( propertyTracker, context, UseMoreIterationsMessageFormat, propertyTracker.MatchSetter(), propertyTracker.MatchProperty(new MemberDescriptor(KnownType.System_Security_Cryptography_Rfc2898DeriveBytes, "IterationCount")), x => HasFewIterations(x, propertyTracker)); } private static void BouncyCastle(SonarAnalysisContext context) { var tracker = CSharpFacade.Instance.Tracker.Argument; Track( tracker, context, "Use a cost factor of at least 12 here.", tracker.Or( tracker.MatchArgument( ArgumentDescriptor.MethodInvocation(KnownType.Org_BouncyCastle_Crypto_Generators_OpenBsdBCrypt, "Generate", "cost", x => x is 2 or 3)), tracker.MatchArgument( ArgumentDescriptor.MethodInvocation(KnownType.Org_BouncyCastle_Crypto_Generators_BCrypt, "Generate", "cost", 2))), x => ArgumentLessThan(x, 12)); Track( tracker, context, UseMoreIterationsMessageFormat, tracker.MatchArgument( ArgumentDescriptor.MethodInvocation(KnownType.Org_BouncyCastle_Crypto_PbeParametersGenerator, "Init", "iterationCount", 2)), x => ArgumentLessThan(x, IterationCountThreshold)); TrackSCrypt("N", 2, "Use a cost factor of at least 2 ^ 12 for N here.", 1 << 12); TrackSCrypt("r", 3, "Use a memory factor of at least 8 for r here.", 8); TrackSCrypt("dkLen", 5, "Use an output length of at least 32 for dkLen here.", 32); void TrackSCrypt(string argumentName, int argumentPosition, string diagnosticMessage, int threshold) => Track( tracker, context, diagnosticMessage, tracker.MatchArgument(ArgumentDescriptor.MethodInvocation( KnownType.Org_BouncyCastle_Crypto_Generators_SCrypt, "Generate", argumentName, argumentPosition)), x => ArgumentLessThan(x, threshold)); } private static bool HasFewIterations(PropertyAccessContext context, PropertyAccessTracker tracker) => tracker.AssignedValue(context) is < IterationCountThreshold; private static bool ArgumentLessThan(ArgumentContext context, int threshold) => context.Model.GetConstantValue(((ArgumentSyntax)context.Node).Expression) is { HasValue: true, Value: int value } && value < threshold; private static void Track(SyntaxTrackerBase tracker, SonarAnalysisContext context, string message, params SyntaxTrackerBase.Condition[] conditions) where TContext : SyntaxBaseContext => tracker.Track(new(context, AnalyzerConfiguration.AlwaysEnabled, Rule), [message], conditions); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PointersShouldBePrivate.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PointersShouldBePrivate : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4000"; private const string MessageFormat = "Make '{0}' 'private' or 'protected readonly'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (fieldDeclaration.Declaration == null) { return; } foreach (var variable in fieldDeclaration.Declaration.Variables) { if (SymbolIfPointerType(fieldDeclaration.Declaration, variable, c.Model) is { } variableSymbol && variableSymbol.GetEffectiveAccessibility() is var accessibility && accessibility != Accessibility.Private && accessibility != Accessibility.Internal && !variableSymbol.IsReadOnly) { c.ReportIssue(Rule, variable, variableSymbol.Name); } } }, SyntaxKind.FieldDeclaration); private static IFieldSymbol SymbolIfPointerType(VariableDeclarationSyntax variableDeclaration, VariableDeclaratorSyntax variableDeclarator, SemanticModel semanticModel) { if (variableDeclaration.Type.IsKind(SyntaxKind.PointerType) || IsUnmanagedFunctionPointer(variableDeclaration)) { return (IFieldSymbol)semanticModel.GetDeclaredSymbol(variableDeclarator); } else { return IsPointerStructure(variableDeclaration) && ((IFieldSymbol)semanticModel.GetDeclaredSymbol(variableDeclarator)) is { } variableSymbol && variableSymbol.Type.IsAny(KnownType.PointerTypes) ? variableSymbol : null; } } private static bool IsPointerStructure(VariableDeclarationSyntax variableDeclaration) => variableDeclaration.Type is IdentifierNameSyntax identifierName ? IsNameOfPointerStruct(identifierName.Identifier.ValueText) : variableDeclaration.Type is QualifiedNameSyntax qualifiedName && qualifiedName.Right is IdentifierNameSyntax identifierNameSyntax && IsNameOfPointerStruct(identifierNameSyntax.Identifier.ValueText); private static bool IsNameOfPointerStruct(string typeName) => typeName.Equals("IntPtr") || typeName.Equals("UIntPtr"); private static bool IsUnmanagedFunctionPointer(VariableDeclarationSyntax variableDeclaration) => variableDeclaration.Type.IsKind(SyntaxKindEx.FunctionPointerType) && (FunctionPointerTypeSyntaxWrapper)variableDeclaration.Type is var functionPointerType && functionPointerType.CallingConvention.SyntaxNode != null && !functionPointerType.CallingConvention.ManagedOrUnmanagedKeyword.IsKind(SyntaxKindEx.ManagedKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PreferGuidEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PreferGuidEmpty : PreferGuidEmptyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PreferGuidEmptyCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class PreferGuidEmptyCodeFix : SonarCodeFix { internal const string Title = "Use Guid.Empty instead"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(PreferGuidEmpty.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var toReplace = ToReplace(root, context); var replacement = Replacement(root, toReplace); context.RegisterCodeFix( Title, token => Task.FromResult(context.Document.WithSyntaxRoot(replacement)), context.Diagnostics); return Task.CompletedTask; } private static SyntaxNode ToReplace(SyntaxNode root, SonarCodeFixContext context) { var replacement = root.FindNode(context.Diagnostics.First().Location.SourceSpan); return replacement is ArgumentSyntax argument ? argument.Expression : replacement; } private static SyntaxNode Replacement(SyntaxNode root, SyntaxNode toReplace) => root.ReplaceNode(toReplace, SyntaxFactory.IdentifierName("Guid.Empty")).WithAdditionalAnnotations(Formatter.Annotation); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PreferJaggedArraysOverMultidimensional.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PreferJaggedArraysOverMultidimensional : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3967"; private const string MessageFormat = "Change this multidimensional array to a jagged array."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => AnalyzeNode(c, (semanticModel, variable) => semanticModel.GetDeclaredSymbol(variable.Variables[0]).GetSymbolType(), variable => variable.Variables[0].Identifier.GetLocation()), SyntaxKind.VariableDeclaration); context.RegisterNodeAction( c => AnalyzeNode(c, (semanticModel, parameter) => semanticModel.GetDeclaredSymbol(parameter)?.Type, parameter => parameter.Identifier.GetLocation()), SyntaxKind.Parameter); context.RegisterNodeAction( c => AnalyzeNode(c, (semanticModel, method) => semanticModel.GetDeclaredSymbol(method)?.ReturnType, method => method.Identifier.GetLocation()), SyntaxKind.MethodDeclaration); context.RegisterNodeAction( c => AnalyzeNode(c, (semanticModel, property) => semanticModel.GetDeclaredSymbol(property)?.Type, property => property.Identifier.GetLocation()), SyntaxKind.PropertyDeclaration); } private static void AnalyzeNode(SonarSyntaxNodeReportingContext context, Func getTypeSymbol, Func getLocation) where TSyntax : SyntaxNode { var syntax = (TSyntax)context.Node; var typeSymbol = getTypeSymbol(context.Model, syntax); if (typeSymbol == null) { return; } if (IsMultiDimensionalArray(typeSymbol)) { context.ReportIssue(rule, getLocation(syntax)); } } private static bool IsMultiDimensionalArray(ITypeSymbol type) { var currentType = type; while (currentType.TypeKind == TypeKind.Array) { var arrayType = (IArrayTypeSymbol)currentType; if (arrayType.Rank > 1) { return true; } currentType = arrayType.ElementType; } return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PrivateFieldUsedAsLocalVariable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PrivateFieldUsedAsLocalVariable : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1450"; private const string MessageFormat = "Remove the field '{0}' and declare it as a local variable in the relevant methods."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet NonPrivateModifiers = new HashSet { SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword, }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var typeDeclaration = (TypeDeclarationSyntax)c.Node; if (c.IsRedundantPositionalRecordContext() || typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) { return; } var methodNames = typeDeclaration.Members.OfType().Select(x => x.Identifier.ValueText).ToHashSet(); var privateFields = GetPrivateFields(c.Model, typeDeclaration); var collector = new FieldAccessCollector(c.Model, privateFields, methodNames); if (!collector.SafeVisit(typeDeclaration)) { // We couldn't finish the exploration so we cannot take any decision return; } foreach (var fieldSymbol in privateFields.Keys.Where(collector.IsRemovableField)) { c.ReportIssue(Rule, privateFields[fieldSymbol], fieldSymbol.Name); } }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static IDictionary GetPrivateFields(SemanticModel model, TypeDeclarationSyntax typeDeclaration) { return typeDeclaration.Members .OfType() .Where(IsPrivate) .Where(HasNoAttributes) .SelectMany(x => x.Declaration.Variables) .ToDictionary( x => (IFieldSymbol)model.GetDeclaredSymbol(x), x => x); bool IsPrivate(FieldDeclarationSyntax fieldDeclaration) => !fieldDeclaration.Modifiers.Select(x => x.Kind()).Any(NonPrivateModifiers.Contains); bool HasNoAttributes(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.AttributeLists.Count == 0; } private sealed class FieldAccessCollector : SafeCSharpSyntaxWalker { // Contains statements that READ field values. First grouped by field symbol (that is read), // then by method/property/ctor symbol (that contains the statements) private readonly Dictionary> readsByField = new(); // Contains statements that WRITE field values. First grouped by field symbol (that is written), // then by method/property/ctor symbol (that contains the statements) private readonly Dictionary> writesByField = new(); // Contains all method/property invocations grouped by the statement that contains them. private readonly Lookup invocations = new(); private readonly SemanticModel model; private readonly IDictionary privateFields; private readonly HashSet methodNames; public FieldAccessCollector(SemanticModel model, IDictionary privateFields, HashSet methodNames) { this.model = model; this.privateFields = privateFields; this.methodNames = methodNames; } public bool IsRemovableField(IFieldSymbol fieldSymbol) { var writesByEnclosingSymbol = writesByField.GetValueOrDefault(fieldSymbol); var readsByEnclosingSymbol = readsByField.GetValueOrDefault(fieldSymbol); // No methods overwrite the field value if (writesByEnclosingSymbol is null) { return false; } // A field is removable when no method reads it, or all methods that read it, overwrite it before reading // However, as S4487 reports on fields that are written but not read, we only raise on the latter case return readsByEnclosingSymbol?.Keys.All(ValueOverwrittenBeforeReading) ?? false; bool ValueOverwrittenBeforeReading(ISymbol enclosingSymbol) { var writeStatements = writesByEnclosingSymbol.GetValueOrDefault(enclosingSymbol); var readStatements = readsByEnclosingSymbol.GetValueOrDefault(enclosingSymbol); // Note that Enumerable.All() will return true if readStatements is empty. The collection // will be empty if the field is read only in property/field initializers or returned from // expression-bodied methods. return writeStatements is not null && (readStatements?.All(IsPrecededWithWrite) ?? false); // Returns true when readStatement is preceded with a statement that overwrites fieldSymbol, // or false when readStatement is preceded with an invocation of a method or property that // overwrites fieldSymbol. bool IsPrecededWithWrite(SyntaxNode readStatementOrArrowExpression) { if (readStatementOrArrowExpression is StatementSyntax readStatement) { foreach (var statement in readStatement.GetPreviousStatements()) { // When the readStatement is preceded with a write statement (that is also not a read statement) // we want to report this field. if (IsOverwritingValue(statement)) { return true; } // When the readStatement is preceded with an invocation that has side effects, e.g. writes the field // we don't want to report this field because it could be difficult or impossible to change the code. if (IsInvocationWithSideEffects(statement)) { return false; } } } // ArrowExpressionClauseSyntax cannot be preceded by anything... return false; } bool IsOverwritingValue(StatementSyntax statement) => writeStatements.Contains(statement) && !readStatements.Contains(statement); bool IsInvocationWithSideEffects(StatementSyntax statement) => invocations.TryGetValue(statement, out var invocationsInStatement) && invocationsInStatement.Any(writesByEnclosingSymbol.ContainsKey); } } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (privateFields.Keys.Any(x => x.Name == node.Identifier.ValueText) || (node.Parent is not InvocationExpressionSyntax && methodNames.Contains(node.Identifier.ValueText))) { var memberReference = GetTopmostSyntaxWithTheSameSymbol(node); if (memberReference.Symbol is IFieldSymbol fieldSymbol && privateFields.ContainsKey(fieldSymbol)) { ClassifyFieldReference(model.GetEnclosingSymbol(memberReference.Node.SpanStart), memberReference); } else if (memberReference.Symbol is IMethodSymbol && GetParentPseudoStatement(memberReference) is { } pseudoStatement) { // Adding method group to the invocation list invocations.GetOrAdd(pseudoStatement, _ => new HashSet()).Add(memberReference.Symbol); } } base.VisitIdentifierName(node); } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.GetMethodCallIdentifier() is { } methodCallIdentifier && methodNames.Contains(methodCallIdentifier.ValueText) && GetTopmostSyntaxWithTheSameSymbol(node) is var memberReference && memberReference.Symbol is IMethodSymbol && GetParentPseudoStatement(memberReference) is { } pseudoStatement) { invocations.GetOrAdd(pseudoStatement, _ => new HashSet()) .Add(memberReference.Symbol); } base.VisitInvocationExpression(node); } // A PseudoStatement is a Statement or an ArrowExpressionClauseSyntax (which denotes an expression-bodied member). private static SyntaxNode GetParentPseudoStatement(NodeAndSymbol memberReference) => memberReference.Node.Ancestors().FirstOrDefault(x => x is StatementSyntax or ArrowExpressionClauseSyntax); /// /// Stores the statement that contains the provided field reference in one of the "reads" or "writes" collections, /// first grouped by field symbol, then by containing method. /// private void ClassifyFieldReference(ISymbol enclosingSymbol, NodeAndSymbol fieldReference) { // It is important to create the field access HashSet regardless of the statement (see the local var below) // being null or not, because the rule will not be able to detect field reads from inline property // or field initializers. var fieldAccessInMethod = (IsWrite(fieldReference) ? writesByField : readsByField) .GetOrAdd((IFieldSymbol)fieldReference.Symbol, _ => new Lookup()) .GetOrAdd(enclosingSymbol, _ => new HashSet()); var pseudoStatement = GetParentPseudoStatement(fieldReference); if (pseudoStatement is not null) { fieldAccessInMethod.Add(pseudoStatement); } } private static bool IsWrite(NodeAndSymbol fieldReference) { // If the field is not static and is not from the current instance we // consider the reference as read. if (!fieldReference.Symbol.IsStatic && !(fieldReference.Node as ExpressionSyntax).RemoveParentheses().IsOnThis()) { return false; } return IsLeftSideOfAssignment(fieldReference.Node) || IsOutArgument(fieldReference.Node); bool IsOutArgument(SyntaxNode node) => node.Parent is ArgumentSyntax argument && argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword); bool IsLeftSideOfAssignment(SyntaxNode node) => node.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression) && node.Parent is AssignmentExpressionSyntax assignmentExpression && assignmentExpression.Left == node; } private NodeAndSymbol GetTopmostSyntaxWithTheSameSymbol(SyntaxNode node) => // All of the cases below could be parts of invocation or other expressions node.Parent switch { // this.identifier or a.identifier or ((a)).identifier, but not identifier.other MemberAccessExpressionSyntax memberAccess when memberAccess.Name == node => new NodeAndSymbol(memberAccess.GetSelfOrTopParenthesizedExpression(), model.GetSymbolInfo(memberAccess).Symbol), // this?.identifier or a?.identifier or ((a))?.identifier, but not identifier?.other MemberBindingExpressionSyntax memberBinding when memberBinding.Name == node => new NodeAndSymbol(memberBinding.Parent.GetSelfOrTopParenthesizedExpression(), model.GetSymbolInfo(memberBinding).Symbol), // identifier or ((identifier)) _ => new NodeAndSymbol(node.GetSelfOrTopParenthesizedExpression(), model.GetSymbolInfo(node).Symbol) }; } private sealed class Lookup : Dictionary> { } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PrivateStaticMethodUsedOnlyByNestedClass.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PrivateStaticMethodUsedOnlyByNestedClass : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3398"; private const string MessageFormat = "Move this method inside '{0}'."; private static readonly SyntaxKind[] AnalyzedSyntaxKinds = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration ]; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var outerType = (TypeDeclarationSyntax)c.Node; if (!IsPartial(outerType) && HasNestedTypeDeclarations(outerType) && PrivateStaticMethodsOf(outerType) is { Length: > 0 } candidates) { var methodReferences = TypesWhichUseTheMethods(candidates, outerType, c.Model); foreach (var reference in methodReferences) { var typeToMoveInto = LowestCommonAncestorOrSelf(reference.Types); if (typeToMoveInto != outerType) { var nestedTypeName = typeToMoveInto.Identifier.ValueText; c.ReportIssue(Rule, reference.Method.Identifier, nestedTypeName); } } } }, AnalyzedSyntaxKinds); private static bool IsPartial(TypeDeclarationSyntax type) => type.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)); private static bool HasNestedTypeDeclarations(TypeDeclarationSyntax type) => type.Members .OfType() .Any(); private static MethodDeclarationSyntax[] PrivateStaticMethodsOf(TypeDeclarationSyntax type) => type.Members .OfType() .Where(x => IsPrivateAndStatic(x, type)) .ToArray(); private static bool IsPrivateAndStatic(MethodDeclarationSyntax method, TypeDeclarationSyntax containingType) { return method.Modifiers.Any(x => x.IsKind(SyntaxKind.StaticKeyword)) && (IsExplicitlyPrivate() || IsImplicityPrivate()); bool IsExplicitlyPrivate() => method.Modifiers.Any(SyntaxKind.PrivateKeyword) && !method.Modifiers.Any(SyntaxKind.ProtectedKeyword); // The default accessibility for record class members is private, but for record structs (like all structs) it's internal. bool IsImplicityPrivate() => !method.Modifiers.Any(x => x.Kind() is SyntaxKind.PublicKeyword or SyntaxKind.ProtectedKeyword or SyntaxKind.InternalKeyword) && IsClassOrRecordClassOrInterfaceDeclaration(containingType); static bool IsClassOrRecordClassOrInterfaceDeclaration(TypeDeclarationSyntax type) => type is ClassDeclarationSyntax or InterfaceDeclarationSyntax || (RecordDeclarationSyntaxWrapper.IsInstance(type) && !((RecordDeclarationSyntaxWrapper)type).ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword)); } private static TypeDeclarationSyntax LowestCommonAncestorOrSelf(IEnumerable declaredTypes) { var typeHierarchyFromTopToBottom = declaredTypes.Select(PathFromTop); var minPathLength = typeHierarchyFromTopToBottom.Min(x => x.Length); var firstPath = typeHierarchyFromTopToBottom.First(); var lastCommonPathIndex = 0; for (var i = 0; i < minPathLength; i++) { var isPartOfCommonPath = typeHierarchyFromTopToBottom.All(x => x[i] == firstPath[i]); if (isPartOfCommonPath) { lastCommonPathIndex = i; } else { break; } } return firstPath[lastCommonPathIndex]; static TypeDeclarationSyntax[] PathFromTop(SyntaxNode node) => node.AncestorsAndSelf() .OfType() .Where(x => x.Kind() is not SyntaxKindEx.ExtensionBlockDeclaration) .Distinct() .Reverse() .ToArray(); } private static IEnumerable TypesWhichUseTheMethods( IEnumerable methods, TypeDeclarationSyntax outerType, SemanticModel model) { var collector = new PotentialMethodReferenceCollector(methods); collector.SafeVisit(outerType); return collector.PotentialMethodReferences .Where(x => !OnlyUsedByOuterType(x)) .Select(DeclaredTypesWhichActuallyUseTheMethod) .Where(x => x.Types.Any()) .ToArray(); MethodUsedByTypes DeclaredTypesWhichActuallyUseTheMethod(MethodWithPotentialReferences m) { var methodSymbol = model.GetDeclaredSymbol(m.Method); var typesWhichUseTheMethod = m.PotentialReferences .Where(x => !IsRecursiveMethodCall(x, m.Method) && model.GetSymbolOrCandidateSymbol(x) is IMethodSymbol { } methodReference && (methodReference.Equals(methodSymbol) || methodReference.ConstructedFrom.Equals(methodSymbol))) .Select(ContainingTypeDeclaration) .Distinct() .ToArray(); return new MethodUsedByTypes(m.Method, typesWhichUseTheMethod); } bool IsRecursiveMethodCall(IdentifierNameSyntax methodCall, MethodDeclarationSyntax methodDeclaration) => methodCall.Ancestors().OfType().FirstOrDefault() == methodDeclaration; bool OnlyUsedByOuterType(MethodWithPotentialReferences m) => m.PotentialReferences.All(x => ContainingTypeDeclaration(x) == outerType); static TypeDeclarationSyntax ContainingTypeDeclaration(IdentifierNameSyntax identifier) => identifier .Ancestors() .OfType() .First(); } private sealed record MethodWithPotentialReferences(MethodDeclarationSyntax Method, IdentifierNameSyntax[] PotentialReferences); private sealed record MethodUsedByTypes(MethodDeclarationSyntax Method, TypeDeclarationSyntax[] Types); /// /// Collects all the potential references to a set of methods inside the given syntax node. /// The collector looks for identifiers which match any of the methods' names, but does not try to resolve them to symbols with the semantic model. /// Performance gains: by only using the syntax tree to find matches we can eliminate certain methods (which are only used by the type which has declared it) /// without using the more costly symbolic lookup. /// private sealed class PotentialMethodReferenceCollector : SafeCSharpSyntaxWalker { private readonly ISet methodsToFind; private readonly Dictionary> potentialMethodReferences; public IEnumerable PotentialMethodReferences => potentialMethodReferences.Select(x => new MethodWithPotentialReferences(x.Key, x.Value.ToArray())); public PotentialMethodReferenceCollector(IEnumerable methodsToFind) { this.methodsToFind = new HashSet(methodsToFind); potentialMethodReferences = []; } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (methodsToFind.FirstOrDefault(x => x.Identifier.ValueText == node.Identifier.ValueText) is { } method) { var referenceList = potentialMethodReferences.GetOrAdd(method, _ => []); referenceList.Add(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertiesAccessCorrectField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertiesAccessCorrectField : PropertiesAccessCorrectFieldBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IEnumerable FindFieldAssignments(IPropertySymbol property, Compilation compilation) { if (property.SetMethod.GetFirstSyntaxRef() is not AccessorDeclarationSyntax setter) { return []; } // we only keep information for the first location of the symbol var assignments = new Dictionary(); FillAssignments(assignments, compilation, setter, true); // If there're no candidate variables, we'll try to inspect one local method invocation with value as argument if (assignments.Count == 0 && (setter.ExpressionBody?.Expression ?? SingleInvocation(setter.Body)) is { } expression && FindInvokedMethod(compilation, property.ContainingType, expression) is MethodDeclarationSyntax invokedMethod) { FillAssignments(assignments, compilation, invokedMethod, false); } return assignments.Values; } protected override IEnumerable FindFieldReads(IPropertySymbol property, Compilation compilation) { // We don't handle properties with multiple returns that return different fields if (property.GetMethod.GetFirstSyntaxRef() is not AccessorDeclarationSyntax getter) { return []; } var reads = new Dictionary(); FillReads(getter, true); // If there're no candidate variables, we'll try inspect one return of local method invocation if (reads.Count == 0 && (getter.ExpressionBody?.Expression ?? SingleReturn(getter.Body)) is InvocationExpressionSyntax returnExpression && FindInvokedMethod(compilation, property.ContainingType, returnExpression) is MethodDeclarationSyntax invokedMethod) { FillReads(invokedMethod, false); } return reads.Values; void FillReads(SyntaxNode root, bool useFieldLocation) { var notAssigned = root.DescendantNodes().OfType().Where(x => !IsLeftSideOfAssignment(x)); foreach (var expression in notAssigned) { var readField = ExtractFieldFromExpression(AccessorKind.Getter, expression, compilation, useFieldLocation); // we only keep information for the first location of the symbol if (readField.HasValue && !reads.ContainsKey(readField.Value.Field)) { reads.Add(readField.Value.Field, readField.Value); } } } } protected override bool ShouldIgnoreAccessor(IMethodSymbol accessorMethod, Compilation compilation) { if (accessorMethod.GetFirstSyntaxRef() is not AccessorDeclarationSyntax accessor || ((SyntaxNode)accessor.Body ?? accessor).ContainsGetOrSetOnDependencyProperty(compilation) || AccessesSelfBaseProperty(accessorMethod, accessor, compilation)) { return true; } // Special case: ignore the accessor if the only statement/expression is a throw. if (accessor.Body is null) { // Expression-bodied syntax return accessor.ExpressionBody is { } arrowClause && ThrowExpressionSyntaxWrapper.IsInstance(arrowClause.Expression); } // Statement-bodied syntax return accessor.Body.DescendantNodes().Count(x => x is StatementSyntax) == 1 && accessor.Body.DescendantNodes().Count(x => x is ThrowStatementSyntax) == 1; } protected override bool ImplementsExplicitGetterOrSetter(IPropertySymbol property) => HasExplicitAccessor(property.SetMethod) || HasExplicitAccessor(property.GetMethod); private static void FillAssignments(IDictionary assignments, Compilation compilation, SyntaxNode root, bool useFieldLocation) { foreach (var node in root.DescendantNodes()) { FieldData? foundField = null; if (node is AssignmentExpressionSyntax assignment) { foundField = assignment.Left.DescendantNodesAndSelf().OfType() .Select(x => ExtractFieldFromExpression(AccessorKind.Setter, x, compilation, useFieldLocation)) .FirstOrDefault(x => x is not null); } else if (node is ArgumentSyntax argument && argument.RefOrOutKeyword.Kind() is SyntaxKind.RefKeyword or SyntaxKind.OutKeyword) { foundField = ExtractFieldFromExpression(AccessorKind.Setter, argument.Expression, compilation, useFieldLocation); } if (foundField.HasValue && !assignments.ContainsKey(foundField.Value.Field)) { assignments.Add(foundField.Value.Field, foundField.Value); } } } private static ExpressionSyntax SingleReturn(SyntaxNode body) { if (body is null) { return null; } var returns = body.DescendantNodes().OfType().ToArray(); return returns.Length == 1 ? returns.Single().Expression : null; } private static ExpressionSyntax SingleInvocation(SyntaxNode body) { if (body is null) { return null; } var expressions = body.DescendantNodes().OfType().Select(x => x.Expression).ToArray(); if (expressions.Length == 1) { var expr = expressions.Single(); if (expr is IdentifierNameSyntax or MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax }) { return expr; } } return null; } private static FieldData? ExtractFieldFromExpression(AccessorKind accessorKind, ExpressionSyntax expression, Compilation compilation, bool useFieldLocation) { var model = compilation.GetSemanticModel(expression.SyntaxTree); if (model is null) { return null; } var strippedExpression = expression.RemoveParentheses(); // Check for direct field access: "foo" if (strippedExpression is IdentifierNameSyntax && model.GetSymbolInfo(strippedExpression).Symbol is IFieldSymbol directAccessField) { return new FieldData(accessorKind, directAccessField, strippedExpression, useFieldLocation); } // Check for "this.foo" else if (strippedExpression is MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax } member && model.GetSymbolInfo(strippedExpression).Symbol is IFieldSymbol fieldAccessedWithThis) { return new FieldData(accessorKind, fieldAccessedWithThis, member.Name, useFieldLocation); } else if (strippedExpression is AssignmentExpressionSyntax assignmentExpression && assignmentExpression.Parent is ReturnStatementSyntax && model.GetSymbolInfo(assignmentExpression.Left).Symbol is IFieldSymbol fieldAssignedFromExpression) { return new FieldData(accessorKind, fieldAssignedFromExpression, assignmentExpression.Left, useFieldLocation); } return null; } private static bool IsLeftSideOfAssignment(ExpressionSyntax expression) { var strippedExpression = expression.RemoveParentheses(); return strippedExpression.IsLeftSideOfAssignment() || (strippedExpression.Parent is ExpressionSyntax parent && parent.IsLeftSideOfAssignment()); // for this.field } private static bool HasExplicitAccessor(ISymbol symbol) => symbol.GetFirstSyntaxRef() is AccessorDeclarationSyntax accessor && accessor.DescendantNodes().Any(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertiesShouldBePreferred.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertiesShouldBePreferred : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4049"; private const string MessageFormat = "Consider making method '{0}' a property."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext() || c.Model.GetDeclaredSymbol(c.Node) is not INamedTypeSymbol typeSymbol) { return; } var propertyCandidates = typeSymbol .GetMembers() .OfType() .Where(x => HasCandidateName(x) && HasCandidateReturnType(x) && HasCandidateSignature(x) && UsageAttributesAllowProperties(x) && !x.IsGenericMethod); foreach (var candidate in propertyCandidates) { c.ReportIssue(Rule, candidate.Locations.FirstOrDefault(), candidate.Name); } }, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static bool HasCandidateSignature(IMethodSymbol method) => method.IsPubliclyAccessible() && method.Parameters.Length == 0 && !method.IsConstructor() && !method.IsOverride && method.MethodKind != MethodKind.PropertyGet && method.InterfaceMembers().IsEmpty(); private static bool HasCandidateReturnType(IMethodSymbol method) => !method.ReturnsVoid && !method.IsAsync && !method.ReturnType.Is(TypeKind.Array) && !method.ReturnType.OriginalDefinition.IsAny(KnownType.SystemTasks); private static bool HasCandidateName(IMethodSymbol method) { if (method.Name is nameof(IEnumerable.GetEnumerator) or nameof(Task.GetAwaiter)) { return false; } var nameParts = method.Name.SplitCamelCaseToWords().ToList(); return nameParts.Count > 1 && nameParts[0] == "GET"; } private static bool UsageAttributesAllowProperties(IMethodSymbol method) => method.GetAttributes() .Select(attribute => attribute.AttributeClass) .SelectMany(cls => cls.GetAttributes(KnownType.System_AttributeUsageAttribute)) .Select(attr => attr.ConstructorArguments[0].Value) .Cast() .All(targets => targets.HasFlag(AttributeTargets.Property)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertyGetterWithThrow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertyGetterWithThrow : PropertyGetterWithThrowBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override SyntaxKind ThrowSyntaxKind => SyntaxKind.ThrowStatement; protected override bool IsGetter(AccessorDeclarationSyntax propertyGetter) => propertyGetter.IsKind(SyntaxKind.GetAccessorDeclaration); protected override bool IsIndexer(AccessorDeclarationSyntax propertyGetter) => propertyGetter.Parent.Parent is IndexerDeclarationSyntax; protected override SyntaxNode GetThrowExpression(SyntaxNode syntaxNode) => ((ThrowStatementSyntax)syntaxNode).Expression; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertyNamesShouldNotMatchGetMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertyNamesShouldNotMatchGetMethods : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4059"; private const string MessageFormat = "Change either the name of property '{0}' or the name of method '{1}' to make them distinguishable."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction(c => // Invoked twice for partial properties: once for the property declaration and one for the implementation { var propertySymbol = (IPropertySymbol)c.Symbol; if (!propertySymbol.IsPubliclyAccessible() || propertySymbol.IsOverride) { return; } var methods = propertySymbol.ContainingType.GetMembers().OfType().Where(x => x.IsPubliclyAccessible()).ToArray(); if (Array.Find(methods, x => AreCollidingNames(propertySymbol.Name, x.Name)) is { } collidingMethod) { // When dealing with partial properties, IsPartialDefinition is true only for the declaration, we use this to avoid reporting the secondary location twice List secondaryLocation = propertySymbol.IsPartialDefinition() ? [] : [new(collidingMethod.Locations.First(), string.Empty)]; c.ReportIssue(Rule, propertySymbol.Locations.First(), secondaryLocation, propertySymbol.Name, collidingMethod.Name); } }, SymbolKind.Property); private static bool AreCollidingNames(string propertyName, string methodName) => methodName.Equals(propertyName, StringComparison.OrdinalIgnoreCase) || methodName.Equals("Get" + propertyName, StringComparison.OrdinalIgnoreCase); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertyToAutoProperty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertyToAutoProperty : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2292"; private const string MessageFormat = "Make this an auto-implemented property and remove its backing field."; private const int AccessorCount = 2; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var propertyDeclaration = (PropertyDeclarationSyntax)c.Node; if ((propertyDeclaration.AccessorList?.Accessors is { Count: AccessorCount } accessors && !HasDifferentModifiers(accessors) && !HasAttributes(accessors) && !propertyDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) && accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.GetAccessorDeclaration)) is { } getter && accessors.FirstOrDefault(x => x.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration) is { } setter && FieldFromGetter(getter, c.Model) is { } getterField && FieldFromSetter(setter, c.Model) is { } setterField && getterField.Equals(setterField) && !getterField.GetAttributes().Any() && !getterField.IsVolatile && c.Model.GetDeclaredSymbol(propertyDeclaration) is { } propertySymbol && getterField.IsStatic == propertySymbol.IsStatic && getterField.Type.Equals(propertySymbol.Type)) || (propertyDeclaration.ExpressionBody is ArrowExpressionClauseSyntax expression && expression.Expression.IsKind(SyntaxKindEx.FieldExpression))) { c.ReportIssue(Rule, propertyDeclaration.Identifier); } }, SyntaxKind.PropertyDeclaration); private static bool HasAttributes(SyntaxList accessors) => accessors.Any(x => x.AttributeLists.Any()); private static bool HasDifferentModifiers(SyntaxList accessors) { var modifiers = ModifierKinds(accessors.First()).ToHashSet(); return accessors.Skip(1).Any(x => !modifiers.SetEquals(ModifierKinds(x))); } private static IEnumerable ModifierKinds(AccessorDeclarationSyntax accessor) => accessor.Modifiers.Select(x => x.Kind()); private static IFieldSymbol FieldFromSetter(AccessorDeclarationSyntax setter, SemanticModel model) { var assignment = AssignmentFromBody(setter.Body) ?? AssignmentFromExpressionBody(setter.ExpressionBody); return assignment is { RawKind: (int)SyntaxKind.SimpleAssignmentExpression, Right: { } right } && model.GetSymbolInfo(right).Symbol is IParameterSymbol { Name: "value", IsImplicitlyDeclared: true } ? FieldSymbol(assignment.Left, model.GetDeclaredSymbol(setter).ContainingType, model) : null; AssignmentExpressionSyntax AssignmentFromBody(BlockSyntax body) => body?.Statements.Count == 1 && body.Statements[0] is ExpressionStatementSyntax statement ? statement.Expression as AssignmentExpressionSyntax : null; AssignmentExpressionSyntax AssignmentFromExpressionBody(ArrowExpressionClauseSyntax expressionBody) => expressionBody?.ChildNodes().Count() == 1 ? expressionBody.ChildNodes().Single() as AssignmentExpressionSyntax : null; } private static IFieldSymbol FieldSymbol(ExpressionSyntax expression, INamedTypeSymbol declaringType, SemanticModel model) => expression switch { IdentifierNameSyntax => model.GetSymbolInfo(expression).Symbol as IFieldSymbol, MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.SimpleMemberAccessExpression, Expression: ThisExpressionSyntax } => model.GetSymbolInfo(expression).Symbol as IFieldSymbol, MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.SimpleMemberAccessExpression, Expression: IdentifierNameSyntax identifier } when model.GetSymbolInfo(identifier).Symbol is INamedTypeSymbol type && type.Equals(declaringType) => model.GetSymbolInfo(expression).Symbol as IFieldSymbol, { RawKind: (int)SyntaxKindEx.FieldExpression } => model.GetSymbolInfo(expression).Symbol as IFieldSymbol, _ => null }; private static IFieldSymbol FieldFromGetter(AccessorDeclarationSyntax getter, SemanticModel model) { var returnedExpression = ReturnExpression(getter.Body) ?? getter.ExpressionBody?.Expression; return returnedExpression is null ? null : FieldSymbol(returnedExpression, model.GetDeclaredSymbol(getter).ContainingType, model); static ExpressionSyntax ReturnExpression(BlockSyntax body) => body is not null && body.Statements.Count == 1 && body.Statements[0] is ReturnStatementSyntax returnStatement ? returnStatement.Expression : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PropertyWriteOnly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertyWriteOnly : PropertyWriteOnlyBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind SyntaxKind => SyntaxKind.PropertyDeclaration; protected override bool IsWriteOnlyProperty(PropertyDeclarationSyntax prop) { var accessors = prop.AccessorList; return accessors is {Accessors: {Count: 1}} && accessors.Accessors.First().Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration && !prop.Modifiers.Any(SyntaxKind.OverrideKeyword); // the get may be in the base class } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ProvideDeserializationMethodsForOptionalFields.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ProvideDeserializationMethodsForOptionalFields : ProvideDeserializationMethodsForOptionalFieldsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override Location GetNamedTypeIdentifierLocation(SyntaxNode node) => ((TypeDeclarationSyntax)node).Identifier.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PublicConstantField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PublicConstantField : PublicConstantFieldBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); public override SyntaxKind FieldDeclarationKind => SyntaxKind.FieldDeclaration; public override string MessageArgument => "'static' read-only"; protected override Location GetReportLocation(VariableDeclaratorSyntax node) => node.Identifier.GetLocation(); protected override IEnumerable GetVariables(FieldDeclarationSyntax node) => node.Declaration.Variables; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PublicMethodWithMultidimensionalArray.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PublicMethodWithMultidimensionalArray : PublicMethodWithMultidimensionalArrayBase { private static readonly ImmutableArray KindsOfInterest = ImmutableArray.Create( SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); protected override ILanguageFacade Language => CSharpFacade.Instance; protected override ImmutableArray SyntaxKindsOfInterest => KindsOfInterest; protected override Location GetIssueLocation(SyntaxNode node) => Language.Syntax.NodeIdentifier(node)?.GetLocation(); protected override string GetType(SyntaxNode node) => node is MethodDeclarationSyntax ? "method" : "constructor"; protected override IMethodSymbol MethodSymbolOfNode(SemanticModel semanticModel, SyntaxNode node) => node is TypeDeclarationSyntax typeDeclaration ? typeDeclaration.PrimaryConstructor(semanticModel) : base.MethodSymbolOfNode(semanticModel, node); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/PureAttributeOnVoidMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PureAttributeOnVoidMethod : PureAttributeOnVoidMethodBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterNodeAction( c => { var localFunction = (LocalFunctionStatementSyntaxWrapper)c.Node; if (localFunction.AttributeLists.SelectMany(x => x.Attributes).Any(IsPureAttribute) && InvalidPureDataAttributeUsage((IMethodSymbol)c.Model.GetDeclaredSymbol(c.Node)) is { } pureAttribute) { c.ReportIssue(Rule, pureAttribute.ApplicationSyntaxReference.GetSyntax()); } }, SyntaxKindEx.LocalFunctionStatement); } private static bool IsPureAttribute(AttributeSyntax attribute) => attribute.Name.GetIdentifier() is { ValueText: "Pure" or "PureAttribute" }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundancyInConstructorDestructorDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundancyInConstructorDestructorDeclaration : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3253"; private const string MessageFormat = "Remove this redundant {0}."; private static readonly SyntaxKind[] TypesWithPrimaryConstructorDeclarations = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration ]; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckConstructorDeclaration, SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction(CheckDestructorDeclaration, SyntaxKind.DestructorDeclaration); context.RegisterNodeAction(CheckTypesWithPrimaryConstructor, TypesWithPrimaryConstructorDeclarations); } private static void CheckTypesWithPrimaryConstructor(SonarSyntaxNodeReportingContext context) { var typeDeclaration = (TypeDeclarationSyntax)context.Node; if (!IsInheritingFromTypeWithValueInPrimaryConstructor(typeDeclaration) && typeDeclaration.ParameterList() is { Parameters.Count: 0 } parameterList && !IsStructWithInitializedFieldOrProperty(typeDeclaration, context.Model)) { context.ReportIssue(Rule, parameterList, "primary constructor"); } static bool IsInheritingFromTypeWithValueInPrimaryConstructor(TypeDeclarationSyntax node) => node.BaseList is { } baseList && baseList.DescendantNodes().OfType().Any(); } private static void CheckDestructorDeclaration(SonarSyntaxNodeReportingContext context) { var destructorDeclaration = (DestructorDeclarationSyntax)context.Node; if (destructorDeclaration.Body is { Statements.Count: 0 }) { context.ReportIssue(Rule, destructorDeclaration, "destructor"); } } private static void CheckConstructorDeclaration(SonarSyntaxNodeReportingContext context) { var constructorDeclaration = (ConstructorDeclarationSyntax)context.Node; if (IsConstructorRedundant(constructorDeclaration, context.Model)) { context.ReportIssue(Rule, constructorDeclaration, "constructor"); return; } var initializer = constructorDeclaration.Initializer; if (initializer is not null && IsInitializerRedundant(initializer)) { context.ReportIssue(Rule, initializer, "'base()' call"); } } private static bool IsInitializerRedundant(ConstructorInitializerSyntax initializer) => initializer.IsKind(SyntaxKind.BaseConstructorInitializer) && initializer.ArgumentList is not null && !initializer.ArgumentList.Arguments.Any(); private static bool IsConstructorRedundant(ConstructorDeclarationSyntax constructorDeclaration, SemanticModel model) => constructorDeclaration is { ParameterList.Parameters.Count: 0, Body.Statements.Count: 0, Parent: BaseTypeDeclarationSyntax typeDeclaration } && !IsStructWithInitializedFieldOrProperty(typeDeclaration, model) && (IsSinglePublicConstructor(constructorDeclaration, model) || constructorDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)); private static bool IsStructWithInitializedFieldOrProperty(BaseTypeDeclarationSyntax typeDeclaration, SemanticModel model) => typeDeclaration.Kind() is SyntaxKind.StructDeclaration or SyntaxKindEx.RecordStructDeclaration && model.GetDeclaredSymbol(typeDeclaration) is { } typeSymbol && typeSymbol.GetMembers().Any(x => x.Kind is SymbolKind.Field or SymbolKind.Property && x.DeclaringSyntaxReferences.Any(x => x.GetSyntax().GetInitializer() is not null)); private static bool IsSinglePublicConstructor(ConstructorDeclarationSyntax constructorDeclaration, SemanticModel model) => constructorDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword) && IsInitializerEmptyOrRedundant(constructorDeclaration.Initializer) && constructorDeclaration is { Parent: BaseTypeDeclarationSyntax typeDeclaration } && TypeHasExactlyOneConstructor(typeDeclaration, model); private static bool IsInitializerEmptyOrRedundant(ConstructorInitializerSyntax initializer) => initializer is null || (initializer.ArgumentList.Arguments.Count == 0 && initializer.ThisOrBaseKeyword.IsKind(SyntaxKind.BaseKeyword)); private static bool TypeHasExactlyOneConstructor(BaseTypeDeclarationSyntax containingTypeDeclaration, SemanticModel model) => model.GetDeclaredSymbol(containingTypeDeclaration) is { } typeSymbol && typeSymbol.GetMembers(".ctor").OfType().Count(x => x is { MethodKind: MethodKind.Constructor, IsImplicitlyDeclared: false, IsStatic: false }) == 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundancyInConstructorDestructorDeclarationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundancyInConstructorDestructorDeclarationCodeFix : SonarCodeFix { internal const string TitleRemoveBaseCall = "Remove 'base()' call"; internal const string TitleRemoveConstructor = "Remove constructor"; internal const string TitleRemoveDestructor = "Remove destructor"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundancyInConstructorDestructorDeclaration.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (syntaxNode is ConstructorInitializerSyntax initializer) { RegisterActionForBaseCall(context, root, initializer); return Task.CompletedTask; } var method = syntaxNode.FirstAncestorOrSelf(); if (method is ConstructorDeclarationSyntax) { RegisterActionForConstructor(context, root, method); return Task.CompletedTask; } if (method is DestructorDeclarationSyntax) { RegisterActionForDestructor(context, root, method); } return Task.CompletedTask; } private static void RegisterActionForDestructor(SonarCodeFixContext context, SyntaxNode root, SyntaxNode method) => context.RegisterCodeFix(TitleRemoveDestructor, c => { var newRoot = root.RemoveNode(method, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static void RegisterActionForConstructor(SonarCodeFixContext context, SyntaxNode root, SyntaxNode method) => context.RegisterCodeFix(TitleRemoveConstructor, c => { var newRoot = root.RemoveNode(method, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static void RegisterActionForBaseCall(SonarCodeFixContext context, SyntaxNode root, SyntaxNode initializer) { if (!(initializer.Parent is ConstructorDeclarationSyntax constructor)) { return; } context.RegisterCodeFix(TitleRemoveBaseCall, c => { var newRoot = RemoveInitializer(root, constructor); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static SyntaxNode RemoveInitializer(SyntaxNode root, ConstructorDeclarationSyntax constructor) { var annotation = new SyntaxAnnotation(); var ctor = constructor; var newRoot = root; newRoot = newRoot.ReplaceNode(ctor, ctor.WithAdditionalAnnotations(annotation)); ctor = GetConstructor(newRoot, annotation); var initializer = ctor.Initializer; if (RedundantInheritanceListCodeFix.HasLineEnding(constructor.ParameterList)) { newRoot = newRoot.RemoveNode(initializer, SyntaxRemoveOptions.KeepNoTrivia); ctor = GetConstructor(newRoot, annotation); if (ctor.Body is {HasLeadingTrivia: true}) { var lastTrivia = ctor.Body.GetLeadingTrivia().Last(); var newBody = lastTrivia.IsKind(SyntaxKind.EndOfLineTrivia) ? ctor.Body.WithoutLeadingTrivia() : ctor.Body.WithLeadingTrivia(lastTrivia); newRoot = newRoot.ReplaceNode(ctor.Body, newBody); } } else { var trailingTrivia = SyntaxFactory.TriviaList(); if (initializer.HasTrailingTrivia) { trailingTrivia = initializer.GetTrailingTrivia(); } newRoot = newRoot.RemoveNode(initializer, SyntaxRemoveOptions.KeepNoTrivia); ctor = GetConstructor(newRoot, annotation); if (ctor.Body is {HasLeadingTrivia: true}) { var lastTrivia = ctor.Body.GetLeadingTrivia().Last(); newRoot = newRoot.ReplaceNode(ctor.Body, ctor.Body.WithLeadingTrivia(trailingTrivia.Add(lastTrivia))); } else { if (initializer.HasTrailingTrivia) { newRoot = newRoot.ReplaceNode(ctor.ParameterList, ctor.ParameterList.WithTrailingTrivia(trailingTrivia)); } } } ctor = GetConstructor(newRoot, annotation); return newRoot.ReplaceNode(ctor, ctor.WithoutAnnotations(annotation)); } private static ConstructorDeclarationSyntax GetConstructor(SyntaxNode newRoot, SyntaxAnnotation annotation) => (ConstructorDeclarationSyntax)newRoot.GetAnnotatedNodes(annotation).First(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantArgument.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantArgument : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3254"; private const string MessageFormat = "Remove this default value assigned to parameter '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); internal static bool ArgumentHasDefaultValue(NodeAndSymbol argumentMapping, SemanticModel model) => argumentMapping.Symbol.HasExplicitDefaultValue && model.GetConstantValue(argumentMapping.Node.Expression) is { HasValue: true } argumentValue && Equals(argumentValue.Value, argumentMapping.Symbol.ExplicitDefaultValue); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if (c.Node.ArgumentList() is { Arguments.Count: > 0 } argumentList && !c.IsRedundantPrimaryConstructorBaseTypeContext() && !c.IsInExpressionTree() // Can't use optional arguments in expression trees (CS0584), so skip those && new CSharpMethodParameterLookup(argumentList, c.Model) is { MethodSymbol: not null } methodParameterLookup) { ProcessArgumentMappings(c, methodParameterLookup); } }, SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression, SyntaxKind.BaseConstructorInitializer, SyntaxKind.ThisConstructorInitializer, SyntaxKindEx.PrimaryConstructorBaseType); private static void ProcessArgumentMappings(SonarSyntaxNodeReportingContext c, CSharpMethodParameterLookup methodParameterLookup) { foreach (var argumentMapping in methodParameterLookup.GetAllArgumentParameterMappings().Reverse().Where(x => x.Symbol.HasExplicitDefaultValue)) { if (ArgumentHasDefaultValue(argumentMapping, c.Model)) { c.ReportIssue(Rule, argumentMapping.Node, argumentMapping.Symbol.Name); } else if (argumentMapping.Node.NameColon is null) { break; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantArgumentCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using NodeAndSymbol = SonarAnalyzer.Core.Common.NodeAndSymbol; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantArgumentCodeFix : SonarCodeFix { internal const string TitleRemove = "Remove redundant arguments"; internal const string TitleRemoveWithNameAdditions = "Remove redundant arguments with adding named arguments"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantArgument.DiagnosticId); protected override async Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var invocation = GetInvocation(root, diagnostic.Location.SourceSpan); if (invocation == null) { return; } var semanticModel = await context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false); var methodParameterLookup = new CSharpMethodParameterLookup(invocation, semanticModel); var argumentMappings = methodParameterLookup.GetAllArgumentParameterMappings().ToList(); var methodSymbol = methodParameterLookup.MethodSymbol; if (methodSymbol == null) { return; } var argumentsWithDefaultValues = new List(); var argumentsCanBeRemovedWithoutNamed = new List(); var canBeRemovedWithoutNamed = true; var reversedMappings = new List(argumentMappings); reversedMappings.Reverse(); foreach (var argumentMapping in reversedMappings) { var argument = argumentMapping.Node; if (RedundantArgument.ArgumentHasDefaultValue(argumentMapping, semanticModel)) { argumentsWithDefaultValues.Add(argument); if (canBeRemovedWithoutNamed) { argumentsCanBeRemovedWithoutNamed.Add(argument); } } else { if (argument.NameColon == null) { canBeRemovedWithoutNamed = false; } } } if (argumentsCanBeRemovedWithoutNamed.Any()) { context.RegisterCodeFix( TitleRemove, c => RemoveArgumentsAsync(context.Document, argumentsCanBeRemovedWithoutNamed, c), context.Diagnostics); } var cannotBeRemoved = argumentsWithDefaultValues.Except(argumentsCanBeRemovedWithoutNamed); if (cannotBeRemoved.Any()) { context.RegisterCodeFix( TitleRemoveWithNameAdditions, c => RemoveArgumentsAndAddNecessaryNamesAsync(context.Document, invocation.ArgumentList, argumentMappings, argumentsWithDefaultValues, semanticModel, c), context.Diagnostics); } } private static async Task RemoveArgumentsAndAddNecessaryNamesAsync(Document document, ArgumentListSyntax argumentList, IEnumerable argumentMappings, List argumentsToRemove, SemanticModel semanticModel, CancellationToken cancel) { var root = await document.GetSyntaxRootAsync(cancel).ConfigureAwait(false); var newArgumentList = SyntaxFactory.ArgumentList(); var alreadyRemovedOne = false; foreach (var argumentMapping in argumentMappings .Where(argumentMapping => !argumentMapping.Symbol.IsParams)) { var argument = argumentMapping.Node; if (argumentsToRemove.Contains(argument)) { alreadyRemovedOne = true; continue; } newArgumentList = AddArgument(newArgumentList, argumentMapping.Symbol.Name, argument, alreadyRemovedOne); } var paramsArguments = argumentMappings .Where(mapping => mapping.Symbol.IsParams) .ToList(); if (paramsArguments.Any()) { newArgumentList = AddParamsArguments(semanticModel, paramsArguments, newArgumentList); } var newRoot = root.ReplaceNode(argumentList, newArgumentList); return document.WithSyntaxRoot(newRoot); } private static ArgumentListSyntax AddArgument(ArgumentListSyntax argumentList, string parameterName, ArgumentSyntax argument, bool alreadyRemovedOne) { return alreadyRemovedOne ? argumentList.AddArguments( SyntaxFactory.Argument( SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(parameterName)), argument.RefOrOutKeyword, argument.Expression)) : argumentList.AddArguments(argument); } private static ArgumentListSyntax AddParamsArguments(SemanticModel semanticModel, IEnumerable paramsArguments, ArgumentListSyntax argumentList) { var firstParamsMapping = paramsArguments.First(); var firstParamsArgument = firstParamsMapping.Node; var paramsParameter = firstParamsMapping.Symbol; if (firstParamsArgument.NameColon != null) { return argumentList.AddArguments(firstParamsArgument); } if (paramsArguments.Count() == 1 && paramsParameter.Type.Equals( semanticModel.GetTypeInfo(firstParamsArgument.Expression).Type)) { return argumentList.AddArguments( SyntaxFactory.Argument( SyntaxFactory.NameColon( SyntaxFactory.IdentifierName(paramsParameter.Name)), firstParamsArgument.RefOrOutKeyword, firstParamsArgument.Expression)); } return argumentList.AddArguments( SyntaxFactory.Argument( SyntaxFactory.NameColon( SyntaxFactory.IdentifierName(paramsParameter.Name)), SyntaxFactory.Token(SyntaxKind.None), SyntaxFactory.ImplicitArrayCreationExpression( SyntaxFactory.InitializerExpression( SyntaxKind.ArrayInitializerExpression, SyntaxFactory.SeparatedList(paramsArguments.Select(arg => arg.Node.Expression)))))); } private static async Task RemoveArgumentsAsync(Document document, IEnumerable arguments, CancellationToken cancel) { var root = await document.GetSyntaxRootAsync(cancel).ConfigureAwait(false); var newRoot = root.RemoveNodes(arguments, SyntaxRemoveOptions.KeepNoTrivia | SyntaxRemoveOptions.AddElasticMarker); return document.WithSyntaxRoot(newRoot); } private static InvocationExpressionSyntax GetInvocation(SyntaxNode root, TextSpan diagnosticSpan) { var argumentSyntax = root.FindNode(diagnosticSpan) as ArgumentSyntax; return argumentSyntax?.FirstAncestorOrSelf(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantCast.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantCast : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1905"; private const string MessageFormat = "Remove this unnecessary cast to '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ISet CastIEnumerableMethods = new HashSet { "Cast", "OfType" }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var castExpression = (CastExpressionSyntax)c.Node; CheckCastExpression(c, castExpression.Expression, castExpression.Type, castExpression.Type.GetLocation()); }, SyntaxKind.CastExpression); context.RegisterNodeAction( c => { var castExpression = (BinaryExpressionSyntax)c.Node; CheckCastExpression(c, castExpression.Left, castExpression.Right, castExpression.OperatorToken.CreateLocation(castExpression.Right)); }, SyntaxKind.AsExpression); context.RegisterNodeAction( CheckExtensionMethodInvocation, SyntaxKind.InvocationExpression); } private static void CheckCastExpression(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression, ExpressionSyntax type, Location location) { if (!expression.IsDefaultLiteral() && expression.RemoveParentheses().Kind() is not (SyntaxKind.StackAllocArrayCreationExpression or SyntaxKindEx.ImplicitStackAllocArrayCreationExpression) && context.Model.GetTypeInfo(expression) is { Type: { } expressionType } expressionTypeInfo && context.Model.GetTypeInfo(type) is { Type: { } castType } && expressionType.Equals(castType) && FlowStateEquals(context.Model, expressionTypeInfo, type, castType)) { ReportIssue(context, expression, castType, location); } } private static bool FlowStateEquals(SemanticModel model, TypeInfo expressionTypeInfo, ExpressionSyntax castTypeExpression, ITypeSymbol castType) { // The Nullability() annotation of the castType symbol is always "None". We need to look at the syntax to determine the nullability. var castingToNullable = castTypeExpression.IsKind(SyntaxKind.NullableType) || castType.IsNullableValueType(); // Nullable is considered the same as int? checked via SyntaxKind.NullableType above. var outerNullability = expressionTypeInfo.Nullability().FlowState switch { NullableFlowState.None => true, NullableFlowState.MaybeNull => castingToNullable, // false when (string)maybeNull which is a narrowing cast and therefore not redundant NullableFlowState.NotNull => !castingToNullable, // false when (string?)nonNull which is a widening cast but not redundant in cases like tuples (a: (string?)"", b: "") _ => true, }; return outerNullability && InnerNullabilityEquals(model, expressionTypeInfo.Type, castType); // The nullable annotations of the type arguments also needs to be identical: IEnumerable is not the same as IEnumerable. // We recursively check the type arguments for ? annotations. static bool InnerNullabilityEquals(SemanticModel model, ITypeSymbol expressionTypeSymbol, ITypeSymbol castTypeSymbol) { var expressionTypeArguments = (expressionTypeSymbol as INamedTypeSymbol)?.TypeArguments ?? ImmutableArray.Empty; // In opposition to the outer Nullability(), the inner nullability is present on the cast symbol. Therefore we can use the symbols nullability for this check. var castTypeArguments = (castTypeSymbol as INamedTypeSymbol)?.TypeArguments ?? ImmutableArray.Empty; Debug.Assert(expressionTypeArguments.Length == castTypeArguments.Length, "Always true, because otherwise the expressionType.Equals(castType) check in CheckCastExpression is false."); foreach (var typeArgumentPair in expressionTypeArguments.Zip(castTypeArguments, Pair.From)) { var typeArgumentNullablityMatch = typeArgumentPair.Left.NullableAnnotation() == typeArgumentPair.Right.NullableAnnotation(); if (!typeArgumentNullablityMatch || !InnerNullabilityEquals(model, typeArgumentPair.Left, typeArgumentPair.Right)) { return false; } } return true; } } private static void CheckExtensionMethodInvocation(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (GetEnumerableExtensionSymbol(invocation, context.Model) is { } methodSymbol) { var returnType = methodSymbol.ReturnType; if (GetGenericTypeArgument(returnType) is { } castType) { if (methodSymbol.Name == "OfType" && CanHaveNullValue(castType)) { // OfType() filters 'null' values from enumerables return; } var elementType = GetElementType(invocation, methodSymbol, context.Model); if (elementType != null && elementType.Equals(castType) && elementType.NullableAnnotation() == castType.NullableAnnotation()) { var methodCalledAsStatic = methodSymbol.MethodKind == MethodKind.Ordinary; ReportIssue(context, invocation, returnType, GetReportLocation(invocation, methodCalledAsStatic)); } } } } private static void ReportIssue(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression, ITypeSymbol castType, Location location) => context.ReportIssue(Rule, location, castType.ToMinimalDisplayString(context.Model, expression.SpanStart)); /// If the invocation one of the extensions, returns the method symbol. private static IMethodSymbol GetEnumerableExtensionSymbol(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.GetMethodCallIdentifier() is { } methodName && CastIEnumerableMethods.Contains(methodName.ValueText) && semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionOn(KnownType.System_Collections_IEnumerable) ? methodSymbol : null; private static ITypeSymbol GetGenericTypeArgument(ITypeSymbol type) => type is INamedTypeSymbol returnType && returnType.Is(KnownType.System_Collections_Generic_IEnumerable_T) ? returnType.TypeArguments.Single() : null; private static bool CanHaveNullValue(ITypeSymbol type) => type.IsReferenceType || type.Is(KnownType.System_Nullable_T); private static Location GetReportLocation(InvocationExpressionSyntax invocation, bool methodCalledAsStatic) => methodCalledAsStatic is false && invocation.Expression is MemberAccessExpressionSyntax memberAccess ? memberAccess.OperatorToken.CreateLocation(invocation) : invocation.Expression.GetLocation(); private static ITypeSymbol GetElementType(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol, SemanticModel semanticModel) { return semanticModel.GetTypeInfo(CollectionExpression(invocation, methodSymbol)).Type switch { INamedTypeSymbol { TypeArguments: { Length: 1 } typeArguments } => typeArguments.First(), IArrayTypeSymbol { Rank: 1 } arrayType => arrayType.ElementType, // casting is necessary for multidimensional arrays _ => null }; static ExpressionSyntax CollectionExpression(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) => methodSymbol.MethodKind is MethodKind.ReducedExtension ? ReducedExtensionExpression(invocation) : invocation.ArgumentList.Arguments.FirstOrDefault()?.Expression; static ExpressionSyntax ReducedExtensionExpression(InvocationExpressionSyntax invocation) => invocation.Expression is MemberAccessExpressionSyntax { Expression: { } memberAccessExpression } ? memberAccessExpression : invocation.GetParentConditionalAccessExpression()?.Expression; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantCastCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantCastCodeFix : SonarCodeFix { internal const string Title = "Remove redundant cast"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantCast.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); if (syntaxNode.Parent is CastExpressionSyntax castExpression) { //this is handled by IDE0004 code fix. return Task.CompletedTask; } var castInvocation = syntaxNode as InvocationExpressionSyntax; var memberAccess = syntaxNode as MemberAccessExpressionSyntax; if (castInvocation != null || memberAccess != null) { context.RegisterCodeFix( Title, c => { var newRoot = RemoveCall(root, castInvocation, memberAccess); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } if (syntaxNode is BinaryExpressionSyntax asExpression) { context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( asExpression, asExpression.Left.WithTriviaFrom(asExpression)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } private static SyntaxNode RemoveCall(SyntaxNode root, InvocationExpressionSyntax castInvocation, MemberAccessExpressionSyntax memberAccess) { return castInvocation != null ? RemoveExtensionMethodCall(root, castInvocation) : RemoveStaticMemberCall(root, memberAccess); } private static SyntaxNode RemoveStaticMemberCall(SyntaxNode root, MemberAccessExpressionSyntax memberAccess) { var invocation = (InvocationExpressionSyntax)memberAccess.Parent; return root.ReplaceNode(invocation, invocation.ArgumentList.Arguments.First().Expression); } private static SyntaxNode RemoveExtensionMethodCall(SyntaxNode root, InvocationExpressionSyntax invocation) { var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression; return root.ReplaceNode(invocation, memberAccess.Expression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantConditionalAroundAssignment.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantConditionalAroundAssignment : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3440"; private const string MessageFormat = "Remove this useless conditional."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(UselessConditionIfStatement, SyntaxKind.IfStatement); context.RegisterNodeAction(UselessConditionSwitchExpression, SyntaxKindEx.SwitchExpression); } private static void UselessConditionIfStatement(SonarSyntaxNodeReportingContext c) { var ifStatement = (IfStatementSyntax)c.Node; if (ifStatement.Else is not null || ifStatement.Parent is ElseClauseSyntax || ifStatement.FirstAncestorOrSelf()?.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKindEx.InitAccessorDeclaration || !TryGetNotEqualsCondition(ifStatement, out var condition) || !TryGetSingleAssignment(ifStatement, out var assignment)) { return; } var expression1Condition = condition.Left?.RemoveParentheses(); var expression2Condition = condition.Right?.RemoveParentheses(); var expression1Assignment = assignment.Left?.RemoveParentheses(); var expression2Assignment = assignment.Right?.RemoveParentheses(); if (!AreMatchingExpressions(expression1Condition, expression2Condition, expression2Assignment, expression1Assignment) && !AreMatchingExpressions(expression1Condition, expression2Condition, expression1Assignment, expression2Assignment)) { return; } if (c.Model.GetSymbolInfo(assignment.Left).Symbol is not IPropertySymbol) { c.ReportIssue(Rule, condition); } } private static void UselessConditionSwitchExpression(SonarSyntaxNodeReportingContext c) { var switchExpression = (SwitchExpressionSyntaxWrapper)c.Node; if (switchExpression.SyntaxNode.GetFirstNonParenthesizedParent() is not AssignmentExpressionSyntax) { return; } var hasDiscard = switchExpression.Arms.Any(x => DiscardPatternSyntaxWrapper.IsInstance(x.Pattern)); foreach (var switchArm in switchExpression.Arms) { var condition = switchArm.Pattern.SyntaxNode; var constantPattern = condition.DescendantNodesAndSelf().FirstOrDefault(x => x.IsKind(SyntaxKindEx.ConstantPattern)); var expression = switchArm.Expression; if ((constantPattern is not null && !(condition.IsKind(SyntaxKindEx.NotPattern) && (switchArm.WhenClause.SyntaxNode is not null || switchExpression.Arms.Count != 1)) && CSharpEquivalenceChecker.AreEquivalent(expression, ((ConstantPatternSyntaxWrapper)constantPattern).Expression) && !hasDiscard) || (condition.IsKind(SyntaxKindEx.DiscardPattern) && switchExpression.Arms.Count == 1)) { c.ReportIssue(Rule, condition); } } } private static bool TryGetNotEqualsCondition(IfStatementSyntax ifStatement, out BinaryExpressionSyntax condition) { condition = ifStatement.Condition?.RemoveParentheses() as BinaryExpressionSyntax; return condition is not null && condition.IsKind(SyntaxKind.NotEqualsExpression); } private static bool TryGetSingleAssignment(IfStatementSyntax ifStatement, out AssignmentExpressionSyntax assignment) { var statement = ifStatement.Statement; if (statement is not BlockSyntax block || block.Statements.Count != 1) { assignment = null; return false; } statement = block.Statements.First(); assignment = (statement as ExpressionStatementSyntax)?.Expression as AssignmentExpressionSyntax; return assignment is not null && assignment.IsKind(SyntaxKind.SimpleAssignmentExpression); } private static bool AreMatchingExpressions(SyntaxNode condition1, SyntaxNode condition2, SyntaxNode assignment1, SyntaxNode assignment2) => CSharpEquivalenceChecker.AreEquivalent(condition1, assignment1) && CSharpEquivalenceChecker.AreEquivalent(condition2, assignment2); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantConditionalAroundAssignmentCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantConditionalAroundAssignmentCodeFix : SonarCodeFix { private const string Title = "Remove redundant conditional"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantConditionalAroundAssignment.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var condition = root.FindNode(diagnosticSpan) as ExpressionSyntax; var ifStatement = condition?.FirstAncestorOrSelf(); if (ifStatement is not null) { return HandleIfStatement(root, context, ifStatement); } var switchExpression = root.FindNode(diagnosticSpan).FirstAncestorOrSelf(x => x.IsKind(SyntaxKindEx.SwitchExpression)); if (switchExpression is not null) { return HandleSwitchExpression(root, context, switchExpression); } else { return Task.CompletedTask; } } private static Task HandleIfStatement(SyntaxNode root, SonarCodeFixContext context, IfStatementSyntax ifStatement) { var statement = ifStatement.Statement; if (statement is BlockSyntax block) { statement = block.Statements.FirstOrDefault(); } if (statement is null) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( ifStatement, statement.WithTriviaFrom(ifStatement)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static Task HandleSwitchExpression(SyntaxNode root, SonarCodeFixContext context, SyntaxNode switchExpression) { var switchArm = ((SwitchExpressionSyntaxWrapper)switchExpression).Arms.FirstOrDefault(); if (switchArm.SyntaxNode is null || switchArm.SyntaxNode.Parent.ChildNodes().Count(x => x.IsKind(SyntaxKindEx.SwitchExpressionArm)) != 1) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( switchExpression, switchArm.Expression.WithTriviaFrom(switchExpression)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantDeclaration : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3257"; internal const string DiagnosticTypeKey = "diagnosticType"; internal const string ParameterNameKey = "ParameterNameKey"; private const string MessageFormat = "Remove the {0}; it is redundant."; private const string UseDiscardMessageFormat = "'{0}' is not used. Use discard parameter instead."; internal enum RedundancyType { LambdaParameterType, ArraySize, ArrayType, ExplicitDelegate, ExplicitNullable, ObjectInitializer, DelegateParameterList } private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor DiscardRule = DescriptorFactory.Create(DiagnosticId, UseDiscardMessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule, DiscardRule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { ReportOnExplicitDelegateCreation(c); ReportRedundantNullableConstructorCall(c); ReportOnRedundantObjectInitializer(c); }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); context.RegisterNodeAction(ReportOnRedundantParameterList, SyntaxKind.AnonymousMethodExpression); context.RegisterNodeAction(ReportRedundancyInArrayCreation, SyntaxKind.ArrayCreationExpression); context.RegisterNodeAction(VisitParenthesizedLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); } #region Type specification in lambda private static readonly ISet RefOutKeywords = new HashSet { SyntaxKind.RefKeyword, SyntaxKind.OutKeyword }; private static void VisitParenthesizedLambdaExpression(SonarSyntaxNodeReportingContext context) { var lambda = (ParenthesizedLambdaExpressionSyntax)context.Node; CheckUnusedParameters(context, lambda); CheckTypeSpecifications(context, lambda); } private static void CheckTypeSpecifications(SonarSyntaxNodeReportingContext context, ParenthesizedLambdaExpressionSyntax lambda) { if (!IsParameterListModifiable(lambda)) { return; } if (!(context.Model.GetSymbolInfo(lambda).Symbol is IMethodSymbol symbol)) { return; } var newParameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(lambda.ParameterList.Parameters.Select(x => SyntaxFactory.Parameter(x.Identifier)))); if (lambda.ChangeSyntaxElement(lambda.WithParameterList(newParameterList), context.Model, out var newSemanticModel) is { } newLambda && newSemanticModel.GetSymbolInfo(newLambda) is { Symbol: IMethodSymbol newSymbol } && ParameterTypesMatch(symbol, newSymbol)) { foreach (var parameter in lambda.ParameterList.Parameters) { context.ReportIssue( Rule, parameter.Type, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.LambdaParameterType)), "type specification"); } } } private static void CheckUnusedParameters(SonarSyntaxNodeReportingContext context, ParenthesizedLambdaExpressionSyntax lambda) { if (context.Compilation.IsLambdaDiscardParameterSupported()) { var usedIdentifiers = UsedIdentifiers(lambda).ToList(); foreach (var parameter in lambda.ParameterList.Parameters) { var parameterName = parameter.Identifier.Text; if (parameterName != SyntaxConstants.Discard && !usedIdentifiers.Contains(parameterName)) { context.ReportIssue( DiscardRule, parameter, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.LambdaParameterType)).Add(ParameterNameKey, parameterName), parameterName); } } } } private static IEnumerable UsedIdentifiers(ParenthesizedLambdaExpressionSyntax lambda) => lambda.Body.DescendantNodesAndSelf().OfType().Select(x => x.Identifier.Text); private static bool IsParameterListModifiable(ParenthesizedLambdaExpressionSyntax lambda) => lambda is { ParameterList.Parameters: { Count: > 0 } parameters } && parameters.All(x => x.Type is not null && x.Modifiers.All(m => !RefOutKeywords.Contains(m.Kind()))); private static bool ParameterTypesMatch(IMethodSymbol method1, IMethodSymbol method2) { for (var i = 0; i < method1.Parameters.Length; i++) { if (!method1.Parameters[i].Type.Equals(method2.Parameters[i].Type)) { return false; } } return true; } #endregion #region Nullable constructor call private static void ReportRedundantNullableConstructorCall(SonarSyntaxNodeReportingContext context) { var objectCreation = ObjectCreationFactory.Create(context.Node); if (!IsNullableCreation(objectCreation, context.Model)) { return; } if (IsInNotVarDeclaration(objectCreation.Expression) || IsInAssignmentOrReturnValue(objectCreation.Expression) || IsInArgumentAndCanBeChanged(objectCreation, context.Model)) { ReportIssueOnRedundantObjectCreation(context, objectCreation.Expression, "explicit nullable type creation", RedundancyType.ExplicitNullable); } } private static bool IsNullableCreation(IObjectCreation objectCreation, SemanticModel model) => objectCreation.ArgumentList is { Arguments.Count: 1 } && objectCreation.TypeSymbol(model).OriginalDefinition.Is(KnownType.System_Nullable_T); private static bool IsInAssignmentOrReturnValue(SyntaxNode objectCreation) => objectCreation.GetFirstNonParenthesizedParent() switch { AssignmentExpressionSyntax _ => true, ReturnStatementSyntax _ => true, LambdaExpressionSyntax _ => true, _ => false }; private static bool IsInNotVarDeclaration(SyntaxNode objectCreation) { var variableDeclaration = objectCreation.GetSelfOrTopParenthesizedExpression() .Parent?.Parent?.Parent as VariableDeclarationSyntax; return variableDeclaration is { Type.IsVar: false }; } #endregion #region Array (creation, size, type) private static void ReportRedundancyInArrayCreation(SonarSyntaxNodeReportingContext context) { var array = (ArrayCreationExpressionSyntax)context.Node; ReportRedundantArraySizeSpecifier(context, array); ReportRedundantArrayTypeSpecifier(context, array); } private static void ReportRedundantArraySizeSpecifier(SonarSyntaxNodeReportingContext context, ArrayCreationExpressionSyntax array) { if (array.Initializer is null || array.Type is null) { return; } var rankSpecifier = array.Type.RankSpecifiers.FirstOrDefault(); if (rankSpecifier is null || rankSpecifier.Sizes.Any(SyntaxKind.OmittedArraySizeExpression)) { return; } foreach (var size in rankSpecifier.Sizes) { context.ReportIssue( Rule, size, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.ArraySize)), "array size specification"); } } private static void ReportRedundantArrayTypeSpecifier(SonarSyntaxNodeReportingContext context, ArrayCreationExpressionSyntax array) { if (array.Initializer is null || !array.Initializer.Expressions.Any() || array.Initializer.Expressions.All(ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance) || array.Type is null || array.Type.RankSpecifiers.Count > 1) { return; } var rankSpecifier = array.Type.RankSpecifiers.FirstOrDefault(); if (rankSpecifier is null || rankSpecifier.Sizes.Any(x => !x.IsKind(SyntaxKind.OmittedArraySizeExpression))) { return; } if (context.Model.GetTypeInfo(array.Type).Type is not IArrayTypeSymbol { ElementType: { TypeKind: not TypeKind.Error } arrayElementType }) { return; } var canBeSimplified = array.Initializer.Expressions .Select(x => context.Model.GetTypeInfo(x).Type) .All(arrayElementType.Equals); if (canBeSimplified) { var location = Location.Create(array.SyntaxTree, TextSpan.FromBounds(array.Type.ElementType.SpanStart, array.Type.RankSpecifiers.Last().SpanStart)); context.ReportIssue(Rule, location, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.ArrayType)), "array type"); } } #endregion #region Object initializer private static void ReportOnRedundantObjectInitializer(SonarSyntaxNodeReportingContext context) { var objectCreation = ObjectCreationFactory.Create(context.Node); if (objectCreation.ArgumentList is not null && objectCreation.Initializer is not null && !objectCreation.Initializer.Expressions.Any()) { context.ReportIssue(Rule, objectCreation.Initializer, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.ObjectInitializer)), "initializer"); } } #endregion #region Explicit delegate creation private static void ReportOnExplicitDelegateCreation(SonarSyntaxNodeReportingContext context) { var objectCreation = ObjectCreationFactory.Create(context.Node); var argumentExpression = objectCreation.ArgumentList?.Arguments.FirstOrDefault()?.Expression; if (argumentExpression is null) { return; } if (!IsDelegateCreation(objectCreation, context.Model)) { return; } if (IsInDeclarationNotVarNotDelegate(objectCreation.Expression, context.Model) || IsAssignmentNotDelegate(objectCreation.Expression, context.Model) || IsReturnValueNotDelegate(objectCreation.Expression, context.Model) || IsInArgumentAndCanBeChanged( objectCreation, context.Model, x => x.ArgumentList.Arguments.Any(a => a.Expression.IsDynamic(context.Model)))) { ReportIssueOnRedundantObjectCreation(context, objectCreation.Expression, "explicit delegate creation", RedundancyType.ExplicitDelegate); } } private static bool IsInDeclarationNotVarNotDelegate(SyntaxNode objectCreation, SemanticModel model) { var variableDeclaration = objectCreation.GetSelfOrTopParenthesizedExpression() .Parent?.Parent?.Parent as VariableDeclarationSyntax; var type = variableDeclaration?.Type; if (type is null || type.IsVar) { return false; } var typeInformation = model.GetTypeInfo(type).Type; return !typeInformation.Is(KnownType.System_Delegate); } private static bool IsDelegateCreation(IObjectCreation objectCreation, SemanticModel model) => objectCreation.TypeSymbol(model) is INamedTypeSymbol { TypeKind: TypeKind.Delegate }; private static bool IsReturnValueNotDelegate(SyntaxNode objectCreation, SemanticModel model) { var parent = objectCreation.GetFirstNonParenthesizedParent(); if (parent is not ReturnStatementSyntax and not LambdaExpressionSyntax) { return false; } if (model.GetEnclosingSymbol(objectCreation.SpanStart) is not IMethodSymbol enclosing) { return false; } return enclosing.ReturnType is not null && !enclosing.ReturnType.Is(KnownType.System_Delegate); } private static bool IsAssignmentNotDelegate(SyntaxNode objectCreation, SemanticModel model) { var parent = objectCreation.GetFirstNonParenthesizedParent(); if (!(parent is AssignmentExpressionSyntax assignment)) { return false; } var typeInformation = model.GetTypeInfo(assignment.Left).Type; return !typeInformation.Is(KnownType.System_Delegate); } #endregion #region Parameter list private static void ReportOnRedundantParameterList(SonarSyntaxNodeReportingContext context) { var anonymousMethod = (AnonymousMethodExpressionSyntax)context.Node; if (anonymousMethod.ParameterList is null) { return; } if (context.Model.GetSymbolInfo(anonymousMethod).Symbol is not IMethodSymbol methodSymbol) { return; } var parameterNames = methodSymbol.Parameters.Select(x => x.Name).ToHashSet(); var usedParameters = anonymousMethod.Body.DescendantNodes() .OfType() .Where(x => parameterNames.Contains(x.Identifier.ValueText)) .Select(x => context.Model.GetSymbolInfo(x).Symbol as IParameterSymbol) .WhereNotNull() .ToHashSet(); if (!usedParameters.Intersect(methodSymbol.Parameters).Any()) { context.ReportIssue(Rule, anonymousMethod.ParameterList, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, nameof(RedundancyType.DelegateParameterList)), "parameter list"); } } #endregion private static bool IsInArgumentAndCanBeChanged(IObjectCreation objectCreation, SemanticModel model, Func additionalFilter = null) { if (!(objectCreation.Expression.GetFirstNonParenthesizedParent() is ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } argument)) { return false; } if (additionalFilter is not null && additionalFilter(invocation)) { return false; } // In C# 10 and later, the natural type of a lambda expression is Func or Action, // which are implicit convertable to Delegate. In earlier versions // CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type // is raised. // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/lambda-improvements#natural-function-type // If the user specified another delegate type than Action or Func, like EventHandler, we // assume that the called method explicitly requires the more specific concrete delegate type. if (model.GetTypeInfo(objectCreation.Expression) is { Type: { } from, ConvertedType: { } convertedTo } && convertedTo.Is(KnownType.System_Delegate) && (model.SyntaxTree.Options is CSharpParseOptions { LanguageVersion: < LanguageVersionEx.CSharp10 } || !(from.IsAny(KnownType.SystemFuncVariants) || from.IsAny(KnownType.SystemActionVariants)))) { return false; } var methodSymbol = model.GetSymbolInfo(invocation).Symbol; if (methodSymbol is null) { return false; } var newArgument = argument.WithExpression(objectCreation.ArgumentList.Arguments.First().Expression); var newInvocation = invocation.WithArgumentList(invocation.ArgumentList.ReplaceNode(argument, newArgument)); var overloadResolution = model.GetSpeculativeSymbolInfo(invocation.SpanStart, newInvocation, SpeculativeBindingOption.BindAsExpression); // The speculative binding is sometimes unable to do proper overload resolution and returns candidate overloads. // This is good enough for us as long as the original method is part of that list. Note: Any attempts with ChangeSyntaxElement and // TryGetSpeculativeSemanticModel failed to produce better results. return overloadResolution.AllSymbols().Any(x => x.Equals(methodSymbol)); } private static void ReportIssueOnRedundantObjectCreation(SonarSyntaxNodeReportingContext context, SyntaxNode node, string message, RedundancyType redundancyType) { var location = node is ObjectCreationExpressionSyntax objectCreation ? objectCreation.CreateLocation(objectCreation.Type) : node.GetLocation(); context.ReportIssue(Rule, location, ImmutableDictionary.Empty.Add(DiagnosticTypeKey, redundancyType.ToString()), message); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantDeclarationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantDeclarationCodeFix : SonarCodeFix { internal const string TitleRedundantArraySize = "Remove redundant array size"; internal const string TitleRedundantArrayType = "Remove redundant array type"; internal const string TitleRedundantLambdaParameterType = "Remove redundant type declaration"; internal const string TitleRedundantExplicitDelegate = "Remove redundant explicit delegate creation"; internal const string TitleRedundantExplicitNullable = "Remove redundant explicit nullable creation"; internal const string TitleRedundantObjectInitializer = "Remove redundant object initializer"; internal const string TitleRedundantDelegateParameterList = "Remove redundant parameter list"; internal const string TitleRedundantParameterName = "Use discard parameter"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantDeclaration.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); if (!Enum.TryParse(diagnostic.Properties[RedundantDeclaration.DiagnosticTypeKey], out RedundantDeclaration.RedundancyType diagnosticType)) { return Task.CompletedTask; } RegisterAction(syntaxNode, root, diagnosticType, context.Document, diagnostic.Properties, context); return Task.CompletedTask; } private static void RegisterRedundantLambdaParameterAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, ImmutableDictionary properties, SonarCodeFixContext context) { var parentExpression = syntaxNode.Parent?.Parent; if (parentExpression is ParenthesizedLambdaExpressionSyntax lambdaExpressionSyntax) { context.RegisterCodeFix( TitleRedundantParameterName, c => { var parameterName = properties[RedundantDeclaration.ParameterNameKey]; var parameter = lambdaExpressionSyntax.ParameterList.Parameters.Single(parameter => parameter.Identifier.Text == parameterName); var newRoot = root.ReplaceNode(parameter, SyntaxFactory.Parameter(SyntaxFactory.Identifier(SyntaxConstants.Discard))); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } else if (parentExpression is ParameterListSyntax parameterList) { context.RegisterCodeFix( TitleRedundantLambdaParameterType, c => { var newParameterList = parameterList.WithParameters(SyntaxFactory.SeparatedList(parameterList.Parameters.Select(p => SyntaxFactory.Parameter(p.Identifier).WithTriviaFrom(p)))); var newRoot = root.ReplaceNode(parameterList, newParameterList); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } } private static void RegisterRedundantArraySizeAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, SonarCodeFixContext context) { if (syntaxNode.Parent is not ArrayRankSpecifierSyntax arrayRank) { return; } context.RegisterCodeFix( TitleRedundantArraySize, c => { var newArrayRankSpecifier = arrayRank.WithSizes( SyntaxFactory.SeparatedList(arrayRank.Sizes.Select(s => SyntaxFactory.OmittedArraySizeExpression()))); var newRoot = root.ReplaceNode(arrayRank, newArrayRankSpecifier); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterRedundantArrayTypeAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, SonarCodeFixContext context) { var arrayTypeSyntax = syntaxNode as ArrayTypeSyntax ?? syntaxNode.Parent as ArrayTypeSyntax; if (arrayTypeSyntax?.Parent is not ArrayCreationExpressionSyntax arrayCreation) { return; } context.RegisterCodeFix( TitleRedundantArrayType, c => { var implicitArrayCreation = SyntaxFactory.ImplicitArrayCreationExpression(arrayCreation.Initializer); var newRoot = root.ReplaceNode(arrayCreation, implicitArrayCreation); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterRedundantExplicitObjectCreationAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, RedundantDeclaration.RedundancyType diagnosticType, SonarCodeFixContext context) { var title = diagnosticType == RedundantDeclaration.RedundancyType.ExplicitDelegate ? TitleRedundantExplicitDelegate : TitleRedundantExplicitNullable; if (syntaxNode is not ObjectCreationExpressionSyntax objectCreation) { return; } var newExpression = objectCreation.ArgumentList?.Arguments.FirstOrDefault()?.Expression; if (newExpression == null) { return; } context.RegisterCodeFix( title, c => { newExpression = newExpression.WithTriviaFrom(objectCreation); var newRoot = root.ReplaceNode(objectCreation, newExpression); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterRedundantObjectInitializerAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, SonarCodeFixContext context) { if (syntaxNode.Parent is not ObjectCreationExpressionSyntax objectCreation) { return; } context.RegisterCodeFix( TitleRedundantObjectInitializer, c => { var newObjectCreation = objectCreation.WithInitializer(null).WithAdditionalAnnotations(Formatter.Annotation); var newRoot = root.ReplaceNode(objectCreation, newObjectCreation); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterRedundantParameterTypeAction(SyntaxNode syntaxNode, SyntaxNode root, Document document, SonarCodeFixContext context) { if (syntaxNode.Parent is not AnonymousMethodExpressionSyntax anonymousMethod) { return; } context.RegisterCodeFix( TitleRedundantDelegateParameterList, c => { var newAnonymousMethod = anonymousMethod.WithParameterList(null); var newRoot = root.ReplaceNode(anonymousMethod, newAnonymousMethod); return Task.FromResult(document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } private static void RegisterAction(SyntaxNode syntaxNode, SyntaxNode root, RedundantDeclaration.RedundancyType diagnosticType, Document document, ImmutableDictionary properties, SonarCodeFixContext context) { switch (diagnosticType) { case RedundantDeclaration.RedundancyType.LambdaParameterType: RegisterRedundantLambdaParameterAction(syntaxNode, root, document, properties, context); break; case RedundantDeclaration.RedundancyType.ArraySize: RegisterRedundantArraySizeAction(syntaxNode, root, document, context); break; case RedundantDeclaration.RedundancyType.ArrayType: RegisterRedundantArrayTypeAction(syntaxNode, root, document, context); break; case RedundantDeclaration.RedundancyType.ExplicitDelegate: case RedundantDeclaration.RedundancyType.ExplicitNullable: RegisterRedundantExplicitObjectCreationAction(syntaxNode, root, document, diagnosticType, context); break; case RedundantDeclaration.RedundancyType.ObjectInitializer: RegisterRedundantObjectInitializerAction(syntaxNode, root, document, context); break; case RedundantDeclaration.RedundancyType.DelegateParameterList: RegisterRedundantParameterTypeAction(syntaxNode, root, document, context); break; default: throw new NotSupportedException(); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantInheritanceList.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantInheritanceList : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1939"; internal const string RedundantIndexKey = "redundantIndex"; private const string MessageEnum = "'int' should not be explicitly used as the underlying type."; private const string MessageObjectBase = "'Object' should not be explicitly extended."; private const string MessageAlreadyImplements = "'{0}' implements '{1}' so '{1}' can be removed from the inheritance list."; private const string MessageFormat = "{0}"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext() || c.Node is not BaseTypeDeclarationSyntax { BaseList: { Types: { Count: > 0 } } }) { return; } switch (c.Node) { case EnumDeclarationSyntax enumDeclaration: ReportRedundantBaseType(c, enumDeclaration, KnownType.System_Int32, MessageEnum); break; case InterfaceDeclarationSyntax interfaceDeclaration: ReportRedundantInterfaces(c, interfaceDeclaration); break; case TypeDeclarationSyntax nonInterfaceDeclaration: ReportRedundantBaseType(c, nonInterfaceDeclaration, KnownType.System_Object, MessageObjectBase); ReportRedundantInterfaces(c, nonInterfaceDeclaration); break; } }, SyntaxKind.EnumDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static void ReportRedundantBaseType(SonarSyntaxNodeReportingContext context, BaseTypeDeclarationSyntax typeDeclaration, KnownType redundantType, string message) { var baseTypeSyntax = typeDeclaration.BaseList.Types.First().Type; if (context.Model.GetSymbolInfo(baseTypeSyntax).Symbol is ITypeSymbol baseTypeSymbol && baseTypeSymbol.Is(redundantType)) { var location = GetLocationWithToken(baseTypeSyntax, typeDeclaration.BaseList.Types); context.ReportIssue(Rule, location, DiagnosticsProperties(0), message); } } private static void ReportRedundantInterfaces(SonarSyntaxNodeReportingContext context, BaseTypeDeclarationSyntax typeDeclaration) { var declaredType = context.Model.GetDeclaredSymbol(typeDeclaration); if (declaredType is null) { return; } var baseList = typeDeclaration.BaseList; var interfaceTypesWithAllInterfaces = GetImplementedInterfaceMappings(baseList, context.Model); for (var i = 0; i < baseList.Types.Count; i++) { var baseType = baseList.Types[i]; if (context.Model.GetSymbolInfo(baseType.Type).Symbol is INamedTypeSymbol interfaceType && interfaceType.IsInterface() && CollidingDeclaration(declaredType, interfaceType, interfaceTypesWithAllInterfaces) is { } collidingDeclaration) { var location = GetLocationWithToken(baseType.Type, baseList.Types); var message = string.Format(MessageAlreadyImplements, collidingDeclaration.ToMinimalDisplayString(context.Model, baseType.Type.SpanStart), interfaceType.ToMinimalDisplayString(context.Model, baseType.Type.SpanStart)); context.ReportIssue(Rule, location, DiagnosticsProperties(i), message); } } } private static MultiValueDictionary GetImplementedInterfaceMappings(BaseListSyntax baseList, SemanticModel semanticModel) => baseList.Types .Select(baseType => semanticModel.GetSymbolInfo(baseType.Type).Symbol as INamedTypeSymbol) .WhereNotNull() .Distinct() .ToMultiValueDictionary(x => x.AllInterfaces); private static INamedTypeSymbol CollidingDeclaration(INamedTypeSymbol declaredType, INamedTypeSymbol interfaceType, MultiValueDictionary interfaceMappings) { var collisionMapping = interfaceMappings.FirstOrDefault(x => x.Key.IsInterface() && x.Value.Contains(interfaceType)); if (collisionMapping.Key is not null) { return collisionMapping.Key; } var baseClassMapping = interfaceMappings.FirstOrDefault(x => x.Key.IsClass()); if (baseClassMapping.Key is null) { return null; } var canBeRemoved = CanInterfaceBeRemovedBasedOnMembers(declaredType, interfaceType); return canBeRemoved ? baseClassMapping.Key : null; } private static bool CanInterfaceBeRemovedBasedOnMembers(INamedTypeSymbol declaredType, INamedTypeSymbol interfaceType) { var allMembersOfInterface = AllInterfacesAndSelf(interfaceType).SelectMany(x => x.GetMembers()).ToImmutableArray(); if (!allMembersOfInterface.Any()) { return false; } foreach (var interfaceMember in allMembersOfInterface) { if (declaredType.FindImplementationForInterfaceMember(interfaceMember) is { } classMember && (classMember.ContainingType.Equals(declaredType) || !classMember.ContainingType.Interfaces.Any(x => AllInterfacesAndSelf(x).Contains(interfaceType)))) { return false; } } return true; static IEnumerable AllInterfacesAndSelf(INamedTypeSymbol interfaceType) => interfaceType.AllInterfaces.Concat(new[] { interfaceType }); } private static Location GetLocationWithToken(TypeSyntax type, SeparatedSyntaxList baseTypes) { var span = baseTypes.Count == 1 || baseTypes.First().Type != type ? TextSpan.FromBounds(type.GetFirstToken().GetPreviousToken().Span.Start, type.Span.End) : TextSpan.FromBounds(type.SpanStart, type.GetLastToken().GetNextToken().Span.End); return Location.Create(type.SyntaxTree, span); } private static ImmutableDictionary DiagnosticsProperties(int redundantIndex) => ImmutableDictionary.Create().Add(RedundantIndexKey, redundantIndex.ToString()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantInheritanceListCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantInheritanceListCodeFix : SonarCodeFix { private const string Title = "Remove redundant declaration"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantInheritanceList.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var baseList = (BaseListSyntax)root.FindNode(diagnosticSpan); var redundantIndex = int.Parse(diagnostic.Properties[RedundantInheritanceList.RedundantIndexKey]); context.RegisterCodeFix( Title, c => { var newRoot = RemoveDeclaration(root, baseList, redundantIndex); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } internal static bool HasLineEnding(SyntaxNode node) => node.HasTrailingTrivia && node.GetTrailingTrivia().Last().IsKind(SyntaxKind.EndOfLineTrivia); private static bool HasLineEnding(SyntaxToken token) => token.HasTrailingTrivia && token.TrailingTrivia.Last().IsKind(SyntaxKind.EndOfLineTrivia); private static SyntaxNode RemoveDeclaration(SyntaxNode root, BaseListSyntax baseList, int redundantIndex) { var newBaseList = baseList.RemoveNode(baseList.Types[redundantIndex], SyntaxRemoveOptions.KeepNoTrivia) .WithAdditionalAnnotations(Formatter.Annotation); if (newBaseList.Types.Count != 0) { return root.ReplaceNode(baseList, newBaseList); } var baseTypeHadLineEnding = HasLineEnding(baseList.Types[redundantIndex]); var colonHadLineEnding = HasLineEnding(baseList.ColonToken); var typeNameHadLineEnding = HasLineEnding(((BaseTypeDeclarationSyntax)baseList.Parent).Identifier); var annotation = new SyntaxAnnotation(); var newRoot = root.ReplaceNode(baseList.Parent, baseList.Parent.WithAdditionalAnnotations(annotation)); var declaration = (BaseTypeDeclarationSyntax)newRoot.GetAnnotatedNodes(annotation).First(); newRoot = newRoot.RemoveNode(declaration.BaseList, SyntaxRemoveOptions.KeepNoTrivia); declaration = (BaseTypeDeclarationSyntax)newRoot.GetAnnotatedNodes(annotation).First(); var needsNewLine = !typeNameHadLineEnding && (colonHadLineEnding || baseTypeHadLineEnding); if (needsNewLine) { var trivia = SyntaxFactory.TriviaList(); if (declaration.Identifier.HasTrailingTrivia) { trivia = declaration.Identifier.TrailingTrivia; } trivia = colonHadLineEnding ? trivia.Add(baseList.ColonToken.TrailingTrivia.Last()) : trivia.AddRange(baseList.Types[redundantIndex].GetTrailingTrivia()); newRoot = newRoot.ReplaceToken(declaration.Identifier, declaration.Identifier.WithTrailingTrivia(trivia)); } declaration = (BaseTypeDeclarationSyntax)newRoot.GetAnnotatedNodes(annotation).First(); return newRoot.ReplaceNode(declaration, declaration.WithoutAnnotations(annotation)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantJumpStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantJumpStatement : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3626"; private const string MessageFormat = "Remove this redundant jump."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( CheckForRedundantJumps, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.AddAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.AnonymousMethodExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); private static void CheckForRedundantJumps(SonarSyntaxNodeReportingContext context) { if (!CSharpControlFlowGraph.TryGet(context.Node, context.Model, out var cfg)) { return; } var yieldStatementCount = context.Node.DescendantNodes().OfType().Count(); var removableJumps = cfg.Blocks.OfType().Where(x => IsJumpRemovable(x, yieldStatementCount)); foreach (var jumpBlock in removableJumps) { context.ReportIssue(Rule, jumpBlock.JumpNode); } } private static bool IsJumpRemovable(JumpBlock jumpBlock, int yieldStatementCount) => !IsInsideSwitch(jumpBlock) && !IsReturnWithExpression(jumpBlock) && !IsThrow(jumpBlock) && !IsYieldReturn(jumpBlock) && !IsOnlyYieldBreak(jumpBlock, yieldStatementCount) && !IsValidJumpInsideTryCatch(jumpBlock) && !IsReturnWithFollowingLocalFunction(jumpBlock) && jumpBlock.SuccessorBlock == jumpBlock.WouldBeSuccessor; private static bool IsValidJumpInsideTryCatch(JumpBlock jumpBlock) => jumpBlock.WouldBeSuccessor is BranchBlock branchBlock && branchBlock.BranchingNode is FinallyClauseSyntax && branchBlock.AllSuccessorBlocks.Count > 1; private static bool IsInsideSwitch(JumpBlock jumpBlock) => // Not reporting inside switch, as the jumps might not be removable jumpBlock.JumpNode.AncestorsAndSelf().OfType().Any(); private static bool IsYieldReturn(JumpBlock jumpBlock) => // yield return cannot be redundant jumpBlock.JumpNode is YieldStatementSyntax yieldStatement && yieldStatement.IsKind(SyntaxKind.YieldReturnStatement); private static bool IsOnlyYieldBreak(JumpBlock jumpBlock, int yieldStatementCount) => jumpBlock.JumpNode is YieldStatementSyntax yieldStatement && yieldStatement.IsKind(SyntaxKind.YieldBreakStatement) && yieldStatementCount == 1; private static bool IsThrow(JumpBlock jumpBlock) => jumpBlock.JumpNode.Kind() is SyntaxKind.ThrowStatement or SyntaxKindEx.ThrowExpression; private static bool IsReturnWithExpression(JumpBlock jumpBlock) => jumpBlock.JumpNode is ReturnStatementSyntax { Expression: not null }; private static bool IsReturnWithFollowingLocalFunction(JumpBlock jumpBlock) => jumpBlock.JumpNode.Parent.ChildNodes().Reverse().TakeWhile(x => x != jumpBlock.JumpNode).Any(x => x.IsKind(SyntaxKindEx.LocalFunctionStatement)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantModifier.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantModifier : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2333"; private const string MessageFormat = "'{0}' is {1} in this context."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet UnsafeConstructKinds = new HashSet { SyntaxKind.AddressOfExpression, SyntaxKind.PointerIndirectionExpression, SyntaxKind.SizeOfExpression, SyntaxKind.PointerType, SyntaxKind.FixedStatement, SyntaxKind.StackAllocArrayCreationExpression }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( CheckSealedMemberInSealedClass, SyntaxKind.EventDeclaration, SyntaxKind.EventFieldDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( CheckTypeDeclarationForRedundantPartial, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); context.RegisterNodeAction( CheckForUnnecessaryUnsafeBlocks, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); context.RegisterNodeAction(c => { if (CheckedWalker.IsTopLevel(c.Node)) { new CheckedWalker(c).SafeVisit(c.Node); } }, SyntaxKind.CheckedExpression, SyntaxKind.CheckedStatement, SyntaxKind.UncheckedExpression, SyntaxKind.UncheckedStatement); } private static void CheckForUnnecessaryUnsafeBlocks(SonarSyntaxNodeReportingContext context) { var typeDeclaration = (TypeDeclarationSyntax)context.Node; if (typeDeclaration.Parent is TypeDeclarationSyntax || context.IsRedundantPositionalRecordContext()) { // only process top level type declarations return; } CheckForUnnecessaryUnsafeBlocksBelow(context, typeDeclaration); } private static void CheckForUnnecessaryUnsafeBlocksBelow(SonarSyntaxNodeReportingContext context, TypeDeclarationSyntax typeDeclaration) { var unsafeKeyword = FindUnsafeKeyword(typeDeclaration); if (unsafeKeyword == default) { foreach (var member in typeDeclaration.Members) { CheckForUnnecessaryUnsafeBlocksInMember(context, member); } } else { MarkAllUnsafeBlockInside(context, typeDeclaration); if (!HasUnsafeConstructInside(typeDeclaration, context.Model)) { ReportOnUnsafeBlock(context, unsafeKeyword.GetLocation()); } } } private static void CheckForUnnecessaryUnsafeBlocksInMember(SonarSyntaxNodeReportingContext context, MemberDeclarationSyntax member) { var unsafeKeyword = FindUnsafeKeyword(member); if (unsafeKeyword != default) { MarkAllUnsafeBlockInside(context, member); if (!HasUnsafeConstructInside(member, context.Model)) { ReportOnUnsafeBlock(context, unsafeKeyword.GetLocation()); } } else if (member is TypeDeclarationSyntax nestedTypeDeclaration) { CheckForUnnecessaryUnsafeBlocksBelow(context, nestedTypeDeclaration); } else { var topLevelUnsafeBlocks = member.DescendantNodes(n => !n.IsKind(SyntaxKind.UnsafeStatement)).OfType(); foreach (var topLevelUnsafeBlock in topLevelUnsafeBlocks) { MarkAllUnsafeBlockInside(context, topLevelUnsafeBlock); if (!HasUnsafeConstructInside(member, context.Model)) { ReportOnUnsafeBlock(context, topLevelUnsafeBlock.UnsafeKeyword.GetLocation()); } } } } private static bool HasUnsafeConstructInside(SyntaxNode container, SemanticModel semanticModel) => ContainsUnsafeConstruct(container) || ContainsFixedDeclaration(container) || ContainsUnsafeTypedIdentifier(container, semanticModel) || ContainsUnsafeInvocationReturnValue(container, semanticModel) || ContainsUnsafeParameter(container, semanticModel); private static bool ContainsUnsafeParameter(SyntaxNode container, SemanticModel semanticModel) => container.DescendantNodes() .OfType() .Any(x => IsUnsafe(semanticModel.GetDeclaredSymbol(x)?.Type)); private static bool ContainsUnsafeInvocationReturnValue(SyntaxNode container, SemanticModel semanticModel) => container.DescendantNodes() .OfType() .Any(x => semanticModel.GetSymbolInfo(x).Symbol is IMethodSymbol method && IsUnsafe(method.ReturnType)); private static bool ContainsUnsafeTypedIdentifier(SyntaxNode container, SemanticModel semanticModel) => container.DescendantNodes() .OfType() .Any(x => IsUnsafe(semanticModel.GetTypeInfo(x).Type)); private static bool ContainsFixedDeclaration(SyntaxNode container) => container.DescendantNodes() .OfType() .Any(x => x.Modifiers.Any(SyntaxKind.FixedKeyword)); private static bool ContainsUnsafeConstruct(SyntaxNode container) => container.DescendantNodes().Any(x => UnsafeConstructKinds.Contains(x.Kind())); private static bool IsUnsafe(ITypeSymbol type) => type != null && (type.TypeKind == TypeKind.Pointer || (type.TypeKind == TypeKind.Array && IsUnsafe(((IArrayTypeSymbol)type).ElementType))); private static void MarkAllUnsafeBlockInside(SonarSyntaxNodeReportingContext context, SyntaxNode container) { foreach (var @unsafe in container.DescendantNodes().SelectMany(x => x.ChildTokens()).Where(x => x.IsKind(SyntaxKind.UnsafeKeyword))) { ReportOnUnsafeBlock(context, @unsafe.GetLocation()); } } private static void ReportOnUnsafeBlock(SonarSyntaxNodeReportingContext context, Location issueLocation) => context.ReportIssue(Rule, issueLocation, "unsafe", "redundant"); private static SyntaxToken FindUnsafeKeyword(MemberDeclarationSyntax memberDeclaration) => Modifiers(memberDeclaration).FirstOrDefault(x => x.IsKind(SyntaxKind.UnsafeKeyword)); private static void CheckTypeDeclarationForRedundantPartial(SonarSyntaxNodeReportingContext context) { var typeDeclaration = (TypeDeclarationSyntax)context.Node; if (!context.IsRedundantPositionalRecordContext() && typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) && context.Model.GetDeclaredSymbol(typeDeclaration) is { DeclaringSyntaxReferences.Length: <= 1 }) { var keyword = typeDeclaration.Modifiers.First(m => m.IsKind(SyntaxKind.PartialKeyword)); context.ReportIssue(Rule, keyword, "partial", "gratuitous"); } } private static SyntaxTokenList Modifiers(MemberDeclarationSyntax memberDeclaration) => memberDeclaration switch { BasePropertyDeclarationSyntax propertyDeclaration => propertyDeclaration.Modifiers, BaseMethodDeclarationSyntax methodDeclaration => methodDeclaration.Modifiers, BaseFieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Modifiers, DelegateDeclarationSyntax delegateDeclaration => delegateDeclaration.Modifiers, TypeDeclarationSyntax typeDeclaration => typeDeclaration.Modifiers, _ => default }; private static void CheckSealedMemberInSealedClass(SonarSyntaxNodeReportingContext context) { if (Modifiers((MemberDeclarationSyntax)context.Node) is var modifiers && modifiers.Any(SyntaxKind.SealedKeyword) && context.ContainingSymbol.ContainingType is { IsSealed: true }) { var keyword = modifiers.First(m => m.IsKind(SyntaxKind.SealedKeyword)); context.ReportIssue(Rule, keyword, "sealed", "redundant"); } } private sealed class CheckedWalker : SafeCSharpSyntaxWalker { private static readonly ISet BinaryOperationsForChecked = new HashSet { SyntaxKind.AddExpression, SyntaxKind.SubtractExpression, SyntaxKind.MultiplyExpression, SyntaxKind.DivideExpression }; private static readonly ISet AssignmentsForChecked = new HashSet { SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression }; private static readonly ISet UnaryOperationsForChecked = new HashSet { SyntaxKind.UnaryMinusExpression, SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression, SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression }; private readonly SonarSyntaxNodeReportingContext context; private bool isCurrentContextChecked; private bool currentContextHasIntegralOperation; public CheckedWalker(SonarSyntaxNodeReportingContext context) { this.context = context; isCurrentContextChecked = context.Node switch { CheckedStatementSyntax statement => statement.IsKind(SyntaxKind.CheckedStatement), CheckedExpressionSyntax expression => expression.IsKind(SyntaxKind.CheckedExpression), _ => false }; } public override void VisitCheckedExpression(CheckedExpressionSyntax node) => VisitChecked(node, SyntaxKind.CheckedExpression, node.Keyword, base.VisitCheckedExpression); public override void VisitCheckedStatement(CheckedStatementSyntax node) => VisitChecked(node, SyntaxKind.CheckedStatement, node.Keyword, base.VisitCheckedStatement); public override void VisitAssignmentExpression(AssignmentExpressionSyntax node) { base.VisitAssignmentExpression(node); if (AssignmentsForChecked.Contains(node.Kind())) { SetHasIntegralOperation(node); } } public override void VisitBinaryExpression(BinaryExpressionSyntax node) { base.VisitBinaryExpression(node); if (BinaryOperationsForChecked.Contains(node.Kind())) { SetHasIntegralOperation(node); } } public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node) { base.VisitPrefixUnaryExpression(node); if (UnaryOperationsForChecked.Contains(node.Kind())) { SetHasIntegralOperation(node); } } public override void VisitCastExpression(CastExpressionSyntax node) { base.VisitCastExpression(node); SetHasIntegralOperation(node); } public static bool IsTopLevel(SyntaxNode node) => !node.HasAncestor(SyntaxKind.CheckedStatement, SyntaxKind.CheckedExpression); private void VisitChecked(T node, SyntaxKind checkedKind, SyntaxToken tokenToReport, Action baseCall) where T : SyntaxNode { var isThisNodeChecked = node.IsKind(checkedKind); var originalIsCurrentContextChecked = isCurrentContextChecked; var originalContextHasIntegralOperation = currentContextHasIntegralOperation; isCurrentContextChecked = isThisNodeChecked; currentContextHasIntegralOperation = false; baseCall(node); var isSimplyRedundant = IsCurrentNodeEmbeddedInsideSameChecked(node, isThisNodeChecked, originalIsCurrentContextChecked); if (isSimplyRedundant || !currentContextHasIntegralOperation) { var keywordToReport = isThisNodeChecked ? "checked" : "unchecked"; context.ReportIssue(Rule, tokenToReport, keywordToReport, "redundant"); } isCurrentContextChecked = originalIsCurrentContextChecked; currentContextHasIntegralOperation = originalContextHasIntegralOperation || (currentContextHasIntegralOperation && isSimplyRedundant); } private bool IsCurrentNodeEmbeddedInsideSameChecked(SyntaxNode node, bool isThisNodeChecked, bool isCurrentContextChecked) => isThisNodeChecked == isCurrentContextChecked && node != context.Node; private void SetHasIntegralOperation(CastExpressionSyntax node) { if (!currentContextHasIntegralOperation) { var expressionType = context.Model.GetTypeInfo(node.Expression).Type; var castedToType = context.Model.GetTypeInfo(node.Type).Type; currentContextHasIntegralOperation = castedToType is not null && expressionType is not null && castedToType.IsAny(KnownType.IntegralNumbers); } } private void SetHasIntegralOperation(ExpressionSyntax node) => currentContextHasIntegralOperation = currentContextHasIntegralOperation || (context.Model.GetSymbolInfo(node).Symbol is IMethodSymbol methodSymbol && methodSymbol.ReceiverType.IsAny(KnownType.IntegralNumbers)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantModifierCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantModifierCodeFix : SonarCodeFix { internal const string TitleUnsafe = "Remove redundant 'unsafe' modifier"; internal const string TitleChecked = "Remove redundant 'checked' and 'unchecked' modifier"; internal const string TitlePartial = "Remove redundant 'partial' modifier"; internal const string TitleSealed = "Remove redundant 'sealed' modifier"; private static readonly SyntaxKind[] SimpleTokenKinds = { SyntaxKind.PartialKeyword, SyntaxKind.SealedKeyword }; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantModifier.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var token = root.FindToken(diagnosticSpan.Start); if (token.IsKind(SyntaxKind.UnsafeKeyword)) { context.RegisterCodeFix(TitleUnsafe, c => ReplaceRoot(context, RemoveRedundantUnsafe(root, token)), context.Diagnostics); } else if (SimpleTokenKinds.Contains(token.Kind())) { var title = token.IsKind(SyntaxKind.PartialKeyword) ? TitlePartial : TitleSealed; context.RegisterCodeFix(title, c => ReplaceRoot(context, RemoveRedundantToken(root, token)), context.Diagnostics); } else if (token.Parent is CheckedStatementSyntax checkedStatement) { context.RegisterCodeFix(TitleChecked, c => ReplaceRoot(context, RemoveRedundantCheckedStatement(root, checkedStatement)), context.Diagnostics); } else if (token.Parent is CheckedExpressionSyntax checkedExpression) { context.RegisterCodeFix(TitleChecked, c => ReplaceRoot(context, RemoveRedundantCheckedExpression(root, checkedExpression)), context.Diagnostics); } return Task.CompletedTask; } private static Task ReplaceRoot(SonarCodeFixContext context, SyntaxNode newRoot) => Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); private static SyntaxNode RemoveRedundantUnsafe(SyntaxNode root, SyntaxToken token) { if (token.Parent is UnsafeStatementSyntax unsafeStatement) { return unsafeStatement.Parent is BlockSyntax parentBlock && parentBlock.Statements.Count == 1 ? root.ReplaceNode(parentBlock, parentBlock.WithStatements(unsafeStatement.Block.Statements).WithAdditionalAnnotations(Formatter.Annotation)) : root.ReplaceNode(unsafeStatement, unsafeStatement.Block.WithAdditionalAnnotations(Formatter.Annotation)); } else { return RemoveRedundantToken(root, token); } } private static SyntaxNode RemoveRedundantToken(SyntaxNode root, SyntaxToken token) { var oldParent = token.Parent; var newParent = oldParent.ReplaceToken(token, SyntaxFactory.Token(SyntaxKind.None)); return root.ReplaceNode(oldParent, newParent.WithLeadingTrivia(oldParent.GetLeadingTrivia())); } private static SyntaxNode RemoveRedundantCheckedStatement(SyntaxNode root, CheckedStatementSyntax checkedStatement) => root.ReplaceNode(checkedStatement, SyntaxFactory.Block(checkedStatement.Block.Statements).WithTriviaFrom(checkedStatement)); private static SyntaxNode RemoveRedundantCheckedExpression(SyntaxNode root, CheckedExpressionSyntax checkedExpression) => root.ReplaceNode(checkedExpression, checkedExpression.Expression.WithTriviaFrom(checkedExpression)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantNullCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantNullCheck : RedundantNullCheckBase { private const string MessageFormat = "Remove this unnecessary null check; 'is' returns false for nulls."; private const string MessageFormatForPatterns = "Remove this unnecessary null check; it is already done by the pattern match."; private static readonly DiagnosticDescriptor RuleForIs = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor RuleForPatternSyntax = DescriptorFactory.Create(DiagnosticId, MessageFormatForPatterns); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(RuleForIs, RuleForPatternSyntax); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckAndExpression, SyntaxKind.LogicalAndExpression); context.RegisterNodeAction(CheckOrExpression, SyntaxKind.LogicalOrExpression); context.RegisterNodeAction(CheckAndPattern, SyntaxKindEx.AndPattern); context.RegisterNodeAction(CheckOrPattern, SyntaxKindEx.OrPattern); } protected override SyntaxNode GetLeftNode(BinaryExpressionSyntax binaryExpression) => binaryExpression.Left.RemoveParentheses(); protected override SyntaxNode GetRightNode(BinaryExpressionSyntax binaryExpression) => binaryExpression.Right.RemoveParentheses(); protected override SyntaxNode GetNullCheckVariable(SyntaxNode node) => GetNullCheckVariable(node, true); protected override SyntaxNode GetNonNullCheckVariable(SyntaxNode node) => GetNullCheckVariable(node, false); protected override SyntaxNode GetIsOperatorCheckVariable(SyntaxNode node) { var innerExpression = node.RemoveParentheses(); if (innerExpression is BinaryExpressionSyntax binaryExpression && binaryExpression.IsKind(SyntaxKind.IsExpression)) { return GetLeftNode(binaryExpression); } else if (innerExpression.IsKind(SyntaxKindEx.IsPatternExpression)) { var isPatternExpression = (IsPatternExpressionSyntaxWrapper)innerExpression.RemoveParentheses(); if (IsAffirmativePatternMatch(isPatternExpression)) { return isPatternExpression.Expression.RemoveParentheses(); } } return null; // Verifies the given pattern is like "foo is Bar" - where Bar can be various Patterns, except 'null'. static bool IsAffirmativePatternMatch(IsPatternExpressionSyntaxWrapper isPatternWrapper) => !isPatternWrapper.IsNull() && !isPatternWrapper.IsNot(); } protected override SyntaxNode GetInvertedIsOperatorCheckVariable(SyntaxNode node) { var innerExpression = node.RemoveParentheses(); if (innerExpression is PrefixUnaryExpressionSyntax prefixUnary && prefixUnary.IsKind(SyntaxKind.LogicalNotExpression)) { return GetIsOperatorCheckVariable(prefixUnary.Operand); } else if (innerExpression.IsKind(SyntaxKindEx.IsPatternExpression)) { var isPatternExpression = (IsPatternExpressionSyntaxWrapper)innerExpression.RemoveParentheses(); if (IsNegativePatternMatch(isPatternExpression)) { return isPatternExpression.Expression.RemoveParentheses(); } } return null; // Verifies the pattern is like "foo is not Bar" - where Bar can be various Patterns, except 'null'. static bool IsNegativePatternMatch(IsPatternExpressionSyntaxWrapper patternSyntaxWrapper) => patternSyntaxWrapper.IsNot() && !patternSyntaxWrapper.IsNotNull(); } protected override bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) => CSharpEquivalenceChecker.AreEquivalent(node1, node2); private static void CheckAndPattern(SonarSyntaxNodeReportingContext context) { var binaryPatternNode = (BinaryPatternSyntaxWrapper)context.Node; var left = binaryPatternNode.Left.SyntaxNode.RemoveParentheses(); var right = binaryPatternNode.Right.SyntaxNode.RemoveParentheses(); if (IsNotNullPattern(left) && IsAffirmativePatternMatch(right)) { context.ReportIssue(RuleForPatternSyntax, left); } else if (IsNotNullPattern(right) && IsAffirmativePatternMatch(left)) { context.ReportIssue(RuleForPatternSyntax, right); } static bool IsNotNullPattern(SyntaxNode node) => UnaryPatternSyntaxWrapper.IsInstance(node) && ((UnaryPatternSyntaxWrapper)node) is var unaryPatternSyntaxWrapper && unaryPatternSyntaxWrapper.IsNotNull(); // Verifies the given pattern is an affirmative pattern - constant pattern (except 'null'), Declaration pattern, Recursive pattern. // The PatternSyntax appears e.g. in switch arms and is different from IsPatternSyntax. static bool IsAffirmativePatternMatch(SyntaxNode node) => PatternSyntaxWrapper.IsInstance(node) && ((PatternSyntaxWrapper)node) is var isPatternWrapper && !isPatternWrapper.IsNot() && !isPatternWrapper.IsNull(); } private static void CheckOrPattern(SonarSyntaxNodeReportingContext context) { var binaryPatternNode = (BinaryPatternSyntaxWrapper)context.Node; var left = binaryPatternNode.Left.SyntaxNode.RemoveParentheses(); var right = binaryPatternNode.Right.SyntaxNode.RemoveParentheses(); if (PatternSyntaxWrapper.IsInstance(left) && PatternSyntaxWrapper.IsInstance(right)) { var leftPattern = (PatternSyntaxWrapper)left; var rightPattern = (PatternSyntaxWrapper)right; if (leftPattern.IsNull() && IsNegativePatternMatch(rightPattern)) { context.ReportIssue(RuleForPatternSyntax, left); } else if (rightPattern.IsNull() && IsNegativePatternMatch(leftPattern)) { context.ReportIssue(RuleForPatternSyntax, right); } } // Verifies that it's like a negative pattern except 'not null' e.g. 'not Apple', 'not (5 or 6)'. // The PatternSyntax appears e.g. in switch arms and is different from IsPatternSyntax. static bool IsNegativePatternMatch(PatternSyntaxWrapper node) => node.IsNot() && ((UnaryPatternSyntaxWrapper)node) is var unaryPatternSyntaxWrapper && !unaryPatternSyntaxWrapper.Pattern.IsNull(); } /// /// Retrieves the variable that gets null-checked only if the null-check respects the expectation of the caller (it is either an affirmative or a negative check). /// For example: /// - if the node is "foo is null" / "foo == null" and expectedAffirmative is "true", the method will return "foo". /// - if the node is "foo is null" / "foo == null" and expectedAffirmative is "false", the method will return null. /// - if the node is "foo is not null" / "foo != null" / "!(foo is null)" and expectedAffirmative is "true", the method will return null. /// - if the node is "foo is not null" / "foo != null" / "!(foo is null)" and expectedAffirmative is "false", the method will return "foo". /// private static SyntaxNode GetNullCheckVariable(SyntaxNode node, bool expectedAffirmative) { var innerExpression = node.RemoveParentheses(); if (innerExpression is PrefixUnaryExpressionSyntax prefixUnary && prefixUnary.IsKind(SyntaxKind.LogicalNotExpression)) { innerExpression = prefixUnary.Operand; expectedAffirmative = !expectedAffirmative; } if (((ExpressionSyntax)innerExpression).TryGetExpressionComparedToNull(out var compared, out var actualAffirmative) && actualAffirmative == expectedAffirmative) { return compared; } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantNullCheckCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantNullCheckCodeFix : SonarCodeFix { internal const string Title = "Remove this unnecessary null check"; public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(RedundantNullCheck.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var diagnosticNode = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); if (diagnosticNode is BinaryExpressionSyntax nullCheckNode) { RegisterBinaryExpressionCodeFix(context, root, nullCheckNode); } else if (diagnosticNode is PrefixUnaryExpressionSyntax prefixUnary && prefixUnary.IsKind(SyntaxKind.LogicalNotExpression)) { RegisterBinaryExpressionCodeFix(context, root, prefixUnary); } else if (IsPatternExpressionSyntaxWrapper.IsInstance(diagnosticNode)) { var isPatternExpression = (IsPatternExpressionSyntaxWrapper)diagnosticNode.RemoveParentheses(); RegisterBinaryExpressionCodeFix(context, root, isPatternExpression.SyntaxNode); } else if (PatternSyntaxWrapper.IsInstance(diagnosticNode)) { RegisterBinaryPatternCodeFix(context, root, ((PatternSyntaxWrapper)diagnosticNode).SyntaxNode); } else if (diagnosticNode.IsNullLiteral() && diagnosticNode.Parent.IsKind(SyntaxKindEx.ConstantPattern)) { RegisterBinaryPatternCodeFix(context, root, diagnosticNode.Parent); } return Task.CompletedTask; } private static void RegisterBinaryExpressionCodeFix(SonarCodeFixContext context, SyntaxNode root, SyntaxNode mustBeReplaced) => context.RegisterCodeFix( Title, c => { var binaryExpression = mustBeReplaced.Parent.FirstAncestorOrSelf(); var newRoot = root; if (binaryExpression != null) { newRoot = ReplaceNode(root, binaryExpression, binaryExpression.Left, binaryExpression.Right, mustBeReplaced); } return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static void RegisterBinaryPatternCodeFix(SonarCodeFixContext context, SyntaxNode root, SyntaxNode mustBeReplaced) => context.RegisterCodeFix( Title, c => { var binaryExpression = mustBeReplaced.Parent.FirstAncestorOrSelf(n => BinaryPatternSyntaxWrapper.IsInstance(n)); var newRoot = root; if (binaryExpression != null) { var binaryPatternNode = (BinaryPatternSyntaxWrapper)binaryExpression; newRoot = ReplaceNode(root, binaryExpression, binaryPatternNode.Left.SyntaxNode, binaryPatternNode.Right.SyntaxNode, mustBeReplaced); } return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); private static SyntaxNode ReplaceNode(SyntaxNode root, SyntaxNode binaryExpression, SyntaxNode binaryLeft, SyntaxNode binaryRight, SyntaxNode mustBeReplaced) => binaryLeft.RemoveParentheses() == mustBeReplaced ? root.ReplaceNode(binaryExpression, binaryRight.WithTriviaFrom(binaryExpression)) : root.ReplaceNode(binaryExpression, binaryLeft.WithTriviaFrom(binaryExpression)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantNullableTypeComparison.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantNullableTypeComparison : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3610"; private const string MessageFormat = "Remove this redundant type comparison."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; CheckGetTypeAndTypeOfEquality(c, binary.Left, binary.Right, binary.GetLocation()); CheckGetTypeAndTypeOfEquality(c, binary.Right, binary.Left, binary.GetLocation()); }, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); } private static void CheckGetTypeAndTypeOfEquality(SonarSyntaxNodeReportingContext context, ExpressionSyntax sideA, ExpressionSyntax sideB, Location location) { if (!(sideA as InvocationExpressionSyntax).IsGetTypeCall(context.Model)) { return; } var typeSyntax = (sideB as TypeOfExpressionSyntax)?.Type; if (typeSyntax == null) { return; } var typeSymbol = context.Model.GetTypeInfo(typeSyntax).Type; if (typeSymbol != null && typeSymbol.OriginalDefinition.Is(KnownType.System_Nullable_T)) { context.ReportIssue(rule, location); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantParentheses.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantParentheses : RedundantParenthesesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxKind ParenthesizedExpressionSyntaxKind { get; } = SyntaxKind.ParenthesizedExpression; protected override SyntaxNode GetExpression(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.Expression; protected override SyntaxToken GetOpenParenToken(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.OpenParenToken; protected override SyntaxToken GetCloseParenToken(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.CloseParenToken; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantParenthesesCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantParenthesesCodeFix : SonarCodeFix { internal const string Title = "Remove redundant parentheses"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantParentheses.DiagnosticId, RedundantParenthesesObjectsCreation.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (syntaxNode is ArgumentListSyntax || syntaxNode is AttributeArgumentListSyntax) { context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepExteriorTrivia | SyntaxRemoveOptions.KeepEndOfLine); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } else { // Do nothing, we don't want to mess the code if we don't find what we expect } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantParenthesesObjectsCreation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantParenthesesObjectsCreation : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3235"; private const string MessageFormat = "Remove these redundant parentheses."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var argumentList = (AttributeArgumentListSyntax)c.Node; if (!argumentList.Arguments.Any()) { c.ReportIssue(rule, argumentList); } }, SyntaxKind.AttributeArgumentList); context.RegisterNodeAction( c => { var objectCreation = (ObjectCreationExpressionSyntax)c.Node; var argumentList = objectCreation.ArgumentList; if (argumentList != null && objectCreation.Initializer != null && !argumentList.Arguments.Any()) { c.ReportIssue(rule, argumentList); } }, SyntaxKind.ObjectCreationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantPropertyNamesInAnonymousClass.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantPropertyNamesInAnonymousClass : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3441"; private const string MessageFormat = "Remove the redundant '{0} ='."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var anonymousObjectCreation = (AnonymousObjectCreationExpressionSyntax)c.Node; foreach (var initializer in GetRedundantInitializers(anonymousObjectCreation.Initializers)) { c.ReportIssue(rule, initializer.NameEquals, initializer.NameEquals.Name.Identifier.ValueText); } }, SyntaxKind.AnonymousObjectCreationExpression); } private static IEnumerable GetRedundantInitializers( IEnumerable initializers) { var initializersToReportOn = new List(); foreach (var initializer in initializers.Where(initializer => initializer.NameEquals != null)) { if (initializer.Expression is IdentifierNameSyntax identifier && identifier.Identifier.ValueText == initializer.NameEquals.Name.Identifier.ValueText) { initializersToReportOn.Add(initializer); } } return initializersToReportOn; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantPropertyNamesInAnonymousClassCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantPropertyNamesInAnonymousClassCodeFix : SonarCodeFix { internal const string Title = "Remove redundant explicit property names"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantPropertyNamesInAnonymousClass.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var nameEquals = root.FindNode(diagnosticSpan) as NameEqualsSyntax; if (!(nameEquals?.Parent?.Parent is AnonymousObjectCreationExpressionSyntax anonymousObjectCreation)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newInitializersWithSeparators = anonymousObjectCreation.Initializers.GetWithSeparators() .Select(item => GetNewSyntaxListItem(item)); var newAnonymousObjectCreation = anonymousObjectCreation .WithInitializers(SyntaxFactory.SeparatedList(newInitializersWithSeparators)) .WithTriviaFrom(anonymousObjectCreation); var newRoot = root.ReplaceNode( anonymousObjectCreation, newAnonymousObjectCreation); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static SyntaxNodeOrToken GetNewSyntaxListItem(SyntaxNodeOrToken item) { if (!item.IsNode) { return item; } var member = (AnonymousObjectMemberDeclaratorSyntax)item.AsNode(); if (member.Expression is IdentifierNameSyntax identifier && identifier.Identifier.ValueText == member.NameEquals.Name.Identifier.ValueText) { return SyntaxFactory.AnonymousObjectMemberDeclarator(member.Expression).WithTriviaFrom(member); } return item; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantToArrayCall.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantToArrayCall : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3456"; private const string MessageFormat = "Remove this redundant '{0}' call."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var memberAccess = GetRedundantMemberAccess(c, "ToCharArray", KnownType.System_String) ?? GetRedundantMemberAccess(c, "ToArray", KnownType.System_ReadOnlySpan_T); if (memberAccess is not null) { c.ReportIssue(Rule, memberAccess.Name, memberAccess.Name.Identifier.ValueText); } }, SyntaxKind.InvocationExpression); private static MemberAccessExpressionSyntax GetRedundantMemberAccess(SonarSyntaxNodeReportingContext context, string targetMethodName, KnownType targetKnownType) { var invocation = (InvocationExpressionSyntax)context.Node; if ((invocation.Parent is ElementAccessExpressionSyntax || invocation.Parent is ForEachStatementSyntax) && invocation.Expression is MemberAccessExpressionSyntax memberAccess && IsTargetMethod(memberAccess)) { return memberAccess; } return null; bool IsTargetMethod(MemberAccessExpressionSyntax memberAccess) => memberAccess.Name.Identifier.ValueText == targetMethodName && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsInType(targetKnownType) && methodSymbol.Parameters.Length == 0; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantToArrayCallCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantToArrayCallCodeFix : SonarCodeFix { private const string Title = "Remove redundant 'ToArray' call"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantToArrayCall.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var simpleNameSyntax = root.FindNode(diagnosticSpan) as SimpleNameSyntax; var memberAccessExpressionSyntax = simpleNameSyntax?.Parent as MemberAccessExpressionSyntax; if (memberAccessExpressionSyntax?.Parent is not InvocationExpressionSyntax invocationExpressionSyntax) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode(invocationExpressionSyntax, memberAccessExpressionSyntax.Expression); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantToStringCall.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RedundantToStringCall : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1858"; private const string MessageFormat = "There's no need to call 'ToString()'{0}."; internal const string MessageCallOnString = " on a string"; internal const string MessageCompiler = ", the compiler will do it for you"; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private const string additionOperatorName = "op_Addition"; protected override void Initialize(SonarAnalysisContext context) { CheckToStringInvocationsOnStringAndInStringFormat(context); CheckSidesOfAddExpressionsForToStringCall(context); CheckRightSideOfAddAssignmentsForToStringCall(context); } private static void CheckRightSideOfAddAssignmentsForToStringCall(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; var operation = c.Model.GetSymbolInfo(assignment).Symbol as IMethodSymbol; if (!IsOperationAddOnString(operation)) { return; } CheckRightExpressionForRemovableToStringCall(c, assignment); }, SyntaxKind.AddAssignmentExpression); } private static void CheckSidesOfAddExpressionsForToStringCall(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; var operation = c.Model.GetSymbolInfo(binary).Symbol as IMethodSymbol; if (!IsOperationAddOnString(operation)) { return; } CheckLeftExpressionForRemovableToStringCall(c, binary); CheckRightExpressionForRemovableToStringCall(c, binary); }, SyntaxKind.AddExpression); } private static void CheckToStringInvocationsOnStringAndInStringFormat(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (!IsArgumentlessToStringCallNotOnBaseExpression(invocation, c.Model, out var location, out var methodSymbol)) { return; } if (methodSymbol.IsInType(KnownType.System_String)) { c.ReportIssue(rule, location, MessageCallOnString); return; } if (!TryGetExpressionTypeOfOwner(invocation, c.Model, out var subExpressionType) || subExpressionType.IsValueType) { return; } var stringFormatArgument = invocation?.Parent as ArgumentSyntax; if (!(stringFormatArgument?.Parent?.Parent is InvocationExpressionSyntax stringFormatInvocation) || !IsStringFormatCall(c.Model.GetSymbolInfo(stringFormatInvocation).Symbol as IMethodSymbol)) { return; } var parameterLookup = new CSharpMethodParameterLookup(stringFormatInvocation, c.Model); if (parameterLookup.TryGetSymbol(stringFormatArgument, out var argParameter) && argParameter.Name.StartsWith("arg", StringComparison.Ordinal)) { c.ReportIssue(rule, location, MessageCompiler); } }, SyntaxKind.InvocationExpression); } private static void CheckLeftExpressionForRemovableToStringCall(SonarSyntaxNodeReportingContext context, BinaryExpressionSyntax binary) { CheckExpressionForRemovableToStringCall(context, binary.Left, binary.Right, 0); } private static void CheckRightExpressionForRemovableToStringCall(SonarSyntaxNodeReportingContext context, BinaryExpressionSyntax binary) { CheckExpressionForRemovableToStringCall(context, binary.Right, binary.Left, 1); } private static void CheckRightExpressionForRemovableToStringCall(SonarSyntaxNodeReportingContext context, AssignmentExpressionSyntax assignment) { CheckExpressionForRemovableToStringCall(context, assignment.Right, assignment.Left, 1); } private static void CheckExpressionForRemovableToStringCall(SonarSyntaxNodeReportingContext context, ExpressionSyntax expressionWithToStringCall, ExpressionSyntax otherOperandOfAddition, int checkedSideIndex) { if (!IsArgumentlessToStringCallNotOnBaseExpression(expressionWithToStringCall, context.Model, out var location, out var methodSymbol) || methodSymbol.IsInType(KnownType.System_String)) { return; } var sideBType = context.Model.GetTypeInfo(otherOperandOfAddition).Type; if (!sideBType.Is(KnownType.System_String)) { return; } if (!TryGetExpressionTypeOfOwner((InvocationExpressionSyntax)expressionWithToStringCall, context.Model, out var subExpressionType) || subExpressionType.IsValueType) { return; } var stringParameterIndex = (checkedSideIndex + 1) % 2; if (!DoesCollidingAdditionExist(subExpressionType, stringParameterIndex)) { context.ReportIssue(rule, location, MessageCompiler); } } private static bool TryGetExpressionTypeOfOwner(InvocationExpressionSyntax invocation, SemanticModel semanticModel, out ITypeSymbol subExpressionType) { subExpressionType = null; var subExpression = (invocation.Expression as MemberAccessExpressionSyntax)?.Expression; if (subExpression == null) { return false; } subExpressionType = semanticModel.GetTypeInfo(subExpression).Type; return subExpressionType != null; } private static bool DoesCollidingAdditionExist(ITypeSymbol subExpressionType, int stringParameterIndex) { return subExpressionType.GetMembers(additionOperatorName) .OfType() .Where(method => method.MethodKind == MethodKind.BuiltinOperator || method.MethodKind == MethodKind.UserDefinedOperator) .Any(method => method.Parameters.Length == 2 && method.Parameters[stringParameterIndex].IsType(KnownType.System_String)); } private static bool IsStringFormatCall(IMethodSymbol stringFormatSymbol) { return stringFormatSymbol != null && stringFormatSymbol.Name == "Format" && (stringFormatSymbol.ContainingType == null || stringFormatSymbol.IsInType(KnownType.System_String)); } private static bool IsOperationAddOnString(IMethodSymbol operation) { return operation != null && operation.Name == additionOperatorName && operation.IsInType(KnownType.System_String); } private static bool IsArgumentlessToStringCallNotOnBaseExpression(ExpressionSyntax expression, SemanticModel semanticModel, out Location location, out IMethodSymbol methodSymbol) { location = null; methodSymbol = null; if (!(expression is InvocationExpressionSyntax invocation) || invocation.ArgumentList.CloseParenToken.IsMissing) { return false; } if (!(invocation.Expression is MemberAccessExpressionSyntax memberAccess) || memberAccess.Expression is BaseExpressionSyntax) { return false; } methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; if (!IsParameterlessToString(methodSymbol)) { return false; } location = memberAccess.OperatorToken.CreateLocation(invocation); return true; } private static bool IsParameterlessToString(IMethodSymbol methodSymbol) { return methodSymbol != null && methodSymbol.Name == "ToString" && !methodSymbol.Parameters.Any(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RedundantToStringCallCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class RedundantToStringCallCodeFix : SonarCodeFix { internal const string Title = "Remove redundant 'ToString' call"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RedundantToStringCall.DiagnosticId); public override FixAllProvider GetFixAllProvider() => null; // This CodeFix doesn't support FixAll (yet), because removing one .ToString() can invalidate other diagnostic in the same expression protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var invocation = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) as InvocationExpressionSyntax; if (!(invocation?.Expression is MemberAccessExpressionSyntax memberAccess)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode(invocation, memberAccess.Expression.WithTriviaFrom(invocation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReferenceEqualityCheckWhenEqualsExists.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReferenceEqualityCheckWhenEqualsExists : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1698"; private const string MessageFormat = "Consider using 'Equals' if value comparison was intended."; private const string EqualsName = nameof(Equals); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly ImmutableArray AllowedTypes = ImmutableArray.Create( KnownType.System_Type, KnownType.System_Reflection_Assembly, KnownType.System_Reflection_MemberInfo, KnownType.System_Reflection_Module, KnownType.System_Data_Common_CommandTrees_DbExpression, KnownType.System_Object); private static readonly ImmutableArray AllowedTypesWithAllDerived = ImmutableArray.Create(KnownType.System_Windows_DependencyObject); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( compilationStartContext => { var allInterfacesWithImplementationsOverriddenEquals = compilationStartContext.Compilation.GlobalNamespace .GetAllNamedTypes() .Where(t => t.AllInterfaces.Any() && HasEqualsOverride(t)) .SelectMany(t => t.AllInterfaces) .ToHashSet(); compilationStartContext.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; if (!IsBinaryCandidateForReporting(binary, c.Model)) { return; } var typeLeft = c.Model.GetTypeInfo(binary.Left).Type; var typeRight = c.Model.GetTypeInfo(binary.Right).Type; if (IsAllowedTypeOrNull(typeLeft) || IsAllowedTypeOrNull(typeRight)) { return; } if (MightOverrideEquals(typeLeft, allInterfacesWithImplementationsOverriddenEquals) || MightOverrideEquals(typeRight, allInterfacesWithImplementationsOverriddenEquals)) { c.ReportIssue(Rule, binary.OperatorToken); } }, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); }); private static bool MightOverrideEquals(ITypeSymbol type, ISet allInterfacesWithImplementationsOverriddenEquals) => HasEqualsOverride(type) || allInterfacesWithImplementationsOverriddenEquals.Contains(type) || HasTypeConstraintsWhichMightOverrideEquals(type, allInterfacesWithImplementationsOverriddenEquals); private static bool HasTypeConstraintsWhichMightOverrideEquals(ITypeSymbol type, ISet allInterfacesWithImplementationsOverriddenEquals) => type is ITypeParameterSymbol genericParameter && genericParameter.ConstraintTypes.Any(x => MightOverrideEquals(x, allInterfacesWithImplementationsOverriddenEquals)); private static bool IsAllowedTypeOrNull(ITypeSymbol type) => type is null || type.IsAny(AllowedTypes) || HasAllowedBaseType(type); private static bool HasAllowedBaseType(ITypeSymbol type) => type.GetSelfAndBaseTypes().Any(t => t.IsAny(AllowedTypesWithAllDerived)); private static bool IsBinaryCandidateForReporting(BinaryExpressionSyntax binary, SemanticModel semanticModel) => semanticModel.GetSymbolInfo(binary).Symbol is IMethodSymbol equalitySymbol && equalitySymbol.IsInType(KnownType.System_Object) && !IsInEqualsOverride(semanticModel.GetEnclosingSymbol(binary.SpanStart) as IMethodSymbol); private static bool HasEqualsOverride(ITypeSymbol type) => GetEqualsOverrides(type).Any(x => x.OverriddenMethod.IsInType(KnownType.System_Object)); private static IEnumerable GetEqualsOverrides(ITypeSymbol type) { var candidateEqualsMethods = new HashSet(); foreach (var currentType in type.GetSelfAndBaseTypes().TakeWhile(tp => !tp.Is(KnownType.System_Object))) { candidateEqualsMethods.UnionWith(currentType .GetMembers(EqualsName) .OfType() .Where(method => method.IsOverride && method.OverriddenMethod != null)); } return candidateEqualsMethods; } private static bool IsInEqualsOverride(IMethodSymbol method) { var currentMethod = method; while (currentMethod != null) { if (currentMethod.Name == EqualsName && currentMethod.IsInType(KnownType.System_Object)) { return true; } currentMethod = currentMethod.OverriddenMethod; } return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReferenceEqualsOnValueType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReferenceEqualsOnValueType : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2995"; private const string MessageFormat = "Use a different kind of comparison for these value types."; private const string ReferenceEqualsName = "ReferenceEquals"; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax) c.Node; var methodSymbol = c.Model.GetSymbolInfo(invocation).Symbol as IMethodSymbol; if (methodSymbol.IsInType(KnownType.System_Object) && methodSymbol.Name == ReferenceEqualsName && AnyArgumentIsValueType(invocation.ArgumentList, c.Model)) { c.ReportIssue(rule, invocation.Expression); } }, SyntaxKind.InvocationExpression); } private static bool AnyArgumentIsValueType(ArgumentListSyntax argumentList, SemanticModel semanticModel) { return argumentList.Arguments.Any(argument => { var type = semanticModel.GetTypeInfo(argument.Expression).Type; return type != null && type.IsValueType; }); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RegularExpressions/RegexMustHaveValidSyntax.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RegexMustHaveValidSyntax : RegexMustHaveValidSyntaxBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RequireAttributeUsageAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RequireAttributeUsageAttribute : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3993"; private const string MessageFormat = "Specify AttributeUsage on '{0}'{1}."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; if (c.Model.GetDeclaredSymbol(classDeclaration) is { IsAbstract: false } classSymbol && classSymbol.DerivesFrom(KnownType.System_Attribute) && !classSymbol.HasAttribute(KnownType.System_AttributeUsageAttribute)) { var additionalText = InheritsAttributeUsage(classSymbol) ? " to improve readability, even though it inherits it from its base type" : string.Empty; c.ReportIssue(Rule, classDeclaration.Identifier, classSymbol.Name, additionalText); } }, SyntaxKind.ClassDeclaration); private static bool InheritsAttributeUsage(INamedTypeSymbol classSymbol) => classSymbol.GetSelfAndBaseTypes() // System.Attribute already has AttributeUsage, we don't want to report it .TakeWhile(x => !x.Is(KnownType.System_Attribute)) .Any(x => x.HasAttribute(KnownType.System_AttributeUsageAttribute)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReturnEmptyCollectionInsteadOfNull.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReturnEmptyCollectionInsteadOfNull : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1168"; private const string MessageFormat = "Return an empty collection instead of null."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray CollectionTypes = ImmutableArray.Create( KnownType.System_Collections_IEnumerable, KnownType.System_Array); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ReportIfReturnsNullOrDefault, SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.PropertyDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.ConversionOperatorDeclaration); private static void ReportIfReturnsNullOrDefault(SonarSyntaxNodeReportingContext context) { if (ExpressionBody(context.Node) is { } expressionBody) { var nullOrDefaultLiterals = NullOrDefaultExpressions(expressionBody.Expression) .Select(x => x.GetLocation()) .ToList(); ReportIfAny(nullOrDefaultLiterals); } else if (Body(context.Node) is { } body) { var nullOrDefaultLiterals = ReturnNullOrDefaultExpressions(body) .Select(x => x.GetLocation()) .ToList(); ReportIfAny(nullOrDefaultLiterals); } void ReportIfAny(List nullOrDefaultLiterals) { if (nullOrDefaultLiterals.Count > 0 && IsReturningCollection(context)) { context.ReportIssue(Rule, nullOrDefaultLiterals[0], nullOrDefaultLiterals.Skip(1).ToSecondary(MessageFormat)); } } } private static BlockSyntax Body(SyntaxNode node) => node is BasePropertyDeclarationSyntax property ? GetAccessor(property)?.Body : node.GetBody(); private static bool IsReturningCollection(SonarSyntaxNodeReportingContext context) => DeclaredType(context) is { } type && !type.Is(KnownType.System_String) && !type.DerivesFrom(KnownType.System_Xml_XmlNode) && type.DerivesOrImplementsAny(CollectionTypes) && type.NullableAnnotation() != NullableAnnotation.Annotated; private static ITypeSymbol DeclaredType(SonarSyntaxNodeReportingContext context) { var symbol = context.Model.GetDeclaredSymbol(context.Node); return symbol is IPropertySymbol property ? property.Type : ((IMethodSymbol)symbol).ReturnType; } private static ArrowExpressionClauseSyntax ExpressionBody(SyntaxNode node) => node switch { BaseMethodDeclarationSyntax method => method.ExpressionBody(), BasePropertyDeclarationSyntax property => property.ArrowExpressionBody() ?? GetAccessor(property)?.ExpressionBody, _ => ((LocalFunctionStatementSyntaxWrapper)node).ExpressionBody, }; private static AccessorDeclarationSyntax GetAccessor(BasePropertyDeclarationSyntax property) => property.AccessorList.Accessors.FirstOrDefault(x => x.IsKind(SyntaxKind.GetAccessorDeclaration)); private static IEnumerable ReturnNullOrDefaultExpressions(SyntaxNode methodBlock) => methodBlock.DescendantNodes(x => !(x.Kind() is SyntaxKindEx.LocalFunctionStatement or SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression)) .OfType() .SelectMany(x => NullOrDefaultExpressions(x.Expression)); private static IEnumerable NullOrDefaultExpressions(SyntaxNode node) { node = node.RemoveParentheses(); if (node.IsNullLiteral() || node?.Kind() is SyntaxKindEx.DefaultLiteralExpression or SyntaxKind.DefaultExpression) { yield return node; yield break; } if (node is ConditionalExpressionSyntax c) { foreach (var innerNode in NullOrDefaultExpressions(c.WhenTrue)) { yield return innerNode; } foreach (var innerNode in NullOrDefaultExpressions(c.WhenFalse)) { yield return innerNode; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReturnTypeNamedPartialShouldBeEscaped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReturnTypeNamedPartialShouldBeEscaped : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S8380"; private const string MessageFormat = "Return types named 'partial' should be escaped with '@'"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!compilationStart.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp14)) { compilationStart.RegisterNodeAction(c => { if (!IsGenericMethod(c.Node) && c.Node.TypeSyntax() is { } returnType && (returnType.Kind() == SyntaxKindEx.RefType ? ((RefTypeSyntaxWrapper)returnType).Type : returnType) is { } unWrapped && unWrapped is NameSyntax { Arity: 0 } name && name.GetIdentifier() is { Text: "partial" } identifier) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.MethodDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKindEx.LocalFunctionStatement); } }); private static bool IsGenericMethod(SyntaxNode node) => node is MethodDeclarationSyntax { Arity: > 0 } || node is DelegateDeclarationSyntax { Arity: > 0 } || (node.IsKind(SyntaxKindEx.LocalFunctionStatement) && (LocalFunctionStatementSyntaxWrapper)node is { TypeParameterList.Parameters.Count: > 0 }); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReturnValueIgnored.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReturnValueIgnored : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2201"; private const string MessageFormat = "Use the return value of method '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray ImmutableKnownTypes = ImmutableArray.Create( KnownType.System_Object, KnownType.System_Int16, KnownType.System_Int32, KnownType.System_Int64, KnownType.System_UInt16, KnownType.System_UInt32, KnownType.System_UInt64, KnownType.System_IntPtr, KnownType.System_UIntPtr, KnownType.System_Char, KnownType.System_Byte, KnownType.System_SByte, KnownType.System_Single, KnownType.System_Double, KnownType.System_Decimal, KnownType.System_Boolean, KnownType.System_String, KnownType.System_Collections_Frozen_FrozenSet, KnownType.System_Collections_Immutable_ImmutableArray, KnownType.System_Collections_Immutable_ImmutableArray_T, KnownType.System_Collections_Immutable_ImmutableDictionary, KnownType.System_Collections_Immutable_ImmutableDictionary_TKey_TValue, KnownType.System_Collections_Immutable_ImmutableHashSet, KnownType.System_Collections_Immutable_ImmutableHashSet_T, KnownType.System_Collections_Immutable_ImmutableList, KnownType.System_Collections_Immutable_ImmutableList_T, KnownType.System_Collections_Immutable_ImmutableQueue, KnownType.System_Collections_Immutable_ImmutableQueue_T, KnownType.System_Collections_Immutable_ImmutableSortedDictionary, KnownType.System_Collections_Immutable_ImmutableSortedDictionary_TKey_TValue, KnownType.System_Collections_Immutable_ImmutableSortedSet, KnownType.System_Collections_Immutable_ImmutableSortedSet_T, KnownType.System_Collections_Immutable_ImmutableStack, KnownType.System_Collections_Immutable_ImmutableStack_T); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var expressionStatement = (ExpressionStatementSyntax)c.Node; CheckExpressionForPureMethod(c, expressionStatement.Expression); }, SyntaxKind.ExpressionStatement); context.RegisterNodeAction( c => { var lambda = (LambdaExpressionSyntax)c.Node; if (c.Model.GetSymbolInfo(lambda).Symbol is not IMethodSymbol { ReturnsVoid: true } symbol) { return; } var expression = lambda.Body as ExpressionSyntax; CheckExpressionForPureMethod(c, expression); }, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression); } private static void CheckExpressionForPureMethod(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) { if (expression is InvocationExpressionSyntax invocation && context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol { ReturnsVoid: false } invokedMethodSymbol && invokedMethodSymbol.Parameters.All(p => p.RefKind == RefKind.None) && IsSideEffectFreeOrPure(invokedMethodSymbol)) { context.ReportIssue(Rule, expression, invokedMethodSymbol.Name); } } private static bool IsSideEffectFreeOrPure(IMethodSymbol invokedMethodSymbol) { var constructedFrom = invokedMethodSymbol.ContainingType.ConstructedFrom; return IsLinqMethod(invokedMethodSymbol) || HasOnlySideEffectFreeMethods(constructedFrom) || IsPureMethod(invokedMethodSymbol, constructedFrom); } private static bool IsPureMethod(IMethodSymbol invokedMethodSymbol, INamedTypeSymbol containingType) => invokedMethodSymbol.HasAttribute(KnownType.System_Diagnostics_Contracts_PureAttribute) || containingType.HasAttribute(KnownType.System_Diagnostics_Contracts_PureAttribute); private static bool HasOnlySideEffectFreeMethods(INamedTypeSymbol containingType) => containingType.IsAny(ImmutableKnownTypes); private static bool IsLinqMethod(IMethodSymbol methodSymbol) => methodSymbol.ContainingType.Is(KnownType.System_Linq_Enumerable) || methodSymbol.ContainingType.Is(KnownType.System_Linq_ImmutableArrayExtensions); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReuseClientBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; public abstract class ReuseClientBase : SonarDiagnosticAnalyzer { private static readonly HashSet ConditionalKinds = [ SyntaxKind.IfStatement, SyntaxKind.SwitchStatement, SyntaxKindEx.SwitchExpression, SyntaxKind.ConditionalExpression, SyntaxKindEx.CoalesceAssignmentExpression ]; protected abstract ImmutableArray ReusableClients { get; } protected static bool IsAssignedForReuse(SonarSyntaxNodeReportingContext context) => !IsInVariableDeclaration(context.Node) && (IsInConditionalCode(context.Node) || IsInFieldOrPropertyInitializer(context.Node) || IsAssignedToStaticFieldOrProperty(context)); protected bool IsReusableClient(SonarSyntaxNodeReportingContext context) { var objectCreation = ObjectCreationFactory.Create(context.Node); return ReusableClients.Any(x => objectCreation.IsKnownType(x, context.Model)); } private static bool IsInVariableDeclaration(SyntaxNode node) => node.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax or UsingStatementSyntax } } }; private static bool IsInFieldOrPropertyInitializer(SyntaxNode node) => node.HasAncestor(SyntaxKind.FieldDeclaration, SyntaxKind.PropertyDeclaration) && !node.HasAncestor(SyntaxKind.GetAccessorDeclaration, SyntaxKind.SetAccessorDeclaration) && !node.Parent.IsKind(SyntaxKind.ArrowExpressionClause); private static bool IsInConditionalCode(SyntaxNode node) => node.HasAncestor(ConditionalKinds); private static bool IsAssignedToStaticFieldOrProperty(SonarSyntaxNodeReportingContext context) => context.Node.Parent.WalkUpParentheses() is AssignmentExpressionSyntax assignment && context.Model.GetSymbolInfo(assignment.Left, context.Cancel).Symbol is { IsStatic: true, Kind: SymbolKind.Field or SymbolKind.Property }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ReversedOperators.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ReversedOperators : ReversedOperatorsBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( GetAnalysisAction(Rule), SyntaxKind.UnaryMinusExpression, SyntaxKind.UnaryPlusExpression, SyntaxKind.LogicalNotExpression); protected override SyntaxToken GetOperatorToken(PrefixUnaryExpressionSyntax e) => e.OperatorToken; protected override bool IsEqualsToken(SyntaxToken token) => token.IsKind(SyntaxKind.EqualsToken); protected override bool IsMinusToken(SyntaxToken token) => token.IsKind(SyntaxKind.MinusToken); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/RightCurlyBraceStartsLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class RightCurlyBraceStartsLine : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1109"; private const string MessageFormat = "Move this closing curly brace to the next line."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterTreeAction( c => { var root = c.Tree.GetRoot(); foreach (var closeBraceToken in GetDescendantCloseBraceTokens(root) .Where(closeBraceToken => !StartsLine(closeBraceToken) && !IsOnSameLineAsOpenBrace(closeBraceToken) && !IsInitializer(closeBraceToken.Parent))) { c.ReportIssue(rule, closeBraceToken); } }); } private static bool StartsLine(SyntaxToken token) { return token.GetPreviousToken().GetLocation().EndLine() != token.GetLocation().StartLine(); } private static bool IsOnSameLineAsOpenBrace(SyntaxToken closeBraceToken) { var openBraceToken = closeBraceToken.Parent.ChildTokens().Single(token => token.IsKind(SyntaxKind.OpenBraceToken)); return openBraceToken.GetLocation().StartLine() == closeBraceToken.GetLocation().StartLine(); } private static bool IsInitializer(SyntaxNode node) { return node.IsKind(SyntaxKind.ArrayInitializerExpression) || node.IsKind(SyntaxKind.CollectionInitializerExpression) || node.IsKind(SyntaxKind.AnonymousObjectCreationExpression) || node.IsKind(SyntaxKind.ObjectInitializerExpression); } private static IEnumerable GetDescendantCloseBraceTokens(SyntaxNode node) { return node.DescendantTokens().Where(token => token.IsKind(SyntaxKind.CloseBraceToken)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SecurityPInvokeMethodShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SecurityPInvokeMethodShouldNotBeCalled : SecurityPInvokeMethodShouldNotBeCalledBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IMethodSymbol MethodSymbolForInvalidInvocation(SyntaxNode syntaxNode, SemanticModel semanticModel) => syntaxNode is IdentifierNameSyntax identifierName && InvalidMethods.Contains(identifierName.Identifier.ValueText) && semanticModel.GetSymbolInfo(syntaxNode).Symbol is IMethodSymbol methodSymbol ? methodSymbol : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SelfAssignment.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SelfAssignment : SelfAssignmentBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var expression = (AssignmentExpressionSyntax)c.Node; if (expression.Parent is InitializerExpressionSyntax) { return; } foreach (var assigment in expression.MapAssignmentArguments().Where(x => CSharpEquivalenceChecker.AreEquivalent(x.Left, x.Right))) { c.ReportIssue(Rule, assigment.Left, [assigment.Right.ToSecondaryLocation()]); } }, SyntaxKind.SimpleAssignmentExpression, SyntaxKindEx.CoalesceAssignmentExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SerializationConstructorsShouldBeSecured.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [Obsolete("This rule has been deprecated since 9.14")] [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SerializationConstructorsShouldBeSecured : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4212"; private const string MessageFormat = "Secure this serialization constructor."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly AttributeComparer attributeComparer = new AttributeComparer(); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(FindPossibleViolations, SyntaxKind.ConstructorDeclaration); } private static void FindPossibleViolations(SonarSyntaxNodeReportingContext c) { var constructorSyntax = (ConstructorDeclarationSyntax)c.Node; var reportLocation = constructorSyntax?.Identifier.GetLocation(); if (reportLocation == null) { return; } var serializationConstructor = c.Model.GetDeclaredSymbol(constructorSyntax); if (!serializationConstructor.IsSerializationConstructor()) { return; } var classSymbol = serializationConstructor.ContainingType; if (!classSymbol.Implements(KnownType.System_Runtime_Serialization_ISerializable)) { return; } var isAssemblyIsPartiallyTrusted = c.Model.Compilation.Assembly .HasAttribute(KnownType.System_Security_AllowPartiallyTrustedCallersAttribute); if (!isAssemblyIsPartiallyTrusted) { return; } var serializationConstructorAttributes = GetCASAttributes(serializationConstructor).ToHashSet(); bool isConstructorMissingAttributes = classSymbol.Constructors .SelectMany(m => GetCASAttributes(m)) .Any(attr => !serializationConstructorAttributes.Contains(attr, attributeComparer)); if (isConstructorMissingAttributes) { c.ReportIssue(rule, reportLocation); } } private static IEnumerable GetCASAttributes(IMethodSymbol methodSymbol) { return methodSymbol.GetAttributes().Where(IsCASAttribute); bool IsCASAttribute(AttributeData data) => data?.AttributeClass.DerivesFrom(KnownType.System_Security_Permissions_CodeAccessSecurityAttribute) ?? false; } private class AttributeComparer : IEqualityComparer { public bool Equals(AttributeData x, AttributeData y) { return Equals(x.AttributeConstructor, y.AttributeConstructor) && Enumerable.SequenceEqual(x.ConstructorArguments, y.ConstructorArguments) && AreNamedArgumentsEqual(x.NamedArguments, y.NamedArguments); } private bool AreNamedArgumentsEqual( IEnumerable> argumentsX, IEnumerable> argumentsY) { var dictX = argumentsX.ToDictionary(p => p.Key, p => p.Value); var dictY = argumentsY.ToDictionary(p => p.Key, p => p.Value); if (dictX.Count != dictY.Count) { return false; } foreach (var key in dictX.Keys) { if (!dictX.TryGetValue(key, out TypedConstant itemX) || !dictY.TryGetValue(key, out TypedConstant itemY) || !Equals(itemX, itemY)) { return false; } } return true; } public int GetHashCode(AttributeData obj) => 1; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SetLocaleForDataTypes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SetLocaleForDataTypes : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4057"; private const string MessageFormat = "Set the locale for this '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray CheckedTypes = ImmutableArray.Create( KnownType.System_Data_DataTable, KnownType.System_Data_DataSet); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override bool EnableConcurrentExecution => false; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( compilationStartContext => { var symbolsWhereTypeIsCreated = new Dictionary(); var symbolsWhereLocaleIsSet = new HashSet(); compilationStartContext.RegisterNodeAction( c => ProcessObjectCreations(c, symbolsWhereTypeIsCreated), SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); compilationStartContext.RegisterNodeAction(c => ProcessSimpleAssignments(c, symbolsWhereLocaleIsSet), SyntaxKind.SimpleAssignmentExpression); compilationStartContext.RegisterCompilationEndAction(c => ProcessCollectedSymbols(c, symbolsWhereTypeIsCreated, symbolsWhereLocaleIsSet)); }); private static void ProcessObjectCreations(SonarSyntaxNodeReportingContext c, IDictionary symbolsWhereTypeIsCreated) { if (GetSymbolFromConstructorInvocation(c.Node, c.Model) is ITypeSymbol objectType && objectType.IsAny(CheckedTypes) && GetAssignmentTargetVariable(c.Node) is { } variableSyntax) { if (DeclarationExpressionSyntaxWrapper.IsInstance(variableSyntax)) { variableSyntax = ((DeclarationExpressionSyntaxWrapper)variableSyntax).Designation; } var variableSymbol = variableSyntax is IdentifierNameSyntax ? c.Model.GetSymbolInfo(variableSyntax).Symbol : c.Model.GetDeclaredSymbol(variableSyntax); if (variableSymbol != null && !symbolsWhereTypeIsCreated.ContainsKey(variableSymbol)) { symbolsWhereTypeIsCreated.Add(variableSymbol, c.Node); } } } private static void ProcessSimpleAssignments(SonarSyntaxNodeReportingContext c, ISet symbolsWhereLocaleIsSet) { var assignmentExpression = (AssignmentExpressionSyntax)c.Node; var variableSymbols = assignmentExpression.AssignmentTargets() .Where(x => c.Model.GetSymbolInfo(x).Symbol is IPropertySymbol propertySymbol && propertySymbol.Name == "Locale" && propertySymbol.ContainingType.IsAny(CheckedTypes)) .Select(x => GetAccessedVariable(x, c.Model)) .WhereNotNull(); symbolsWhereLocaleIsSet.UnionWith(variableSymbols); } private static void ProcessCollectedSymbols(SonarCompilationReportingContext c, IDictionary symbolsWhereTypeIsCreated, ISet symbolsWhereLocaleIsSet) { foreach (var invalidCreation in symbolsWhereTypeIsCreated.Where(x => !symbolsWhereLocaleIsSet.Contains(x.Key))) { if (invalidCreation.Key.GetSymbolType() is { } type) { c.ReportIssue(Rule, invalidCreation.Value.GetLocation(), type.Name); } } } private static ISymbol GetSymbolFromConstructorInvocation(SyntaxNode constructorCall, SemanticModel semanticModel) => constructorCall is ObjectCreationExpressionSyntax objectCreation ? semanticModel.GetSymbolInfo(objectCreation.Type).Symbol : semanticModel.GetSymbolInfo(constructorCall).Symbol?.ContainingType; private static SyntaxNode GetAssignmentTargetVariable(SyntaxNode objectCreation) => objectCreation.GetFirstNonParenthesizedParent() switch { AssignmentExpressionSyntax assignment => assignment.Left, EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax declarator } => declarator, ArgumentSyntax argument => argument.FindAssignmentComplement(), _ => null, }; private static ISymbol GetAccessedVariable(SyntaxNode node, SemanticModel model) => node.RemoveParentheses() switch { IdentifierNameSyntax { Parent: AssignmentExpressionSyntax { Parent: InitializerExpressionSyntax { Parent: { RawKind: (int)SyntaxKind.ObjectCreationExpression or (int)SyntaxKindEx.ImplicitObjectCreationExpression } objectCreation } } } => GetAssignmentTargetSymbol(objectCreation, model), // Locale is assigned in an object initializer. Find the target of the object creation. MemberAccessExpressionSyntax memberAccessExpression => model.GetSymbolInfo(memberAccessExpression.Expression).Symbol, _ => null, }; private static ISymbol GetAssignmentTargetSymbol(SyntaxNode objectCreation, SemanticModel model) { var leftSideOfParentAssignment = objectCreation.GetFirstNonParenthesizedParent() switch { // var dt = new DataTable { Locale = l } EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax declarator } => declarator, // dt = new DataTable { Locale = l } AssignmentExpressionSyntax assignment => assignment.Left, // var (dt, _) = (new DataTable { Locale = l }, 42) ArgumentSyntax argumentSyntax => argumentSyntax.FindAssignmentComplement(), _ => null, }; return leftSideOfParentAssignment switch { null => null, IdentifierNameSyntax => model.GetSymbolInfo(leftSideOfParentAssignment).Symbol, _ when DeclarationExpressionSyntaxWrapper.IsInstance(leftSideOfParentAssignment) => model.GetSymbolInfo(leftSideOfParentAssignment).Symbol, _ => model.GetDeclaredSymbol(leftSideOfParentAssignment), }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SetPropertiesInsteadOfMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SetPropertiesInsteadOfMethods : SetPropertiesInsteadOfMethodsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool HasCorrectArgumentCount(InvocationExpressionSyntax invocation) => invocation.HasExactlyNArguments(0); protected override bool TryGetOperands(InvocationExpressionSyntax invocation, out SyntaxNode typeNode, out SyntaxNode methodNode) { if (invocation.Operands() is { Left: { } left, Right: { } right }) { typeNode = left; methodNode = right; return true; } else { typeNode = null; methodNode = null; return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ShiftDynamicNotInteger.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ShiftDynamicNotInteger : ShiftDynamicNotIntegerBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool ShouldRaise(SemanticModel model, SyntaxNode left, SyntaxNode right) => left.IsDynamic(model) && !IsConvertibleToInt(right, model); protected override bool CanBeConvertedTo(SyntaxNode expression, ITypeSymbol type, SemanticModel model) => model.ClassifyConversion(expression as ExpressionSyntax, type) is { Exists: true } and ({ IsIdentity: true } or { IsImplicit: true }); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ShouldImplementExportedInterfaces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ShouldImplementExportedInterfaces : ShouldImplementExportedInterfacesBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SeparatedSyntaxList? GetAttributeArguments(AttributeSyntax attributeSyntax) => attributeSyntax.ArgumentList?.Arguments; protected override SyntaxNode GetAttributeName(AttributeSyntax attributeSyntax) => attributeSyntax.Name; protected override bool IsClassOrRecordSyntax(SyntaxNode syntaxNode) => syntaxNode?.Kind() is SyntaxKind.ClassDeclaration or SyntaxKindEx.RecordDeclaration; protected override SyntaxNode GetTypeOfOrGetTypeExpression(SyntaxNode expressionSyntax) => (expressionSyntax as TypeOfExpressionSyntax)?.Type; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SingleStatementPerLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SingleStatementPerLine : SingleStatementPerLineBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override bool StatementShouldBeExcluded(StatementSyntax statement) => StatementIsBlock(statement) || StatementIsSingleInLambda(statement); private static bool StatementIsSingleInLambda(StatementSyntax st) => !st.DescendantNodes().OfType().Any() && st.Parent is BlockSyntax parentBlock && parentBlock.Statements.Count <= 1 && parentBlock.Parent is AnonymousFunctionExpressionSyntax; private static bool StatementIsBlock(StatementSyntax st) => st is BlockSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyIFormatProviderOrCultureInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SpecifyIFormatProviderOrCultureInfo : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4056"; private const string MessageFormat = "Use the overload that takes a 'CultureInfo' or 'IFormatProvider' parameter."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray FormatAndCultureType = ImmutableArray.Create( KnownType.System_IFormatProvider, KnownType.System_Globalization_CultureInfo); private static readonly ImmutableArray FormattableTypes = ImmutableArray.Create( KnownType.System_String, KnownType.System_Object); private static readonly IReadOnlyCollection IgnoredMethods = [ new(KnownType.System_Activator, "CreateInstance"), new(KnownType.System_Resources_ResourceManager, "GetObject"), new(KnownType.System_Resources_ResourceManager, "GetString"), // Those methods take a IFormatProvider parameter, but it is not used in the implementation. // https://github.com/dotnet/runtime/blob/c0c7f02be7285a1ef0d3022b8c2f38be4025545f/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L1825 new(KnownType.System_Guid, "Parse"), // https://github.com/dotnet/runtime/blob/c0c7f02be7285a1ef0d3022b8c2f38be4025545f/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L1838 new(KnownType.System_Guid, "TryParse"), // These methods take a IFormatProvider parameter and use it. // Controversial: There are edge cases like culture "fa-AF" where the 0x2212 (MINUS SIGN) is used instead of the usual 0x002D (HYPHEN-MINUS) new(KnownType.System_Int16, "Parse"), new(KnownType.System_Int16, "TryParse"), new(KnownType.System_Int32, "Parse"), new(KnownType.System_Int32, "TryParse"), new(KnownType.System_Int64, "Parse"), new(KnownType.System_Int64, "TryParse"), new(KnownType.System_Int128, "Parse"), new(KnownType.System_Int128, "TryParse"), ]; private static IReadOnlyCollection WhitelistedMethods = [ new(KnownType.System_Char, "ToUpper"), new(KnownType.System_Char, "ToLower"), ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public static bool HasAnyFormatOrCultureParameter(ISymbol method) => method.GetParameters().Any(x => x.Type.IsAny(FormatAndCultureType)); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.Expression is not null && c.Model.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol methodSymbol && !IsIgnored(methodSymbol) && CanPotentiallyRaise(methodSymbol) && invocation.HasOverloadWithType(c.Model, FormatAndCultureType)) { c.ReportIssue(Rule, invocation, invocation.Expression.ToString()); } }, SyntaxKind.InvocationExpression); private static bool CanPotentiallyRaise(IMethodSymbol methodSymbol) => ReturnsOrAcceptsFormattableType(methodSymbol) || WhitelistedMethods.Any(x => Matches(x, methodSymbol)); private static bool IsIgnored(IMethodSymbol methodSymbol) => SpecifyStringComparison.HasAnyStringComparisonParameter(methodSymbol) || HasAnyFormatOrCultureParameter(methodSymbol) || IgnoredMethods.Any(x => Matches(x, methodSymbol)); private static bool ReturnsOrAcceptsFormattableType(IMethodSymbol methodSymbol) => methodSymbol.ReturnType.IsAny(FormattableTypes) || methodSymbol.GetParameters().Any(x => x.Type.IsAny(FormattableTypes)); private static bool Matches(MemberDescriptor memberDescriptor, IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.ContainingType.Is(memberDescriptor.ContainingType) && methodSymbol.Name == memberDescriptor.Name; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SpecifyStringComparison.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SpecifyStringComparison : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4058"; private const string MessageFormat = "Change this call to '{0}' to an overload that accepts a " + "'StringComparison' as a parameter."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray stringComparisonType = ImmutableArray.Create(KnownType.System_StringComparison); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.Expression != null && IsInvalidCall(invocation.Expression, c.Model) && SonarAnalyzer.CSharp.Syntax.Extensions.InvocationExpressionSyntaxExtensions.HasOverloadWithType(invocation, c.Model, stringComparisonType)) { c.ReportIssue(rule, invocation, invocation.Expression.ToString()); } }, SyntaxKind.InvocationExpression); } private static bool IsInvalidCall(ExpressionSyntax expression, SemanticModel semanticModel) { return semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol && !HasAnyStringComparisonParameter(methodSymbol) && methodSymbol.GetParameters().Any(parameter => parameter.Type.Is(KnownType.System_String)) && !SpecifyIFormatProviderOrCultureInfo.HasAnyFormatOrCultureParameter(methodSymbol); } public static bool HasAnyStringComparisonParameter(IMethodSymbol method) { return method.GetParameters() .Any(parameter => parameter.Type.Is(KnownType.System_StringComparison)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SqlKeywordsDelimitedBySpace.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SqlKeywordsDelimitedBySpace : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2857"; private const string MessageFormat = "Add a space before '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly NameSyntax[] SqlNamespaces = [ BuildQualifiedNameSyntax("System", "Data"), BuildQualifiedNameSyntax("Microsoft", "EntityFrameworkCore"), BuildQualifiedNameSyntax("ServiceStack", "OrmLite"), BuildQualifiedNameSyntax("System", "Data", "SqlClient"), BuildQualifiedNameSyntax("System", "Data", "SQLite"), BuildQualifiedNameSyntax("System", "Data", "SqlServerCe"), BuildQualifiedNameSyntax("System", "Data", "Entity"), BuildQualifiedNameSyntax("System", "Data", "Odbc"), BuildQualifiedNameSyntax("System", "Data", "OracleClient"), BuildQualifiedNameSyntax("Microsoft", "Data", "SqlClient"), BuildQualifiedNameSyntax("Microsoft", "Data", "Sqlite"), SyntaxFactory.IdentifierName("Dapper"), SyntaxFactory.IdentifierName("NHibernate"), SyntaxFactory.IdentifierName("PetaPoco") ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); // The '@' symbol is used for named parameters. // The '{' and '}' symbols are used in string interpolations. // The '[' and ']' symbols are used to escape keywords, reserved words or special characters in SQL queries. // // We ignore other non-alphanumeric characters (e.g. '>','=') to avoid false positives. private static readonly ISet InvalidCharacters = new HashSet { '@', '{', '}', '[', ']' }; // We are interested in SQL keywords that start a query (so without "FROM", for example) private static readonly IList SqlStartQueryKeywords = new List { "ALTER", "BULK INSERT", "CREATE", "DELETE", "DROP", "EXEC", "EXECUTE", "GRANT", "INSERT", "MERGE", "READTEXT", "SELECT", "TRUNCATE", "UPDATE", "UPDATETEXT", "WRITETEXT" }; private static readonly int SqlKeywordMinSize = SqlStartQueryKeywords .Select(s => s.Length) .OrderBy(i => i) .First(); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var namespaceDeclaration = (BaseNamespaceDeclarationSyntaxWrapper)c.Node; if (namespaceDeclaration.SyntaxNode.Parent is CompilationUnitSyntax compilationUnit && (HasSqlNamespace(compilationUnit.Usings) || HasSqlNamespace(namespaceDeclaration.Usings))) { var visitor = new StringConcatenationWalker(c); foreach (var member in namespaceDeclaration.Members) { visitor.SafeVisit(member); } } }, SyntaxKind.NamespaceDeclaration, SyntaxKindEx.FileScopedNamespaceDeclaration); private static bool HasSqlNamespace(SyntaxList usings) => usings.Select(x => x.Name) .Any(x => SqlNamespaces.Any(usingsNamespace => SyntaxFactory.AreEquivalent(x, usingsNamespace))); // creates a QualifiedNameSyntax "a.b" private static QualifiedNameSyntax BuildQualifiedNameSyntax(string a, string b) => SyntaxFactory.QualifiedName( SyntaxFactory.IdentifierName(a), SyntaxFactory.IdentifierName(b)); // creates a QualifiedNameSyntax "a.b.c" private static QualifiedNameSyntax BuildQualifiedNameSyntax(string a, string b, string c) => SyntaxFactory.QualifiedName( SyntaxFactory.QualifiedName( SyntaxFactory.IdentifierName(a), SyntaxFactory.IdentifierName(b)), SyntaxFactory.IdentifierName(c)); private sealed class StringConcatenationWalker : SafeCSharpSyntaxWalker { private readonly SonarSyntaxNodeReportingContext context; public StringConcatenationWalker(SonarSyntaxNodeReportingContext context) => this.context = context; public override void VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node) { if (TryGetConstantValues(node, out var stringParts) && stringParts.Count > 0 && StartsWithSqlKeyword(stringParts[0].Text.Trim())) { RaiseIssueIfNotDelimited(stringParts); } base.VisitInterpolatedStringExpression(node); } // The assumption is that in a chain of concatenations "a" + "b" + "c" // the AST form is // + // / \ // + "c" // / \ // "a" "b" // So we start from the lower-left node which should contain the SQL start keyword public override void VisitBinaryExpression(BinaryExpressionSyntax node) { if (node.IsKind(SyntaxKind.AddExpression) // we do the analysis only if it's a SQL keyword on the left && TryGetStringWrapper(node.Left, out var leftSide) && TryGetStringWrapper(node.Right, out var rightSide) && StartsWithSqlKeyword(leftSide.Text.Trim())) { var strings = new List { leftSide, rightSide }; if (TryExtractNestedStrings(node, strings)) { RaiseIssueIfNotDelimited(strings); } } Visit(node.Left); Visit(node.Right); } private void RaiseIssueIfNotDelimited(List stringWrappers) { for (var i = 0; i < stringWrappers.Count - 1; i++) { var firstStringText = stringWrappers[i].Text; var secondString = stringWrappers[i + 1]; var secondStringText = secondString.Text; if (firstStringText.Length > 0 && secondStringText.Length > 0 && IsInvalidCombination(firstStringText.Last(), secondStringText.First())) { var word = secondStringText.Split(' ').FirstOrDefault(); context.ReportIssue(Rule, secondString.Node, word); } } } private bool TryGetStringWrapper(ExpressionSyntax expression, out StringWrapper stringWrapper) { if (expression is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression)) { stringWrapper = new StringWrapper(literal, literal.Token.ValueText); return true; } else if (expression is InterpolatedStringExpressionSyntax interpolatedString) { stringWrapper = new StringWrapper(interpolatedString, interpolatedString.ContentsText()); return true; } // if this is a nested binary, we skip it so that we can raise when we visit it. // Otherwise, FindConstantValue will merge it into one value. else if (expression.RemoveParentheses() is not BinaryExpressionSyntax && expression.FindConstantValue(context.Model) is string constantValue) { stringWrapper = new StringWrapper(expression, constantValue); return true; } else { stringWrapper = null; return false; } } /** * Returns * - true if all the found elements have constant string value. * - false if, inside the chain of binary expressions, some element's value cannot be computed or * some binary expressions are not additions. */ private bool TryExtractNestedStrings(BinaryExpressionSyntax node, List strings) { // this is the left-most node of a concatenation chain // collect all string literals var parent = node.Parent; while (parent is BinaryExpressionSyntax concatenation) { if (concatenation.IsKind(SyntaxKind.AddExpression) && TryGetStringWrapper(concatenation.Right, out var stringWrapper)) { strings.Add(stringWrapper); } else { // we are in a binary expression, but it's not only of constants or concatenations return false; } parent = parent.Parent; } return true; } private bool TryGetConstantValues(InterpolatedStringExpressionSyntax interpolatedStringExpression, out List parts) { parts = []; foreach (var content in interpolatedStringExpression.Contents) { if (content is InterpolationSyntax interpolation && interpolation.Expression.FindConstantValue(context.Model) is string constantValue) { parts.Add(new StringWrapper(content, constantValue)); } else if (content is InterpolatedStringTextSyntax interpolatedText) { parts.Add(new StringWrapper(interpolatedText, interpolatedText.TextToken.Text)); } else { parts = null; return false; } } return true; } private static bool StartsWithSqlKeyword(string firstString) => firstString.Length >= SqlKeywordMinSize && SqlStartQueryKeywords.Any(x => firstString.StartsWith(x, StringComparison.OrdinalIgnoreCase)); private static bool IsInvalidCombination(char first, char second) { // Concatenation of a named parameter with or without string interpolation. if (first == '@' && (char.IsLetterOrDigit(second) || second == '{')) { return false; } return IsAlphaNumericOrInvalidCharacters(first) && IsAlphaNumericOrInvalidCharacters(second); static bool IsAlphaNumericOrInvalidCharacters(char c) => char.IsLetterOrDigit(c) || InvalidCharacters.Contains(c); } } private sealed class StringWrapper { public SyntaxNode Node { get; } public string Text { get; } internal StringWrapper(SyntaxNode node, string text) { Node = node; Text = text; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldInGenericClass.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticFieldInGenericClass : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2743"; private const string MessageFormat = "A static field in a generic type is not shared among instances of different close constructed types."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var typeDeclaration = (TypeDeclarationSyntax)c.Node; var typeParameterNames = CollectTypeParameterNames(typeDeclaration); if (c.IsRedundantPositionalRecordContext() || typeParameterNames.Length == 0 || BaseTypeHasGenericTypeArgument(typeDeclaration, typeParameterNames)) { return; } var variables = typeDeclaration.Members .OfType() .Where(x => x.Modifiers.Any(SyntaxKind.StaticKeyword) && !HasGenericType(c, x.Declaration.Type, typeParameterNames)) .SelectMany(x => x.Declaration.Variables); foreach (var variable in variables) { CheckMember(c, variable, variable.Identifier.GetLocation(), typeParameterNames); } foreach (var property in typeDeclaration.Members.OfType().Where(x => x.Modifiers.Any(SyntaxKind.StaticKeyword) && x.ExpressionBody is null)) { CheckMember(c, property, property.Identifier.GetLocation(), typeParameterNames); } }, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); private static void CheckMember(SonarSyntaxNodeReportingContext context, SyntaxNode root, Location location, string[] typeParameterNames) { if (!HasGenericType(context, root, typeParameterNames)) { context.ReportIssue(Rule, location); } } private static string[] CollectTypeParameterNames(SyntaxNode current) { var names = new HashSet(); while (current is not null) { if (current is TypeDeclarationSyntax { TypeParameterList: not null } typeDeclaration) { names.AddRange(typeDeclaration.TypeParameterList.Parameters.Select(x => x.Identifier.ValueText)); } current = current.Parent; } return names.ToArray(); } private static bool HasGenericType(SonarSyntaxNodeReportingContext context, SyntaxNode root, string[] typeParameterNames) => root.DescendantNodesAndSelf() .OfType() .Any(x => typeParameterNames.Contains(x.Identifier.Value) && context.Model.GetSymbolInfo(x).Symbol is { Kind: SymbolKind.TypeParameter }); private static bool BaseTypeHasGenericTypeArgument(TypeDeclarationSyntax typeDeclaration, string[] typeParameterNames) => typeDeclaration.BaseList is { } baseList && baseList.Types.Any(x => x.Type is GenericNameSyntax genericType && HasGenericTypeArgument(genericType, typeParameterNames)); private static bool HasGenericTypeArgument(GenericNameSyntax genericType, string[] typeParameterNames) => genericType.TypeArgumentList.Arguments.OfType().Any(x => typeParameterNames.Contains(x.Identifier.ValueText)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldInitializerOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticFieldInitializerOrder : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3263"; private const string MessageFormat = "Move this field's initializer into a static constructor."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet EnclosingTypes = [ SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, ]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (!fieldDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)) { return; } var variables = fieldDeclaration.Declaration.Variables.Where(x => x.Initializer != null).ToArray(); if (variables.Length == 0) { return; } var containingType = c.Model.GetDeclaredSymbol(variables[0]).ContainingType; var typeDeclaration = fieldDeclaration.FirstAncestorOrSelf(x => x.IsAnyKind(EnclosingTypes)); foreach (var variable in variables) { if (IdentifierFields(variable, containingType, c.Model) .Select(x => new IdentifierTypeDeclarationMapping(x, GetTypeDeclaration(x))) .Any(x => x.TypeDeclaration is not null && (x.TypeDeclaration != typeDeclaration || x.Field.DeclaringSyntaxReferences.First().Span.Start > variable.SpanStart))) { c.ReportIssue(Rule, variable.Initializer); } } }, SyntaxKind.FieldDeclaration); private static IEnumerable IdentifierFields(VariableDeclaratorSyntax variable, INamedTypeSymbol containingType, SemanticModel semanticModel) { foreach (var identifier in variable.Initializer.DescendantNodes().OfType()) { if (containingType.MemberNames.Contains(identifier.Identifier.ValueText) && semanticModel.GetSymbolInfo(identifier).Symbol is IFieldSymbol { IsConst: false, IsStatic: true } field && containingType.Equals(field.ContainingType) && semanticModel.GetEnclosingSymbol(identifier.SpanStart) is IFieldSymbol enclosingSymbol && enclosingSymbol.ContainingType.Equals(field.ContainingType)) { yield return field; } } } private static TypeDeclarationSyntax GetTypeDeclaration(IFieldSymbol field) => field.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().FirstAncestorOrSelf(); private sealed record IdentifierTypeDeclarationMapping(IFieldSymbol Field, TypeDeclarationSyntax TypeDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldVisible.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticFieldVisible : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2223"; private const string MessageFormat = "Change the visibility of '{0}' or make it 'const' or 'readonly'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { foreach (var diagnostic in GetDiagnostics(c.Model, (FieldDeclarationSyntax)c.Node)) { c.ReportIssue(diagnostic); } }, SyntaxKind.FieldDeclaration); private static IEnumerable GetDiagnostics(SemanticModel model, FieldDeclarationSyntax declaration) => FieldIsRelevant(declaration) ? declaration.Declaration.Variables .Where(x => !FieldIsThreadSafe(model.GetDeclaredSymbol(x) as IFieldSymbol)) .Select(x => Diagnostic.Create(Rule, x.Identifier.GetLocation(), x.Identifier.ValueText)) : Enumerable.Empty(); private static bool FieldIsRelevant(FieldDeclarationSyntax node) => node.Modifiers.Count > 1 && node.Modifiers.Any(SyntaxKind.StaticKeyword) && !node.Modifiers.Any(SyntaxKind.VolatileKeyword) && (!node.Modifiers.Any(SyntaxKind.PrivateKeyword) || node.Modifiers.Any(SyntaxKind.ProtectedKeyword)) && !node.Modifiers.Any(SyntaxKind.ReadOnlyKeyword); private static bool FieldIsThreadSafe(IFieldSymbol fieldSymbol) => fieldSymbol.GetAttributes().Any(x => x.AttributeClass.Is(KnownType.System_ThreadStaticAttribute)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldWrittenFrom.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { public abstract class StaticFieldWrittenFrom : SonarDiagnosticAnalyzer { protected abstract DiagnosticDescriptor Rule { get; } protected override bool EnableConcurrentExecution => false; protected abstract bool IsValidCodeBlockContext(SyntaxNode node, ISymbol owningSymbol); protected abstract string GetDiagnosticMessageArgument(SyntaxNode node, ISymbol owningSymbol, IFieldSymbol field); public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCodeBlockStartAction(cbc => { if (!IsValidCodeBlockContext(cbc.CodeBlock, cbc.OwningSymbol)) { return; } var locationsForFields = new MultiValueDictionary(); cbc.RegisterNodeAction(c => { var assignment = (AssignmentExpressionSyntax)c.Node; foreach (var target in assignment.AssignmentTargets()) { if (GetStaticFieldSymbol(c.Model, target) is { } fieldSymbol) { locationsForFields.Add(fieldSymbol, target.CreateLocation(to: assignment.OperatorToken)); } } }, SyntaxKind.SimpleAssignmentExpression, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression, SyntaxKind.MultiplyAssignmentExpression, SyntaxKind.DivideAssignmentExpression, SyntaxKind.ModuloAssignmentExpression, SyntaxKind.AndAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression, SyntaxKind.OrAssignmentExpression, SyntaxKind.LeftShiftAssignmentExpression, SyntaxKind.RightShiftAssignmentExpression, SyntaxKindEx.CoalesceAssignmentExpression, SyntaxKindEx.UnsignedRightShiftAssignmentExpression); cbc.RegisterNodeAction(c => { var unary = (PrefixUnaryExpressionSyntax)c.Node; CollectLocationOfStaticField(c.Model, locationsForFields, unary.Operand); }, SyntaxKind.PreDecrementExpression, SyntaxKind.PreIncrementExpression); cbc.RegisterNodeAction(c => { var unary = (PostfixUnaryExpressionSyntax)c.Node; CollectLocationOfStaticField(c.Model, locationsForFields, unary.Operand); }, SyntaxKind.PostDecrementExpression, SyntaxKind.PostIncrementExpression); cbc.RegisterCodeBlockEndAction(c => { foreach (var fieldWithLocations in locationsForFields) { var firstPosition = fieldWithLocations.Value.Select(x => x.SourceSpan.Start).Min(); var location = fieldWithLocations.Value.First(x => x.SourceSpan.Start == firstPosition); var message = GetDiagnosticMessageArgument(c.CodeBlock, c.OwningSymbol, fieldWithLocations.Key); var secondaryLocations = fieldWithLocations.Key.DeclaringSyntaxReferences.Select(x => x.GetSyntax().ToSecondaryLocation()); c.ReportIssue(Rule, location, secondaryLocations, message); } }); }); private static void CollectLocationOfStaticField(SemanticModel semanticModel, MultiValueDictionary locationsForFields, ExpressionSyntax expression) { if (GetStaticFieldSymbol(semanticModel, expression) is { } fieldSymbol) { locationsForFields.Add(fieldSymbol, expression.GetLocation()); } } private static IFieldSymbol GetStaticFieldSymbol(SemanticModel semanticModel, SyntaxNode node) => semanticModel.GetSymbolInfo(node) is { Symbol: IFieldSymbol { IsStatic: true } fieldSymbol } ? fieldSymbol : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldWrittenFromInstanceConstructor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticFieldWrittenFromInstanceConstructor : StaticFieldWrittenFrom { private const string DiagnosticId = "S3010"; private const string MessageFormat = "Remove this assignment of '{0}' or initialize it statically."; protected override DiagnosticDescriptor Rule => DescriptorFactory.Create(DiagnosticId, MessageFormat); protected override bool IsValidCodeBlockContext(SyntaxNode node, ISymbol owningSymbol) => node is ConstructorDeclarationSyntax declaration && !declaration.Modifiers.Any(SyntaxKind.StaticKeyword); protected override string GetDiagnosticMessageArgument(SyntaxNode node, ISymbol owningSymbol, IFieldSymbol field) => field.Name; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticFieldWrittenFromInstanceMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticFieldWrittenFromInstanceMember : StaticFieldWrittenFrom { private const string DiagnosticId = "S2696"; private const string MessageFormat = "{0}"; private const string MessageFormatMultipleOptions = "Make the enclosing instance {0} 'static' or remove this set on the 'static' field."; private const string MessageFormatRemoveSet = "Remove this set, which updates a 'static' field from an instance {0}."; protected override DiagnosticDescriptor Rule => DescriptorFactory.Create(DiagnosticId, MessageFormat); protected override bool IsValidCodeBlockContext(SyntaxNode node, ISymbol owningSymbol) => owningSymbol is { IsStatic: false } && owningSymbol is not IMethodSymbol { IsExtension: true } && node is MethodDeclarationSyntax or AccessorDeclarationSyntax; protected override string GetDiagnosticMessageArgument(SyntaxNode node, ISymbol owningSymbol, IFieldSymbol field) { var messageFormat = owningSymbol.IsChangeable() ? MessageFormatMultipleOptions : MessageFormatRemoveSet; var declarationType = DeclarationType(node); return string.Format(messageFormat, declarationType); } private static string DeclarationType(SyntaxNode declaration) => declaration switch { MethodDeclarationSyntax => "method", AccessorDeclarationSyntax => "property", _ => throw new NotSupportedException($"Not expected syntax kind {declaration.RawKind}.") }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StaticSealedClassProtectedMembers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StaticSealedClassProtectedMembers : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2156"; private const string MessageFormat = "Remove this 'protected' modifier."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { ReportDiagnostics(c, c.Node, ((BaseMethodDeclarationSyntax)c.Node).Modifiers); }, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction(c => { ReportDiagnostics(c, c.Node, ((BasePropertyDeclarationSyntax)c.Node).Modifiers); }, SyntaxKind.PropertyDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.EventDeclaration); context.RegisterNodeAction(c => { var fieldDeclaration = (BaseFieldDeclarationSyntax)c.Node; ReportDiagnostics(c, fieldDeclaration.Declaration.Variables.FirstOrDefault(), fieldDeclaration.Modifiers); }, SyntaxKind.FieldDeclaration, SyntaxKind.EventFieldDeclaration); } private static void ReportDiagnostics(SonarSyntaxNodeReportingContext context, SyntaxNode declaration, IEnumerable modifiers) { var symbol = context.Model.GetDeclaredSymbol(declaration); if (symbol == null || symbol.IsOverride || !symbol.ContainingType.IsSealed) { return; } modifiers .Where(m => m.IsKind(SyntaxKind.ProtectedKeyword)) .Select(m => Diagnostic.Create(rule, m.GetLocation())) .ToList() .ForEach(d => context.ReportIssue(d)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StreamReadStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StreamReadStatement : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2674"; private const string MessageFormat = "Check the return value of the '{0}' call to see how many bytes were read."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet ReadMethodNames = new HashSet(StringComparer.Ordinal) { nameof(Stream.Read), nameof(Stream.ReadAsync), "ReadAtLeast", // Net7: https://learn.microsoft.com/dotnet/api/system.io.stream.readatleast#applies-to "ReadAtLeastAsync", }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var statement = (ExpressionStatementSyntax)c.Node; var expression = statement.Expression switch { AwaitExpressionSyntax awaitExpression => awaitExpression.AwaitedExpressionWithoutConfigureAwait(), var x => x, }; expression = expression.RemoveConditionalAccess(); if (expression is InvocationExpressionSyntax invocation && invocation.GetMethodCallIdentifier() is { } methodIdentifier && ReadMethodNames.Contains(methodIdentifier.Text) && c.Model.GetSymbolInfo(expression).Symbol is IMethodSymbol method && (method.ContainingType.Is(KnownType.System_IO_Stream) || (method.IsOverride && method.ContainingType.DerivesOrImplements(KnownType.System_IO_Stream)))) { c.ReportIssue(Rule, expression, method.Name); } }, SyntaxKind.ExpressionStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringConcatenationInLoop.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringConcatenationInLoop : StringConcatenationInLoopBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] CompoundAssignmentKinds { get; } = [SyntaxKind.AddAssignmentExpression]; protected override ISet ExpressionConcatenationKinds { get; } = new HashSet { SyntaxKind.AddExpression }; protected override ISet LoopKinds { get; } = new HashSet { SyntaxKind.WhileStatement, SyntaxKind.DoStatement, SyntaxKind.ForStatement, SyntaxKind.ForEachStatement }; protected override SyntaxNode LeftMostExpression(SyntaxNode expression) => expression is ElementAccessExpressionSyntax ? null : ((ExpressionSyntax)expression).LeftMostInMemberAccess(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringFormatValidator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using System.Text.RegularExpressions; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringFormatValidator : SonarDiagnosticAnalyzer { private const string BugDiagnosticId = "S2275"; private const string CodeSmellDiagnosticId = "S3457"; private const string MessageFormat = "{0}"; // This is the value as defined in .Net Framework private const int MaxValueForArgumentIndexAndAlignment = 1_000_000; private static readonly DiagnosticDescriptor BugRule = DescriptorFactory.Create(BugDiagnosticId, MessageFormat); private static readonly DiagnosticDescriptor CodeSmellRule = DescriptorFactory.Create(CodeSmellDiagnosticId, MessageFormat); private static readonly HashSet HandledFormatMethods = new HashSet { new MemberDescriptor(KnownType.System_String, "Format"), new MemberDescriptor(KnownType.System_Console, "Write"), new MemberDescriptor(KnownType.System_Console, "WriteLine"), new MemberDescriptor(KnownType.System_Text_StringBuilder, "AppendFormat"), new MemberDescriptor(KnownType.System_IO_TextWriter, "Write"), new MemberDescriptor(KnownType.System_IO_TextWriter, "WriteLine"), new MemberDescriptor(KnownType.System_IO_StreamWriter, "Write"), new MemberDescriptor(KnownType.System_IO_StreamWriter, "WriteLine"), new MemberDescriptor(KnownType.System_Diagnostics_Debug, "WriteLine"), new MemberDescriptor(KnownType.System_Diagnostics_Trace, "TraceError"), new MemberDescriptor(KnownType.System_Diagnostics_Trace, "TraceInformation"), new MemberDescriptor(KnownType.System_Diagnostics_Trace, "TraceWarning"), new MemberDescriptor(KnownType.System_Diagnostics_TraceSource, "TraceInformation") }; private static readonly HashSet BugRelatedFailures = new HashSet { ValidationFailure.UnknownError, ValidationFailure.NullFormatString, ValidationFailure.InvalidCharacterAfterOpenCurlyBrace, ValidationFailure.UnbalancedCurlyBraceCount, ValidationFailure.FormatItemMalformed, ValidationFailure.FormatItemIndexBiggerThanArgsCount, ValidationFailure.FormatItemIndexBiggerThanMaxValue, ValidationFailure.FormatItemAlignmentBiggerThanMaxValue }; private static readonly HashSet CodeSmellRelatedFailures = new HashSet { ValidationFailure.SimpleString, ValidationFailure.MissingFormatItemIndex, ValidationFailure.UnusedFormatArguments }; // pattern is: index[,alignment][:formatString] private static readonly Regex StringFormatItemRegex = new Regex(@"^(?\d+)(\s*,\s*(?-?\d+)\s*)?(:(?.+))?$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(BugRule, CodeSmellRule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(CheckForFormatStringIssues, SyntaxKind.InvocationExpression); private static void CheckForFormatStringIssues(SonarSyntaxNodeReportingContext analysisContext) { var invocation = (InvocationExpressionSyntax)analysisContext.Node; if (!(analysisContext.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol) || !methodSymbol.Parameters.Any() || methodSymbol.Parameters.All(x => x.Name != "format")) { return; } var currentMethodSignature = HandledFormatMethods .Where(hfm => methodSymbol.ContainingType.Is(hfm.ContainingType)) .FirstOrDefault(method => method.Name == methodSymbol.Name); if (currentMethodSignature == null) { return; } var formatArgumentIndex = methodSymbol.Parameters[0].IsType(KnownType.System_IFormatProvider) ? 1 : 0; var formatStringExpression = invocation.ArgumentList.Arguments[formatArgumentIndex].Expression; var failure = formatStringExpression.IsNullLiteral() ? new ValidationFailureWithAdditionalData(ValidationFailure.NullFormatString) : TryParseAndValidate(formatStringExpression.FindStringConstant(analysisContext.Model), invocation.ArgumentList, formatArgumentIndex, analysisContext.Model); if (failure == null || CanIgnoreFailure(failure, currentMethodSignature.Name, invocation.ArgumentList.Arguments.Count)) { return; } if (BugRelatedFailures.Contains(failure.Failure)) { analysisContext.ReportIssue(BugRule, invocation.Expression, failure.ToString()); } if (CodeSmellRelatedFailures.Contains(failure.Failure)) { analysisContext.ReportIssue(CodeSmellRule, invocation.Expression, failure.ToString()); } } private static bool CanIgnoreFailure(ValidationFailureWithAdditionalData failure, string methodName, int argumentsCount) { if (methodName.EndsWith("Format") || failure.Failure == ValidationFailure.UnusedFormatArguments || failure.Failure == ValidationFailure.FormatItemIndexBiggerThanArgsCount) { return false; } // All methods in HandledFormatMethods that do not end on Format have an overload // with only one argument and the rule should not raise an issue return argumentsCount == 1; } private static ValidationFailureWithAdditionalData TryParseAndValidate(string formatStringText, ArgumentListSyntax argumentList, int formatArgumentIndex, SemanticModel semanticModel) => formatStringText == null ? null : ExtractFormatItems(formatStringText, out var formatStringItems) ?? TryValidateFormatString(formatStringItems, argumentList, formatArgumentIndex, semanticModel); private static ValidationFailureWithAdditionalData ExtractFormatItems(string formatString, out List formatStringItems) { formatStringItems = new List(); var curlyBraceCount = 0; StringBuilder currentFormatItemBuilder = null; var isEscapingOpenCurlyBrace = false; var isEscapingCloseCurlyBrace = false; for (var i = 0; i < formatString.Length; i++) { var currentChar = formatString[i]; var previousChar = i > 0 ? formatString[i - 1] : '\0'; if (currentChar == '{') { if (previousChar == '{' && !isEscapingOpenCurlyBrace) { curlyBraceCount--; isEscapingOpenCurlyBrace = true; currentFormatItemBuilder = null; continue; } curlyBraceCount++; isEscapingOpenCurlyBrace = false; currentFormatItemBuilder ??= new StringBuilder(); continue; } if (previousChar == '{' && !char.IsDigit(currentChar) && currentFormatItemBuilder != null) { return new ValidationFailureWithAdditionalData(ValidationFailure.InvalidCharacterAfterOpenCurlyBrace); } if (currentChar == '}') { isEscapingCloseCurlyBrace = previousChar == '}' && !isEscapingCloseCurlyBrace; curlyBraceCount = isEscapingCloseCurlyBrace ? curlyBraceCount + 1 : curlyBraceCount - 1; if (currentFormatItemBuilder != null) { if (TryParseItem(currentFormatItemBuilder.ToString(), out var formatStringItem) is { } failure) { return new ValidationFailureWithAdditionalData(failure); } formatStringItems.Add(formatStringItem); currentFormatItemBuilder = null; } continue; } currentFormatItemBuilder?.Append(currentChar); } return curlyBraceCount == 0 ? null : new ValidationFailureWithAdditionalData(ValidationFailure.UnbalancedCurlyBraceCount); } private static ValidationFailure TryParseItem(string formatItem, out FormatStringItem formatStringItem) { formatStringItem = null; var matchResult = StringFormatItemRegex.SafeMatch(formatItem); if (!matchResult.Success) { return ValidationFailure.FormatItemMalformed; } var index = int.Parse(matchResult.Groups["Index"].Value); var alignment = matchResult.Groups["Alignment"].Success ? (int?)int.Parse(matchResult.Groups["Alignment"].Value) : null; formatStringItem = new FormatStringItem(index, alignment); return null; } private static ValidationFailureWithAdditionalData TryValidateFormatString(ICollection formatStringItems, ArgumentListSyntax argumentList, int formatArgumentIndex, SemanticModel semanticModel) { if (formatStringItems.Any(x => x.Index > MaxValueForArgumentIndexAndAlignment)) { return new ValidationFailureWithAdditionalData(ValidationFailure.FormatItemIndexBiggerThanMaxValue); } if (formatStringItems.Any(x => x.Alignment > MaxValueForArgumentIndexAndAlignment)) { return new ValidationFailureWithAdditionalData(ValidationFailure.FormatItemAlignmentBiggerThanMaxValue); } var formatArguments = argumentList.Arguments .Skip(formatArgumentIndex + 1) .Select(arg => FormatStringArgument.Create(arg.Expression, semanticModel)) .ToList(); var maxFormatItemIndex = formatStringItems.Max(item => (int?)item.Index); var realArgumentsCount = formatArguments.Count; if (formatArguments.Count == 1 && formatArguments[0].TypeSymbol.Is(TypeKind.Array)) { realArgumentsCount = formatArguments[0].ArraySize; if (realArgumentsCount == -1) { // can't statically check the override that supplies args in an array variable return null; } } return IsSimpleString(formatStringItems.Count, realArgumentsCount) ?? HasFormatItemIndexTooBig(maxFormatItemIndex, realArgumentsCount) ?? HasMissingFormatItemIndex(formatStringItems, maxFormatItemIndex) ?? HasUnusedArguments(formatArguments, maxFormatItemIndex); } private static ValidationFailureWithAdditionalData HasFormatItemIndexTooBig(int? maxFormatItemIndex, int argumentsCount) => maxFormatItemIndex.HasValue && maxFormatItemIndex.Value + 1 > argumentsCount ? new ValidationFailureWithAdditionalData(ValidationFailure.FormatItemIndexBiggerThanArgsCount) : null; private static ValidationFailureWithAdditionalData IsSimpleString(int formatStringItemsCount, int argumentsCount) => formatStringItemsCount == 0 && argumentsCount == 0 ? new ValidationFailureWithAdditionalData(ValidationFailure.SimpleString) : null; private static ValidationFailureWithAdditionalData HasMissingFormatItemIndex(IEnumerable formatStringItems, int? maxFormatItemIndex) { if (!maxFormatItemIndex.HasValue) { return null; } var missingFormatItemIndexes = Enumerable.Range(0, maxFormatItemIndex.Value + 1) .Except(formatStringItems.Select(item => item.Index)) .Select(i => i.ToString()) .ToList(); if (missingFormatItemIndexes.Count > 0) { return new ValidationFailureWithAdditionalData(ValidationFailure.MissingFormatItemIndex, missingFormatItemIndexes); } return null; } private static ValidationFailureWithAdditionalData HasUnusedArguments(List formatArguments, int? maxFormatItemIndex) { var unusedArgumentNames = formatArguments.Skip((maxFormatItemIndex ?? -1) + 1) .Select(arg => arg.Name) .ToList(); if (unusedArgumentNames.Count > 0) { return new ValidationFailureWithAdditionalData(ValidationFailure.UnusedFormatArguments, unusedArgumentNames); } return null; } public class ValidationFailure { public static readonly ValidationFailure NullFormatString = new ValidationFailure("Invalid string format, the format string cannot be null."); public static readonly ValidationFailure InvalidCharacterAfterOpenCurlyBrace = new ValidationFailure("Invalid string format, opening curly brace can only be followed by a digit or an opening curly brace."); public static readonly ValidationFailure UnbalancedCurlyBraceCount = new ValidationFailure("Invalid string format, unbalanced curly brace count."); public static readonly ValidationFailure FormatItemMalformed = new ValidationFailure("Invalid string format, all format items should comply with the following pattern '{index[,alignment][:formatString]}'."); public static readonly ValidationFailure FormatItemIndexBiggerThanArgsCount = new ValidationFailure("Invalid string format, the highest string format item index should not be greater than the arguments count."); public static readonly ValidationFailure FormatItemIndexBiggerThanMaxValue = new ValidationFailure($"Invalid string format, the string format item index should not be greater than {MaxValueForArgumentIndexAndAlignment}."); public static readonly ValidationFailure FormatItemAlignmentBiggerThanMaxValue = new ValidationFailure($"Invalid string format, the string format item alignment should not be greater than {MaxValueForArgumentIndexAndAlignment}."); public static readonly ValidationFailure SimpleString = new ValidationFailure("Remove this formatting call and simply use the input string."); public static readonly ValidationFailure UnknownError = new ValidationFailure("Invalid string format, the format string is invalid and is likely to throw at runtime."); public static readonly ValidationFailure MissingFormatItemIndex = new ValidationFailure("The format string might be wrong, the following item indexes are missing: "); public static readonly ValidationFailure UnusedFormatArguments = new ValidationFailure("The format string might be wrong, the following arguments are unused: "); public string Message { get; } private ValidationFailure(string message) => Message = message; } private class ValidationFailureWithAdditionalData { public ValidationFailure Failure { get; } private IEnumerable AdditionalData { get; } public ValidationFailureWithAdditionalData(ValidationFailure failure, IEnumerable additionalData = null) { Failure = failure; AdditionalData = additionalData; } public override string ToString() => AdditionalData == null ? Failure.Message : string.Concat(Failure.Message, AdditionalData.ToSentence(quoteWords: true), "."); } private sealed class FormatStringItem { public int Index { get; } public int? Alignment { get; } public FormatStringItem(int index, int? alignment) { Index = index; Alignment = alignment; } } private sealed class FormatStringArgument { public string Name { get; } public ITypeSymbol TypeSymbol { get; } public int ArraySize { get; } private FormatStringArgument(string name, ITypeSymbol typeSymbol, int arraySize = -1) { Name = name; TypeSymbol = typeSymbol; ArraySize = arraySize; } public static FormatStringArgument Create(ExpressionSyntax expression, SemanticModel semanticModel) { var type = semanticModel.GetTypeInfo(expression).Type; var arraySize = -1; if (type != null && type.Is(TypeKind.Array)) { if (expression is ImplicitArrayCreationExpressionSyntax implicitArray) { arraySize = implicitArray.Initializer.Expressions.Count; } if (expression is ArrayCreationExpressionSyntax array && array.Initializer != null) { arraySize = array.Initializer.Expressions.Count; } } return new FormatStringArgument(expression.ToString(), type, arraySize); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringLiteralShouldNotBeDuplicated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringLiteralShouldNotBeDuplicated : StringLiteralShouldNotBeDuplicatedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.CompilationUnit }; private HashSet TypeDeclarationSyntaxKinds { get; } = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration ]; protected override bool IsMatchingMethodParameterName(LiteralExpressionSyntax literalExpression) => literalExpression.FirstAncestorOrSelf() ?.ParameterList ?.Parameters .Any(p => p.Identifier.ValueText == literalExpression.Token.ValueText) ?? false; protected override bool IsInnerInstance(SonarSyntaxNodeReportingContext context) => context.Node.Ancestors().Any(x => x.IsAnyKind(TypeDeclarationSyntaxKinds) || (x.IsKind(SyntaxKind.CompilationUnit) && x.ChildNodes().Any(y => y.IsKind(SyntaxKind.GlobalStatement)))); protected override IEnumerable FindLiteralExpressions(SyntaxNode node) => node.DescendantNodes(x => !x.IsKind(SyntaxKind.AttributeList)) .Where(x => x.IsKind(SyntaxKind.StringLiteralExpression)) .Cast(); protected override SyntaxToken LiteralToken(LiteralExpressionSyntax literal) => literal.Token; protected override bool IsNamedTypeOrTopLevelMain(SonarSyntaxNodeReportingContext context) => IsNamedType(context) || context.IsTopLevelMain; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringOffsetMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringOffsetMethods : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4635"; private const string MessageFormat = "Replace '{0}' with the overload that accepts a startIndex parameter."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); private readonly string[] methodsToCheck = new string[] { "IndexOf", "IndexOfAny", "LastIndexOf", "LastIndexOfAny" }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(analysisContext => { var invocationExpression = (InvocationExpressionSyntax)analysisContext.Node; var semanticModel = analysisContext.Model; if (IsTargetMethodInvocation(invocationExpression, semanticModel) && HasSubstringMethodInvocationChild(invocationExpression, semanticModel)) { var memberAccess = (MemberAccessExpressionSyntax)invocationExpression.Expression; analysisContext.ReportIssue(rule, invocationExpression, memberAccess.Name.ToString()); } }, SyntaxKind.InvocationExpression); } private bool IsTargetMethodInvocation(InvocationExpressionSyntax invocationExpression, SemanticModel semanticModel) => methodsToCheck.Any(methodName => invocationExpression.IsMethodInvocation(KnownType.System_String, methodName, semanticModel)); private bool HasSubstringMethodInvocationChild(InvocationExpressionSyntax invocationExpression, SemanticModel semanticModel) => invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Expression is InvocationExpressionSyntax childInvocationExpression && childInvocationExpression.IsMethodInvocation(KnownType.System_String, "Substring", semanticModel); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringOperationWithoutCulture.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringOperationWithoutCulture : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1449"; private const string MessageFormat = "{0}"; internal const string MessageDefineLocale = "Define the locale to be used in this string operation."; internal const string MessageChangeCompareTo = "Use 'CompareOrdinal' or 'Compare' with the locale specified instead of 'CompareTo'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( ReportOnViolation, SyntaxKind.InvocationExpression); } private static void ReportOnViolation(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (!(invocation.Expression is MemberAccessExpressionSyntax memberAccess)) { return; } if (!(context.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol calledMethod)) { return; } if (context.IsInExpressionTree()) { return; // We cannot specify the culture in an expression tree } if (calledMethod.IsInType(KnownType.System_String) && CommonCultureSpecificMethodNames.Contains(calledMethod.Name) && !calledMethod.Parameters.Any(param => param.Type.IsAny(StringCultureSpecifierNames))) { context.ReportIssue(rule, memberAccess.Name, MessageDefineLocale); return; } if (calledMethod.IsInType(KnownType.System_String) && IndexLookupMethodNames.Contains(calledMethod.Name) && calledMethod.Parameters.Any(param => param.Type.SpecialType == SpecialType.System_String) && !calledMethod.Parameters.Any(param => param.Type.IsAny(StringCultureSpecifierNames))) { context.ReportIssue(rule, memberAccess.Name, MessageDefineLocale); return; } if (IsMethodOnNonIntegralOrDateTime(calledMethod) && calledMethod.Name == ToStringMethodName && calledMethod.Parameters.Length == 0) { context.ReportIssue(rule, memberAccess.Name, MessageDefineLocale); return; } if (calledMethod.IsInType(KnownType.System_String) && calledMethod.Name == CompareToMethodName) { context.ReportIssue(rule, memberAccess.Name, MessageChangeCompareTo); } } private static bool IsMethodOnNonIntegralOrDateTime(IMethodSymbol methodSymbol) { return methodSymbol.IsInType(KnownType.NonIntegralNumbers) || methodSymbol.IsInType(KnownType.System_DateTime); } private static readonly ISet CommonCultureSpecificMethodNames = new HashSet { "ToLower", "ToUpper", "Compare" }; private static readonly ISet IndexLookupMethodNames = new HashSet { "IndexOf", "LastIndexOf" }; private const string CompareToMethodName = "CompareTo"; private const string ToStringMethodName = "ToString"; private static readonly ImmutableArray StringCultureSpecifierNames = ImmutableArray.Create( KnownType.System_Globalization_CultureInfo, KnownType.System_Globalization_CompareOptions, KnownType.System_StringComparison ); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/StringOrIntegralTypesForIndexers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class StringOrIntegralTypesForIndexers : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3876"; private const string MessageFormat = "Use string, integral, index or range type here, or refactor this indexer into a method."; private static readonly ImmutableArray AllowedIndexerTypes = new[] { KnownType.System_Object, KnownType.System_String, KnownType.System_Index, KnownType.System_Range } .Concat(KnownType.IntegralNumbers) .ToImmutableArray(); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var indexerDeclaration = (IndexerDeclarationSyntax)c.Node; if (indexerDeclaration.ParameterList.Parameters.Count != 1) { return; } var parameter = indexerDeclaration.ParameterList.Parameters.First(); var parameterSymbol = c.Model.GetDeclaredSymbol(parameter); if (parameterSymbol.Type == null || parameterSymbol.Type.TypeKind == TypeKind.Dynamic || parameterSymbol.Type.IsAny(AllowedIndexerTypes) || parameterSymbol.IsParams || IsGenericTypeParameter(parameterSymbol)) { return; } c.ReportIssue(Rule, parameter.Type); }, SyntaxKind.IndexerDeclaration); private static bool IsGenericTypeParameter(IParameterSymbol parameterSymbol) => parameterSymbol.ContainingType.ConstructedFrom.TypeParameters.Any(parameterSymbol.Type.Equals); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SuppressFinalizeUseless.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SuppressFinalizeUseless : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3234"; private const string MessageFormat = "Remove this useless call to 'GC.SuppressFinalize'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; var suppressFinalizeSymbol = c.Model.GetSymbolInfo(invocation.Expression).Symbol as IMethodSymbol; if (suppressFinalizeSymbol?.Name != "SuppressFinalize" || !invocation.HasExactlyNArguments(1) || !suppressFinalizeSymbol.IsInType(KnownType.System_GC)) { return; } var argument = invocation.ArgumentList.Arguments.First(); var argumentType = c.Model.GetTypeInfo(argument.Expression).Type as INamedTypeSymbol; if (!argumentType.IsClass() || !argumentType.IsSealed) { return; } var hasFinalizer = argumentType.GetSelfAndBaseTypes() .Where(type => !type.Is(KnownType.System_Object)) .SelectMany(type => type.GetMembers()) .OfType() .Any(methodSymbol => methodSymbol.MethodKind == MethodKind.Destructor); if (!hasFinalizer) { c.ReportIssue(rule, invocation); } }, SyntaxKind.InvocationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SuppressFinalizeUselessCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class SuppressFinalizeUselessCodeFix : SonarCodeFix { internal const string Title = "Remove useless 'SuppressFinalize' call"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(SuppressFinalizeUseless.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntaxNode = root.FindNode(diagnosticSpan); if (syntaxNode.Parent is ExpressionStatementSyntax) { context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode.Parent, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwaggerActionReturnType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwaggerActionReturnType : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6968"; private const string MessageFormat = "{0}"; private const string NoAttributeMessageFormat = "Annotate this method with ProducesResponseType containing the return type for successful responses."; private const string NoTypeMessageFormat = "Use the ProducesResponseType overload containing the return type for successful responses."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray ControllerActionReturnTypes = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_IActionResult, KnownType.Microsoft_AspNetCore_Http_IResult); private static readonly ImmutableArray ProducesAttributes = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_ProducesAttribute, KnownType.Microsoft_AspNetCore_Mvc_ProducesResponseTypeAttribute); private static HashSet ActionResultMethods => [ "Ok", "Created", "CreatedAtAction", "CreatedAtRoute", "Accepted", "AcceptedAtAction", "AcceptedAtRoute" ]; private static HashSet ResultMethods => [ "Ok", "Created", "CreatedAtRoute", "Accepted", "AcceptedAtRoute" ]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { if (!compilationStart.Compilation.Assembly.HasAttribute(KnownType.Microsoft_AspNetCore_Mvc_ApiConventionTypeAttribute) && compilationStart.Compilation.ReferencesAll(KnownAssembly.MicrosoftAspNetCoreMvcCore, KnownAssembly.SwashbuckleAspNetCoreSwagger)) { compilationStart.RegisterSymbolStartAction(symbolStart => { if (IsControllerCandidate(symbolStart.Symbol)) { symbolStart.RegisterSyntaxNodeAction(nodeContext => { var methodDeclaration = (MethodDeclarationSyntax)nodeContext.Node; if (InvalidMethod(methodDeclaration, nodeContext) is { } method) { nodeContext.ReportIssue(Rule, methodDeclaration.Identifier, method.ResponseInvocations.ToSecondaryLocations(), GetMessage(method.Symbol)); } }, SyntaxKind.MethodDeclaration); } }, SymbolKind.NamedType); } }); private static InvalidMethodResult InvalidMethod(BaseMethodDeclarationSyntax methodDeclaration, SonarSyntaxNodeReportingContext nodeContext) { var responseInvocations = FindSuccessResponses(methodDeclaration, nodeContext.Model); return responseInvocations.Length == 0 || nodeContext.Model.GetDeclaredSymbol(methodDeclaration, nodeContext.Cancel) is not { } method || !method.IsControllerActionMethod() || !method.ReturnType.DerivesOrImplementsAny(ControllerActionReturnTypes) || method.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiConventionMethodAttribute) || HasApiExplorerSettingsWithIgnoreApiTrue(x) || HasProducesAttributesWithReturnType(x)) ? null : new InvalidMethodResult(method, responseInvocations); } private static SyntaxNode[] FindSuccessResponses(SyntaxNode node, SemanticModel model) { return ActionResultInvocations().Concat(ObjectCreationInvocations()).Concat(ResultMethodsInvocations()).ToArray(); IEnumerable ActionResultInvocations() => node.DescendantNodes() .OfType() .Where(x => ActionResultMethods.Contains(x.GetName()) && x.ArgumentList.Arguments.Count > 0 && model.GetSymbolInfo(x.Expression).Symbol is { } symbol && symbol.IsInType(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase) && symbol.GetParameters().Any(parameter => parameter.HasAttribute(KnownType.Microsoft_AspNetCore_Mvc_Infrastructure_ActionResultObjectValueAttribute))); IEnumerable ObjectCreationInvocations() => node.DescendantNodes() .OfType() .Where(x => x.GetName() == "ObjectResult" && x.ArgumentList?.Arguments.Count > 0 && model.GetSymbolInfo(x.Type).Symbol.GetSymbolType().Is(KnownType.Microsoft_AspNetCore_Mvc_ObjectResult)); IEnumerable ResultMethodsInvocations() => node.DescendantNodes() .OfType() .Where(x => ResultMethods.Contains(x.GetName()) && x.ArgumentList.Arguments.Count > 0 && model.GetSymbolInfo(x).Symbol.IsInType(KnownType.Microsoft_AspNetCore_Http_Results)); } private static bool IsControllerCandidate(ISymbol symbol) { var hasApiControllerAttribute = false; foreach (var attribute in symbol.GetAttributesWithInherited()) { if (attribute.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiConventionTypeAttribute) || HasProducesAttributesWithReturnType(attribute) || HasApiExplorerSettingsWithIgnoreApiTrue(attribute)) { return false; } hasApiControllerAttribute = hasApiControllerAttribute || attribute.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute); } return hasApiControllerAttribute; } private static string GetMessage(ISymbol symbol) => symbol.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ProducesResponseTypeAttribute)) ? NoTypeMessageFormat : NoAttributeMessageFormat; private static bool HasProducesAttributesWithReturnType(AttributeData attribute) => attribute.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ProducesResponseTypeAttribute_T) || attribute.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ProducesAttribute_T) || (attribute.AttributeClass.DerivesFromAny(ProducesAttributes) && ContainsReturnType(attribute)); private static bool HasApiExplorerSettingsWithIgnoreApiTrue(AttributeData attribute) => attribute.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiExplorerSettingsAttribute) && attribute.NamedArguments.FirstOrDefault(x => x.Key == "IgnoreApi").Value.Value is true; private static bool ContainsReturnType(AttributeData attribute) => !attribute.ConstructorArguments.FirstOrDefault(x => x.Type.Is(KnownType.System_Type)).IsNull || attribute.NamedArguments.FirstOrDefault(x => x.Key == "Type").Value.Value is not null; private sealed record InvalidMethodResult(IMethodSymbol Symbol, SyntaxNode[] ResponseInvocations); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchCaseFallsThroughToDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwitchCaseFallsThroughToDefault : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3458"; private const string MessageFormat = "Remove this empty 'case' clause."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var section = (SwitchSectionSyntax)c.Node; if (section.Statements.Count == 1 || !section.Labels.Any(SyntaxKind.DefaultSwitchLabel)) { return; } foreach (var label in section.Labels.Where(label => !label.IsKind(SyntaxKind.DefaultSwitchLabel))) { c.ReportIssue(rule, label); } }, SyntaxKind.SwitchSection); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchCaseFallsThroughToDefaultCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class SwitchCaseFallsThroughToDefaultCodeFix : SonarCodeFix { internal const string Title = "Remove useless 'case'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(SwitchCaseFallsThroughToDefault.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is CaseSwitchLabelSyntax syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchCasesMinimumThree.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwitchCasesMinimumThree : SwitchCasesMinimumThreeBase { private enum SwitchExpressionType { SingleReturnValue, TwoReturnValues, ManyReturnValues } private const string SwitchStatementMessage = "Replace this 'switch' statement with 'if' statements to increase readability."; private const string TwoReturnValueSwitchExpressionMessage = "Replace this 'switch' expression with a ternary conditional operator to increase readability."; private const string SingleReturnValueSwitchExpressionMessage = "Remove this 'switch' expression to increase readability."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, "{0}"); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var switchNode = (SwitchStatementSyntax)c.Node; if (!HasAtLeastThreeLabels(switchNode)) { c.ReportIssue(rule, switchNode.SwitchKeyword, SwitchStatementMessage); } }, SyntaxKind.SwitchStatement); context.RegisterNodeAction( c => { var switchNode = (SwitchExpressionSyntaxWrapper)c.Node; var message = EvaluateType(switchNode) switch { SwitchExpressionType.SingleReturnValue => SingleReturnValueSwitchExpressionMessage, SwitchExpressionType.TwoReturnValues => TwoReturnValueSwitchExpressionMessage, _ => string.Empty }; if (message != string.Empty) { c.ReportIssue(rule, switchNode.SwitchKeyword, message); } }, SyntaxKindEx.SwitchExpression); } private static bool HasAtLeastThreeLabels(SwitchStatementSyntax node) => node.Sections.Sum(section => section.Labels.Count) >= 3; private static SwitchExpressionType EvaluateType(SwitchExpressionSyntaxWrapper switchExpression) { var numberOfArms = switchExpression.Arms.Count; if (numberOfArms > 2) { return SwitchExpressionType.ManyReturnValues; } var hasDiscardValue = switchExpression.HasDiscardPattern(); if (numberOfArms == 2) { return hasDiscardValue ? SwitchExpressionType.TwoReturnValues : SwitchExpressionType.ManyReturnValues; } if (numberOfArms == 1) { return hasDiscardValue ? SwitchExpressionType.SingleReturnValue : SwitchExpressionType.TwoReturnValues; } return SwitchExpressionType.SingleReturnValue; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchDefaultClauseEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwitchDefaultClauseEmpty : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3532"; private const string MessageFormat = "Remove this empty 'default' clause."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var section = (SwitchSectionSyntax)c.Node; if (!section.Labels.Any(SyntaxKind.DefaultSwitchLabel) || section.Statements.Count != 1) { return; } if (section.Statements[0].IsKind(SyntaxKind.BreakStatement) && !HasAnyComment(section)) { c.ReportIssue(rule, section); } }, SyntaxKind.SwitchSection); } private static bool HasAnyComment(SwitchSectionSyntax section) => section.Labels.Last() .GetTrailingTrivia() // handle comments after last label, which will normally be default: .Union(section.Statements[0].GetLeadingTrivia()) // handle comments before break .Union(section.Statements[0].GetTrailingTrivia()) // handle comments after break .Any(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineCommentTrivia)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchDefaultClauseEmptyCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class SwitchDefaultClauseEmptyCodeFix : SonarCodeFix { internal const string Title = "Remove empty 'default' clause"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(SwitchDefaultClauseEmpty.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is SwitchSectionSyntax syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchSectionShouldNotHaveTooManyStatements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwitchSectionShouldNotHaveTooManyStatements : SwitchSectionShouldNotHaveTooManyStatementsBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var switchSection = (SwitchSectionSyntax)c.Node; if (switchSection.IsMissing || switchSection.Labels.Count <= 0) { return; } var statementsCount = switchSection.Statements.SelectMany(GetSubStatements).Count(); if (statementsCount > Threshold) { c.ReportIssue(rule, switchSection.Labels.First(), "switch section", statementsCount.ToString(), Threshold.ToString(), "method"); } }, SyntaxKind.SwitchSection); } private IEnumerable GetSubStatements(StatementSyntax statement) => statement.DescendantNodesAndSelf() .OfType() .Where(s => !(s is BlockSyntax)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchShouldNotBeNested.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SwitchShouldNotBeNested : SwitchShouldNotBeNestedBase { private const string MessageFormat = "Refactor the code to eliminate this nested 'switch'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var switchStatement = (SwitchStatementSyntax)c.Node; if (switchStatement.Parent?.FirstAncestorOrSelf() != null) { c.ReportIssue(rule, switchStatement.SwitchKeyword); } }, SyntaxKind.SwitchStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/SwitchWithoutDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class SwitchWithoutDefault : SwitchWithoutDefaultBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray kindsOfInterest = ImmutableArray.Create(SyntaxKind.SwitchStatement); public override ImmutableArray SyntaxKindsOfInterest => kindsOfInterest; protected override bool TryGetDiagnostic(SyntaxNode node, out Diagnostic diagnostic) { diagnostic = null; var switchNode = (SwitchStatementSyntax)node; if(!switchNode.HasDefaultLabel()) { diagnostic = Diagnostic.Create(rule, switchNode.SwitchKeyword.GetLocation(), "default", "switch"); return true; } return false; } protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TabCharacter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TabCharacter : TabCharacterBase { private const string DiagnosticId = "S105"; private const string MessageFormat = "Replace all tab characters in this file by sequences of white-spaces."; internal static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TaskConfigureAwait.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TaskConfigureAwait : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3216"; private const string MessageFormat = "Add '.ConfigureAwait(false)' to this call to allow execution to continue in any thread."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if (c.Compilation.Options.OutputKind != OutputKind.DynamicallyLinkedLibrary || !c.Compilation.IsNetFrameworkTarget()) { // This rule only makes sense in libraries under .NET Framework return; } if (((AwaitExpressionSyntax)c.Node).Expression is { } expression && c.Model.GetTypeInfo(expression).Type is { } type && type.DerivesFrom(KnownType.System_Threading_Tasks_Task)) { c.ReportIssue(rule, expression); } }, SyntaxKind.AwaitExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TestClassShouldHaveTestMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TestClassShouldHaveTestMethod : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2187"; private const string MessageFormat = "Add some tests to this {0}."; private static readonly ImmutableArray HandledSetupAndCleanUpAttributes = ImmutableArray.Create( // Only applies to MSTest. // NUnit has equivalent attributes, but they can only be applied to classes // marked with [SetupFixture], which cannot contain tests. KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyInitializeAttribute, KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyCleanupAttribute); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var typeDeclaration = (TypeDeclarationSyntax)c.Node; if (!c.IsRedundantPositionalRecordContext() && !typeDeclaration.Identifier.IsMissing && c.Model.GetDeclaredSymbol(typeDeclaration) is { } typeSymbol && IsViolatingRule(typeSymbol) && !IsExceptionToTheRule(typeSymbol)) { c.ReportIssue(Rule, typeDeclaration.Identifier, typeDeclaration.GetDeclarationTypeName()); } }, SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration); private static bool HasAnyTestMethod(INamespaceOrTypeSymbol symbol) => symbol.GetMembers().OfType().Any(m => m.IsTestMethod()); private static bool IsViolatingRule(INamedTypeSymbol symbol) => symbol.IsTestClass() && !HasAnyTestMethod(symbol); private static bool IsExceptionToTheRule(ITypeSymbol symbol) => symbol.IsAbstract || symbol.GetSelfAndBaseTypes().Any(HasAnyTestMethod) || HasSetupOrCleanupAttributes(symbol); private static bool HasSetupOrCleanupAttributes(INamespaceOrTypeSymbol symbol) => symbol.GetMembers().OfType().Any(m => m.GetAttributes(HandledSetupAndCleanUpAttributes).Any()); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldContainAssertion.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TestMethodShouldContainAssertion : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2699"; private const string MessageFormat = "Add at least one assertion to this test case."; private const string CustomAssertionAttributeName = "AssertionMethodAttribute"; private const int MaxInvocationDepth = 2; // Consider BFS instead of DFS if this gets increased private static readonly Dictionary KnownAssertions = new() { {"DidNotReceive", [KnownType.NSubstitute_SubstituteExtensions] }, {"DidNotReceiveWithAnyArgs", [KnownType.NSubstitute_SubstituteExtensions] }, {"Received", [KnownType.NSubstitute_SubstituteExtensions, KnownType.NSubstitute_ReceivedExtensions_ReceivedExtensions] }, {"ReceivedWithAnyArgs", [KnownType.NSubstitute_SubstituteExtensions, KnownType.NSubstitute_ReceivedExtensions_ReceivedExtensions] }, {"InOrder", [KnownType.NSubstitute_Received] }, }; /// The assertions in the Shouldly, Moq and FsCheck libraries are supported by /// - All assertions in Shouldly contain "Should" in their name. /// - All assertions in Moq contain "Verify" in their name. private static readonly ImmutableArray KnownAssertionTypes = ImmutableArray.Create( KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_Assert, KnownType.NFluent_Check, KnownType.NUnit_Framework_Assert, KnownType.NUnit_Framework_Legacy_ClassicAssert, KnownType.Xunit_Assert); private static readonly ImmutableArray KnownAssertionExceptionTypes = ImmutableArray.Create( KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_AssertFailedException, KnownType.NFluent_FluentCheckException, KnownType.NFluent_Kernel_FluentCheckException, KnownType.NUnit_Framework_AssertionException, KnownType.Xunit_Sdk_AssertException, KnownType.Xunit_Sdk_XunitException); private static readonly ImmutableArray FsCheckPropertyAttributes = ImmutableArray.Create( KnownType.FsCheck_Xunit_PropertyAttribute, KnownType.FsCheck_NUnit_PropertyAttribute); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var declaration = MethodDeclarationFactory.Create(c.Node); if (!declaration.Identifier.IsMissing && declaration.HasImplementation && c.Model.GetDeclaredSymbol(c.Node) is IMethodSymbol method && method.IsTestMethod() && !method.HasExpectedExceptionAttribute() && !method.HasAssertionInAttribute() && !method.IsIgnoredTestMethod() && !method.HasAnyAttribute(FsCheckPropertyAttributes) && !ContainsAssertion(c.Node, c.Model, new HashSet(), 0)) { c.ReportIssue(Rule, declaration.Identifier); } }, SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement); private static bool ContainsAssertion(SyntaxNode methodDeclaration, SemanticModel model, ISet visitedSymbols, int level) { var currentModel = methodDeclaration.EnsureCorrectSemanticModelOrDefault(model); if (currentModel is null) { return false; } var descendantNodes = methodDeclaration.DescendantNodes(); var invocations = descendantNodes.OfType().ToArray(); if (Array.Exists(invocations, IsAssertion) || descendantNodes.OfType().Any(x => x.Expression is not null && currentModel.GetTypeInfo(x.Expression).Type.DerivesFromAny(KnownAssertionExceptionTypes))) { return true; } var invokedSymbols = invocations.Select(x => currentModel.GetSymbolInfo(x).Symbol).OfType(); if (invokedSymbols.Any(x => IsKnownAssertion(x) || IsCustomAssertion(x))) { return true; } if (level == MaxInvocationDepth) { return false; } foreach (var symbol in invokedSymbols.Where(x => !visitedSymbols.Contains(x))) { visitedSymbols.Add(symbol); if (symbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()).OfType().Any(x => ContainsAssertion(x, currentModel, visitedSymbols, level + 1))) { return true; } } return false; } private static bool IsAssertion(InvocationExpressionSyntax invocation) => invocation.Expression .ToString() .SplitCamelCaseToWords() .Intersect(KnownMethods.AssertionMethodParts) .Any(); private static bool IsKnownAssertion(ISymbol methodSymbol) => (KnownAssertions.GetValueOrDefault(methodSymbol.Name) is { } types && Array.Exists(types, x => methodSymbol.ContainingType.ConstructedFrom.Is(x))) || methodSymbol.ContainingType.DerivesFromAny(KnownAssertionTypes); private static bool IsCustomAssertion(ISymbol methodSymbol) => methodSymbol.GetAttributesWithInherited().Any(x => x.AttributeClass.Name == CustomAssertionAttributeName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldHaveCorrectSignature.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TestMethodShouldHaveCorrectSignature : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3433"; private const string MessageFormat = "Make this test method {0}."; private const string MakePublicMessage = "'public'"; private const string MakeNonAsyncOrTaskMessage = "non-'async' or return 'Task'"; private const string MakeNotGenericMessage = "non-generic"; private const string MakeMethodNotLocalFunction = "a public method instead of a local function"; /// /// Validation method. Checks the supplied method and returns the error message, /// or null if there is no issue. /// private delegate string SignatureValidator(SyntaxNode methodNode, IMethodSymbol methodSymbol); private static readonly SignatureValidator NullValidator = (node, symbol) => null; // We currently support three test framework, each of which supports multiple test method attribute markers, and each of which // has differing constraints (public/private, generic/non-generic). // Rather than writing lots of conditional code, we're using a simple table-driven approach. // Currently we use the same validation method for all method types, but we could have a // different validation method for each type in future if necessary. private static readonly Dictionary AttributeToConstraintsMap = new() { // MSTest { KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_TestMethodAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: false) }, { KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_DataTestMethodAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: false) }, // NUnit { KnownType.NUnit_Framework_TestAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: false) }, { KnownType.NUnit_Framework_TestCaseAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: true) }, { KnownType.NUnit_Framework_TestCaseSourceAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: true) }, { KnownType.NUnit_Framework_TheoryAttribute, (node, symbol) => GetFaultMessage(node, symbol, publicOnly: true, allowGenerics: false) }, // XUnit - note that local functions can be test methods, thus we skip checking the syntax node { KnownType.Xunit_FactAttribute, (_, symbol) => GetFaultMessage(symbol, publicOnly: false, allowGenerics: false, allowAsyncVoid: true) }, { KnownType.Xunit_TheoryAttribute, (_, symbol) => GetFaultMessage(symbol, publicOnly: false, allowGenerics: true, allowAsyncVoid: true) }, { KnownType.LegacyXunit_TheoryAttribute, (_, symbol) => GetFaultMessage(symbol, publicOnly: false, allowGenerics: true, allowAsyncVoid: true) } }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement); private static void AnalyzeMethod(SonarSyntaxNodeReportingContext c) { if (HasAttributes(c.Node) && c.Model.GetDeclaredSymbol(c.Node) is IMethodSymbol methodSymbol) { var validator = GetValidator(methodSymbol); var message = validator(c.Node, methodSymbol); if (message != null) { c.ReportIssue(Rule, methodSymbol.Locations.First(), message); } } } private static bool HasAttributes(SyntaxNode node) => node is MethodDeclarationSyntax methodDeclaration ? methodDeclaration.AttributeLists.Count > 0 : ((LocalFunctionStatementSyntaxWrapper)node).AttributeLists.Count > 0; private static SignatureValidator GetValidator(IMethodSymbol method) => // Find the first matching attribute type in the table method.FindFirstTestMethodType() is { } attributeKnownType ? AttributeToConstraintsMap.GetValueOrDefault(attributeKnownType) : NullValidator; private static string GetFaultMessage(SyntaxNode methodNode, IMethodSymbol methodSymbol, bool publicOnly, bool allowGenerics, bool allowAsyncVoid = false) => LocalFunctionStatementSyntaxWrapper.IsInstance(methodNode) ? MakeMethodNotLocalFunction : GetFaultMessage(methodSymbol, publicOnly, allowGenerics, allowAsyncVoid); private static string GetFaultMessage(IMethodSymbol methodSymbol, bool publicOnly, bool allowGenerics, bool allowAsyncVoid) => GetFaultMessageParts(methodSymbol, publicOnly, allowGenerics, allowAsyncVoid).ToSentence(); private static IEnumerable GetFaultMessageParts(IMethodSymbol methodSymbol, bool publicOnly, bool allowGenerics, bool allowAsyncVoid) { if (methodSymbol.DeclaredAccessibility != Accessibility.Public && publicOnly) { yield return MakePublicMessage; } if (methodSymbol.IsGenericMethod && !allowGenerics) { yield return MakeNotGenericMessage; } // Invariant - applies to all test methods if (methodSymbol.IsAsync && methodSymbol.ReturnsVoid && !allowAsyncVoid) { yield return MakeNonAsyncOrTaskMessage; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TestMethodShouldNotBeIgnored.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TestMethodShouldNotBeIgnored : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1607"; private const string MessageFormat = "Either remove this 'Ignore' attribute or add an explanation about why this test is ignored."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray TrackedTestIdentifierAttributes = // xUnit has it's own "ignore" mechanism (by providing a (Skip = "reason") string in the attribute, so there is always an explanation for the test being skipped). KnownType.TestMethodAttributesOfMSTest .Concat(KnownType.TestMethodAttributesOfNUnit) .Append(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_TestClassAttribute) .ToImmutableArray(); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var attribute = (AttributeSyntax)c.Node; if (HasReasonPhrase(attribute) || HasTrailingComment(attribute) || !IsKnownIgnoreAttribute(attribute, c.Model) || attribute.Parent?.Parent is not { } attributeTarget) { return; } var attributes = GetAllAttributes(attributeTarget, c.Model); if (attributes.Any(IsTestOrTestClassAttribute) && !attributes.Any(IsWorkItemAttribute)) { c.ReportIssue(Rule, attribute); } }, SyntaxKind.Attribute); private static IEnumerable GetAllAttributes(SyntaxNode syntaxNode, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(syntaxNode) is { } testMethodOrClass ? testMethodOrClass.GetAttributes() : Enumerable.Empty(); private static bool HasReasonPhrase(AttributeSyntax ignoreAttributeSyntax) => ignoreAttributeSyntax.ArgumentList?.Arguments.Count > 0; // Any ctor argument counts are reason phrase private static bool HasTrailingComment(SyntaxNode ignoreAttributeSyntax) => ignoreAttributeSyntax.Parent .GetTrailingTrivia() .Any(SyntaxKind.SingleLineCommentTrivia); private static bool IsWorkItemAttribute(AttributeData a) => a.AttributeClass.Is(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_WorkItemAttribute); private static bool IsKnownIgnoreAttribute(AttributeSyntax attributeSyntax, SemanticModel semanticModel) { var symbolInfo = semanticModel.GetSymbolInfo(attributeSyntax); var attributeConstructor = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); return attributeConstructor is not null && attributeConstructor.ContainingType.DerivesFromAny(KnownType.IgnoreAttributes); } private static bool IsTestOrTestClassAttribute(AttributeData a) => a.AttributeClass.DerivesFromAny(TrackedTestIdentifierAttributes); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TestsShouldNotUseThreadSleep.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TestsShouldNotUseThreadSleep : TestsShouldNotUseThreadSleepBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode MethodDeclaration(MethodDeclarationSyntax method) => method; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThisShouldNotBeExposedFromConstructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ThisShouldNotBeExposedFromConstructors : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3366"; private const string MessageFormat = "Make sure the use of 'this' doesn't expose partially-constructed instances of this class in multi-threaded environments."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterCodeBlockStartAction( cbc => { if (!IsInstanceConstructor(cbc.CodeBlock)) { return; } cbc.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.ArgumentList == null) { return; } var thisExpression = invocation.ArgumentList.Arguments .Select(a => a.Expression) .FirstOrDefault(IsThisExpression); if (thisExpression != null && !IsClassMember(invocation.Expression) && c.Model.GetSymbolInfo(invocation.Expression).Symbol is IMethodSymbol) { c.ReportIssue(rule, thisExpression); } }, SyntaxKind.InvocationExpression); cbc.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; var right = assignment.Right.RemoveParentheses(); if (IsThisExpression(right) && !IsClassMember(assignment.Left) && c.Model.GetSymbolInfo(assignment.Left).Symbol is IPropertySymbol) { c.ReportIssue(rule, right); } }, SyntaxKind.SimpleAssignmentExpression); }); } private bool IsClassMember(ExpressionSyntax expression) => expression is IdentifierNameSyntax || (expression is MemberAccessExpressionSyntax memberAccess && IsThisExpression(memberAccess.Expression)); private static bool IsThisExpression(ExpressionSyntax expression) => expression != null && expression.RemoveParentheses().IsKind(SyntaxKind.ThisExpression); private static bool IsInstanceConstructor(SyntaxNode node) { bool IsStaticKeyword(SyntaxToken token) => token.IsKind(SyntaxKind.StaticKeyword); return node is ConstructorDeclarationSyntax ctor && !ctor.Modifiers.Any(IsStaticKeyword); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThreadResumeOrSuspendShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ThreadResumeOrSuspendShouldNotBeCalled : ThreadResumeOrSuspendShouldNotBeCalledBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThreadStaticNonStaticField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ThreadStaticNonStaticField : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3005"; private const string MessageFormat = "Remove the 'ThreadStatic' attribute from this definition."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if ((FieldDeclarationSyntax)c.Node is var fieldDeclaration && !fieldDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword) && fieldDeclaration.AttributeLists.GetAttributes(KnownType.System_ThreadStaticAttribute, c.Model).FirstOrDefault() is { } threadStaticAttribute) { c.ReportIssue(Rule, threadStaticAttribute.Name); } }, SyntaxKind.FieldDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThreadStaticNonStaticFieldCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class ThreadStaticNonStaticFieldCodeFix : SonarCodeFix { internal const string Title = "Remove 'ThreadStatic' attribute"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ThreadStaticNonStaticField.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var nodeToRemove = root.FindNode(diagnosticSpan); if (nodeToRemove.Parent is AttributeListSyntax attributeList && attributeList.Attributes.Count == 1) { nodeToRemove = attributeList; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(nodeToRemove, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThreadStaticWithInitializer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ThreadStaticWithInitializer : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2996"; private const string MessageFormat = "Remove this initialization of '{0}' or make it lazy."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (!fieldDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword) || !HasThreadStaticAttribute(fieldDeclaration.AttributeLists, c.Model)) { return; } foreach (var variableDeclaratorSyntax in fieldDeclaration.Declaration.Variables.Where(variableDeclaratorSyntax => variableDeclaratorSyntax.Initializer != null)) { c.ReportIssue(Rule, variableDeclaratorSyntax.Initializer, variableDeclaratorSyntax.Identifier.ValueText); } }, SyntaxKind.FieldDeclaration); private static bool HasThreadStaticAttribute(SyntaxList attributeLists, SemanticModel semanticModel) => attributeLists.Any() && attributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => attribute.IsKnownType(KnownType.System_ThreadStaticAttribute, semanticModel))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ThrowReservedExceptions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ThrowReservedExceptions : ThrowReservedExceptionsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Process(c, ((ThrowStatementSyntax)c.Node).Expression), SyntaxKind.ThrowStatement); context.RegisterNodeAction(c => Process(c, ((ThrowExpressionSyntaxWrapper)c.Node).Expression), SyntaxKindEx.ThrowExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ToStringShouldNotReturnNull.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ToStringShouldNotReturnNull : ToStringShouldNotReturnNullBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind MethodKind => SyntaxKind.MethodDeclaration; protected override void Initialize(SonarAnalysisContext context) { base.Initialize(context); context.RegisterNodeAction( c => ToStringReturnsNull(c, ((MethodDeclarationSyntax)c.Node).ExpressionBody), SyntaxKind.MethodDeclaration); } protected override IEnumerable Conditionals(SyntaxNode expression) => expression is ConditionalExpressionSyntax conditional ? [conditional.WhenTrue, conditional.WhenFalse] : []; protected override bool IsLocalOrLambda(SyntaxNode node) => node?.Kind() is SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression or SyntaxKindEx.LocalFunctionStatement; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyGenericParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TooManyGenericParameters : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2436"; private const string MessageFormat = "Reduce the number of generic parameters in the '{0}' {1} to no more than the {2} authorized."; private const int DefaultMaxNumberOfGenericParametersInClass = 2; private const int DefaultMaxNumberOfGenericParametersInMethod = 3; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("max", PropertyType.Integer, "Maximum authorized number of generic parameters.", DefaultMaxNumberOfGenericParametersInClass)] public int MaxNumberOfGenericParametersInClass { get; set; } = DefaultMaxNumberOfGenericParametersInClass; [RuleParameter("maxMethod", PropertyType.Integer, "Maximum authorized number of generic parameters for methods.", DefaultMaxNumberOfGenericParametersInMethod)] public int MaxNumberOfGenericParametersInMethod { get; set; } = DefaultMaxNumberOfGenericParametersInMethod; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var typeDeclaration = (TypeDeclarationSyntax)c.Node; if (c.IsRedundantPositionalRecordContext() || typeDeclaration.TypeParameterList == null || typeDeclaration.TypeParameterList.Parameters.Count <= MaxNumberOfGenericParametersInClass) { return; } c.ReportIssue(Rule, typeDeclaration.Identifier, typeDeclaration.Identifier.ValueText, typeDeclaration.GetDeclarationTypeName(), MaxNumberOfGenericParametersInClass.ToString()); }, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); context.RegisterNodeAction( c => { var methodDeclaration = MethodDeclarationFactory.Create(c.Node); if (methodDeclaration.TypeParameterList == null || methodDeclaration.TypeParameterList.Parameters.Count <= MaxNumberOfGenericParametersInMethod) { return; } c.ReportIssue( Rule, methodDeclaration.Identifier, new[] { EnclosingTypeName(c.Node), methodDeclaration.Identifier.ValueText }.JoinNonEmpty("."), "method", MaxNumberOfGenericParametersInMethod.ToString()); }, SyntaxKind.MethodDeclaration, SyntaxKindEx.LocalFunctionStatement); } private static string EnclosingTypeName(SyntaxNode node) => node.Ancestors().OfType().FirstOrDefault()?.Identifier.ValueText; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyLabelsInSwitch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TooManyLabelsInSwitch : TooManyLabelsInSwitchBase { private static readonly ISet IgnoredStatementsInSwitch = new HashSet { SyntaxKind.BreakStatement, SyntaxKind.ReturnStatement, SyntaxKind.ThrowStatement }; private static readonly ISet TransparentSyntax = new HashSet { SyntaxKind.Block, SyntaxKind.CatchClause, SyntaxKind.CheckedStatement, SyntaxKind.DoStatement, SyntaxKind.FinallyClause, SyntaxKind.FixedStatement, SyntaxKind.ForEachStatement, SyntaxKindEx.ForEachVariableStatement, SyntaxKind.ForStatement, SyntaxKind.IfStatement, SyntaxKind.LockStatement, SyntaxKind.SwitchStatement, SyntaxKind.TryStatement, SyntaxKind.UncheckedStatement, SyntaxKind.UnsafeStatement, SyntaxKind.UsingStatement, SyntaxKind.WhileStatement }; protected override DiagnosticDescriptor Rule { get; } = DescriptorFactory.Create(DiagnosticId, string.Format(MessageFormat, "switch", "case"), isEnabledByDefault: false); protected override SyntaxKind[] SyntaxKinds { get; } = [SyntaxKind.SwitchStatement]; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxNode GetExpression(SwitchStatementSyntax statement) => statement.Expression; protected override int GetSectionsCount(SwitchStatementSyntax statement) => statement.Sections.Count; protected override bool AllSectionsAreOneLiners(SwitchStatementSyntax statement) => statement.Sections.All(HasOneLine); protected override Location GetKeywordLocation(SwitchStatementSyntax statement) => statement.SwitchKeyword.GetLocation(); private static bool HasOneLine(SwitchSectionSyntax switchSection) => switchSection.Statements .SelectMany(x => x.DescendantNodesAndSelf(descendIntoChildren: c => c.IsAnyKind(TransparentSyntax))) .Count(x => !x.IsAnyKind(IgnoredStatementsInSwitch)) is 0 or 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyLoggingCalls.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TooManyLoggingCalls : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S6664"; private const string MessageFormat = "Reduce the number of {0} logging calls within this code block from {1} to the {2} allowed."; public static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); private static readonly KnownAssembly[] SupportedLoggingLibraries = [ KnownAssembly.MicrosoftExtensionsLoggingAbstractions, KnownAssembly.NLog, KnownAssembly.Serilog, KnownAssembly.Log4Net, KnownAssembly.CastleCore ]; private static readonly ImmutableArray LoggerTypes = ImmutableArray.Create( KnownType.Microsoft_Extensions_Logging_ILogger, KnownType.Microsoft_Extensions_Logging_LoggerExtensions, KnownType.NLog_ILogger, KnownType.NLog_ILoggerBase, KnownType.NLog_ILoggerExtensions, KnownType.Serilog_ILogger, KnownType.Serilog_Log, KnownType.log4net_ILog, KnownType.log4net_Util_ILogExtensions, KnownType.Castle_Core_Logging_ILogger); private static readonly ImmutableArray LoggingCategories = ImmutableArray.Create( new(CategoryNames.Debug, ImmutableHashSet.Create("ConditionalDebug", "ConditionalTrace", "Debug", "DebugFormat", "LogDebug", "LogTrace", "Trace", "TraceFormat", "Verbose")), new(CategoryNames.Error, ImmutableHashSet.Create("Error", "ErrorFormat", "Fatal", "FatalFormat", "LogCritical", "LogError")), new(CategoryNames.Information, ImmutableHashSet.Create("Info", "InfoFormat", "Information", "LogInformation")), new(CategoryNames.Warning, ImmutableHashSet.Create("LogWarning", "Warn", "WarnFormat", "Warning"))); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); [RuleParameter("debugThreshold", PropertyType.Integer, "The maximum number of DEBUG, TRACE and VERBOSE statements allowed in the same code block.", DefaultThresholds.Debug)] public int DebugThreshold { get; set; } = DefaultThresholds.Debug; [RuleParameter("errorThreshold", PropertyType.Integer, "The maximum number of ERROR and FATAL statements allowed in the same code block.", DefaultThresholds.Error)] public int ErrorThreshold { get; set; } = DefaultThresholds.Error; [RuleParameter("informationThreshold", PropertyType.Integer, "The maximum number of INFORMATION statements allowed in the same code block.", DefaultThresholds.Information)] public int InformationThreshold { get; set; } = DefaultThresholds.Information; [RuleParameter("warningThreshold", PropertyType.Integer, "The maximum number of WARNING statements allowed in the same code block.", DefaultThresholds.Warning)] public int WarningThreshold { get; set; } = DefaultThresholds.Warning; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterCompilationStartAction(cc => { if (cc.Compilation.ReferencesAny(SupportedLoggingLibraries)) { cc.RegisterNodeAction(Process, SyntaxKind.Block, SyntaxKind.CompilationUnit); } }); private void Process(SonarSyntaxNodeReportingContext context) { var node = context.Node; if (IsBlockNodeSupported(node)) { var logCallCollector = new LoggingCallCollector(context.Model, node); logCallCollector.Visit(node); foreach (var group in logCallCollector.GroupedLoggingInvocations) { var threshold = Threshold(group.Key); var invocations = group.Value; if (invocations.Count > threshold) { var primaryLocation = invocations[0].GetLocation(); string[] messageParams = [group.Key, invocations.Count.ToString(), threshold.ToString()]; var secondaryLocations = invocations.Skip(1).ToSecondaryLocations(MessageFormat, messageParams); context.ReportIssue(Rule, primaryLocation, secondaryLocations, messageParams); } } } } private int Threshold(string category) { var threshold = category switch { CategoryNames.Debug => DebugThreshold, CategoryNames.Error => ErrorThreshold, CategoryNames.Warning => WarningThreshold, _ => InformationThreshold }; return Math.Max(threshold, 0); } private static bool IsBlockNodeSupported(SyntaxNode node) => node.Kind() == SyntaxKind.Block || (node.Kind() == SyntaxKind.CompilationUnit && node.ChildNodes().OfType().Any()); // for top-level statements private sealed class LoggingCallCollector : SafeCSharpSyntaxWalker { private readonly SemanticModel model; private readonly SyntaxNode currentBlock; public Dictionary> GroupedLoggingInvocations { get; } = []; public LoggingCallCollector(SemanticModel model, SyntaxNode currentBlock) { this.model = model; this.currentBlock = currentBlock; } public override void VisitBlock(BlockSyntax node) { if (currentBlock == node) // Don't visit nested blocks, that will be done by additional LoggingCallCollector instances. { base.VisitBlock(node); } } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (IsLoggerMethod(node.GetName()) && model.GetSymbolInfo(node).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.DerivesOrImplementsAny(LoggerTypes) && LoggingCategoryName(node, methodSymbol) is { } loggingCategory) { if (GroupedLoggingInvocations.TryGetValue(loggingCategory, out var invocationList)) { invocationList.Add(node); } else { GroupedLoggingInvocations.Add(loggingCategory, [node]); } } } public override void VisitIfStatement(IfStatementSyntax node) { // Skip syntax node to avoid FPs } public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { // Skip syntax node to avoid FPs when the lambda body is a single instruction rather than a block } public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { // Skip syntax node to avoid FPs when the lambda body is a single instruction rather than a block } public override void VisitSwitchStatement(SwitchStatementSyntax node) { // Skip syntax node to avoid FPs } private static bool IsLoggerMethod(string methodName) => methodName is "Log" or "Write" || LoggingCategories.Any(x => x.LoggingMethods.Contains(methodName)); private static string LoggingCategoryName(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) => LoggingCategories.FirstOrDefault(x => x.LoggingMethods.Contains(invocation.GetName()))?.CategoryName ?? CategoryNameForLogLevel(invocation, methodSymbol); private static string CategoryNameForLogLevel(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) => LogLevelArgumentName(invocation, methodSymbol) is { } logLevel ? logLevel switch { "Debug" or "Trace" or "Verbose" => CategoryNames.Debug, "Critical" or "Error" or "Fatal" => CategoryNames.Error, "Information" => CategoryNames.Information, "Warning" => CategoryNames.Warning, _ => null } : null; private static string LogLevelArgumentName(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) { var lookup = new CSharpMethodParameterLookup(invocation.ArgumentList, methodSymbol); return lookup.TryGetSyntax("logLevel", out var arguments) || lookup.TryGetSyntax("level", out arguments) ? arguments[0].GetName() : null; } } private sealed record LoggingCategory(string CategoryName, ImmutableHashSet LoggingMethods); private static class DefaultThresholds { public const int Debug = 4; public const int Information = 2; public const int Warning = 1; public const int Error = 1; } private static class CategoryNames { public const string Debug = "Debug"; public const string Information = "Information"; public const string Warning = "Warning"; public const string Error = "Error"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TooManyParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TooManyParameters : TooManyParametersBase { protected override ILanguageFacade Language => CSharpFacade.Instance; private static readonly ImmutableDictionary NodeToDeclarationName = new Dictionary { { SyntaxKind.ConstructorDeclaration, "Constructor" }, { SyntaxKind.StructDeclaration, "Constructor" }, { SyntaxKind.ClassDeclaration, "Constructor" }, { SyntaxKind.MethodDeclaration, "Method" }, { SyntaxKind.DelegateDeclaration, "Delegate" }, { SyntaxKind.AnonymousMethodExpression, "Delegate" }, { SyntaxKind.ParenthesizedLambdaExpression, "Lambda" }, { SyntaxKind.SimpleLambdaExpression, "Lambda" }, { SyntaxKindEx.LocalFunctionStatement, "Local function" } }.ToImmutableDictionary(); protected override string UserFriendlyNameForNode(SyntaxNode node) => NodeToDeclarationName[node.Kind()]; protected override int CountParameters(ParameterListSyntax parameterList) => parameterList.Parameters.Count; protected override bool CanBeChanged(SyntaxNode node, SemanticModel semanticModel) => NodeToDeclarationName.ContainsKey(node.Kind()) && VerifyCanBeChangedBySymbol(node, semanticModel); protected override int BaseParameterCount(SyntaxNode node) => node switch { ConstructorDeclarationSyntax ctorDeclaration => ctorDeclaration.Initializer?.ArgumentList?.Arguments.Count ?? 0, ClassDeclarationSyntax classDeclaration => RetrieveBasePrimaryConstructorArguments(classDeclaration), _ => 0, }; protected override bool IsExtern(SyntaxNode node) => node is BaseMethodDeclarationSyntax { } methodDeclaration && methodDeclaration.IsExtern(); private static int RetrieveBasePrimaryConstructorArguments(ClassDeclarationSyntax node) { var type = node.BaseList?.Types.FirstOrDefault(); return PrimaryConstructorBaseTypeSyntaxWrapper.IsInstance(type) ? ((PrimaryConstructorBaseTypeSyntaxWrapper)type).ArgumentList.Arguments.Count : 0; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TrackNotImplementedException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TrackNotImplementedException : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3717"; private const string MessageFormat = "Implement this method or throw 'NotSupportedException' instead."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var throwStatement = (ThrowStatementSyntax)c.Node; if (throwStatement.Expression == null) { return; } ReportDiagnostic(c, throwStatement.Expression, throwStatement); }, SyntaxKind.ThrowStatement); context.RegisterNodeAction( c => { var throwExpression = (ThrowExpressionSyntaxWrapper)c.Node; ReportDiagnostic(c, throwExpression.Expression, throwExpression); }, SyntaxKindEx.ThrowExpression); } private static void ReportDiagnostic(SonarSyntaxNodeReportingContext c, ExpressionSyntax newExceptionExpression, SyntaxNode throwExpression) { if (c.Model.GetTypeInfo(newExceptionExpression).Type.Is(KnownType.System_NotImplementedException)) { c.ReportIssue(rule, throwExpression); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TryStatementsWithIdenticalCatchShouldBeMerged.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TryStatementsWithIdenticalCatchShouldBeMerged : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2327"; private const string MessageFormat = "Combine this 'try' with the one starting on line {0}."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var tryStatement = (TryStatementSyntax)c.Node; var mergeableTry = tryStatement.GetPreviousStatementsCurrentBlock() .OfType() .FirstOrDefault(t => SameCatches(t.Catches) && SameFinally(t)); if (mergeableTry != null) { c.ReportIssue(rule, tryStatement, messageArgs: mergeableTry.LineNumberToReport().ToString()); } bool SameCatches(IReadOnlyCollection other) => tryStatement.Catches.Count == other.Count && tryStatement.Catches.All(x => other.Any(o => o.IsEquivalentTo(x))); bool SameFinally(TryStatementSyntax other) => tryStatement.Finally == null && other.Finally == null || tryStatement.Finally != null && other.Finally != null && tryStatement.Finally.IsEquivalentTo(other.Finally); }, SyntaxKind.TryStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TypeExaminationOnSystemType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TypeExaminationOnSystemType : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3443"; private const string MessageFormat = "{0}"; private const string MessageGetType = "Remove this use of 'GetType' on a 'System.Type'."; private const string MessageIsInstanceOfType = "Pass an argument that is not a 'System.Type' or consider using 'IsAssignableFrom'."; private const string MessageIsInstanceOfTypeWithGetType = "Consider removing the 'GetType' call, it's suspicious in an 'IsInstanceOfType' call."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (invocation.Expression.ToStringContainsEitherOr(nameof(Type.IsInstanceOfType), nameof(Type.GetType)) && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol) { CheckGetTypeCallOnType(c, invocation, methodSymbol); CheckIsInstanceOfTypeCallWithTypeArgument(c, invocation, methodSymbol); } }, SyntaxKind.InvocationExpression); private static void CheckIsInstanceOfTypeCallWithTypeArgument(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, ISymbol methodSymbol) { if (methodSymbol.Name != nameof(Type.IsInstanceOfType) || !methodSymbol.ContainingType.Is(KnownType.System_Type)) { return; } var argument = invocation.ArgumentList.Arguments.First().Expression; var typeInfo = context.Model.GetTypeInfo(argument).Type; if (!typeInfo.Is(KnownType.System_Type)) { return; } var invocationInArgument = argument as InvocationExpressionSyntax; var message = invocationInArgument.IsGetTypeCall(context.Model) ? MessageIsInstanceOfTypeWithGetType : MessageIsInstanceOfType; context.ReportIssue(Rule, argument, message); } private static void CheckGetTypeCallOnType(SonarSyntaxNodeReportingContext context, InvocationExpressionSyntax invocation, IMethodSymbol invokedMethod) { if (!(invocation.Expression is MemberAccessExpressionSyntax memberCall) || IsException(memberCall, context.Model) || !invokedMethod.IsGetTypeCall()) { return; } var expressionType = context.Model.GetTypeInfo(memberCall.Expression).Type; if (!expressionType.Is(KnownType.System_Type)) { return; } context.ReportIssue(Rule, memberCall.OperatorToken.CreateLocation(invocation), MessageGetType); } private static bool IsException(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) => memberAccess.Expression is TypeOfExpressionSyntax typeOf && typeOf.Type.IsKnownType(KnownType.System_Type, semanticModel); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TypeMemberVisibility.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TypeMemberVisibility : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3059"; private const string MessageFormat = "Types should not have members with visibility set higher than the type's visibility"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet TypeKinds = [ SyntaxKind.ClassDeclaration, SyntaxKind.EnumDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration, ]; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.IsRedundantPositionalRecordContext()) { return; } var typeDeclaration = (BaseTypeDeclarationSyntax)c.Node; var secondaryLocations = GetInvalidMemberLocations(c.Model, typeDeclaration); if (secondaryLocations.Any()) { c.ReportIssue(Rule, typeDeclaration.Identifier, secondaryLocations); } }, [.. TypeKinds]); private static SecondaryLocation[] GetInvalidMemberLocations(SemanticModel semanticModel, BaseTypeDeclarationSyntax type) { var parentType = GetParentType(type); if (parentType is null && type.Modifiers.AnyOfKind(SyntaxKind.InternalKeyword)) { return type.DescendantNodes() .OfType() .Where(x => x.Modifiers.AnyOfKind(SyntaxKind.PublicKeyword) && !x.Modifiers.AnyOfKind(SyntaxKind.OverrideKeyword) // Overridden member need to keep the visibility of the base declaration && !(x.Kind() is SyntaxKind.OperatorDeclaration or SyntaxKind.ConversionOperatorDeclaration) // Operators must be public && !IsInterfaceImplementation(semanticModel, x)) .Select(x => x.Modifiers.Single(modifier => modifier.IsKind(SyntaxKind.PublicKeyword)).ToSecondaryLocation()) .ToArray(); } return []; } private static bool IsInterfaceImplementation(SemanticModel semanticModel, MemberDeclarationSyntax declaration) => semanticModel.GetDeclaredSymbol(declaration)?.InterfaceMembers().Any() is true; private static BaseTypeDeclarationSyntax GetParentType(SyntaxNode node) => (BaseTypeDeclarationSyntax)node.Ancestors().FirstOrDefault(x => x.IsAnyKind(TypeKinds)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TypeNamesShouldNotMatchNamespaces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TypeNamesShouldNotMatchNamespaces : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4041"; private const string MessageFormat = "Change the name of type '{0}' to be different from an existing framework namespace."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); // Based on https://msdn.microsoft.com/en-us/library/gg145045%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 private static readonly ISet FrameworkNamespaces = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "Accessibility", "Activities", "AddIn", "Build", "CodeDom", "Collections", "Componentmodel", "Configuration", "CSharp", "CustomMarshalers", "Data", "Dataflow", "Deployment", "Device", "Diagnostics", "DirectoryServices", "Drawing", "Dynamic", "EnterpriseServices", "Globalization", "IdentityModel", "InteropServices", "IO", "JScript", "Linq", "Location", "Management", "Media", "Messaging", "Microsoft", "Net", "Numerics", "Printing", "Reflection", "Resources", "Runtime", "Security", "Server", "ServiceModel", "ServiceProcess", "Speech", "SqlServer", "System", "Tasks", "Text", "Threading", "Timers", "Transactions", "UIAutomationClientsideProviders", "VisualBasic", "VisualC", "Web", "Win32", "Windows", "Workflow", "Xaml", "XamlGeneratedNamespace", "Xml" }; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (!c.IsRedundantPositionalRecordContext() && c.Node.GetIdentifier() is { } identifier && FrameworkNamespaces.Contains(identifier.ValueText) && c.Model.GetDeclaredSymbol(c.Node)?.DeclaredAccessibility == Accessibility.Public) { c.ReportIssue(Rule, identifier, identifier.ValueText); } }, SyntaxKind.ClassDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKind.StructDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/TypesShouldNotExtendOutdatedBaseTypes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TypesShouldNotExtendOutdatedBaseTypes : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4052"; private const string MessageFormat = "Refactor this type not to derive from an outdated type '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableArray OutdatedTypes = ImmutableArray.Create( KnownType.System_ApplicationException, KnownType.System_Xml_XmlDocument, KnownType.System_Collections_CollectionBase, KnownType.System_Collections_DictionaryBase, KnownType.System_Collections_Queue, KnownType.System_Collections_ReadOnlyCollectionBase, KnownType.System_Collections_SortedList, KnownType.System_Collections_Stack); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var classDeclaration = (ClassDeclarationSyntax)c.Node; var classSymbol = (INamedTypeSymbol)c.ContainingSymbol; if (!classDeclaration.Identifier.IsMissing && classSymbol.BaseType.IsAny(OutdatedTypes)) { c.ReportIssue(Rule, classDeclaration.Identifier, messageArgs: classSymbol.BaseType.ToDisplayString()); } }, // The rule is not applicable for records as at the current moment all the outdated types are classes and records cannot inherit classes. SyntaxKind.ClassDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnaryPrefixOperatorRepeated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnaryPrefixOperatorRepeated : UnaryPrefixOperatorRepeatedBase { protected override DiagnosticDescriptor Rule { get; } = DescriptorFactory.Create(DiagnosticId, MessageFormat); protected override ISet SyntaxKinds { get; } = new HashSet { SyntaxKind.LogicalNotExpression, SyntaxKind.BitwiseNotExpression, }; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } = CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxNode GetOperand(PrefixUnaryExpressionSyntax unarySyntax) => unarySyntax.Operand; protected override SyntaxToken GetOperatorToken(PrefixUnaryExpressionSyntax unarySyntax) => unarySyntax.OperatorToken; protected override bool SameOperators(PrefixUnaryExpressionSyntax expression1, PrefixUnaryExpressionSyntax expression2) => expression1.OperatorToken.IsKind(expression2.OperatorToken.Kind()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnaryPrefixOperatorRepeatedCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UnaryPrefixOperatorRepeatedCodeFix : SonarCodeFix { internal const string Title = "Remove repeated prefix operator(s)"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnaryPrefixOperatorRepeated.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is PrefixUnaryExpressionSyntax prefix)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { GetExpression(prefix, out var expression, out var count); if (count%2 == 1) { expression = SyntaxFactory.PrefixUnaryExpression( prefix.Kind(), expression); } var newRoot = root.ReplaceNode(prefix, expression .WithAdditionalAnnotations(Formatter.Annotation)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } private static void GetExpression(PrefixUnaryExpressionSyntax prefix, out ExpressionSyntax expression, out uint count) { count = 0; var currentUnary = prefix; do { count++; expression = currentUnary.Operand; currentUnary = currentUnary.Operand as PrefixUnaryExpressionSyntax; } while (currentUnary != null); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnchangedLocalVariablesShouldBeConst.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnchangedLocalVariablesShouldBeConst : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3353"; private const string MessageFormat = "Add the 'const' modifier to '{0}'{1}."; // {1} is a placeholder for optional MessageFormatVarHint private const string MessageFormatVarHint = ", and replace 'var' with '{0}'"; private enum DeclarationType { CannotBeConst, Value, Reference, String } private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var localDeclaration = (LocalDeclarationStatementSyntax)c.Node; if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)) { return; } var declaredType = FindDeclarationType(localDeclaration, c.Model); if (declaredType == DeclarationType.CannotBeConst) { return; } localDeclaration.Declaration?.Variables .Where(v => v is { Identifier: { } } // constant string interpolation is only valid in C# 10 and above && (c.Model.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp10) || !ContainsInterpolation(v)) && IsInitializedWithCompatibleConstant(v, c.Model, declaredType) && !HasMutableUsagesInMethod(c.Model, v)) .ToList() .ForEach(x => Report(c, x)); }, SyntaxKind.LocalDeclarationStatement); private static DeclarationType FindDeclarationType(LocalDeclarationStatementSyntax localDeclaration, SemanticModel model) { var declaredTypeSyntax = localDeclaration.Declaration?.Type; if (declaredTypeSyntax is null) { return DeclarationType.CannotBeConst; } var declaredType = model.GetTypeInfo(declaredTypeSyntax).Type; if (declaredType is null) { return DeclarationType.CannotBeConst; } else if (declaredType.Is(KnownType.System_String)) { return DeclarationType.String; } else if (declaredType.OriginalDefinition?.DerivesFrom(KnownType.System_Nullable_T) ?? false) { // Defining nullable as const raises error CS0283. return DeclarationType.CannotBeConst; } else if (declaredType.IsStruct() && declaredType.SpecialType == SpecialType.None && declaredType.GetMembers("op_Implicit").Any(x => !x.IsImplicitlyDeclared)) { // Struct with explicitly declared "implicit operator" return DeclarationType.CannotBeConst; } else { return declaredType.IsValueType ? DeclarationType.Value : DeclarationType.Reference; } } private static bool IsInitializedWithCompatibleConstant(VariableDeclaratorSyntax variableDeclarator, SemanticModel model, DeclarationType declarationType) => variableDeclarator is { Initializer.Value: { } initializer } && model.GetConstantValue(initializer) switch { { HasValue: false } => false, { Value: string } => declarationType == DeclarationType.String, { Value: ValueType } => declarationType == DeclarationType.Value, _ => declarationType is DeclarationType.Reference or DeclarationType.String, }; private static bool HasMutableUsagesInMethod(SemanticModel model, VariableDeclaratorSyntax variable) { var parentSyntax = variable.Ancestors().FirstOrDefault(IsMethodLike); if (parentSyntax is null) { return false; } else if (parentSyntax is GlobalStatementSyntax) { // If the variable is declared in a top level statement we should search inside the compilation unit. parentSyntax = parentSyntax.Parent; } var variableSymbol = model.GetDeclaredSymbol(variable); return variableSymbol is not null && parentSyntax.DescendantNodes() .OfType() .Where(x => x.GetName().Equals(variableSymbol.Name) && variableSymbol.Equals(model.GetSymbolInfo(x).Symbol)) .Any(x => IsMutatingUse(model, x)); static bool IsMethodLike(SyntaxNode arg) => arg is BaseMethodDeclarationSyntax or AccessorDeclarationSyntax or LambdaExpressionSyntax or AnonymousFunctionExpressionSyntax or GlobalStatementSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(arg); } private static bool IsMutatingUse(SemanticModel model, IdentifierNameSyntax identifier) => identifier.Parent switch { AssignmentExpressionSyntax { Left: { } left } => identifier.Equals(left), ArgumentSyntax argumentSyntax => argumentSyntax.IsInTupleAssignmentTarget() || !argumentSyntax.RefOrOutKeyword.IsKind(SyntaxKind.None), PostfixUnaryExpressionSyntax => true, PrefixUnaryExpressionSyntax => true, { } refExpression when RefExpressionSyntaxWrapper.IsInstance(refExpression) => !IsAssignedToRefReadonly(identifier), _ => IsUsedAsLambdaExpression(model, identifier), }; private static bool IsUsedAsLambdaExpression(SemanticModel model, IdentifierNameSyntax identifier) { if (identifier.FirstAncestorOrSelf().GetSelfOrTopParenthesizedExpression() is { } lambda) { if (lambda.Parent is ArgumentSyntax argument && argument.FirstAncestorOrSelf() is { } invocation) { var lookup = new CSharpMethodParameterLookup(invocation, model); return lookup.TryGetSymbol(argument, out var parameter) && parameter.IsType(KnownType.System_Linq_Expressions_Expression_T); } else if (lambda.Parent is AssignmentExpressionSyntax assignment) { // Lambda cannot be on the left side, we don't need to check it return assignment.Left.IsKnownType(KnownType.System_Linq_Expressions_Expression_T, model); } } return false; } private static bool ContainsInterpolation(VariableDeclaratorSyntax declaratorSyntax) => declaratorSyntax is { Initializer.Value: { } initializer } && initializer.DescendantNodesAndSelf().Any(x => x.IsKind(SyntaxKind.Interpolation)); private static void Report(SonarSyntaxNodeReportingContext c, VariableDeclaratorSyntax declaratorSyntax) => c.ReportIssue( Rule, declaratorSyntax.Identifier, declaratorSyntax.Identifier.ValueText, AdditionalMessageHints(c.Model, declaratorSyntax)); private static string AdditionalMessageHints(SemanticModel model, VariableDeclaratorSyntax declaratorSyntax) => declaratorSyntax is { Parent: VariableDeclarationSyntax { Type: { IsVar: true } typeSyntax } } ? string.Format(MessageFormatVarHint, model.GetTypeInfo(typeSyntax).Type.ToMinimalDisplayString(model, typeSyntax.SpanStart)) : string.Empty; private static bool IsAssignedToRefReadonly(IdentifierNameSyntax identifier) => identifier.Ancestors().OfType().FirstOrDefault() is { Parent: VariableDeclarationSyntax { Type: var type } } && RefTypeSyntaxWrapper.IsInstance(type) && ((RefTypeSyntaxWrapper)type).ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnchangedLocalVariablesShouldBeConstCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UnchangedLocalVariablesShouldBeConstCodeFix : SonarCodeFix { private const string Title = "Convert to constant."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnchangedLocalVariablesShouldBeConst.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { if (VariableDeclaration(root, context) is { Parent: LocalDeclarationStatementSyntax oldNode, Variables.Count: 1, // It is not guaranteed that all should be const. } variable) { context.RegisterCodeFix( Title, c => ChangeDocument(context.Document, root, variable, oldNode, c), context.Diagnostics); } return Task.CompletedTask; } public static async Task ChangeDocument( Document document, SyntaxNode root, VariableDeclarationSyntax variable, LocalDeclarationStatementSyntax oldNode, CancellationToken cancel) { var declaration = variable.Type.IsVar ? WithExplictType(variable, await document.GetSemanticModelAsync(cancel).ConfigureAwait(false)) : variable; var newNode = root.ReplaceNode(oldNode, ConstantDeclaration(declaration)); return document.WithSyntaxRoot(newNode); } private static VariableDeclarationSyntax VariableDeclaration(SyntaxNode root, SonarCodeFixContext context) => root.FindNode(context.Diagnostics.First().Location.SourceSpan)?.Parent as VariableDeclarationSyntax; private static LocalDeclarationStatementSyntax ConstantDeclaration(VariableDeclarationSyntax declaration) { var prefix = TokenList(Token(SyntaxKind.ConstKeyword)); return LocalDeclarationStatement(prefix, declaration); } private static VariableDeclarationSyntax WithExplictType(VariableDeclarationSyntax declaration, SemanticModel semanticModel) { var typeSymbol = semanticModel.GetTypeInfo(declaration.Type).Type; var type = typeSymbol is IErrorTypeSymbol ? declaration.Type : IdentifierName(typeSymbol.ToMinimalDisplayString(semanticModel, declaration.GetLocation().SourceSpan.Start)); return declaration.ReplaceNode(declaration.Type, type); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnconditionalJumpStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnconditionalJumpStatement : UnconditionalJumpStatementBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override ISet LoopStatements { get; } = new HashSet { SyntaxKind.ForEachStatement, SyntaxKind.ForStatement, SyntaxKind.WhileStatement, SyntaxKind.DoStatement }; protected override LoopWalkerBase GetWalker(SonarSyntaxNodeReportingContext context) => new LoopWalker(context, LoopStatements); private class LoopWalker : LoopWalkerBase { protected override ISet StatementsThatCanThrow { get; } = new HashSet { SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.PointerMemberAccessExpression, SyntaxKind.ElementAccessExpression }; protected override ISet LambdaSyntaxes { get; } = new HashSet { SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression }; protected override ISet LocalFunctionSyntaxes { get; } = new HashSet { SyntaxKindEx.LocalFunctionStatement }; protected override ISet ConditionalStatements { get; } = new HashSet { SyntaxKind.IfStatement, SyntaxKind.SwitchStatement, SyntaxKind.CatchClause }; protected override ILanguageFacade Language => CSharpFacade.Instance; public LoopWalker(SonarSyntaxNodeReportingContext context, ISet loopStatements) : base(context, loopStatements) { } public override void Visit() { var csWalker = new CsLoopwalker(this); csWalker.SafeVisit(rootExpression); } protected override bool IsPropertyAccess(StatementSyntax node) => node.DescendantNodes().OfType().Any(x => semanticModel.GetSymbolInfo(x).Symbol is { } symbol && symbol.Kind == SymbolKind.Property); protected override bool TryGetTryAncestorStatements(StatementSyntax node, List ancestors, out IEnumerable tryAncestorStatements) { var tryAncestor = (TryStatementSyntax)ancestors.FirstOrDefault(n => n.IsKind(SyntaxKind.TryStatement)); if (tryAncestor == null || tryAncestor.Catches.Count == 0) { tryAncestorStatements = null; return false; } tryAncestorStatements = tryAncestor.Block.Statements; return true; } private sealed class CsLoopwalker : SafeCSharpSyntaxWalker { private readonly LoopWalker walker; public CsLoopwalker(LoopWalker loopWalker) { walker = loopWalker; } public override void VisitContinueStatement(ContinueStatementSyntax node) { base.VisitContinueStatement(node); walker.StoreVisitData(node, walker.ConditionalContinues, walker.UnconditionalContinues); } public override void VisitBreakStatement(BreakStatementSyntax node) { base.VisitBreakStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } public override void VisitReturnStatement(ReturnStatementSyntax node) { base.VisitReturnStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } public override void VisitThrowStatement(ThrowStatementSyntax node) { base.VisitThrowStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UninvokedEventDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UninvokedEventDeclaration : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3264"; private const string MessageFormat = "Remove the unused event '{0}' or invoke it."; private static readonly Accessibility MaxAccessibility = Accessibility.Public; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet EventSyntax = [SyntaxKind.EventFieldDeclaration]; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction(RaiseOnUninvokedEventDeclaration, SymbolKind.NamedType); private static void RaiseOnUninvokedEventDeclaration(SonarSymbolReportingContext context) { var namedType = (INamedTypeSymbol)context.Symbol; if (namedType.IsClassOrStruct() && namedType.ContainingType is null) { var removableDeclarationCollector = new CSharpRemovableDeclarationCollector(namedType, context.Compilation); var removableEventFields = removableDeclarationCollector.RemovableFieldLikeDeclarations(EventSyntax, MaxAccessibility).Where(x => !IsInPartialEventField(x.Node)).ToArray(); if (removableEventFields.Any()) { var usedSymbols = InvokedEventSymbols(removableDeclarationCollector).Concat(PossiblyCopiedSymbols(removableDeclarationCollector)).ToHashSet(); foreach (var field in removableEventFields.Where(x => !usedSymbols.Contains(x.Symbol))) { context.ReportIssue(Rule, Location(field.Node), field.Symbol.Name); } } } static Location Location(SyntaxNode node) => node is VariableDeclaratorSyntax variableDeclarator ? variableDeclarator.Identifier.GetLocation() : ((EventDeclarationSyntax)node).Identifier.GetLocation(); static bool IsInPartialEventField(SyntaxNode node) => node.Ancestors().OfType().FirstOrDefault() is { } eventField && eventField.Modifiers.Any(SyntaxKind.PartialKeyword); } private static IEnumerable InvokedEventSymbols(CSharpRemovableDeclarationCollector removableDeclarationCollector) { var delegateInvocations = removableDeclarationCollector.TypeDeclarations .SelectMany(x => x.Node.DescendantNodes().OfType() .Select(invocation => new NodeSymbolAndModel(invocation, (IMethodSymbol)x.Model.GetSymbolInfo(invocation).Symbol, x.Model))) .Where(x => x.Symbol is not null && IsDelegateInvocation(x.Symbol)); var invokedEventSymbols = delegateInvocations .Select(x => new NodeAndModel(EventExpressionFromInvocation(x.Node, x.Symbol), x.Model)) .Select(x => new NodeSymbolAndModel(x.Node, x.Model.GetSymbolInfo(x.Node).Symbol as IEventSymbol, x.Model)) .Where(x => x.Symbol is not null) .Select(x => x.Symbol.OriginalDefinition); return invokedEventSymbols; } private static IEnumerable PossiblyCopiedSymbols(CSharpRemovableDeclarationCollector removableDeclarationCollector) { var usedSymbols = new List(); foreach (var typeDeclaration in removableDeclarationCollector.TypeDeclarations) { foreach (var node in typeDeclaration.Node.DescendantNodes().Select(Expression).WhereNotNull()) { if (typeDeclaration.Model.GetSymbolInfo(node).Symbol is IEventSymbol symbol) { usedSymbols.Add(symbol.OriginalDefinition); } } } return usedSymbols; static SyntaxNode Expression(SyntaxNode node) => node switch { ArgumentSyntax arg => arg.Expression, EqualsValueClauseSyntax equalsClause => equalsClause.Value, AssignmentExpressionSyntax assignment => assignment.Right, _ => null }; } private static ExpressionSyntax EventExpressionFromInvocation(InvocationExpressionSyntax invocation, IMethodSymbol symbol) { var expression = invocation.Expression switch { MemberAccessExpressionSyntax memberAccess => new NodeAndName(memberAccess.Expression, memberAccess.Name), MemberBindingExpressionSyntax memberBinding => new NodeAndName((invocation.Parent as ConditionalAccessExpressionSyntax)?.Expression, memberBinding.Name), _ => default }; return expression.Node is not null && IsExplicitDelegateInvocation(symbol, expression.Name) ? expression.Node : invocation.Expression; } private static bool IsExplicitDelegateInvocation(IMethodSymbol symbol, SimpleNameSyntax invokedMethodName) => IsDynamicInvoke(symbol) || IsBeginInvoke(symbol) || (symbol.MethodKind == MethodKind.DelegateInvoke && invokedMethodName.Identifier.ValueText == "Invoke"); private static bool IsDelegateInvocation(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.DelegateInvoke || IsInvoke(symbol) || IsDynamicInvoke(symbol) || IsBeginInvoke(symbol); private static bool IsInvoke(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Ordinary && symbol.Name == nameof(EventHandler.Invoke); private static bool IsDynamicInvoke(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Ordinary && symbol.Name == nameof(Delegate.DynamicInvoke) && symbol.ReceiverType.OriginalDefinition.Is(KnownType.System_Delegate); private static bool IsBeginInvoke(IMethodSymbol symbol) => symbol.MethodKind == MethodKind.Ordinary && symbol.Name == nameof(EventHandler.BeginInvoke); } file record struct NodeAndName(ExpressionSyntax Node, SimpleNameSyntax Name); ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnnecessaryBitwiseOperation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnnecessaryBitwiseOperation : UnnecessaryBitwiseOperationBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckBinary(c, -1), SyntaxKind.BitwiseAndExpression); context.RegisterNodeAction( c => CheckBinary(c, 0), SyntaxKind.BitwiseOrExpression, SyntaxKind.ExclusiveOrExpression); context.RegisterNodeAction( c => CheckAssignment(c, -1), SyntaxKind.AndAssignmentExpression); context.RegisterNodeAction( c => CheckAssignment(c, 0), SyntaxKind.OrAssignmentExpression, SyntaxKind.ExclusiveOrAssignmentExpression); } private void CheckAssignment(SonarSyntaxNodeReportingContext context, int constValueToLookFor) { var assignment = (AssignmentExpressionSyntax)context.Node; if (FindIntConstant(context.Model, assignment.Right) is { } constValue && constValue == constValueToLookFor) { var location = assignment.Parent is StatementSyntax ? assignment.Parent.GetLocation() : assignment.OperatorToken.CreateLocation(assignment.Right); context.ReportIssue(Rule, location); } } private void CheckBinary(SonarSyntaxNodeReportingContext context, int constValueToLookFor) { var binary = (BinaryExpressionSyntax)context.Node; CheckBinary(context, binary.Left, binary.OperatorToken, binary.Right, constValueToLookFor); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnnecessaryBitwiseOperationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UnnecessaryBitwiseOperationCodeFix : UnnecessaryBitwiseOperationCodeFixBase { protected override Func CreateNewRoot(SyntaxNode root, TextSpan diagnosticSpan, bool isReportingOnLeft) => root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) switch { StatementSyntax statement => () => root.RemoveNode(statement, SyntaxRemoveOptions.KeepNoTrivia), AssignmentExpressionSyntax assignment => () => root.ReplaceNode(assignment, assignment.Left.WithAdditionalAnnotations(Formatter.Annotation)), BinaryExpressionSyntax binary => () => root.ReplaceNode(binary, (isReportingOnLeft ? binary.Right : binary.Left).WithAdditionalAnnotations(Formatter.Annotation)), _ => null }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnnecessaryMathematicalComparison.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnnecessaryMathematicalComparison : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2198"; private const string MathComparisonMessage = "Comparison to this constant is useless; the constant is outside the range of type '{0}'"; private static readonly DiagnosticDescriptor MathComparisonRule = DescriptorFactory.Create(DiagnosticId, MathComparisonMessage); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(MathComparisonRule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( CheckComparisonOutOfRange, CSharpFacade.Instance.SyntaxKind.ComparisonKinds); private static void CheckComparisonOutOfRange(SonarSyntaxNodeReportingContext context) { if (TryGetConstantValue(context.Model, (BinaryExpressionSyntax)context.Node, out var constant, out var other) && context.Model.GetTypeInfo(other).Type is { } typeSymbolOfOther && TryGetRange(typeSymbolOfOther) is { } range && range.IsOutOfRange(constant)) { var typeName = typeSymbolOfOther.ToMinimalDisplayString(context.Model, other.GetLocation().SourceSpan.Start); context.ReportIssue(MathComparisonRule, other.Parent, typeName); } } private static bool TryGetConstantValue(SemanticModel model, BinaryExpressionSyntax binary, out double constant, out SyntaxNode other) { var optionalLeft = model.GetConstantValue(binary.Left); var optionalRight = model.GetConstantValue(binary.Right); if (optionalLeft.HasValue ^ optionalRight.HasValue) { if (optionalLeft.HasValue && Conversions.ToDouble(optionalLeft.Value) is { } left) { constant = left; other = binary.Right; return true; } else if (optionalRight.HasValue && Conversions.ToDouble(optionalRight.Value) is { } right) { constant = right; other = binary.Left; return true; } } constant = default; other = null; return false; } private static ValuesRange? TryGetRange(ITypeSymbol typeSymbol) => typeSymbol switch { _ when typeSymbol.Is(KnownType.System_Char) => new(char.MinValue, char.MaxValue), _ when typeSymbol.Is(KnownType.System_Single) => new(float.MinValue, float.MaxValue), _ when typeSymbol.Is(KnownType.System_Int64) => new(long.MinValue, long.MaxValue), _ when typeSymbol.Is(KnownType.System_UInt64) => new(ulong.MinValue, ulong.MaxValue), _ => null, }; private readonly record struct ValuesRange(double Min, double Max) { public bool IsOutOfRange(double value) => value < Min || value > Max; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnnecessaryUsings.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnnecessaryUsings : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1128"; private const string MessageFormat = "Remove this unnecessary 'using'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly HashSet IgnoredRazorFiles = new(StringComparer.OrdinalIgnoreCase) { "_Imports.razor", "_ViewImports.cshtml" }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { // When using top level statements, we are called twice for the same compilation unit. The second call has the containing symbol kind equal to `Method`. if (c.ContainingSymbol.Kind == SymbolKind.Method) { return; } var compilationUnit = (CompilationUnitSyntax)c.Node; var simpleNamespaces = compilationUnit.Usings.Where(usingDirective => usingDirective.Alias is null).ToList(); var globalUsingDirectives = simpleNamespaces.Select(x => new EquivalentNameSyntax(x.Name)).ToImmutableHashSet(); var visitor = new CSharpRemovableUsingWalker(c, globalUsingDirectives, null); VisitContent(visitor, compilationUnit.Members); foreach (var attribute in compilationUnit.AttributeLists) { visitor.SafeVisit(attribute); } CheckUnnecessaryUsings(c, simpleNamespaces, visitor.NecessaryNamespaces); }, SyntaxKind.CompilationUnit); private static void VisitContent(ISafeSyntaxWalker visitor, SyntaxList members) { foreach (var member in members) { visitor.SafeVisit(member); } } private static void CheckUnnecessaryUsings(SonarSyntaxNodeReportingContext context, IEnumerable usingDirectives, ISet necessaryNamespaces) { foreach (var usingDirective in usingDirectives) { // This will create some FNs but will kill noise from FPs. // For more info see issues: // - https://github.com/SonarSource/sonar-dotnet/issues/5946 // - https://github.com/SonarSource/sonar-dotnet/issues/7959 if (usingDirective.GetFirstToken().IsKind(SyntaxKind.GlobalKeyword) || (GeneratedCodeRecognizer.IsRazorGeneratedFile(usingDirective.SyntaxTree) && IgnoredRazorFiles.Contains(Path.GetFileName(usingDirective.GetLocation().GetMappedLineSpan().Path)))) { continue; } if (context.Model.GetSymbolInfo(usingDirective.Name).Symbol is INamespaceSymbol namespaceSymbol && !necessaryNamespaces.Contains(namespaceSymbol)) { context.ReportIssue(Rule, usingDirective); } } } private sealed class CSharpRemovableUsingWalker : SafeCSharpSyntaxWalker { public readonly HashSet NecessaryNamespaces = new(NamespaceComparer.Instance); private readonly SonarSyntaxNodeReportingContext context; private readonly IImmutableSet usingDirectivesFromParent; private readonly HashSet currentNamespaceAndAncestors = new(NamespaceComparer.Instance); private bool linqQueryVisited; public CSharpRemovableUsingWalker(SonarSyntaxNodeReportingContext context, IImmutableSet usingDirectivesFromParent, INamespaceSymbol currentNamespace) : base(SyntaxWalkerDepth.StructuredTrivia) { this.context = context; this.usingDirectivesFromParent = usingDirectivesFromParent; for (var ancestor = currentNamespace; ancestor is not null; ancestor = ancestor.ContainingNamespace) { currentNamespaceAndAncestors.Add(ancestor); } } public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) => VisitNamespace(node.Usings, node.Name, node.Members); public override void VisitInitializerExpression(InitializerExpressionSyntax node) { if (node.IsKind(SyntaxKind.CollectionInitializerExpression)) { foreach (var addExpression in node.Expressions) { VisitSymbol(context.Model.GetCollectionInitializerSymbolInfo(addExpression).Symbol); } } base.VisitInitializerExpression(node); } public override void VisitIdentifierName(IdentifierNameSyntax node) { VisitNameNode(node); base.VisitIdentifierName(node); } public override void VisitGenericName(GenericNameSyntax node) { VisitNameNode(node); base.VisitGenericName(node); } public override void VisitAwaitExpression(AwaitExpressionSyntax node) { VisitSymbol(context.Model.GetAwaitExpressionInfo(node).GetAwaiterMethod); base.VisitAwaitExpression(node); } /// /// LINQ Query Syntax do not use symbols from the 'System.Linq' namespace directly, but the using directive is /// still necessary to use the Query Syntax form. /// public override void VisitQueryExpression(QueryExpressionSyntax node) { if (!linqQueryVisited && TryGetSystemLinkNamespace(out var systemLinqNamespaceSymbol)) { NecessaryNamespaces.Add(systemLinqNamespaceSymbol); } linqQueryVisited = true; base.VisitQueryExpression(node); } public override void Visit(SyntaxNode node) { if (node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)) { var fileScopedNamespace = (FileScopedNamespaceDeclarationSyntaxWrapper)node; VisitNamespace(fileScopedNamespace.Usings, fileScopedNamespace.Name, fileScopedNamespace.Members); return; // VisitNamespace processes the members } if (node.IsKind(SyntaxKindEx.ParenthesizedVariableDesignation)) // Tuple deconstruction declaration { NecessaryNamespaces.Add(context.Compilation.GetSpecialType(SpecialType.System_Object).ContainingNamespace); } base.Visit(node); } private void VisitNamespace(SyntaxList usings, NameSyntax name, SyntaxList members) { var simpleNamespaces = usings.Where(x => x.Alias is null).ToList(); var newUsingDirectives = new HashSet(); newUsingDirectives.UnionWith(usingDirectivesFromParent); newUsingDirectives.UnionWith(simpleNamespaces.Select(x => new EquivalentNameSyntax(x.Name))); // We visit the namespace declaration with the updated set of parent 'usings', this is needed in case of nested namespaces var visitingNamespace = context.Model.GetSymbolInfo(name).Symbol as INamespaceSymbol; var visitor = new CSharpRemovableUsingWalker(context, newUsingDirectives.ToImmutableHashSet(), visitingNamespace); VisitContent(visitor, members); CheckUnnecessaryUsings(context, simpleNamespaces, visitor.NecessaryNamespaces); NecessaryNamespaces.UnionWith(visitor.NecessaryNamespaces); } private bool TryGetSystemLinkNamespace(out INamespaceSymbol systemLinqNamespace) { foreach (var usingDirective in usingDirectivesFromParent) { if (context.Model.GetSymbolInfo(usingDirective.Name).Symbol is INamespaceSymbol namespaceSymbol && namespaceSymbol.ToDisplayString() == "System.Linq") { systemLinqNamespace = namespaceSymbol; return true; } } systemLinqNamespace = null; return false; } /// /// We check the symbol of each name node found in the code. If the containing namespace of the symbol is /// neither the current namespace or one of its parent, it is then added to the necessary namespace set, as /// importing that namespace is indeed necessary. /// private void VisitNameNode(ExpressionSyntax node) => VisitSymbol(context.Model.GetSymbolInfo(node).Symbol); private void VisitSymbol(ISymbol symbol) { if (symbol is not null && symbol.ContainingNamespace is INamespaceSymbol namespaceSymbol && (currentNamespaceAndAncestors.IsEmpty() || !currentNamespaceAndAncestors.Contains(namespaceSymbol))) { NecessaryNamespaces.Add(namespaceSymbol); } } } } internal sealed class EquivalentNameSyntax : IEquatable { public NameSyntax Name { get; } public EquivalentNameSyntax(NameSyntax name) => Name = name; public override int GetHashCode() => Name.ToString().GetHashCode(); public override bool Equals(object obj) => obj is EquivalentNameSyntax equivalentName && Equals(equivalentName); public bool Equals(EquivalentNameSyntax other) => other is not null && CSharpEquivalenceChecker.AreEquivalent(Name, other.Name); } internal sealed class NamespaceComparer : IEqualityComparer { public static readonly NamespaceComparer Instance = new(); private NamespaceComparer() { } public bool Equals(INamespaceSymbol x, INamespaceSymbol y) => x.IsSameNamespace(y); public int GetHashCode(INamespaceSymbol obj) => obj.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).GetHashCode(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnnecessaryUsingsCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UnnecessaryUsingsCodeFix : SonarCodeFix { internal const string Title = "Remove this unnecessary 'using'."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnnecessaryUsings.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (!(root.FindNode(diagnosticSpan) is UsingDirectiveSyntax syntaxNode)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnusedPrivateMember : SonarDiagnosticAnalyzer { internal const string S1144DiagnosticId = "S1144"; private const string S1144MessageFormat = "Remove the unused {0} {1} '{2}'."; private const string S1144MessageFormatForPublicCtor = "Remove unused constructor of {0} type '{1}'."; private const string S4487DiagnosticId = "S4487"; private const string S4487MessageFormat = "Remove this unread {0} field '{1}' or refactor the code to use its value."; private static readonly DiagnosticDescriptor RuleS1144 = DescriptorFactory.Create(S1144DiagnosticId, S1144MessageFormat); private static readonly DiagnosticDescriptor RuleS1144ForPublicCtor = DescriptorFactory.Create(S1144DiagnosticId, S1144MessageFormatForPublicCtor); private static readonly DiagnosticDescriptor RuleS4487 = DescriptorFactory.Create(S4487DiagnosticId, S4487MessageFormat); private static readonly ImmutableArray IgnoredTypes = ImmutableArray.Create( KnownType.UnityEditor_AssetModificationProcessor, KnownType.UnityEditor_AssetPostprocessor, KnownType.UnityEngine_MonoBehaviour, KnownType.UnityEngine_ScriptableObject, KnownType.Microsoft_EntityFrameworkCore_Migrations_Migration); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(RuleS1144, RuleS4487); protected override bool EnableConcurrentExecution => false; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( c => { // Collect potentially removable internal types from the project to evaluate when // the compilation is over, depending on whether InternalsVisibleTo attribute is present // or not. var removableInternalTypes = new HashSet(); c.RegisterSymbolAction(x => NamedSymbolAction(x, removableInternalTypes), SymbolKind.NamedType); c.RegisterCompilationEndAction( cc => { var foundInternalsVisibleTo = cc.Compilation.Assembly.HasAttribute(KnownType.System_Runtime_CompilerServices_InternalsVisibleToAttribute); if (foundInternalsVisibleTo || removableInternalTypes.Count == 0) { return; } var usageCollector = new SymbolUsageCollector(cc.Compilation, removableInternalTypes); foreach (var syntaxTree in cc.Compilation.SyntaxTrees.Where(tree => !tree.IsConsideredGenerated(CSharpGeneratedCodeRecognizer.Instance, cc.IsRazorAnalysisEnabled()))) { usageCollector.SafeVisit(syntaxTree.GetRoot()); } ReportUnusedPrivateMembers(cc, usageCollector, removableInternalTypes, SyntaxConstants.Internal, new()); }); }); private static void NamedSymbolAction(SonarSymbolReportingContext context, HashSet removableInternalTypes) { var namedType = (INamedTypeSymbol)context.Symbol; var privateSymbols = new HashSet(); var fieldLikeSymbols = new BidirectionalDictionary(); if (GatherSymbols(namedType, context.Compilation, privateSymbols, removableInternalTypes, fieldLikeSymbols, context) && privateSymbols.Any() && new SymbolUsageCollector(context.Compilation, AssociatedSymbols(privateSymbols)) is var usageCollector && VisitDeclaringReferences(namedType, usageCollector, context, includeGeneratedFile: true)) { ReportUnusedPrivateMembers(context, usageCollector, privateSymbols, SyntaxConstants.Private, fieldLikeSymbols); ReportUsedButUnreadFields(context, usageCollector, privateSymbols); } } private static IEnumerable AssociatedSymbols(IEnumerable privateSymbols) => privateSymbols.Select(x => x is IMethodSymbol { AssociatedSymbol: IPropertySymbol property } ? property : x); private static bool GatherSymbols(INamedTypeSymbol namedType, Compilation compilation, HashSet privateSymbols, HashSet internalSymbols, BidirectionalDictionary fieldLikeSymbols, SonarSymbolReportingContext context) { if (namedType.ContainingType is not null // We skip top level statements since they cannot have fields. Other declared types are analyzed separately. || namedType.IsTopLevelProgram() || namedType.DerivesFromAny(IgnoredTypes) // Collect symbols of private members that could potentially be removed || RetrieveRemovableSymbols(namedType, compilation, context) is not { } removableSymbolsCollector) { return false; } CopyRetrievedSymbols(removableSymbolsCollector, privateSymbols, internalSymbols, fieldLikeSymbols); // Collect symbols of private members that could potentially be removed for the nested classes foreach (var declaration in PrivateNestedMembersFromNonGeneratedCode(namedType, context)) { if (compilation.GetSemanticModel(declaration.SyntaxTree) is { } semanticModel && semanticModel.GetDeclaredSymbol(declaration) is { } declarationSymbol) { var symbolsCollector = RetrieveRemovableSymbols(declarationSymbol, compilation, context); CopyRetrievedSymbols(symbolsCollector, privateSymbols, internalSymbols, fieldLikeSymbols); } } return true; static IEnumerable PrivateNestedMembersFromNonGeneratedCode(INamedTypeSymbol namedType, SonarSymbolReportingContext context) => namedType.DeclaringSyntaxReferences .Where(x => !x.SyntaxTree.IsConsideredGenerated(CSharpGeneratedCodeRecognizer.Instance, context.IsRazorAnalysisEnabled())) .SelectMany(x => x.GetSyntax().ChildNodes().OfType()); } private static void ReportUnusedPrivateMembers(TContext context, SymbolUsageCollector usageCollector, ISet removableSymbols, string accessibility, BidirectionalDictionary fieldLikeSymbols) where TContext : ICompilationReport { var unusedSymbols = UnusedSymbols(usageCollector, removableSymbols); var propertiesWithUnusedAccessor = removableSymbols .Intersect(usageCollector.UsedSymbols) .OfType() .Where(usageCollector.PropertyAccess.ContainsKey) .Where(x => !IsMentionedInDebuggerDisplay(x, usageCollector)); foreach (var property in propertiesWithUnusedAccessor) { ReportProperty(context, property, usageCollector.PropertyAccess); } ReportDiagnosticsForMembers(context, unusedSymbols, accessibility, fieldLikeSymbols); } private static bool IsUsedWithReflection(ISymbol symbol, HashSet symbolsUsedWithReflection) { var currentSymbol = symbol; while (currentSymbol is not null) { if (symbolsUsedWithReflection.Contains(currentSymbol)) { return true; } currentSymbol = currentSymbol.ContainingSymbol; } return false; } private static bool IsMentionedInDebuggerDisplay(ISymbol symbol, SymbolUsageCollector usageCollector) => usageCollector.DebuggerDisplayValues.Any(x => x.Contains(symbol.Name) || (symbol is IPropertySymbol { IsIndexer: true } && x.Contains("this["))); private static void ReportUsedButUnreadFields(SonarSymbolReportingContext context, SymbolUsageCollector usageCollector, IEnumerable removableSymbols) { var unusedSymbols = UnusedSymbols(usageCollector, removableSymbols); var usedButUnreadFields = usageCollector.FieldSymbolUsages.Values .Where(x => x.Symbol.DeclaredAccessibility == Accessibility.Private || x.Symbol.ContainingType?.DeclaredAccessibility == Accessibility.Private) .Where(x => x.Symbol.Kind is SymbolKind.Field or SymbolKind.Event) .Where(x => !unusedSymbols.Contains(x.Symbol) && !IsMentionedInDebuggerDisplay(x.Symbol, usageCollector) && !IsUsedWithReflection(x.Symbol, usageCollector.TypesUsedWithReflection)) .Where(x => x.Declaration is not null && !x.Readings.Any()); foreach (var usage in usedButUnreadFields) { context.ReportIssue(RuleS4487, usage.Declaration.GetLocation(), FieldAccessibilityForMessage(usage.Symbol), usage.Symbol.Name); } } private static HashSet UnusedSymbols(SymbolUsageCollector usageCollector, IEnumerable removableSymbols) => removableSymbols .Except(usageCollector.UsedSymbols) .Where(x => !IsMentionedInDebuggerDisplay(x, usageCollector) && !IsAccessorUsed(x, usageCollector) && !IsDeconstructMethod(x) && !usageCollector.PrivateAttributes.Contains(x) && !IsUsedWithReflection(x, usageCollector.TypesUsedWithReflection)) .ToHashSet(); private static bool IsDeconstructMethod(ISymbol symbol) => symbol is IMethodSymbol { Name: "Deconstruct", Parameters.Length: > 0 } method && method.ReturnType.Is(KnownType.Void) && method.Parameters.All(x => x.RefKind == RefKind.Out); private static bool IsAccessorUsed(ISymbol symbol, SymbolUsageCollector usageCollector) => symbol is IMethodSymbol { AssociatedSymbol: IPropertySymbol property } accessor && usageCollector.PropertyAccess.TryGetValue(property, out var access) && ((access.HasFlag(AccessorAccess.Get) && accessor.MethodKind == MethodKind.PropertyGet) || (access.HasFlag(AccessorAccess.Set) && accessor.MethodKind == MethodKind.PropertySet)); private static string FieldAccessibilityForMessage(ISymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Private ? SyntaxConstants.Private : "private class"; private static void ReportDiagnosticsForMembers(TContext context, ICollection unusedSymbols, string accessibility, BidirectionalDictionary fieldLikeSymbols) where TContext : ICompilationReport { var alreadyReportedFieldLikeSymbols = new HashSet(); var unusedSymbolSyntaxPairs = unusedSymbols.SelectMany(x => x.DeclaringSyntaxReferences.Select(syntax => new NodeAndSymbol(syntax.GetSyntax(), x))); foreach (var unused in unusedSymbolSyntaxPairs) { var syntaxForLocation = unused.Node; var isFieldOrEvent = unused.Symbol.Kind is SymbolKind.Field or SymbolKind.Event; if (isFieldOrEvent && unused.Node.IsKind(SyntaxKind.VariableDeclarator)) { if (alreadyReportedFieldLikeSymbols.Contains(unused.Symbol)) { continue; } var declarations = GetSiblingDeclarators(unused.Node).Select(fieldLikeSymbols.GetByB).ToList(); if (declarations.TrueForAll(unusedSymbols.Contains)) { syntaxForLocation = unused.Node.Parent.Parent; alreadyReportedFieldLikeSymbols.UnionWith(declarations); } } if (unused.Symbol.IsConstructor() && !syntaxForLocation.GetModifiers().Any(SyntaxKind.PrivateKeyword)) { context.ReportIssue(RuleS1144ForPublicCtor, syntaxForLocation, accessibility, unused.Symbol.ContainingType.Name); } else { context.ReportIssue(RuleS1144, IdentifierLocation(syntaxForLocation), accessibility, unused.Symbol.GetClassification(), MemberName(unused.Symbol)); } } static IEnumerable GetSiblingDeclarators(SyntaxNode variableDeclarator) => variableDeclarator.Parent.Parent switch { FieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Declaration.Variables, EventFieldDeclarationSyntax eventDeclaration => eventDeclaration.Declaration.Variables, _ => [], }; static Location IdentifierLocation(SyntaxNode node) => node.GetIdentifier() is { } identifier ? identifier.GetLocation() : node.GetLocation(); static string MemberName(ISymbol symbol) => symbol.IsConstructor() ? symbol.ContainingType.Name : symbol.Name; } private static void ReportProperty(TContext context, IPropertySymbol property, IReadOnlyDictionary propertyAccessorAccess) where TContext : ICompilationReport { var access = propertyAccessorAccess[property]; if (access == AccessorAccess.Get && property.SetMethod is not null && GetAccessorSyntax(property.SetMethod) is { } setter) { context.ReportIssue(RuleS1144, setter.Keyword.GetLocation(), SyntaxConstants.Private, "set accessor in property", property.Name); } else if (access == AccessorAccess.Set && property.GetMethod is not null && GetAccessorSyntax(property.GetMethod) is { } getter && getter.HasBodyOrExpressionBody()) { context.ReportIssue(RuleS1144, getter.Keyword.GetLocation(), SyntaxConstants.Private, "get accessor in property", property.Name); } static AccessorDeclarationSyntax GetAccessorSyntax(ISymbol symbol) => symbol?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as AccessorDeclarationSyntax; } private static bool VisitDeclaringReferences(ISymbol symbol, ISafeSyntaxWalker visitor, SonarSymbolReportingContext context, bool includeGeneratedFile) { var syntaxReferencesToVisit = includeGeneratedFile ? symbol.DeclaringSyntaxReferences : symbol.DeclaringSyntaxReferences.Where(x => !IsGenerated(x)); return syntaxReferencesToVisit.All(x => visitor.SafeVisit(x.GetSyntax())); bool IsGenerated(SyntaxReference syntaxReference) => syntaxReference.SyntaxTree.IsConsideredGenerated(CSharpGeneratedCodeRecognizer.Instance, context.IsRazorAnalysisEnabled()); } private static CSharpRemovableSymbolWalker RetrieveRemovableSymbols(INamedTypeSymbol namedType, Compilation compilation, SonarSymbolReportingContext context) { var removableSymbolsCollector = new CSharpRemovableSymbolWalker(compilation.GetSemanticModel, namedType.DeclaredAccessibility); return VisitDeclaringReferences(namedType, removableSymbolsCollector, context, includeGeneratedFile: false) ? removableSymbolsCollector : null; } private static void CopyRetrievedSymbols(CSharpRemovableSymbolWalker removableSymbolCollector, HashSet privateSymbols, HashSet internalSymbols, BidirectionalDictionary fieldLikeSymbols) { privateSymbols.AddRange(removableSymbolCollector.PrivateSymbols); // Keep the removable internal types for when the compilation ends internalSymbols.AddRange(removableSymbolCollector.InternalSymbols); foreach (var pair in removableSymbolCollector.FieldLikeSymbols.Where(x => !fieldLikeSymbols.ContainsKeyByA(x.Key))) { fieldLikeSymbols.Add(pair.Key, pair.Value); } } /// /// Collects private or internal member symbols that could potentially be removed if they are not used. /// Members that are overridden, overridable, have specific use, etc. are not removable. /// private sealed class CSharpRemovableSymbolWalker : SafeCSharpSyntaxWalker { private readonly Func getSemanticModel; private readonly Accessibility containingTypeAccessibility; public Dictionary FieldLikeSymbols { get; } = []; public HashSet InternalSymbols { get; } = []; public HashSet PrivateSymbols { get; } = []; public CSharpRemovableSymbolWalker(Func getSemanticModel, Accessibility containingTypeAccessibility) { this.getSemanticModel = x => getSemanticModel(x.SyntaxTree, false); this.containingTypeAccessibility = containingTypeAccessibility; } // This override is needed because VisitRecordDeclaration and LocalFunctionStatementSyntax are not available due to the Roslyn version. public override void Visit(SyntaxNode node) { if (node.Kind() is SyntaxKindEx.RecordDeclaration or SyntaxKindEx.RecordStructDeclaration) { VisitBaseTypeDeclaration(node); } if (node.IsKind(SyntaxKindEx.LocalFunctionStatement)) { ConditionalStore((IMethodSymbol)DeclaredSymbol(node), IsRemovableMethod); } base.Visit(node); } public override void VisitClassDeclaration(ClassDeclarationSyntax node) { VisitBaseTypeDeclaration(node); base.VisitClassDeclaration(node); } public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { if (!IsEmptyConstructor(node) && IsPrivateOrInPrivateType(node.Modifiers)) { ConditionalStore((IMethodSymbol)DeclaredSymbol(node), IsRemovableMethod); } base.VisitConstructorDeclaration(node); } public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers, true)) { ConditionalStore(DeclaredSymbol(node), IsRemovableType); } base.VisitDelegateDeclaration(node); } public override void VisitEnumDeclaration(EnumDeclarationSyntax node) { VisitBaseTypeDeclaration(node); base.VisitEnumDeclaration(node); } public override void VisitEventDeclaration(EventDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { var symbol = (IEventSymbol)DeclaredSymbol(node); ConditionalStore(symbol.PartialDefinitionPart ?? symbol, IsRemovableMember); } base.VisitEventDeclaration(node); } public override void VisitEventFieldDeclaration(EventFieldDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { StoreRemovableVariableDeclarations(node); } base.VisitEventFieldDeclaration(node); } public override void VisitFieldDeclaration(FieldDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { StoreRemovableVariableDeclarations(node); } base.VisitFieldDeclaration(node); } public override void VisitIndexerDeclaration(IndexerDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { ConditionalStore(DeclaredSymbol(node), IsRemovableMember); } base.VisitIndexerDeclaration(node); } public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) { VisitBaseTypeDeclaration(node); base.VisitInterfaceDeclaration(node); } public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { var symbol = (IMethodSymbol)DeclaredSymbol(node); ConditionalStore(symbol.AssociatedExtensionImplementation ?? symbol.PartialDefinitionPart ?? symbol, IsRemovableMethod); } base.VisitMethodDeclaration(node); } public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) { if (node.Modifiers.Any(SyntaxKind.PrivateKeyword)) { ConditionalStore(DeclaredSymbol(node), IsRemovable); } base.VisitAccessorDeclaration(node); } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { if (IsPrivateOrInPrivateType(node.Modifiers)) { ConditionalStore(DeclaredSymbol(node), IsRemovableMember); } base.VisitPropertyDeclaration(node); } public override void VisitStructDeclaration(StructDeclarationSyntax node) { VisitBaseTypeDeclaration(node); base.VisitStructDeclaration(node); } private void ConditionalStore(TSymbol symbol, Func condition) where TSymbol : ISymbol { if (condition(symbol)) { if (symbol.GetEffectiveAccessibility() == Accessibility.Private) { PrivateSymbols.Add(symbol); } else if (symbol is INamedTypeSymbol) { InternalSymbols.Add(symbol); } } } private ISymbol DeclaredSymbol(SyntaxNode node) => getSemanticModel(node).GetDeclaredSymbol(node); private void StoreRemovableVariableDeclarations(BaseFieldDeclarationSyntax node) { foreach (var variable in node.Declaration.Variables) { var symbol = DeclaredSymbol(variable); if (IsRemovableMember(symbol)) { PrivateSymbols.Add(symbol); FieldLikeSymbols.Add(symbol, variable); } } } private static bool IsEmptyConstructor(BaseMethodDeclarationSyntax constructorDeclaration) => !constructorDeclaration.HasBodyOrExpressionBody() || constructorDeclaration.Body is { Statements.Count: 0 }; private static bool IsRemovableMethod(IMethodSymbol methodSymbol) => IsRemovableMember(methodSymbol) && (methodSymbol.MethodKind is MethodKind.Ordinary or MethodKind.Constructor or MethodKindEx.LocalFunction) && !methodSymbol.IsMainMethod() && !methodSymbol.IsEventHandler() // Event handlers could be added in XAML and no method reference will be generated in the .g.cs file. && !methodSymbol.IsSerializationConstructor() && !methodSymbol.IsRecordPrintMembers() && !methodSymbol.IsMefConstructor(); private static bool IsRemovable(ISymbol symbol) => symbol is { IsImplicitlyDeclared: false, IsVirtual: false } && !HasAttributes(symbol) && !symbol.IsSerializableMember() && !symbol.ContainingType.IsInterface() && !(symbol.Kind is SymbolKind.Field && symbol.ContainingType.HasAttribute(KnownType.System_Runtime_InteropServices_StructLayoutAttribute)) && symbol.InterfaceMembers().IsEmpty() && symbol.GetOverriddenMember() is null; private static bool HasAttributes(ISymbol symbol) { var attributes = symbol.GetAttributes().AsEnumerable(); if (symbol is IMethodSymbol { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet, AssociatedSymbol: { } property }) { attributes = attributes.Union(property.GetAttributes()); } return attributes.Any(x => !x.AttributeClass.Is(KnownType.System_NonSerializedAttribute)); } private static bool IsRemovableMember(ISymbol symbol) => symbol.GetEffectiveAccessibility() == Accessibility.Private && IsRemovable(symbol); private static bool IsRemovableType(ISymbol typeSymbol) => typeSymbol.GetEffectiveAccessibility() is var accessibility && typeSymbol.ContainingType is not null && (accessibility is Accessibility.Private or Accessibility.Internal) && IsRemovable(typeSymbol) && !(typeSymbol is INamedTypeSymbol namedType && namedType.IsMefExportedType()); private void VisitBaseTypeDeclaration(SyntaxNode node) { if (IsPrivateOrInPrivateType(((BaseTypeDeclarationSyntax)node).Modifiers, true)) { ConditionalStore(DeclaredSymbol(node), IsRemovableType); } } private bool IsPrivateOrInPrivateType(SyntaxTokenList modifiers, bool checkInternal = false) => containingTypeAccessibility == Accessibility.Private || (checkInternal && containingTypeAccessibility == Accessibility.Internal) || !modifiers.Any(x => x.Kind() is SyntaxKind.PrivateKeyword or SyntaxKind.PublicKeyword or SyntaxKind.InternalKeyword or SyntaxKind.ProtectedKeyword) || modifiers.Any(SyntaxKind.PrivateKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMemberCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public class UnusedPrivateMemberCodeFix : SonarCodeFix { internal const string Title = "Remove unused member"; // We only want to fix S1144 and not S4487 because for S4487 the field is written so we don't know the fix public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnusedPrivateMember.S1144DiagnosticId); protected sealed override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(diag => diag.Id == UnusedPrivateMember.S1144DiagnosticId); var diagnosticSpan = diagnostic.Location.SourceSpan; var syntax = root.FindNode(diagnosticSpan); context.RegisterCodeFix( Title, c => { var newRoot = root.RemoveNode(syntax, SyntaxRemoveOptions.KeepNoTrivia); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedReturnValue.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using NodeSymbolAndModel = SonarAnalyzer.Core.Common.NodeSymbolAndModel; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnusedReturnValue : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3241"; private const string MessageFormat = "Change return type to 'void'; not a single caller uses the returned value."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterSymbolAction(AnalyzeNamedTypes, SymbolKind.NamedType); context.RegisterNodeAction(AnalyzeLocalFunctionStatements, SyntaxKindEx.LocalFunctionStatement); } private static void AnalyzeNamedTypes(SonarSymbolReportingContext context) { var namedType = (INamedTypeSymbol)context.Symbol; if (!namedType.IsClassOrStruct() || namedType.ContainingType != null) { return; } var removableDeclarationCollector = new CSharpRemovableDeclarationCollector(namedType, context.Compilation); var declaredPrivateMethodsWithReturn = CollectRemovableMethods(removableDeclarationCollector).ToList(); if (!declaredPrivateMethodsWithReturn.Any()) { return; } var invocations = removableDeclarationCollector.TypeDeclarations.SelectMany(FilterInvocations).ToList(); foreach (var declaredPrivateMethodWithReturn in declaredPrivateMethodsWithReturn) { var matchingInvocations = invocations .Where(invocation => invocation.Symbol.OriginalDefinition.Equals(declaredPrivateMethodWithReturn.Symbol)) .ToList(); // Method invocation is noncompliant when there is at least 1 invocation of the method, and no invocation is using the return value. The case of 0 invocation is handled by S1144. if (matchingInvocations.Any() && !matchingInvocations.Any(IsReturnValueUsed)) { context.ReportIssue(Rule, declaredPrivateMethodWithReturn.Node.ReturnType); } } } private static void AnalyzeLocalFunctionStatements(SonarSyntaxNodeReportingContext context) { var localFunctionSyntax = (LocalFunctionStatementSyntaxWrapper)context.Node; var topMostContainingMethod = localFunctionSyntax.IsTopLevel() ? context.Node.Parent.Parent // .Parent.Parent is the CompilationUnit : context.Node.GetTopMostContainingMethod(); if (topMostContainingMethod == null) { return; } var localFunctionSymbol = (IMethodSymbol)context.Model.GetDeclaredSymbol(localFunctionSyntax); if (localFunctionSymbol.ReturnsVoid || localFunctionSymbol.IsAsync) { return; } var matchingInvocations = GetLocalMatchingInvocations(topMostContainingMethod, localFunctionSymbol, context.Model).ToList(); // Method invocation is noncompliant when there is at least 1 invocation of the method, and no invocation is using the return value. The case of 0 invocation is handled by S1144. if (matchingInvocations.Any() && !matchingInvocations.Any(IsReturnValueUsed)) { context.ReportIssue(Rule, localFunctionSyntax.ReturnType); } } private static bool IsReturnValueUsed(NodeSymbolAndModel matchingInvocation) => !IsExpressionStatement(matchingInvocation.Node.Parent) && !IsActionLambda(matchingInvocation.Node.Parent, matchingInvocation.Model); private static bool IsActionLambda(SyntaxNode node, SemanticModel semanticModel) => node is LambdaExpressionSyntax lambda && semanticModel.GetSymbolInfo(lambda).Symbol is IMethodSymbol { ReturnsVoid: true }; private static bool IsExpressionStatement(SyntaxNode node) => node is ExpressionStatementSyntax; private static IEnumerable GetLocalMatchingInvocations(SyntaxNode containingMethod, IMethodSymbol invocationSymbol, SemanticModel semanticModel) => containingMethod.DescendantNodes() .OfType() .Where(x => semanticModel.GetSymbolInfo(x.Expression).Symbol is IMethodSymbol methodSymbol && invocationSymbol.Equals(methodSymbol)) .Select(x => new NodeSymbolAndModel(x, invocationSymbol, semanticModel)) .ToList(); private static IEnumerable FilterInvocations(NodeAndModel container) => container.Node.DescendantNodes() .OfType() .Select(x => new NodeSymbolAndModel(x, container.Model.GetSymbolInfo(x).Symbol as IMethodSymbol, container.Model)) .Where(x => x.Symbol != null); private static IEnumerable> CollectRemovableMethods(CSharpRemovableDeclarationCollector removableDeclarationCollector) => removableDeclarationCollector.TypeDeclarations .SelectMany(container => container.Node.DescendantNodes(CSharpRemovableDeclarationCollector.IsNodeContainerTypeDeclaration) .OfType() .Select(x => new NodeSymbolAndModel(x, container.Model.GetDeclaredSymbol(x), container.Model))) .Where(x => x.Symbol is { ReturnsVoid: false, IsAsync: false } && CSharpRemovableDeclarationCollector.IsRemovable(x.Symbol, Accessibility.Private)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedStringBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UnusedStringBuilder : UnusedStringBuilderBase { private static readonly HashSet SkipChildren = [ SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.UsingDirective, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode Scope(VariableDeclaratorSyntax declarator) => declarator switch { { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { Parent: BlockSyntax block } } } => block, { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { Parent: GlobalStatementSyntax { Parent: CompilationUnitSyntax compilationUnit } } } } => compilationUnit, _ => null, }; protected override ILocalSymbol RetrieveStringBuilderObject(SemanticModel model, VariableDeclaratorSyntax declarator) => declarator is { Parent.Parent: LocalDeclarationStatementSyntax, Initializer.Value: { } expression, } && IsStringBuilderObjectCreation(expression, model) ? model.GetDeclaredSymbol(declarator) as ILocalSymbol : null; protected override SyntaxNode StringBuilderReadExpression(SemanticModel model, SyntaxNode node) => node switch { InvocationExpressionSyntax invocation when IsAccessInvocation(invocation.Expression.GetName()) => invocation.Expression, ReturnStatementSyntax returnStatement => returnStatement.Expression, InterpolationSyntax interpolation => interpolation.Expression, ElementAccessExpressionSyntax elementAccess => elementAccess.Expression, ArgumentSyntax argument => argument.Expression, MemberAccessExpressionSyntax memberAccess when IsAccessExpression(memberAccess.Name.GetName()) => memberAccess.Expression, VariableDeclaratorSyntax { Initializer.Value: IdentifierNameSyntax identifier } => identifier, VariableDeclaratorSyntax { Initializer.Value: ConditionalAccessExpressionSyntax { Expression: IdentifierNameSyntax identifier } } => identifier, AssignmentExpressionSyntax { Right: IdentifierNameSyntax identifier } => identifier, BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AddExpression } => node, _ => null, }; protected override bool DescendIntoChildren(SyntaxNode node) => !(node.IsAnyKind(SkipChildren) || (node.IsKind(SyntaxKindEx.LocalFunctionStatement) && ((LocalFunctionStatementSyntaxWrapper)node).Modifiers.Any(SyntaxKind.StaticKeyword))); private static bool IsStringBuilderObjectCreation(ExpressionSyntax expression, SemanticModel model) => ObjectCreationFactory.TryCreate(expression) is { } creation && creation.IsKnownType(KnownType.System_Text_StringBuilder, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UriShouldNotBeHardcoded.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UriShouldNotBeHardcoded : UriShouldNotBeHardcodedBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxKind[] StringConcatenateExpressions => [SyntaxKind.AddExpression]; protected override SyntaxKind[] InvocationOrObjectCreationKind => [SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression]; protected override SyntaxNode GetRelevantAncestor(SyntaxNode node) => node switch { _ when node.FirstAncestorOrSelf() is { } propertyAssignment => propertyAssignment.Left, _ when node.FirstAncestorOrSelf() is { } parameterSyntax => parameterSyntax, _ when node.FirstAncestorOrSelf() is { } variableDeclaratorSyntax => variableDeclaratorSyntax, _ => null }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Shared.Extensions; using WellKnownExtensionMethodContainer = SonarAnalyzer.Core.Common.MultiValueDictionary; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseAwaitableMethod : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6966"; private const string MessageFormat = "Await {0} instead."; private static readonly string[] ExcludedMethodNames = ["Add", "AddRange"]; private static readonly ImmutableArray ExcludedTypes = ImmutableArray.Create(KnownType.System_Xml_XmlWriter, KnownType.System_Xml_XmlReader); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { // Not every async method is defined in the same class/interface as its non-async counterpart. // For example the EntityFrameworkQueryableExtensions.AnyAsync() method provides an async version of the Enumerable.Any() method for IQueryable types. // WellKnownExtensionMethodContainer stores where to look for the async versions of certain methods from a type, e.g. async versions of methods from // System.Linq.Enumerable can be found in Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions. var wellKnownExtensionMethodContainer = BuildWellKnownExtensionMethodContainers(compilationStart.Compilation); var exclusions = BuildExclusions(compilationStart.Compilation); compilationStart.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, codeBlockStart => { if (IsAsyncCodeBlock(codeBlockStart.CodeBlock)) { codeBlockStart.RegisterNodeAction(nodeContext => { var invocationExpression = (InvocationExpressionSyntax)nodeContext.Node; var awaitableAlternatives = FindAwaitableAlternatives(wellKnownExtensionMethodContainer, exclusions, invocationExpression, nodeContext.Model, nodeContext.ContainingSymbol, nodeContext.Cancel); if (awaitableAlternatives.FirstOrDefault() is { Name: { } alternative }) { nodeContext.ReportIssue(Rule, invocationExpression, alternative); } }, SyntaxKind.InvocationExpression); } }); }); private static WellKnownExtensionMethodContainer BuildWellKnownExtensionMethodContainers(Compilation compilation) { var wellKnownExtensionMethodContainer = new WellKnownExtensionMethodContainer(); var queryable = compilation.GetTypeByMetadataName(KnownType.System_Linq_Queryable); var enumerable = compilation.GetTypeByMetadataName(KnownType.System_Linq_Enumerable); if (queryable is not null && enumerable is not null) { if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions) is { } entityFrameworkQueryableExtensions) { wellKnownExtensionMethodContainer.Add(queryable, entityFrameworkQueryableExtensions); wellKnownExtensionMethodContainer.Add(enumerable, entityFrameworkQueryableExtensions); } if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions) is { } relationalQueryableExtensions) { wellKnownExtensionMethodContainer.Add(queryable, relationalQueryableExtensions); wellKnownExtensionMethodContainer.Add(enumerable, relationalQueryableExtensions); } } if (compilation.GetTypeByMetadataName(KnownType.System_Net_Sockets_Socket) is { } socket && compilation.GetTypeByMetadataName(KnownType.System_Net_Sockets_SocketTaskExtensions) is { } socketTaskExtensions) { wellKnownExtensionMethodContainer.Add(socket, socketTaskExtensions); } return wellKnownExtensionMethodContainer; } private static ImmutableArray> BuildExclusions(Compilation compilation) { var exclusions = ImmutableArray.CreateBuilder>(); if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_DbSet_TEntity) is not null) { exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbSet_TEntity, ExcludedMethodNames)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbContext, ExcludedMethodNames)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 // https://github.com/SonarSource/sonar-dotnet/issues/9590 exclusions.Add(x => x.IsImplementingInterfaceMember(KnownType.Microsoft_EntityFrameworkCore_IDbContextFactory_TContext, "CreateDbContext")); } if (compilation.GetTypeByMetadataName(KnownType.FluentValidation_IValidator) is not null) { exclusions.Add(x => x.IsImplementingInterfaceMember(KnownType.FluentValidation_IValidator, "Validate")); // https://github.com/SonarSource/sonar-dotnet/issues/9339 exclusions.Add(x => x.IsImplementingInterfaceMember(KnownType.FluentValidation_IValidator_T, "Validate")); // https://github.com/SonarSource/sonar-dotnet/issues/9339 } if (compilation.GetTypeByMetadataName(KnownType.MongoDB_Driver_IMongoCollectionExtensions) is not null) { exclusions.Add(x => x.Is(KnownType.MongoDB_Driver_IMongoCollectionExtensions, "Find")); // https://github.com/SonarSource/sonar-dotnet/issues/9265 } return exclusions.ToImmutableArray(); } private static ImmutableArray FindAwaitableAlternatives(WellKnownExtensionMethodContainer wellKnownExtensionMethodContainer, ImmutableArray> exclusions, InvocationExpressionSyntax invocationExpression, SemanticModel model, ISymbol containingSymbol, CancellationToken cancel) { var awaitableRoot = GetAwaitableRootOfInvocation(invocationExpression); if (awaitableRoot is not { Parent: AwaitExpressionSyntax } // Invocation result is already awaited. && invocationExpression.EnclosingScope() is { } scope && IsAsyncCodeBlock(scope) && model.GetSymbolInfo(invocationExpression, cancel).Symbol is IMethodSymbol { MethodKind: not MethodKind.DelegateInvoke } methodSymbol && !(methodSymbol.IsAwaitableNonDynamic() // The invoked method returns something awaitable (but it isn't awaited). || methodSymbol.ContainingType.DerivesFromAny(ExcludedTypes)) && !exclusions.Any(x => x(methodSymbol))) { // Perf: Before doing (expensive) speculative re-binding in SpeculativeBindCandidates, we check if there is an "..Async()" alternative in scope. var invokedType = invocationExpression.Expression.GetLeftOfDot() is { } expression && model.GetTypeInfo(expression) is { Type: { } type } ? type // A dotted expression: Lookup the type, left of the dot (this may be different from methodSymbol.ContainingType) : containingSymbol.ContainingType; // If not dotted, than the scope is the current type. Local function support is missing here. var members = GetMethodSymbolsInScope($"{methodSymbol.Name}Async", wellKnownExtensionMethodContainer, invokedType, methodSymbol.ContainingType); var awaitableCandidates = members.Where(x => x.IsAwaitableNonDynamic()); // Get the method alternatives and exclude candidates that would resolve to the containing method (endless loop) var awaitableAlternatives = SpeculativeBindCandidates(model, awaitableRoot, invocationExpression, awaitableCandidates) .Where(x => !containingSymbol.Equals(x)) .ToImmutableArray(); return awaitableAlternatives; } return ImmutableArray.Empty; } private static IEnumerable GetMethodSymbolsInScope(string methodName, WellKnownExtensionMethodContainer wellKnownExtensionMethodContainer, ITypeSymbol invokedType, ITypeSymbol methodContainer) => ((ITypeSymbol[])[.. invokedType.GetSelfAndBaseTypes(), .. WellKnownExtensionMethodContainer(wellKnownExtensionMethodContainer, methodContainer), methodContainer]) .Distinct() .SelectMany(x => x.GetMembers(methodName)) .OfType() .Where(x => !x.HasAttribute(KnownType.System_ObsoleteAttribute)); private static IEnumerable WellKnownExtensionMethodContainer(WellKnownExtensionMethodContainer lookup, ITypeSymbol invokedType) => lookup.TryGetValue(invokedType, out var extensionMethodContainer) ? extensionMethodContainer : []; private static IEnumerable SpeculativeBindCandidates(SemanticModel model, SyntaxNode awaitableRoot, InvocationExpressionSyntax invocationExpression, IEnumerable awaitableCandidates) => awaitableCandidates .Select(x => x.Name) .Distinct() .Select(x => SpeculativeBindCandidate(model, x, awaitableRoot, invocationExpression)) .WhereNotNull(); private static IMethodSymbol SpeculativeBindCandidate(SemanticModel model, string candidateName, SyntaxNode awaitableRoot, InvocationExpressionSyntax invocationExpression) { var invocationIdentifierName = invocationExpression.GetMethodCallIdentifier()?.Parent; if (invocationIdentifierName is null) { return null; } var invocationReplaced = ReplaceInvocation(awaitableRoot, invocationExpression, invocationIdentifierName, candidateName); var speculativeSymbolInfo = model.GetSpeculativeSymbolInfo(invocationReplaced.SpanStart, invocationReplaced, SpeculativeBindingOption.BindAsExpression); var speculativeSymbol = speculativeSymbolInfo.Symbol as IMethodSymbol; return speculativeSymbol; } private static SyntaxNode ReplaceInvocation(SyntaxNode awaitableRoot, InvocationExpressionSyntax invocationExpression, SyntaxNode invocationIdentifierName, string candidateName) { var root = invocationExpression.SyntaxTree.GetRoot(); var invocationAnnotation = new SyntaxAnnotation(); var replace = root.ReplaceNodes([awaitableRoot, invocationIdentifierName, invocationExpression], (original, newNode) => { var result = newNode; if (original == invocationIdentifierName) { var newIdentifierToken = SyntaxFactory.Identifier(candidateName); var simpleName = invocationIdentifierName switch { IdentifierNameSyntax => (SimpleNameSyntax)SyntaxFactory.IdentifierName(newIdentifierToken), GenericNameSyntax { TypeArgumentList: { } typeArguments } => SyntaxFactory.GenericName(newIdentifierToken, typeArguments), _ => null, }; result = simpleName is null ? newNode : simpleName.WithTriviaFrom(invocationIdentifierName); } if (original == invocationExpression) { result = result.WithAdditionalAnnotations(invocationAnnotation); } if (original == awaitableRoot && result is ExpressionSyntax resultExpression) { result = SyntaxFactory.ParenthesizedExpression( SyntaxFactory.AwaitExpression(resultExpression.WithoutTrivia().WithLeadingTrivia(SyntaxFactory.ElasticSpace))).WithTriviaFrom(resultExpression); } return result; }); return replace.GetAnnotatedNodes(invocationAnnotation).First(); } private static ExpressionSyntax GetAwaitableRootOfInvocation(ExpressionSyntax expression) => expression switch { { Parent: ConditionalAccessExpressionSyntax conditional } => conditional.GetRootConditionalAccessExpression(), { Parent: MemberAccessExpressionSyntax memberAccess } => memberAccess.GetRootConditionalAccessExpression() ?? GetAwaitableRootOfInvocation(memberAccess), { Parent: PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression } parent } => GetAwaitableRootOfInvocation(parent), { Parent: ParenthesizedExpressionSyntax parent } => GetAwaitableRootOfInvocation(parent), { } self => self, }; private static bool IsAsyncCodeBlock(SyntaxNode codeBlock) => codeBlock switch { CompilationUnitSyntax => true, BaseMethodDeclarationSyntax { Modifiers: { } modifiers } => modifiers.AnyOfKind(SyntaxKind.AsyncKeyword), AnonymousFunctionExpressionSyntax { AsyncKeyword: { } asyncKeyword } => asyncKeyword.IsKind(SyntaxKind.AsyncKeyword), var localFunction when LocalFunctionStatementSyntaxWrapper.IsInstance(localFunction) => ((LocalFunctionStatementSyntaxWrapper)localFunction).Modifiers.AnyOfKind(SyntaxKind.AsyncKeyword), _ => false, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseCharOverloadOfStringMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseCharOverloadOfStringMethods : UseCharOverloadOfStringMethodsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool HasCorrectArguments(InvocationExpressionSyntax invocation) => invocation.HasExactlyNArguments(1) && invocation.ArgumentList.Arguments[0].Expression is { } literal && literal.IsKind(SyntaxKind.StringLiteralExpression) && ((LiteralExpressionSyntax)literal).Token.ValueText.Length == 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseCharOverloadOfStringMethodsCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Rules; [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UseCharOverloadOfStringMethodsCodeFix : SonarCodeFix { private const string Title = "Convert to char."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseCharOverloadOfStringMethods.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); if (root.FindNode(diagnostic.Location.SourceSpan) is { Parent: { Parent: InvocationExpressionSyntax invocation } } && invocation.ArgumentList.Arguments[0].Expression is LiteralExpressionSyntax node) { context.RegisterCodeFix( Title, c => Task.FromResult(context.Document.WithSyntaxRoot(root.ReplaceNode(node, Convert(node)))), context.Diagnostics); } return Task.CompletedTask; } private static LiteralExpressionSyntax Convert(LiteralExpressionSyntax node) => LiteralExpression(SyntaxKind.CharacterLiteralExpression, Literal(node.Token.ValueText[0])); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseConstantLoggingTemplate.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseConstantLoggingTemplate : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2629"; private const string MessageFormat = "{0}"; private const string OnUsingStringInterpolation = "Don't use string interpolation in logging message templates."; private const string OnUsingStringFormat = "Don't use String.Format in logging message templates."; private const string OnUsingStringConcatenation = "Don't use string concatenation in logging message templates."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ImmutableDictionary Messages = new Dictionary { {SyntaxKind.AddExpression, OnUsingStringConcatenation}, {SyntaxKind.InterpolatedStringExpression, OnUsingStringInterpolation}, {SyntaxKind.InvocationExpression, OnUsingStringFormat}, }.ToImmutableDictionary(); private static readonly ImmutableArray LoggerTypes = ImmutableArray.Create( KnownType.Castle_Core_Logging_ILogger, KnownType.log4net_ILog, KnownType.log4net_Util_ILogExtensions, KnownType.Microsoft_Extensions_Logging_LoggerExtensions, KnownType.NLog_ILogger, KnownType.NLog_ILoggerBase, KnownType.NLog_ILoggerExtensions, KnownType.Serilog_ILogger, KnownType.Serilog_Log); private static readonly ImmutableHashSet LoggerMethodNames = ImmutableHashSet.Create( "ConditionalDebug", "ConditionalTrace", "Debug", "DebugFormat", "Error", "ErrorFormat", "Fatal", "FatalFormat", "Info", "InfoFormat", "Information", "Log", "LogCritical", "LogDebug", "LogError", "LogFormat", "LogInformation", "LogTrace", "LogWarning", "Trace", "TraceFormat", "Verbose", "Warn", "WarnFormat", "Warning"); private static readonly ImmutableHashSet LogMessageParameterNames = ImmutableHashSet.Create( "format", "message", "messageTemplate"); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocation = (InvocationExpressionSyntax)c.Node; if (LoggerMethodNames.Contains(invocation.GetName()) && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol method && !IsLog4NetExceptionMethod(method) && LoggerTypes.Any(x => x.Matches(method.ContainingType)) && method.Parameters.FirstOrDefault(x => LogMessageParameterNames.Contains(x.Name)) is { } messageParameter && ArgumentValue(invocation, method, messageParameter) is { } argumentValue && InvalidSyntaxNode(argumentValue, c.Model) is { } invalidNode) { c.ReportIssue(Rule, invalidNode, Messages[invalidNode.Kind()]); } }, SyntaxKind.InvocationExpression); private static CSharpSyntaxNode ArgumentValue(InvocationExpressionSyntax invocation, IMethodSymbol method, IParameterSymbol parameter) { if (invocation.ArgumentList.Arguments.FirstOrDefault(x => x.NameColon?.GetName() == parameter.Name) is { } argument) { return argument.Expression; } else { var paramIndex = method.Parameters.IndexOf(parameter); return invocation.ArgumentList.Arguments[paramIndex].Expression; } } private static bool IsLog4NetExceptionMethod(IMethodSymbol method) => method.ContainingType.Is(KnownType.log4net_ILog) && method.Parameters.Any(x => x.Type.Is(KnownType.System_Exception)); private static SyntaxNode InvalidSyntaxNode(SyntaxNode messageArgument, SemanticModel model) => messageArgument.DescendantNodesAndSelf().FirstOrDefault(x => (x as InterpolatedStringExpressionSyntax is { } interpolatedString && !interpolatedString.HasConstantValue(model)) || (x is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AddExpression } concatenation && !AllMembersAreConstantStrings(concatenation, model)) || IsStringFormatInvocation(x, model)); private static bool AllMembersAreConstantStrings(BinaryExpressionSyntax addExpression, SemanticModel model) => IsConstantStringOrConcatenation(addExpression.Left, model) && IsConstantStringOrConcatenation(addExpression.Right, model); private static bool IsConstantStringOrConcatenation(SyntaxNode node, SemanticModel model) => node.Kind() == SyntaxKind.StringLiteralExpression || (node as InterpolatedStringExpressionSyntax is { } interpolatedString && interpolatedString.HasConstantValue(model)) || (node.Kind() == SyntaxKind.IdentifierName && model.GetSymbolInfo(node).Symbol is IFieldSymbol { HasConstantValue: true } or ILocalSymbol { HasConstantValue: true}) || (node is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AddExpression } concatenation && AllMembersAreConstantStrings(concatenation, model)); private static bool IsStringFormatInvocation(SyntaxNode node, SemanticModel model) => node is InvocationExpressionSyntax invocation && node.GetName() == "Format" && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol method && KnownType.System_String.Matches(method.ContainingType); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseConstantsWhereAppropriate.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseConstantsWhereAppropriate : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3962"; private const string MessageFormat = "Replace this 'static readonly' declaration with 'const'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray RelevantTypes = ImmutableArray.Create( KnownType.System_Boolean, KnownType.System_Byte, KnownType.System_SByte, KnownType.System_Char, KnownType.System_Decimal, KnownType.System_Double, KnownType.System_Single, KnownType.System_Int32, KnownType.System_UInt32, KnownType.System_Int64, KnownType.System_UInt64, KnownType.System_Int16, KnownType.System_UInt16, KnownType.System_String ); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; var firstVariableWithInitialization = fieldDeclaration.Declaration.Variables .FirstOrDefault(v => v.Initializer != null); if (firstVariableWithInitialization == null) { return; } var fieldSymbol = c.Model.GetDeclaredSymbol(firstVariableWithInitialization) as IFieldSymbol; if (!IsFieldRelevant(fieldSymbol)) { return; } var constValue = c.Model.GetConstantValue( firstVariableWithInitialization.Initializer.Value); if (!constValue.HasValue) { return; } c.ReportIssue(rule, firstVariableWithInitialization.Identifier); }, SyntaxKind.FieldDeclaration); } private static bool IsFieldRelevant(IFieldSymbol fieldSymbol) { return fieldSymbol != null && fieldSymbol.IsStatic && fieldSymbol.IsReadOnly && !fieldSymbol.IsPubliclyAccessible() && fieldSymbol.Type.IsAny(RelevantTypes); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseCurlyBraces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseCurlyBraces : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S121"; private const string MessageFormat = "Add curly braces around the nested statement(s) in this '{0}' block."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private sealed class CheckedKind { public SyntaxKind Kind { get; set; } public string Value { get; set; } public Func Validator { get; set; } public Func IssueReportLocation { get; set; } } private static readonly ImmutableList CheckedKinds = ImmutableList.Create( new CheckedKind { Kind = SyntaxKind.IfStatement, Value = "if", Validator = node => ((IfStatementSyntax)node).Statement.IsKind(SyntaxKind.Block), IssueReportLocation = node => ((IfStatementSyntax)node).IfKeyword.GetLocation() }, new CheckedKind { Kind = SyntaxKind.ElseClause, Value = "else", Validator = node => { var statement = ((ElseClauseSyntax)node).Statement; return statement.IsKind(SyntaxKind.IfStatement) || statement.IsKind(SyntaxKind.Block); }, IssueReportLocation = node => ((ElseClauseSyntax)node).ElseKeyword.GetLocation() }, new CheckedKind { Kind = SyntaxKind.ForStatement, Value = "for", Validator = node => ((ForStatementSyntax)node).Statement.IsKind(SyntaxKind.Block), IssueReportLocation = node => ((ForStatementSyntax)node).ForKeyword.GetLocation() }, new CheckedKind { Kind = SyntaxKind.ForEachStatement, Value = "foreach", Validator = node => ((ForEachStatementSyntax)node).Statement.IsKind(SyntaxKind.Block), IssueReportLocation = node => ((ForEachStatementSyntax)node).ForEachKeyword.GetLocation() }, new CheckedKind { Kind = SyntaxKind.DoStatement, Value = "do", Validator = node => ((DoStatementSyntax)node).Statement.IsKind(SyntaxKind.Block), IssueReportLocation = node => ((DoStatementSyntax)node).DoKeyword.GetLocation() }, new CheckedKind { Kind = SyntaxKind.WhileStatement, Value = "while", Validator = node => ((WhileStatementSyntax)node).Statement.IsKind(SyntaxKind.Block), IssueReportLocation = node => ((WhileStatementSyntax)node).WhileKeyword.GetLocation() }); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var checkedKind = CheckedKinds.Single(e => c.Node.IsKind(e.Kind)); if (!checkedKind.Validator(c.Node)) { c.ReportIssue(rule, checkedKind.IssueReportLocation(c.Node), checkedKind.Value); } }, CheckedKinds.Select(e => e.Kind).ToArray()); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseDateTimeOffsetInsteadOfDateTime.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseDateTimeOffsetInsteadOfDateTime : UseDateTimeOffsetInsteadOfDateTimeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override string[] ValidNames { get; } = new[] { "DateTime" }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseFindSystemTimeZoneById.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseFindSystemTimeZoneById : UseFindSystemTimeZoneByIdBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseGenericEventHandlerInstances.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseGenericEventHandlerInstances : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3908"; private const string MessageFormat = "Refactor this delegate to use 'System.EventHandler'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray allowedTypes = ImmutableArray.Create( KnownType.System_EventHandler, KnownType.System_EventHandler_TEventArgs ); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var eventField = (EventFieldDeclarationSyntax)c.Node; var eventFirstVariable = eventField.Declaration.Variables.FirstOrDefault(); if (eventFirstVariable != null) { AnalyzeEventType(c, eventFirstVariable, eventField.Declaration.Type.GetLocation); } }, SyntaxKind.EventFieldDeclaration); context.RegisterNodeAction( c => { var eventDeclaration = (EventDeclarationSyntax)c.Node; AnalyzeEventType(c, eventDeclaration, eventDeclaration.Type.GetLocation); }, SyntaxKind.EventDeclaration); } private static void AnalyzeEventType(SonarSyntaxNodeReportingContext analysisContext, SyntaxNode eventNode, Func getLocationToReportOn) { if (analysisContext.Model.GetDeclaredSymbol(eventNode) is IEventSymbol eventSymbol && !eventSymbol.IsOverride && eventSymbol.InterfaceMembers().IsEmpty() && (eventSymbol.Type as INamedTypeSymbol)?.ConstructedFrom.IsAny(allowedTypes) == false) { analysisContext.ReportIssue(rule, getLocationToReportOn()); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseGenericWithRefParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseGenericWithRefParameters : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S4047"; private const string MessageFormat = "Make this method generic and replace the 'object' parameter with a type parameter."; private const string SecondaryMessage = "Replace this parameter with a type parameter."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var methodDeclaration = (MethodDeclarationSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol is null || methodDeclaration.Identifier.IsMissing) { return; } var refObjectParameters = methodSymbol .GetParameters() .Where(IsRefObject) .ToList(); if (refObjectParameters.Count > 0) { var parameterLocations = refObjectParameters.Select(p => p.Locations.FirstOrDefault()?.ToSecondary(SecondaryMessage)).WhereNotNull(); c.ReportIssue(Rule, methodDeclaration.Identifier, parameterLocations); } }, SyntaxKind.MethodDeclaration); private static bool IsRefObject(IParameterSymbol parameter) => parameter.RefKind == RefKind.Ref && parameter.Type.Is(KnownType.System_Object); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseIFormatProviderForParsingDateAndTime.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseIFormatProviderForParsingDateAndTime : UseIFormatProviderForParsingDateAndTimeBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseIndexingInsteadOfLinqMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseIndexingInsteadOfLinqMethods : UseIndexingInsteadOfLinqMethodsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override int GetArgumentCount(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.Count; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseLambdaParameterInConcurrentDictionary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseLambdaParameterInConcurrentDictionary : UseLambdaParameterInConcurrentDictionaryBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SeparatedSyntaxList GetArguments(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments; protected override bool IsLambdaAndContainsIdentifier(ArgumentSyntax argument, string keyName) => argument.Expression switch { SimpleLambdaExpressionSyntax simpleLambda => !simpleLambda.Parameter.GetName().Equals(keyName) && IsContainingValidIdentifier(simpleLambda.Body, keyName), ParenthesizedLambdaExpressionSyntax parentesizedLambda => !parentesizedLambda.ParameterList.Parameters.Any(x => x.GetName().Equals(keyName)) && IsContainingValidIdentifier(parentesizedLambda.Body, keyName), AnonymousMethodExpressionSyntax anonymousMethod => !anonymousMethod.ParameterList.Parameters.Any(x => x.GetName().Equals(keyName)) && IsContainingValidIdentifier(anonymousMethod.Block, keyName), _ => false }; protected override bool TryGetKeyName(ArgumentSyntax argument, out string keyName) { keyName = string.Empty; if (argument.Expression is IdentifierNameSyntax identifier) { keyName = identifier.GetName(); return true; } return false; } private bool IsContainingValidIdentifier(SyntaxNode node, string keyName) => node.DescendantNodesAndSelf().OfType().Any(p => p.GetName().Equals(keyName) && !IsContainedInNameOfInvocation(p)); private bool IsContainedInNameOfInvocation(IdentifierNameSyntax identifier) => identifier.Parent is ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax { Expression: IdentifierNameSyntax expression } } } && expression.NameIs("nameof"); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseNumericLiteralSeparator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseNumericLiteralSeparator : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2148"; private const string MessageFormat = "Add underscores to this numeric value for readability."; private const int BinaryMinimumDigits = 9 + 2; // +2 for 0b private const int HexadecimalMinimumDigits = 9 + 2; // +2 for 0x private const int DecimalMinimumDigits = 6; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { if (!c.Compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp7)) { return; } var numericLiteral = (LiteralExpressionSyntax)c.Node; if (numericLiteral.Token.Text.IndexOf('_') < 0 && ShouldHaveSeparator(numericLiteral)) { c.ReportIssue(rule, numericLiteral); } }, SyntaxKind.NumericLiteralExpression); } private static bool ShouldHaveSeparator(LiteralExpressionSyntax numericLiteral) { if (numericLiteral.Token.Text.StartsWith("0x")) { return numericLiteral.Token.Text.Length > HexadecimalMinimumDigits; } if (numericLiteral.Token.Text.StartsWith("0b")) { return numericLiteral.Token.Text.Length > BinaryMinimumDigits; } var indexOfDot = numericLiteral.Token.Text.IndexOf('.'); var beforeDotPartLength = indexOfDot < 0 ? numericLiteral.Token.Text.Length : numericLiteral.Token.Text.Remove(indexOfDot).Length; return beforeDotPartLength > DecimalMinimumDigits; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseParamsForVariableArguments.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseParamsForVariableArguments : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4061"; private const string MessageFormat = "Use the 'params' keyword instead of '__arglist'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { if (c.Node.ParameterList() is { Parameters: { Count: > 0 and var count } parameters } && parameters[count - 1].Identifier.IsKind(SyntaxKind.ArgListKeyword) && CheckModifiers(c.Node) && c.Node.GetIdentifier() is { IsMissing: false } identifier && MethodSymbol(c.Node, c.Model) is { } methodSymbol && !methodSymbol.IsOverride && methodSymbol.IsPubliclyAccessible() && methodSymbol.InterfaceMembers().IsEmpty()) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); private static IMethodSymbol MethodSymbol(SyntaxNode node, SemanticModel semanticModel) => node is TypeDeclarationSyntax type ? type.PrimaryConstructor(semanticModel) : semanticModel.GetDeclaredSymbol(node) as IMethodSymbol; private static bool CheckModifiers(SyntaxNode node) => node is not BaseMethodDeclarationSyntax method || !method.Modifiers.Any(SyntaxKind.ExternKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseShortCircuitingOperator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseShortCircuitingOperator : UseShortCircuitingOperatorBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override string GetSuggestedOpName(BinaryExpressionSyntax node) => OperatorNames[ShortCircuitingAlternative[node.Kind()]]; protected override string GetCurrentOpName(BinaryExpressionSyntax node) => OperatorNames[node.Kind()]; protected override SyntaxToken GetOperator(BinaryExpressionSyntax expression) => expression.OperatorToken; internal static readonly IDictionary ShortCircuitingAlternative = new Dictionary { { SyntaxKind.BitwiseAndExpression, SyntaxKind.LogicalAndExpression }, { SyntaxKind.BitwiseOrExpression, SyntaxKind.LogicalOrExpression } }.ToImmutableDictionary(); private static readonly IDictionary OperatorNames = new Dictionary { { SyntaxKind.BitwiseAndExpression, "&" }, { SyntaxKind.BitwiseOrExpression, "|" }, { SyntaxKind.LogicalAndExpression, "&&" }, { SyntaxKind.LogicalOrExpression, "||" }, }.ToImmutableDictionary(); protected override ImmutableArray SyntaxKindsOfInterest => ImmutableArray.Create( SyntaxKind.BitwiseAndExpression, SyntaxKind.BitwiseOrExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseShortCircuitingOperatorCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public class UseShortCircuitingOperatorCodeFix : UseShortCircuitingOperatorCodeFixBase { internal override bool IsCandidateExpression(BinaryExpressionSyntax expression) { return UseShortCircuitingOperator.ShortCircuitingAlternative.ContainsKey(expression.Kind()); } protected override BinaryExpressionSyntax GetShortCircuitingExpressionNode(BinaryExpressionSyntax expression) { var alternativeKind = expression.IsKind(SyntaxKind.BitwiseAndExpression) ? SyntaxKind.LogicalAndExpression : SyntaxKind.LogicalOrExpression; return SyntaxFactory.BinaryExpression(alternativeKind, expression.Left, expression.Right); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseStringCreate.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseStringCreate : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6618"; private const string MessageFormat = """Use "string.Create" instead of "FormattableString"."""; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private ImmutableArray methodNames = ImmutableArray.Create( "CurrentCulture", "Invariant"); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { // The string.Create method with IFormatProvider parameter is only available from .NET 6.0 if (!c.Compilation.IsMemberAvailable( KnownType.System_String, "Create", method => method.Parameters.Any(x => KnownType.System_IFormatProvider.Matches(x.Type)))) { return; } c.RegisterNodeAction(c => { var node = (InvocationExpressionSyntax)c.Node; if (methodNames.Any(x => NameIsEqual(node, x)) && node.Operands().Left is { } left && NameIsEqual(left, nameof(FormattableString)) && node.HasExactlyNArguments(1) && node.ArgumentList.Arguments[0].Expression is InterpolatedStringExpressionSyntax && c.Model.GetTypeInfo(left).Type.Is(KnownType.System_FormattableString)) { c.ReportIssue(Rule, node.GetIdentifier()?.GetLocation()); } }, SyntaxKind.InvocationExpression); }); private static bool NameIsEqual(SyntaxNode node, string name) => node.GetName().Equals(name, StringComparison.Ordinal); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseStringIsNullOrEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseStringIsNullOrEmpty : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3256"; private const string MessageFormat = "Use 'string.IsNullOrEmpty()' instead of comparing to empty string."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private const string EqualsName = nameof(string.Equals); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocationExpression = (InvocationExpressionSyntax)c.Node; if (invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name.Identifier.ValueText == EqualsName && invocationExpression.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && memberAccessExpression.IsMemberAccessOnKnownType(EqualsName, KnownType.System_String, c.Model)) { // x.Equals(value), where x is string.Empty, "" or const "", and value is some string if (IsStringIdentifier(firstArgument.Expression, c.Model) && IsConstantEmptyString(memberAccessExpression.Expression, c.Model)) { c.ReportIssue(rule, invocationExpression, MessageFormat); return; } // value.Equals(x), where x is string.Empty, "" or const "", and value is some string if (IsStringIdentifier(memberAccessExpression.Expression, c.Model) && IsConstantEmptyString(firstArgument.Expression, c.Model)) { c.ReportIssue(rule, invocationExpression, MessageFormat); } } }, SyntaxKind.InvocationExpression); } private static bool IsStringIdentifier(ExpressionSyntax expression, SemanticModel semanticModel) { if (!(expression is IdentifierNameSyntax identifierNameExpression)) { return false; } var expressionType = semanticModel.GetTypeInfo(identifierNameExpression).Type; return expressionType != null && expressionType.Is(KnownType.System_String); } private static bool IsConstantEmptyString(ExpressionSyntax expression, SemanticModel semanticModel) => IsStringEmptyLiteral(expression) || IsStringEmptyConst(expression, semanticModel) || expression.IsStringEmpty(semanticModel); private static bool IsStringEmptyConst(ExpressionSyntax expression, SemanticModel semanticModel) { var constValue = semanticModel.GetConstantValue(expression); return constValue.HasValue && constValue.Value is string stringConstValue && stringConstValue == string.Empty; } private static bool IsStringEmptyLiteral(ExpressionSyntax expression) { var literalExpression = expression as LiteralExpressionSyntax; return literalExpression?.Token.ValueText == string.Empty; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseTestableTimeProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseTestableTimeProvider : UseTestableTimeProviderBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool Ignore(SyntaxNode ancestor, SemanticModel semanticModel) => ancestor is XmlCrefAttributeSyntax || (ancestor is InvocationExpressionSyntax invocation && invocation.IsNameof(semanticModel)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseTrueForAll.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class UseTrueForAll : UseTrueForAllBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseUnixEpoch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseUnixEpoch : UseUnixEpochBase { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override bool IsDateTimeKindUtc(MemberAccessExpressionSyntax memberAccess) => memberAccess.NameIs("Utc") && memberAccess.Expression.NameIs("DateTimeKind"); protected override bool IsGregorianCalendar(SyntaxNode node) => node is ObjectCreationExpressionSyntax objectCreation && objectCreation.Type.NameIs("GregorianCalendar"); protected override bool IsZeroTimeOffset(SyntaxNode node) => node switch { MemberAccessExpressionSyntax memberAccess => memberAccess.NameIs("Zero") && memberAccess.Expression.NameIs("TimeSpan"), ObjectCreationExpressionSyntax objectCreation => objectCreation.Type.NameIs("TimeSpan") && objectCreation?.ArgumentList != null && objectCreation.ArgumentList.Arguments.Count is 1 && objectCreation.ArgumentList.Arguments[0].Expression is LiteralExpressionSyntax literal && IsValueEqualTo(literal, 0), _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseUnixEpochCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class UseUnixEpochCodeFix : UseUnixEpochCodeFixBase { protected override SyntaxNode ReplaceConstructorWithField(SyntaxNode root, SyntaxNode node, SonarCodeFixContext context) { ExpressionSyntax typeNode; if (node.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression)) { var semanticModel = context.Document.GetSemanticModelAsync(context.Cancel).ConfigureAwait(false).GetAwaiter().GetResult(); typeNode = SyntaxFactory.IdentifierName(semanticModel.GetTypeInfo(node).Type.Name); } else { typeNode = ((ObjectCreationExpressionSyntax)node).Type; } var leadingTrivia = node.GetLeadingTrivia(); var trailingTrivia = node.GetTrailingTrivia(); return root.ReplaceNode(node, SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, typeNode, SyntaxFactory.IdentifierName("UnixEpoch")).WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseUriInsteadOfString.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseUriInsteadOfString : SonarDiagnosticAnalyzer { private const string DiagnosticIdRuleS3994 = "S3994"; private const string DiagnosticIdRuleS3995 = "S3995"; private const string DiagnosticIdRuleS3996 = "S3996"; private const string DiagnosticIdRuleS3997 = "S3997"; private const string DiagnosticIdRuleS4005 = "S4005"; private const string MessageFormatRuleS3994 = "Either change this parameter type to 'System.Uri' or provide an overload which takes a 'System.Uri' parameter."; private const string MessageFormatRuleS3995 = "Change this return type to 'System.Uri'."; private const string MessageFormatRuleS3996 = "Change the '{0}' property type to 'System.Uri'."; private const string MessageFormatRuleS3997 = "Refactor this method so it invokes the overload accepting a 'System.Uri' parameter."; private const string MessageFormatRuleS4005 = "Call the overload that takes a 'System.Uri' as an argument instead."; private static readonly DiagnosticDescriptor RuleS3994 = DescriptorFactory.Create(DiagnosticIdRuleS3994, MessageFormatRuleS3994); private static readonly DiagnosticDescriptor RuleS3995 = DescriptorFactory.Create(DiagnosticIdRuleS3995, MessageFormatRuleS3995); private static readonly DiagnosticDescriptor RuleS3996 = DescriptorFactory.Create(DiagnosticIdRuleS3996, MessageFormatRuleS3996); private static readonly DiagnosticDescriptor RuleS3997 = DescriptorFactory.Create(DiagnosticIdRuleS3997, MessageFormatRuleS3997); private static readonly DiagnosticDescriptor RuleS4005 = DescriptorFactory.Create(DiagnosticIdRuleS4005, MessageFormatRuleS4005); private static readonly ISet UrlNameVariants = new HashSet { "URI", "URL", "URN" }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(RuleS3994, RuleS3995, RuleS3996, RuleS3997, RuleS4005); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(VerifyMethodDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction(VerifyPropertyDeclaration, SyntaxKind.PropertyDeclaration); context.RegisterNodeAction( VerifyInvocationAndCreation, SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); context.RegisterNodeAction( VerifyRecordDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration); } private static void VerifyMethodDeclaration(SonarSyntaxNodeReportingContext context) { var methodDeclaration = (BaseMethodDeclarationSyntax)context.Node; var methodSymbol = context.Model.GetDeclaredSymbol(methodDeclaration); if (methodSymbol is null || methodSymbol.IsOverride) { return; } VerifyReturnType(context, methodDeclaration, methodSymbol); var stringUrlParams = StringUrlParamIndexes(methodSymbol); if (!stringUrlParams.Any()) { return; } var methodOverloads = FindOverloadsThatUseUriTypeInPlaceOfString(methodSymbol, stringUrlParams).ToHashSet(); if (methodOverloads.Any()) { if (!methodDeclaration.IsKind(SyntaxKind.ConstructorDeclaration) && !methodDeclaration.ContainsMethodInvocation(context.Model, x => true, x => methodOverloads.Contains(x))) { context.ReportIssue(RuleS3997, methodDeclaration.FindIdentifierLocation()); } } else { foreach (var paramIdx in stringUrlParams) { context.ReportIssue(RuleS3994, methodDeclaration.ParameterList.Parameters[paramIdx].Type); } } } private static void VerifyPropertyDeclaration(SonarSyntaxNodeReportingContext context) { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; var propertySymbol = context.Model.GetDeclaredSymbol(propertyDeclaration); if (propertySymbol.Type.Is(KnownType.System_String) && !propertySymbol.IsOverride && NameContainsUri(propertySymbol.Name)) { context.ReportIssue(RuleS3996, propertyDeclaration.Type, propertyDeclaration.GetName()); } } private static void VerifyRecordDeclaration(SonarSyntaxNodeReportingContext context) { var declaration = (RecordDeclarationSyntaxWrapper)context.Node; if (!context.IsRedundantPositionalRecordContext() && StringUriParams(declaration.ParameterList, context.Model) is { } stringUriParams) { foreach (var param in stringUriParams) { context.ReportIssue(RuleS3996, param, param.GetName()); } } } private static IEnumerable StringUriParams(BaseParameterListSyntax parameterList, SemanticModel model) => parameterList?.Parameters.Where(x => NameContainsUri(x.Identifier.Text) && model.GetDeclaredSymbol(x).IsType(KnownType.System_String)); private static void VerifyInvocationAndCreation(SonarSyntaxNodeReportingContext context) { if (context.Model.GetSymbolInfo(context.Node).Symbol is IMethodSymbol invokedMethodSymbol && !invokedMethodSymbol.IsInType(KnownType.System_Uri) && StringUrlParamIndexes(invokedMethodSymbol) is { Count: not 0 } stringUrlParams && FindOverloadsThatUseUriTypeInPlaceOfString(invokedMethodSymbol, stringUrlParams).Any()) { context.ReportIssue(RuleS4005, context.Node); } } private static void VerifyReturnType(SonarSyntaxNodeReportingContext context, BaseMethodDeclarationSyntax methodDeclaration, IMethodSymbol methodSymbol) { if ((methodDeclaration as MethodDeclarationSyntax)?.ReturnType?.GetLocation() is { } returnTypeLocation && methodSymbol.ReturnType.Is(KnownType.System_String) && NameContainsUri(methodSymbol.Name)) { context.ReportIssue(RuleS3995, returnTypeLocation); } } private static IEnumerable FindOverloadsThatUseUriTypeInPlaceOfString(IMethodSymbol originalMethodSymbol, ISet paramIdx) { if (paramIdx.Any()) { foreach (var methodSymbol in OtherMethodOverrides(originalMethodSymbol)) { if (methodSymbol.Parameters.Where((x, index) => UsesUriInPlaceOfStringUri(x, originalMethodSymbol.Parameters[index], paramIdx.Contains(index))).Any()) { yield return methodSymbol; } } } } private static ISet StringUrlParamIndexes(IMethodSymbol methodSymbol) { var ret = new HashSet(); for (var i = 0; i < methodSymbol.Parameters.Length; i++) { var parameter = methodSymbol.Parameters[i]; if (parameter.Type.Is(KnownType.System_String) && NameContainsUri(parameter.Name)) { ret.Add(i); } } return ret; } private static IEnumerable OtherMethodOverrides(IMethodSymbol methodSymbol) => methodSymbol.ContainingType .GetMembers(methodSymbol.Name) .OfType() .Where(x => x.Parameters.Length == methodSymbol.Parameters.Length && !x.Equals(methodSymbol)); private static bool UsesUriInPlaceOfStringUri(IParameterSymbol paramSymbol, IParameterSymbol originalParamSymbol, bool isStringUri) => isStringUri ? paramSymbol.Type.Is(KnownType.System_Uri) : Equals(paramSymbol, originalParamSymbol); private static bool NameContainsUri(string name) { var wordsInName = name.SplitCamelCaseToWords(); return UrlNameVariants.Overlaps(wordsInName); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseValueParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseValueParameter : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3237"; private const string MessageFormat = "Use the 'value' contextual keyword in this {0} accessor declaration."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var accessor = (AccessorDeclarationSyntax)c.Node; if ((accessor.Body == null && accessor.ExpressionBody == null) || OnlyThrows(accessor) || accessor.DescendantNodes().OfType().Any(x => IsAccessorValue(x, c.Model))) { return; } var interfaceMember = c.Model.GetDeclaredSymbol(accessor).InterfaceMembers(); if (interfaceMember.Any() && accessor.Body?.Statements.Count == 0) // No need to check ExpressionBody, it can't be empty { return; } c.ReportIssue(Rule, accessor.Keyword, GetAccessorType(accessor)); }, SyntaxKind.SetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.AddAccessorDeclaration); private static bool OnlyThrows(AccessorDeclarationSyntax accessor) => (accessor.Body?.Statements.Count == 1 && accessor.Body.Statements[0] is ThrowStatementSyntax) || ThrowExpressionSyntaxWrapper.IsInstance(accessor.ExpressionBody?.Expression); private static bool IsAccessorValue(IdentifierNameSyntax identifier, SemanticModel semanticModel) { if (identifier.Identifier.ValueText != "value") { return false; } return semanticModel.GetSymbolInfo(identifier).Symbol is IParameterSymbol { IsImplicitlyDeclared: true }; } private static string GetAccessorType(AccessorDeclarationSyntax accessorDeclaration) => accessorDeclaration.Parent.Parent switch { IndexerDeclarationSyntax _ => "indexer set", PropertyDeclarationSyntax _ => GetPropertyAccessorKind(accessorDeclaration), EventDeclarationSyntax _ => "event", _ => null }; private static string GetPropertyAccessorKind(AccessorDeclarationSyntax accessorDeclaration) => accessorDeclaration.IsKind(SyntaxKind.SetAccessorDeclaration) ? "property set" : "property init"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseWhereBeforeOrderBy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseWhereBeforeOrderBy : UseWhereBeforeOrderByBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/UseWhileLoopInstead.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseWhileLoopInstead : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1264"; private const string MessageFormat = "Replace this 'for' loop with a 'while' loop."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var forStatement = (ForStatementSyntax)c.Node; if (forStatement.Declaration is null && forStatement.Incrementors.Count == 0) { c.ReportIssue(rule, forStatement.ForKeyword); } }, SyntaxKind.ForStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/AnalysisWarningAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AnalysisWarningAnalyzer : AnalysisWarningAnalyzerBase { } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/CopyPasteTokenAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class CopyPasteTokenAnalyzer : CopyPasteTokenAnalyzerBase { private static readonly HashSet StringKinds = [ SyntaxKind.StringLiteralToken, SyntaxKind.InterpolatedStringTextToken, SyntaxKindEx.SingleLineRawStringLiteralToken, SyntaxKindEx.MultiLineRawStringLiteralToken, SyntaxKindEx.Utf8StringLiteralToken, SyntaxKindEx.Utf8SingleLineRawStringLiteralToken, SyntaxKindEx.Utf8MultiLineRawStringLiteralToken ]; protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override bool IsUsingDirective(SyntaxNode node) => node is UsingDirectiveSyntax; protected override string GetCpdValue(SyntaxToken token) { if (token.IsKind(SyntaxKind.NumericLiteralToken)) { return "$num"; } else if (token.IsAnyKind(StringKinds)) { return "$str"; } else if (token.IsKind(SyntaxKind.CharacterLiteralToken)) { return "$char"; } else { return token.Text; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/FileMetadataAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class FileMetadataAnalyzer : FileMetadataAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/LogAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LogAnalyzer : LogAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override string LanguageVersion(Compilation compilation) => ((CSharpCompilation)compilation).LanguageVersion.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/MetricsAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; using SonarAnalyzer.CSharp.Metrics; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class MetricsAnalyzer : MetricsAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override MetricsBase GetMetrics(SyntaxTree syntaxTree, SemanticModel semanticModel) => new CSharpMetrics(syntaxTree, semanticModel); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/SymbolReferenceAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class SymbolReferenceAnalyzer : SymbolReferenceAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override SyntaxNode GetBindableParent(SyntaxToken token) => token.GetBindableParent(); protected override ReferenceInfo[] CreateDeclarationReferenceInfo(SyntaxNode node, SemanticModel model) => node switch { BaseTypeDeclarationSyntax typeDeclaration => [CreateDeclarationReferenceInfo(node, typeDeclaration.Identifier, model)], VariableDeclarationSyntax variableDeclaration => CreateDeclarationReferenceInfo(variableDeclaration, model), MethodDeclarationSyntax methodDeclaration => [CreateDeclarationReferenceInfo(node, methodDeclaration.Identifier, model)], ParameterSyntax parameterSyntax => [CreateDeclarationReferenceInfo(node, parameterSyntax.Identifier, model)], LocalDeclarationStatementSyntax localDeclarationStatement => CreateDeclarationReferenceInfo(localDeclarationStatement.Declaration, model), PropertyDeclarationSyntax propertyDeclaration => [CreateDeclarationReferenceInfo(node, propertyDeclaration.Identifier, model)], TypeParameterSyntax typeParameterSyntax => [CreateDeclarationReferenceInfo(node, typeParameterSyntax.Identifier, model)], var localFunction when LocalFunctionStatementSyntaxWrapper.IsInstance(localFunction) => [CreateDeclarationReferenceInfo(node, ((LocalFunctionStatementSyntaxWrapper)localFunction).Identifier, model)], var singleVariableDesignation when SingleVariableDesignationSyntaxWrapper.IsInstance(singleVariableDesignation) => [CreateDeclarationReferenceInfo(node, ((SingleVariableDesignationSyntaxWrapper)singleVariableDesignation).Identifier, model)], _ => null }; protected override IList GetDeclarations(SyntaxNode node) { var walker = new DeclarationsFinder(); walker.SafeVisit(node); return walker.Declarations; } private static ReferenceInfo[] CreateDeclarationReferenceInfo(VariableDeclarationSyntax declaration, SemanticModel model) => declaration.Variables.Select(x => CreateDeclarationReferenceInfo(x, x.Identifier, model)).ToArray(); private static ReferenceInfo CreateDeclarationReferenceInfo(SyntaxNode node, SyntaxToken identifier, SemanticModel model) => new(node, identifier, model.GetDeclaredSymbol(node), true); private sealed class DeclarationsFinder : SafeCSharpSyntaxWalker { public readonly List Declarations = []; private readonly ISet declarationKinds = new HashSet { SyntaxKind.ClassDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.EventDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.LocalDeclarationStatement, SyntaxKind.MethodDeclaration, SyntaxKind.Parameter, SyntaxKind.PropertyDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.TypeParameter, SyntaxKind.VariableDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, SyntaxKindEx.SingleVariableDesignation }.Cast().ToHashSet(); public override void Visit(SyntaxNode node) { if (declarationKinds.Contains((ushort)node.RawKind)) { Declarations.Add(node); } base.Visit(node); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/TelemetryAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TelemetryAnalyzer : TelemetryAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override string LanguageVersion(Compilation compilation) => ((CSharpCompilation)compilation).LanguageVersion.ToString(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/TestMethodDeclarationsAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TestMethodDeclarationsAnalyzer : TestMethodDeclarationsAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override IEnumerable GetMethodDeclarations(SyntaxNode node) => node.DescendantNodes().OfType(); protected override IEnumerable GetTypeDeclarations(SyntaxNode node) => node.DescendantNodes().OfType(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/Utilities/TokenTypeAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Roslyn.Utilities; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TokenTypeAnalyzer : TokenTypeAnalyzerBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override TokenClassifierBase GetTokenClassifier(SemanticModel semanticModel, bool skipIdentifierTokens) => new TokenClassifier(semanticModel, skipIdentifierTokens); protected override TriviaClassifierBase GetTriviaClassifier() => new TriviaClassifier(); internal sealed class TokenClassifier : TokenClassifierBase { private static readonly SyntaxKind[] StringLiteralTokens = { SyntaxKind.StringLiteralToken, SyntaxKind.CharacterLiteralToken, SyntaxKindEx.SingleLineRawStringLiteralToken, SyntaxKindEx.MultiLineRawStringLiteralToken, SyntaxKindEx.Utf8StringLiteralToken, SyntaxKindEx.Utf8SingleLineRawStringLiteralToken, SyntaxKindEx.Utf8MultiLineRawStringLiteralToken, SyntaxKind.InterpolatedStringStartToken, SyntaxKind.InterpolatedVerbatimStringStartToken, SyntaxKindEx.InterpolatedSingleLineRawStringStartToken, SyntaxKindEx.InterpolatedMultiLineRawStringStartToken, SyntaxKind.InterpolatedStringTextToken, SyntaxKind.InterpolatedStringEndToken, SyntaxKindEx.InterpolatedRawStringEndToken, }; public TokenClassifier(SemanticModel semanticModel, bool skipIdentifiers) : base(semanticModel, skipIdentifiers) { } protected override SyntaxNode GetBindableParent(SyntaxToken token) => token.GetBindableParent(); protected override bool IsIdentifier(SyntaxToken token) => token.IsKind(SyntaxKind.IdentifierToken); protected override bool IsKeyword(SyntaxToken token) => SyntaxFacts.IsKeywordKind(token.Kind()); protected override bool IsNumericLiteral(SyntaxToken token) => token.IsKind(SyntaxKind.NumericLiteralToken); protected override bool IsStringLiteral(SyntaxToken token) => token.IsAnyKind(StringLiteralTokens); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] protected override TokenTypeInfo.Types.TokenInfo ClassifyIdentifier(SyntaxToken token) => // Based on in SonarAnalyzer.CFG/ShimLayer\Syntax.xml // order by https://docs.google.com/spreadsheets/d/1hb6Oz8NE1y4kfv57npSrGEzMd7tm9gYQtI1dABOneMk token.Parent switch { SimpleNameSyntax x when token == x.Identifier && ClassifySimpleName(x) is { } tokenType => TokenInfo(token, tokenType), VariableDeclaratorSyntax x when token == x.Identifier => null, ParameterSyntax x when token == x.Identifier => null, MethodDeclarationSyntax x when token == x.Identifier => null, PropertyDeclarationSyntax x when token == x.Identifier => null, TypeParameterSyntax x when token == x.Identifier => TokenInfo(token, TokenType.TypeName), BaseTypeDeclarationSyntax x when token == x.Identifier => TokenInfo(token, TokenType.TypeName), ConstructorDeclarationSyntax x when token == x.Identifier => TokenInfo(token, TokenType.TypeName), FromClauseSyntax x when token == x.Identifier => null, LetClauseSyntax x when token == x.Identifier => null, JoinClauseSyntax x when token == x.Identifier => null, JoinIntoClauseSyntax x when token == x.Identifier => null, QueryContinuationSyntax x when token == x.Identifier => null, LabeledStatementSyntax x when token == x.Identifier => null, ForEachStatementSyntax x when token == x.Identifier => null, CatchDeclarationSyntax x when token == x.Identifier => null, ExternAliasDirectiveSyntax x when token == x.Identifier => null, EnumMemberDeclarationSyntax x when token == x.Identifier => null, EventDeclarationSyntax x when token == x.Identifier => null, AccessorDeclarationSyntax x when token == x.Keyword => null, DelegateDeclarationSyntax x when token == x.Identifier => TokenInfo(token, TokenType.TypeName), DestructorDeclarationSyntax x when token == x.Identifier => TokenInfo(token, TokenType.TypeName), AttributeTargetSpecifierSyntax x when token == x.Identifier => TokenInfo(token, TokenType.Keyword), // for unknown target specifier [unknown: Obsolete] // Wrapper checks. HotPath: Make sure to test for SyntaxKind to avoid Wrapper.IsInstance calls // which are slow and allocating. Check the documentation for associated SyntaxKinds and that the // node class is sealed. { RawKind: (int)SyntaxKindEx.FunctionPointerUnmanagedCallingConvention } x when token == ((FunctionPointerUnmanagedCallingConventionSyntaxWrapper)x).Name => null, { RawKind: (int)SyntaxKindEx.TupleElement } x when token == ((TupleElementSyntaxWrapper)x).Identifier => null, { RawKind: (int)SyntaxKindEx.LocalFunctionStatement } x when token == ((LocalFunctionStatementSyntaxWrapper)x).Identifier => null, { RawKind: (int)SyntaxKindEx.SingleVariableDesignation } x when token == ((SingleVariableDesignationSyntaxWrapper)x).Identifier => null, _ => base.ClassifyIdentifier(token), }; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifySimpleName(SimpleNameSyntax x) => IsInTypeContext(x) ? ClassifySimpleNameType(x) : ClassifySimpleNameExpression(x); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifySimpleNameExpression(SimpleNameSyntax name) => name.Parent is MemberAccessExpressionSyntax ? ClassifyMemberAccess(name) : ClassifySimpleNameExpressionSpecialContext(name, name); /// /// The is likely not referring a type, but there are some and /// special cases where it still might bind to a type or is treated as a keyword. The /// is the member access of the . e.g. for A.B.C may /// refer to "B" and would be the parent member access expression A.B and recursively A.B.C. /// [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifySimpleNameExpressionSpecialContext(SyntaxNode context, SimpleNameSyntax name) => context.Parent switch { // some identifier can be bound to a type or a constant: CaseSwitchLabelSyntax => ClassifyIdentifierByModel(name), // case i: { } parent when NameIsRightOfIsExpression(name, parent) => ClassifyIdentifierByModel(name), // is i { RawKind: (int)SyntaxKindEx.ConstantPattern } => ClassifyIdentifierByModel(name), // is { X: i } // nameof(i) can be bound to a type or a member ArgumentSyntax x when IsNameOf(x) => IsValueParameterOfSetter(name) ? TokenType.Keyword : ClassifyIdentifierByModel(name), // walk up memberaccess to detect cases like above MemberAccessExpressionSyntax x => ClassifySimpleNameExpressionSpecialContext(x, name), _ => ClassifySimpleNameExpressionSpecialNames(name) }; private bool IsNameOf(ArgumentSyntax argument) => argument is { Parent: ArgumentListSyntax { Arguments.Count: 1, Parent: InvocationExpressionSyntax { Expression: IdentifierNameSyntax { Identifier.Text: "nameof" } } } }; private bool NameIsRightOfIsExpression(NameSyntax name, SyntaxNode binary) => binary is BinaryExpressionSyntax { RawKind: (int)SyntaxKind.IsExpression, Right: { } x } && x == name; /// /// Some expression identifier are classified differently, like "value" in a setter. /// private TokenType ClassifySimpleNameExpressionSpecialNames(SimpleNameSyntax name) => // "value" in a setter is a classified as keyword IsValueParameterOfSetter(name) ? TokenType.Keyword : TokenType.UnknownTokentype; private bool IsValueParameterOfSetter(SimpleNameSyntax simpleName) => simpleName is IdentifierNameSyntax { Identifier.Text: "value" } && IsLeftMostMemberAccess(simpleName) && SemanticModel.GetSymbolInfo(simpleName).Symbol is IParameterSymbol { ContainingSymbol: IMethodSymbol { MethodKind: MethodKind.PropertySet or MethodKind.EventAdd or MethodKind.EventRemove } }; private static bool IsLeftMostMemberAccess(SimpleNameSyntax simpleName) => simpleName is { Parent: not MemberAccessExpressionSyntax } || (simpleName is { Parent: MemberAccessExpressionSyntax { Expression: { } expression } } && expression == simpleName); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifyMemberAccess(SimpleNameSyntax name) => name switch { { Parent: MemberAccessExpressionSyntax // Most right hand side of a member access? { Parent: not MemberAccessExpressionSyntax, // Topmost in a memberaccess tree Name: { } parentName // Right hand side } parent } when parentName == name => ClassifySimpleNameExpressionSpecialContext(parent, name), _ when IsValueParameterOfSetter(name) => TokenType.Keyword, // 'name' can not be a nested type, if there is an expression to the left of the member access, // that can not bind to a type. The only things that can bind to a type are SimpleNames (Identifier or GenericName) // or pre-defined types. None of the pre-defined types have a nested type, so we can exclude these as well. { Parent: MemberAccessExpressionSyntax x } when AnyMemberAccessLeftIsNotAType(x) => TokenType.UnknownTokentype, // The left side of a pointer member access must be a pointer and can not be a type { Parent: MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.PointerMemberAccessExpression } } => TokenType.UnknownTokentype, _ => ClassifyIdentifierByModel(name), }; private static bool AnyMemberAccessLeftIsNotAType(MemberAccessExpressionSyntax memberAccess) => memberAccess switch { { Expression: not SimpleNameSyntax and not MemberAccessExpressionSyntax and not AliasQualifiedNameSyntax } => true, { Expression: MemberAccessExpressionSyntax left } => AnyMemberAccessLeftIsNotAType(left), // Heuristic: any MemberAccess that starts with a lowercase on the most left hand side, is assumed to start // as an expression (e.g. s.Length). Rational: It is (almost) granted that Types (including enums) start // with an uppercase in C#. Any identifier, that starts with a lower case is assumed to refer a local, a parameter, // or a field. { Expression: SimpleNameSyntax { Identifier.ValueText: { Length: >= 1 } mostLeftIdentifier } } => char.IsLower(mostLeftIdentifier[0]), _ => false, }; private TokenType ClassifyIdentifierByModel(SimpleNameSyntax name) => SemanticModel.GetSymbolInfo(name).Symbol is INamedTypeSymbol or ITypeParameterSymbol ? TokenType.TypeName : TokenType.UnknownTokentype; private TokenType ClassifyAliasDeclarationByModel(UsingDirectiveSyntax usingDirective) => SemanticModel.GetDeclaredSymbol(usingDirective) is { Target: INamedTypeSymbol } ? TokenType.TypeName : TokenType.UnknownTokentype; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifySimpleNameType(SimpleNameSyntax name) => name is GenericNameSyntax ? TokenType.TypeName : ClassifySimpleNameTypeSpecialContext(name, name); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType? ClassifySimpleNameTypeSpecialContext(SyntaxNode context, SimpleNameSyntax name) => context.Parent switch { // namespace X; or namespace X { } -> always unknown NamespaceDeclarationSyntax or { RawKind: (int)SyntaxKindEx.FileScopedNamespaceDeclaration } => TokenType.UnknownTokentype, // using System; -> normal using UsingDirectiveSyntax { Alias: null, StaticKeyword.RawKind: (int)SyntaxKind.None } => TokenType.UnknownTokentype, // using Alias = System; -> "System" can be a type or a namespace UsingDirectiveSyntax { Alias: not null } => ClassifyIdentifierByModel(name), // using Alias = System; -> "Alias" can be a type or a namespace NameEqualsSyntax { Parent: UsingDirectiveSyntax { Alias.Name: { } aliasName } usingDirective } when aliasName == name => ClassifyAliasDeclarationByModel(usingDirective), // using static System.Math; -> most right hand side must be a type UsingDirectiveSyntax { StaticKeyword.RawKind: (int)SyntaxKind.StaticKeyword, Name: QualifiedNameSyntax { Right: SimpleNameSyntax x } } => x == name ? TokenType.TypeName : ClassifyIdentifierByModel(name), // Walk up classified names (to detect namespace and using context) QualifiedNameSyntax parent => ClassifySimpleNameTypeSpecialContext(parent, name), AliasQualifiedNameSyntax parent => ClassifySimpleNameTypeSpecialContext(parent, name), // We are in a "normal" type context like a declaration _ => ClassifySimpleNameTypeInTypeContext(name), }; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private TokenType ClassifySimpleNameTypeInTypeContext(SimpleNameSyntax name) => name switch { // unqualified types called "var" or "dynamic" are classified as keywords. { Parent: not QualifiedNameSyntax and not AliasQualifiedNameSyntax } => name is { Identifier.Text: "var" or "dynamic" } ? TokenType.Keyword : TokenType.TypeName, { Parent: QualifiedNameSyntax { Parent: { } parentOfTopMostQualifiedName, Right: { } right } topMostQualifiedName } when right == name // On the right hand side? && parentOfTopMostQualifiedName is not QualifiedNameSyntax // Is this the most right hand side? // This is a type, except on the right side of "is" where it might also be a constant like Int32.MaxValue && !NameIsRightOfIsExpression(topMostQualifiedName, parentOfTopMostQualifiedName) => TokenType.TypeName, // Name is directly after alias global::SomeType { Parent: AliasQualifiedNameSyntax { Name: { } x, Parent: not (QualifiedNameSyntax or MemberAccessExpressionSyntax) } } when name == x => TokenType.TypeName, // We are somewhere in a qualified name. It probably is a namespace but could also be the outer type of a nested type. _ => ClassifyIdentifierByModel(name), }; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7805", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private static bool IsInTypeContext(SimpleNameSyntax name) => // Based on Syntax.xml search for Type="TypeSyntax" and Type="NameSyntax" // order by https://docs.google.com/spreadsheets/d/1hb6Oz8NE1y4kfv57npSrGEzMd7tm9gYQtI1dABOneMk // Important: "False" is the default (meaning "expression" context). The "true" returning path must be complete to avoid missclassifications. // HotPath: Some "false" returning checks are included for the most common expression context kinds. name.Parent switch { MemberAccessExpressionSyntax x when x.Expression == name || x.Name == name => false, // Performance optimization ArgumentSyntax x when x.Expression == name => false, // Performance optimization InvocationExpressionSyntax x when x.Expression == name => false, // Performance optimization EqualsValueClauseSyntax x when x.Value == name => false, // Performance optimization AssignmentExpressionSyntax x when x.Right == name || x.Left == name => false, // Performance optimization VariableDeclarationSyntax x => x.Type == name, QualifiedNameSyntax => true, ParameterSyntax x => x.Type == name, NullableTypeSyntax x => x.ElementType == name, NamespaceDeclarationSyntax x => x.Name == name, AliasQualifiedNameSyntax x => x.Name == name, BaseTypeSyntax x => x.Type == name, TypeArgumentListSyntax => true, ObjectCreationExpressionSyntax x => x.Type == name, BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AsExpression } x => x.Right == name, ArrayTypeSyntax x => x.ElementType == name, RefValueExpressionSyntax x => x.Type == name, DefaultExpressionSyntax x => x.Type == name, TypeOfExpressionSyntax x => x.Type == name, SizeOfExpressionSyntax x => x.Type == name, CastExpressionSyntax x => x.Type == name, StackAllocArrayCreationExpressionSyntax x => x.Type == name, FromClauseSyntax x => x.Type == name, JoinClauseSyntax x => x.Type == name, ForEachStatementSyntax x => x.Type == name, CatchDeclarationSyntax x => x.Type == name, DelegateDeclarationSyntax x => x.ReturnType == name, TypeConstraintSyntax x => x.Type == name, TypeParameterConstraintClauseSyntax x => x.Name == name, MethodDeclarationSyntax x => x.ReturnType == name, OperatorDeclarationSyntax x => x.ReturnType == name, ConversionOperatorDeclarationSyntax x => x.Type == name, BasePropertyDeclarationSyntax x => x.Type == name, PointerTypeSyntax x => x.ElementType == name, AttributeSyntax x => x.Name == name, ExplicitInterfaceSpecifierSyntax x => x.Name == name, UsingDirectiveSyntax x => x.Name == name, NameEqualsSyntax { Parent: UsingDirectiveSyntax { Alias.Name: { } x } } => x == name, // Wrapper. HotPath: Use SyntaxKind checks instead of Wrapper.IsInstance (slow and allocating). // Make sure to check the associated syntax kinds in the documentation and/or that the types are sealed. { RawKind: (int)SyntaxKindEx.FunctionPointerParameter } x => ((FunctionPointerParameterSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.DeclarationPattern } x => ((DeclarationPatternSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.RecursivePattern } x => ((RecursivePatternSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.TypePattern } x => ((TypePatternSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.LocalFunctionStatement } x => ((LocalFunctionStatementSyntaxWrapper)x).ReturnType == name, { RawKind: (int)SyntaxKindEx.DeclarationExpression } x => ((DeclarationExpressionSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKind.ParenthesizedLambdaExpression } x => ((ParenthesizedLambdaExpressionSyntax)x).ReturnType == name, { RawKind: (int)SyntaxKindEx.FileScopedNamespaceDeclaration } x => ((FileScopedNamespaceDeclarationSyntaxWrapper)x).Name == name, { RawKind: (int)SyntaxKindEx.TupleElement } x => ((TupleElementSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.RefType } x => ((RefTypeSyntaxWrapper)x).Type == name, { RawKind: (int)SyntaxKindEx.ScopedType } x => ((ScopedTypeSyntaxWrapper)x).Type == name, _ => false, }; } internal sealed class TriviaClassifier : TriviaClassifierBase { private static readonly HashSet RegularCommentToken = [ SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia, ]; private static readonly HashSet DocCommentToken = [ SyntaxKind.SingleLineDocumentationCommentTrivia, SyntaxKind.MultiLineDocumentationCommentTrivia, ]; protected override bool IsRegularComment(SyntaxTrivia trivia) => trivia.IsAnyKind(RegularCommentToken); protected override bool IsDocComment(SyntaxTrivia trivia) => trivia.IsAnyKind(DocCommentToken); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/ValuesUselesslyIncremented.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ValuesUselesslyIncremented : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2123"; private const string MessageFormat = "Remove this {0} or correct the code not to waste it."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var increment = (PostfixUnaryExpressionSyntax)c.Node; var symbol = c.Model.GetSymbolInfo(increment.Operand).Symbol; if (symbol is ILocalSymbol || symbol is IParameterSymbol { RefKind: RefKind.None }) { VisitParent(c, increment); } }, SyntaxKind.PostIncrementExpression, SyntaxKind.PostDecrementExpression); private static void VisitParent(SonarSyntaxNodeReportingContext context, PostfixUnaryExpressionSyntax increment) { switch (increment.Parent) { case ReturnStatementSyntax: case ArrowExpressionClauseSyntax: case CastExpressionSyntax castExpressionSyntax when castExpressionSyntax.Parent?.Kind() is SyntaxKind.ReturnStatement or SyntaxKind.ArrowExpressionClause: case ArgumentSyntax argumentInAssignment when argumentInAssignment.FindAssignmentComplement() is { } assignmentTarget && CSharpEquivalenceChecker.AreEquivalent(assignmentTarget, increment.Operand): case ArgumentSyntax argumentInReturn when argumentInReturn.OutermostTuple() is { SyntaxNode.Parent: ReturnStatementSyntax or ArrowExpressionClauseSyntax }: case AssignmentExpressionSyntax assignment when assignment.IsKind(SyntaxKind.SimpleAssignmentExpression) && assignment.Right == increment && CSharpEquivalenceChecker.AreEquivalent(assignment.Left, increment.Operand): var operatorText = increment.OperatorToken.IsKind(SyntaxKind.PlusPlusToken) ? "increment" : "decrement"; context.ReportIssue(Rule, increment, operatorText); return; default: return; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/VariableShadowsField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class VariableShadowsField : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S1117"; private const string MessageFormat = "Rename '{0}' which hides the {1} with the same name."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => Process(c, GetDeclarationOrDesignation(c.Node)), SyntaxKind.LocalDeclarationStatement, SyntaxKind.ForStatement, SyntaxKind.UsingStatement, SyntaxKind.FixedStatement, SyntaxKindEx.DeclarationExpression, SyntaxKindEx.RecursivePattern, SyntaxKindEx.VarPattern, SyntaxKindEx.DeclarationPattern, SyntaxKindEx.ListPattern, SyntaxKind.ForEachStatement); private static SyntaxNode GetDeclarationOrDesignation(SyntaxNode node) => node switch { LocalDeclarationStatementSyntax localDeclaration => localDeclaration.Declaration, ForStatementSyntax forStatement => forStatement.Declaration, UsingStatementSyntax usingStatement => usingStatement.Declaration, FixedStatementSyntax fixedStatement => fixedStatement.Declaration, ForEachStatementSyntax forEachStatement => forEachStatement, _ when DeclarationExpressionSyntaxWrapper.IsInstance(node) => ((DeclarationExpressionSyntaxWrapper)node).Designation, _ when RecursivePatternSyntaxWrapper.IsInstance(node) => ((RecursivePatternSyntaxWrapper)node).Designation, _ when VarPatternSyntaxWrapper.IsInstance(node) => ((VarPatternSyntaxWrapper)node).Designation, _ when DeclarationPatternSyntaxWrapper.IsInstance(node) => ((DeclarationPatternSyntaxWrapper)node).Designation, _ when ListPatternSyntaxWrapper.IsInstance(node) => ((ListPatternSyntaxWrapper)node).Designation, _ => null }; private static void Process(SonarSyntaxNodeReportingContext context, SyntaxNode node) { if (ExtractIdentifiers(node) is { Count: > 0 } identifiers && GetContextSymbols(context) is var members) { foreach (var identifier in identifiers) { ReportOnVariableMatchingField(context, members, identifier); } } } private static List ExtractIdentifiers(SyntaxNode node) => node switch { VariableDeclarationSyntax variableDeclaration => variableDeclaration.Variables.Select(x => x.Identifier).ToList(), ForEachStatementSyntax foreachStatement => new() { foreachStatement.Identifier }, _ when VariableDesignationSyntaxWrapper.IsInstance(node) => ((VariableDesignationSyntaxWrapper)node).AllVariables().Select(x => x.Identifier).ToList(), _ => new() }; private static List GetContextSymbols(SonarSyntaxNodeReportingContext context) { var members = context.ContainingSymbol.ContainingType.GetMembers(); var primaryConstructorParameters = members.OfType().FirstOrDefault(x => x.IsPrimaryConstructor)?.Parameters; var fieldsAndProperties = members.Where(x => x is IPropertySymbol or IFieldSymbol).ToList(); return primaryConstructorParameters is null ? fieldsAndProperties : fieldsAndProperties.Concat(primaryConstructorParameters).ToList(); } private static void ReportOnVariableMatchingField(SonarSyntaxNodeReportingContext context, IEnumerable members, SyntaxToken identifier) { if (members.FirstOrDefault(x => x.Name == identifier.ValueText && (x.IsStatic || !identifier.Parent.EnclosingScope().GetModifiers().Any(x => x.Kind() == SyntaxKind.StaticKeyword))) is { } matchingMember) { context.ReportIssue(Rule, identifier, identifier.Text, GetSymbolName(matchingMember)); } } private static string GetSymbolName(ISymbol symbol) => symbol switch { IFieldSymbol => "field", IPropertySymbol => "property", IParameterSymbol => "primary constructor parameter", _ => string.Empty }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/VariableUnused.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class VariableUnused : VariableUnusedBase { protected override ILanguageFacade Language { get; } = CSharpFacade.Instance; protected override bool IsExcludedDeclaration(SyntaxNode node) => node is ForEachStatementSyntax || node is VariableDeclaratorSyntax { Parent.Parent: UsingStatementSyntax or ForStatementSyntax } || (node is VariableDeclaratorSyntax { Parent.Parent: LocalDeclarationStatementSyntax lds } && lds.UsingKeyword.IsKind(SyntaxKind.UsingKeyword)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/VirtualEventField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class VirtualEventField : SonarDiagnosticAnalyzer { private const string MessageFormat = "Remove this 'virtual' modifier of {0}."; internal const string DiagnosticId = "S2290"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var eventField = (EventFieldDeclarationSyntax)c.Node; if (eventField.Modifiers.Any(SyntaxKind.VirtualKeyword)) { var virt = eventField.Modifiers.First(modifier => modifier.IsKind(SyntaxKind.VirtualKeyword)); var names = string.Join(", ", eventField.Declaration.Variables.Select(syntax => $"'{syntax.Identifier.ValueText}'").OrderBy(s => s).JoinAnd()); c.ReportIssue(Rule, virt, names); } }, SyntaxKind.EventFieldDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/VirtualEventFieldCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class VirtualEventFieldCodeFix : SonarCodeFix { internal const string Title = "Remove 'virtual' keyword"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(VirtualEventField.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var token = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceToken(token, SyntaxFactory.Token(SyntaxKind.None)); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/WcfMissingContractAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class WcfMissingContractAttribute : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3597"; private const string MessageFormat = "Add the '{0}' attribute to {1}."; private const string MessageOperation = "the methods of this {0}"; private const string MessageService = " this {0}"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var namedType = (INamedTypeSymbol)c.Symbol; if (namedType.Is(TypeKind.Struct)) { return; } var hasServiceContract = namedType.HasAttribute(KnownType.System_ServiceModel_ServiceContractAttribute); var hasAnyMethodWithOperationContract = HasAnyMethodWithOperationContract(namedType); if (!(hasServiceContract ^ hasAnyMethodWithOperationContract)) { return; } var declarationSyntax = GetTypeDeclaration(c, namedType); if (declarationSyntax == null) { return; } string message; string attributeToAdd; if (hasServiceContract) { message = MessageOperation; attributeToAdd = "OperationContract"; } else { message = MessageService; attributeToAdd = "ServiceContract"; } var classOrInterface = namedType.IsClass() ? "class" : "interface"; message = string.Format(message, classOrInterface); c.ReportIssue(Rule, declarationSyntax.Identifier, attributeToAdd, message); }, SymbolKind.NamedType); private static bool HasAnyMethodWithOperationContract(INamespaceOrTypeSymbol namedType) => namedType.GetMembers() .OfType() .Any(m => m.HasAttribute(KnownType.System_ServiceModel_OperationContractAttribute)); private static TypeDeclarationSyntax GetTypeDeclaration(SonarSymbolReportingContext context, ISymbol namedType) => namedType.DeclaringSyntaxReferences .Where(x => context.ShouldAnalyzeTree(x.SyntaxTree, CSharpGeneratedCodeRecognizer.Instance)) .Select(x => x.GetSyntax() as TypeDeclarationSyntax) .FirstOrDefault(x => x is not null); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/WcfNonVoidOneWay.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class WcfNonVoidOneWay : WcfNonVoidOneWayBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; protected override SyntaxKind MethodDeclarationKind => SyntaxKind.MethodDeclaration; protected override Location GetReturnTypeLocation(MethodDeclarationSyntax method) => method.ReturnType.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/WeakSslTlsProtocols.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class WeakSslTlsProtocols : WeakSslTlsProtocolsBase { protected override ILanguageFacade Language => CSharpFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/XMLSignatureCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class XmlSignatureCheck : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6377"; private const string MessageFormat = "Change this code to only accept signatures computed from a trusted party."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { var tracker = CSharpFacade.Instance.Tracker.Invocation; tracker.Track( new TrackerInput(context, AnalyzerConfiguration.AlwaysEnabled, Rule), tracker.Or( tracker.And( tracker.MethodHasParameters(0), tracker.MatchMethod(new MemberDescriptor(KnownType.System_Security_Cryptography_Xml_SignedXml, "CheckSignature"))), tracker.MatchMethod(new MemberDescriptor(KnownType.System_Security_Cryptography_Xml_SignedXml, "CheckSignatureReturningKey")))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/XXE/XmlReaderSettingsValidator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml; namespace SonarAnalyzer.CSharp.Rules.XXE { /// /// This class is responsible to check if a XmlReaderSettings node is vulnerable to XXE attacks. /// /// By default the XmlReaderSettings is safe: /// - before .Net 4.5.2 it has ProhibitDtd set to true (even if the internal XmlResolver is not secure) /// - starting with .Net 4.5.2 XmlResolver is set to null, ProhibitDtd set to true and DtdProcessing set to ignore /// /// If the properties are modified, in order to be secure, we have to check that either ProhibitDtd is set to true, DtdProcessing set to Ignore or /// the internal XmlResolver is secure. /// internal class XmlReaderSettingsValidator { private readonly SemanticModel semanticModel; private readonly bool isXmlResolverSafeByDefault; public XmlReaderSettingsValidator(SemanticModel semanticModel, NetFrameworkVersion version) { this.semanticModel = semanticModel; isXmlResolverSafeByDefault = IsXmlResolverPropertySafeByDefault(version); } /// /// Gets the assignment locations of XmlReaderSettings properties which are unsafe and are used in invocations afterwards (e.g. XmlReader.Create). /// /// A method invocation syntax node (e.g. XmlReader.Create). /// The symbol of the XmlReaderSettings node received as parameter. This is used to check /// if certain properties (ProhibitDtd, DtdProcessing or XmlUrlResolver) were modified for the given symbol. /// The list of unsafe assignment locations. public IList GetUnsafeAssignmentLocations(InvocationExpressionSyntax invocation, ISymbol settings, string message) { var unsafeAssignmentLocations = new List(); // By default ProhibitDtd is 'true' and DtdProcessing is 'ignore' var unsafeDtdProcessing = false; var unsafeResolver = isXmlResolverSafeByDefault; var objectCreation = GetObjectCreation(settings, invocation, semanticModel); var objectCreationAssignments = objectCreation?.InitializerExpressions.OfType() ?? Enumerable.Empty(); var propertyAssignments = GetAssignments(invocation.FirstAncestorOrSelf()) .Where(assignment => IsMemberAccessOnSymbol(assignment.Left, settings, semanticModel)); foreach (var assignment in objectCreationAssignments.Union(propertyAssignments)) { var name = assignment.Left.GetName(); if (name == "ProhibitDtd" || name == "DtdProcessing") { unsafeDtdProcessing = IsXmlResolverDtdProcessingUnsafe(assignment, semanticModel); if (unsafeDtdProcessing) { unsafeAssignmentLocations.Add(assignment.ToSecondaryLocation(message)); } } else if (name == "XmlResolver") { unsafeResolver = IsXmlResolverAssignmentUnsafe(assignment, semanticModel); if (unsafeResolver) { unsafeAssignmentLocations.Add(assignment.ToSecondaryLocation(message)); } } } return unsafeDtdProcessing && unsafeResolver ? unsafeAssignmentLocations : []; } private static bool IsMemberAccessOnSymbol(ExpressionSyntax expression, ISymbol symbol, SemanticModel semanticModel) => expression is MemberAccessExpressionSyntax memberAccess && semanticModel.GetTypeInfo(memberAccess.Expression).Type.Is(KnownType.System_Xml_XmlReaderSettings) && symbol.Equals(semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol); private static IEnumerable GetAssignments(SyntaxNode node) => node == null ? Enumerable.Empty() : node.DescendantNodes().OfType(); private static bool IsXmlResolverPropertySafeByDefault(NetFrameworkVersion version) => version == NetFrameworkVersion.Probably35 || version == NetFrameworkVersion.Between4And451; private static IObjectCreation GetObjectCreation(ISymbol symbol, InvocationExpressionSyntax invocation, SemanticModel semanticModel) => // First we search for object creations at the syntax level to see if the object is created inline // and if not we look for the identifier declaration. invocation.DescendantNodes() .Union(symbol.LocationNodes(invocation)) .Where(x => x?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression) .Select(ObjectCreationFactory.Create) .FirstOrDefault(objectCreation => IsXmlReaderSettingsCreationWithInitializer(objectCreation, semanticModel)); private static bool IsXmlReaderSettingsCreationWithInitializer(IObjectCreation objectCreation, SemanticModel semanticModel) => objectCreation.Initializer != null && objectCreation.TypeSymbol(semanticModel).Is(KnownType.System_Xml_XmlReaderSettings); private static bool IsXmlResolverDtdProcessingUnsafe(AssignmentExpressionSyntax assignment, SemanticModel semanticModel) => semanticModel.GetConstantValue(assignment.Right).Value switch { false => true, // If ProhibitDtd is set to false the settings will be unsafe (parsing is allowed) (int)DtdProcessing.Parse => true, _ => false }; private static bool IsXmlResolverAssignmentUnsafe(AssignmentExpressionSyntax assignment, SemanticModel semanticModel) { if (assignment.Right.IsKind(SyntaxKind.NullLiteralExpression)) { return false; } var type = semanticModel.GetTypeInfo(assignment.Right).Type; return type.IsAny(KnownType.System_Xml_XmlUrlResolver, KnownType.System_Xml_Resolvers_XmlPreloadedResolver); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Rules/XmlExternalEntityShouldNotBeParsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml; using SonarAnalyzer.CSharp.Core.Trackers; using SonarAnalyzer.CSharp.Rules.XXE; namespace SonarAnalyzer.CSharp.Rules { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class XmlExternalEntityShouldNotBeParsed : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S2755"; private const string MessageFormat = "Disable access to external entities in XML parsing."; private const string SecondaryMessage = "This value enables external entities in XML parsing."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); // For the XXE rule we actually need to know about .NET 4.5.2, // but it is good enough given the other .NET 4.x do not have support anymore private readonly NetFrameworkVersionProvider versionProvider; public XmlExternalEntityShouldNotBeParsed() : this(new NetFrameworkVersionProvider()) { } internal /*for testing*/ XmlExternalEntityShouldNotBeParsed(NetFrameworkVersionProvider netFrameworkVersionProvider) => versionProvider = netFrameworkVersionProvider; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( ccc => { ccc.RegisterNodeAction( c => { var objectCreation = ObjectCreationFactory.Create(c.Node); var netFrameworkVersion = versionProvider.Version(c.Compilation); var constructorIsSafe = ConstructorIsSafe(netFrameworkVersion); var trackers = TrackerFactory.Create(); if (trackers.XmlDocumentTracker.ShouldBeReported(objectCreation, c.Model, constructorIsSafe) || trackers.XmlTextReaderTracker.ShouldBeReported(objectCreation, c.Model, constructorIsSafe)) { c.ReportIssue(Rule, objectCreation.Expression); } VerifyXPathDocumentConstructor(c, objectCreation); }, SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression); ccc.RegisterNodeAction( c => { var assignment = (AssignmentExpressionSyntax)c.Node; var trackers = TrackerFactory.Create(); if (trackers.XmlDocumentTracker.ShouldBeReported(assignment, c.Model) || trackers.XmlTextReaderTracker.ShouldBeReported(assignment, c.Model)) { c.ReportIssue(Rule, assignment); } }, SyntaxKind.SimpleAssignmentExpression); ccc.RegisterNodeAction(VerifyXmlReaderInvocations, SyntaxKind.InvocationExpression); }); private void VerifyXmlReaderInvocations(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if (!invocation.IsMemberAccessOnKnownType("Create", KnownType.System_Xml_XmlReader, context.Model)) { return; } var settings = invocation.GetArgumentSymbolsOfKnownType(KnownType.System_Xml_XmlReaderSettings, context.Model).FirstOrDefault(); if (settings == null) { return; // safe by default } var xmlReaderSettingsValidator = new XmlReaderSettingsValidator(context.Model, versionProvider.Version(context.Compilation)); if (xmlReaderSettingsValidator.GetUnsafeAssignmentLocations(invocation, settings, SecondaryMessage) is { } secondaryLocations && secondaryLocations.Any()) { context.ReportIssue(Rule, invocation, secondaryLocations); } } private void VerifyXPathDocumentConstructor(SonarSyntaxNodeReportingContext context, IObjectCreation objectCreation) { if (!context.Model.GetTypeInfo(objectCreation.Expression).Type.Is(KnownType.System_Xml_XPath_XPathDocument) // If a XmlReader is provided in the constructor, XPathDocument will be as safe as the received reader. // In this case we don't raise a warning since the XmlReader has it's own checks. || objectCreation.ArgumentList.Arguments.GetArgumentsOfKnownType(KnownType.System_Xml_XmlReader, context.Model).Any()) { return; } if (!IsXPathDocumentSecureByDefault(versionProvider.Version(context.Compilation))) { context.ReportIssue(Rule, objectCreation.Expression); } } private static bool IsXPathDocumentSecureByDefault(NetFrameworkVersion version) => // XPathDocument is secure by default starting with .Net 4.5.2 version == NetFrameworkVersion.After452 || version == NetFrameworkVersion.Unknown; // The XmlDocument and XmlTextReader constructors were made safe-by-default in .NET 4.5.2 private static bool ConstructorIsSafe(NetFrameworkVersion version) => version switch { NetFrameworkVersion.After452 => true, NetFrameworkVersion.Between4And451 => false, NetFrameworkVersion.Probably35 => false, _ => true }; private static class TrackerFactory { private static ImmutableArray UnsafeXmlResolvers { get; } = ImmutableArray.Create( KnownType.System_Xml_XmlUrlResolver, KnownType.System_Xml_Resolvers_XmlPreloadedResolver ); private static readonly ImmutableArray XmlDocumentTrackedTypes = ImmutableArray.Create( KnownType.System_Xml_XmlDocument, KnownType.System_Xml_XmlDataDocument, KnownType.System_Configuration_ConfigXmlDocument, KnownType.Microsoft_Web_XmlTransform_XmlFileInfoDocument, KnownType.Microsoft_Web_XmlTransform_XmlTransformableDocument ); private static readonly ISet XmlTextReaderTrackedProperties = ImmutableHashSet.Create( "XmlResolver", // should be null "DtdProcessing", // should not be Parse "ProhibitDtd" // should be true in .NET 3.5 ); public static TrackersHolder Create() { var xmlDocumentTracker = new CSharpObjectInitializationTracker( // we do not expect any constant values for XmlResolver isAllowedConstantValue: constantValue => false, trackedTypes: XmlDocumentTrackedTypes, isTrackedPropertyName: propertyName => propertyName == "XmlResolver", isAllowedObject: (symbol, _, __) => IsAllowedObject(symbol) ); var xmlTextReaderTracker = new CSharpObjectInitializationTracker( isAllowedConstantValue: IsAllowedValueForXmlTextReader, trackedTypes: ImmutableArray.Create(KnownType.System_Xml_XmlTextReader), isTrackedPropertyName: XmlTextReaderTrackedProperties.Contains, isAllowedObject: (symbol, _, __) => IsAllowedObject(symbol) ); return new TrackersHolder(xmlDocumentTracker, xmlTextReaderTracker); } private static bool IsAllowedValueForXmlTextReader(object constantValue) { if (constantValue == null) { return true; } if (constantValue is int integerValue) { return integerValue != (int)DtdProcessing.Parse; } // treat the ProhibitDtd property return constantValue is bool value && value; } private static bool IsUnsafeXmlResolverConstructor(ISymbol symbol) => symbol.Kind == SymbolKind.Method && symbol.ContainingType.GetSymbolType().IsAny(UnsafeXmlResolvers); private static bool IsAllowedObject(ISymbol symbol) => !IsUnsafeXmlResolverConstructor(symbol) && !symbol.GetSymbolType().IsAny(UnsafeXmlResolvers) && !IsUnsafeXmlResolverReturnType(symbol); private static bool IsUnsafeXmlResolverReturnType(ISymbol symbol) => symbol is IMethodSymbol methodSymbol && methodSymbol.ReturnType.IsAny(UnsafeXmlResolvers); } private readonly struct TrackersHolder { internal readonly CSharpObjectInitializationTracker XmlDocumentTracker; internal readonly CSharpObjectInitializationTracker XmlTextReaderTracker; internal TrackersHolder(CSharpObjectInitializationTracker xmlDocumentTracker, CSharpObjectInitializationTracker xmlTextReaderTracker) { XmlDocumentTracker = xmlDocumentTracker; XmlTextReaderTracker = xmlTextReaderTracker; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/SonarAnalyzer.CSharp.csproj ================================================  netstandard2.0 true false SonarAnalyzer C# NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/ExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class ExpressionSyntaxExtensions { /// /// Maps the tuple arguments of to the positional sub-pattern of . /// For a pattern like: (x, y) is (1, 2)x is mapped to numeric literal 1 and y is mapped to 2. /// /// A tuple expression. /// A pattern that can be matched to the tuple . /// The mapping between the tuple arguments and the positional sub-patterns. public static Dictionary MapToPattern(this ExpressionSyntax expression, SyntaxNode pattern) { var map = new Dictionary(); FillPatternMap(map, expression, pattern); return map; } private static void FillPatternMap(Dictionary map, ExpressionSyntax expression, SyntaxNode pattern) { expression = expression.RemoveParentheses(); pattern = pattern.RemoveParentheses(); if (TupleExpressionSyntaxWrapper.IsInstance(expression) && (TupleExpressionSyntaxWrapper)expression is var tupleExpression && RecursivePatternSyntaxWrapper.IsInstance(pattern) && (RecursivePatternSyntaxWrapper)pattern is var recursivePattern && recursivePattern.PositionalPatternClause.SyntaxNode is not null && recursivePattern.PositionalPatternClause.Subpatterns.Count == tupleExpression.Arguments.Count) { for (var i = 0; i < tupleExpression.Arguments.Count; i++) { FillPatternMap(map, tupleExpression.Arguments[i].Expression, recursivePattern.PositionalPatternClause.Subpatterns[i].Pattern); } } else { map.Add(expression, pattern); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/IMethodSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class IMethodSymbolExtensions { public static bool IsConditionalDebugMethod(this IMethodSymbol method) { if (method is null) { return false; } // Conditional attribute can be applied to a class, but it does nothing unless // the class is an attribute class. So we only need to worry about whether the // conditional attribute is on the method. return method.GetAttributes(KnownType.System_Diagnostics_ConditionalAttribute) .Any(x => x.ConstructorArguments.Any(constructorArg => constructorArg.Type.Is(KnownType.System_String) && (string)constructorArg.Value == "DEBUG")); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/IfStatementSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class IfStatementSyntaxExtensions { public static IList PrecedingIfsInConditionChain(this IfStatementSyntax ifStatement) { var ifStatements = new List(); while (PrecedingIf(ifStatement) is { } precedingIf) { ifStatements.Add(precedingIf); ifStatement = precedingIf; } ifStatements.Reverse(); return ifStatements; static IfStatementSyntax PrecedingIf(IfStatementSyntax ifStatement) => ifStatement.Parent switch { ElseClauseSyntax { Parent: IfStatementSyntax parentIf } => parentIf, BlockSyntax { Parent: ElseClauseSyntax { Parent: IfStatementSyntax parentIf } } block when block.Statements[0] == ifStatement => parentIf, _ => null, }; } public static IEnumerable PrecedingStatementsInConditionChain(this IfStatementSyntax ifStatement) => ifStatement.PrecedingIfsInConditionChain().Select(x => x.Statement); public static IEnumerable PrecedingConditionsInConditionChain(this IfStatementSyntax ifStatement) => ifStatement.PrecedingIfsInConditionChain().Select(x => x.Condition); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/InvocationExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class InvocationExpressionSyntaxExtensions { public static bool HasOverloadWithType(this InvocationExpressionSyntax invocation, SemanticModel model, ImmutableArray types) => model.GetMemberGroup(invocation.Expression) .OfType() .Where(x => !x.HasAttribute(KnownType.System_ObsoleteAttribute)) .Where(x => IsCompatibleOverload(invocation, x)) .Any(x => SameParametersExceptWantedType(x, InvocationParameters(invocation, model), types)); // must have same number of arguments + 1 (the argument that should be added) OR is params argument private static bool IsCompatibleOverload(InvocationExpressionSyntax invocation, IMethodSymbol m) { var parameters = m.GetParameters().ToArray(); return parameters.Length - invocation.ArgumentList.Arguments.Count == 1 || (parameters.Length != 0 && parameters[parameters.Length - 1].IsParams); } private static IMethodSymbol InvocationParameters(InvocationExpressionSyntax invocation, SemanticModel model) => model.GetSymbolInfo(invocation.Expression).Symbol as IMethodSymbol; private static bool SameParametersExceptWantedType(IMethodSymbol possibleOverload, IMethodSymbol invocationMethodSymbol, ImmutableArray types) { var withTypeParam = possibleOverload.IsGenericMethod && invocationMethodSymbol.IsGenericMethod // attempt to create the possibleOverload method symbol with same type arguments as the invocation method ? ConstructTypedPossibleOverload(possibleOverload, invocationMethodSymbol) : possibleOverload; var invocationParameters = invocationMethodSymbol.GetParameters().ToArray(); var parametersWithoutWantedType = withTypeParam.GetParameters().Where(x => !x.Type.IsAny(types)).ToArray(); if (parametersWithoutWantedType.Length == possibleOverload.GetParameters().Count()) { return false; } if (parametersWithoutWantedType.Length > 0 && parametersWithoutWantedType.Length <= invocationParameters.Length && parametersWithoutWantedType[parametersWithoutWantedType.Length - 1].IsParams) { // check whether has a parameter array argument which matches the invocationParameters return VerifyCompatibility(invocationParameters, parametersWithoutWantedType, parametersWithoutWantedType[parametersWithoutWantedType.Length - 1]); } else if (invocationParameters.Length == parametersWithoutWantedType.Length) { // parameters must have the same type return invocationParameters.Select((x, index) => x.Type.DerivesOrImplements(parametersWithoutWantedType[index].Type)).All(x => x); } else { return false; } } private static IMethodSymbol ConstructTypedPossibleOverload(IMethodSymbol possibleOverload, IMethodSymbol invocationMethodSymbol) => possibleOverload.TypeParameters.Length == invocationMethodSymbol.TypeArguments.Length ? possibleOverload.ConstructedFrom.Construct(invocationMethodSymbol.TypeArguments.ToArray()) : possibleOverload; /** * Verifies the compatibility between the invocation parameters and the parameters of a possible overload that * has the last parameter of 'params' type (variable length parameter). */ private static bool VerifyCompatibility(IParameterSymbol[] invocationParameters, IParameterSymbol[] overloadCandidateParameters, IParameterSymbol paramsParameter) { if (paramsParameter.Type is not IArrayTypeSymbol) { return false; } var i = 0; // check parameters before the last parameter for (; i < overloadCandidateParameters.Length - 1; i++) { if (!invocationParameters[i].Type.DerivesOrImplements(overloadCandidateParameters[i].Type)) { return false; } } // make sure the rest of the invocation parameters match with the 'params' type var paramsType = ParamsElementType(paramsParameter.Type); for (; i < invocationParameters.Length - 1; i++) { if (!invocationParameters[i].Type.DerivesOrImplements(paramsType)) { return false; } } var lastInvocationParameter = invocationParameters[invocationParameters.Length - 1]; return lastInvocationParameter.IsParams ? ParamsElementType(lastInvocationParameter.Type).DerivesOrImplements(paramsType) : lastInvocationParameter.Type.DerivesOrImplements(paramsType); static ITypeSymbol ParamsElementType(ITypeSymbol type) => type is IArrayTypeSymbol symbol ? symbol.ElementType : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/SwitchSectionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class SwitchSectionSyntaxExtensions { public static IEnumerable PrecedingSections(this SwitchSectionSyntax caseStatement) { if (caseStatement is null) { return []; } else { var switchStatement = (SwitchStatementSyntax)caseStatement.Parent; var currentSectionIndex = switchStatement.Sections.IndexOf(caseStatement); return switchStatement.Sections.Take(currentSectionIndex); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Extensions/SyntaxNodeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Extensions; internal static class SyntaxNodeExtensions { public static bool IsInDebugBlock(this SyntaxNode node) => node.ActiveConditionalCompilationSections().Contains("DEBUG"); public static bool IsInConditionalDebug(this SyntaxNode node, SemanticModel model) { var method = node.FirstAncestorOrSelf() is { } containingMethod ? model.GetDeclaredSymbol(containingMethod) : null; return method.IsConditionalDebugMethod(); } /// /// Returns a list of the names of #if [NAME] sections that the specified /// node is contained in. /// /// /// Note: currently we only handle directives with simple identifiers e.g. #if FOO, #elif FOO /// We don't handle logical operators e.g. #if !DEBUG, and we don't handle cases like /// #if !DEBUG ... #else... :DEBUG must be true in the else case. /// public static IEnumerable ActiveConditionalCompilationSections(this SyntaxNode node) { var directives = CollectPrecedingDirectiveSyntax(node); if (directives.Count == 0) { return []; } var activeDirectives = new Stack(); foreach (var directive in directives) { switch (directive.RawKind) { case (int)SyntaxKind.IfDirectiveTrivia: activeDirectives.Push((BranchingDirectiveTriviaSyntax)directive); break; case (int)SyntaxKind.ElseDirectiveTrivia: case (int)SyntaxKind.ElifDirectiveTrivia: // If we hit an if or elif then that effective acts as an "end" for the previous if/elif block -> pop it SafePop(activeDirectives); activeDirectives.Push((BranchingDirectiveTriviaSyntax)directive); break; case (int)SyntaxKind.EndIfDirectiveTrivia: SafePop(activeDirectives); break; default: Debug.Fail($"Unexpected token type: {directive.Kind()}"); break; } } Debug.Assert(activeDirectives.All(x => x.IsActive), "Not all of the collected directives were active"); Debug.Assert(activeDirectives.All(x => x.BranchTaken), "Not all of the collected directives were for the branch that was taken"); return activeDirectives.Select(FindDirectiveName).WhereNotNull().ToHashSet(); } private static string FindDirectiveName(BranchingDirectiveTriviaSyntax directiveTriviaSyntax) => directiveTriviaSyntax is ConditionalDirectiveTriviaSyntax conditionalDirective && conditionalDirective.Condition is IdentifierNameSyntax identifierName ? identifierName.Identifier.ValueText : null; private static void SafePop(Stack stack) { if (stack.Count > 0) // This should never be empty { stack.Pop(); } } private static IList CollectPrecedingDirectiveSyntax(SyntaxNode node) { var walker = new BranchingDirectiveCollector(node); return walker.SafeVisit(node.SyntaxTree.GetRoot()) ? walker.CollectedDirectives : []; } /// /// Collects all of the #if, #else, #elsif and #endif directives occuring in the /// syntax tree up to the specified node /// private sealed class BranchingDirectiveCollector : SafeCSharpSyntaxWalker { private readonly SyntaxNode terminatingNode; private bool found; public List CollectedDirectives { get; } = new(); public BranchingDirectiveCollector(SyntaxNode terminatingNode) : base(SyntaxWalkerDepth.StructuredTrivia) => this.terminatingNode = terminatingNode; public override void Visit(SyntaxNode node) { // Stop traversing once we've walked down to the terminating node if (found) { return; } if (node == terminatingNode) { VisitTerminatingNodeLeadingTrivia(); found = true; } else { base.Visit(node); } } public override void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node) { AddDirective(node); base.VisitIfDirectiveTrivia(node); } public override void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node) { AddDirective(node); base.VisitElseDirectiveTrivia(node); } public override void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node) { AddDirective(node); base.VisitElifDirectiveTrivia(node); } public override void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node) { AddDirective(node); base.VisitEndIfDirectiveTrivia(node); } private void AddDirective(DirectiveTriviaSyntax node) { if (node.IsActive) { CollectedDirectives.Add(node); } } private void VisitTerminatingNodeLeadingTrivia() { // Special case: the leading trivia of the terminating node could contain directives. However, we won't have processed // these yet, as they are treated as children of the node even though they appear before it in the text if (terminatingNode.HasLeadingTrivia) { VisitLeadingTrivia(terminatingNode.GetFirstToken(includeZeroWidth: true)); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Utilities/AttributeSyntaxSymbolMapping.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Utilities; internal class AttributeSyntaxSymbolMapping { public AttributeSyntax Node { get; } public IMethodSymbol Symbol { get; } private AttributeSyntaxSymbolMapping(AttributeSyntax node, IMethodSymbol symbol) { Node = node; Symbol = symbol; } public static IEnumerable GetAttributesForParameter(ParameterSyntax parameter, SemanticModel model) => parameter.AttributeLists .SelectMany(x => x.Attributes) .Select(x => new AttributeSyntaxSymbolMapping(x, model.GetSymbolInfo(x).Symbol as IMethodSymbol)) .Where(x => x.Symbol is not null); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Utilities/SymbolUsage.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; internal class SymbolUsage { public ISymbol Symbol { get; } public SyntaxNode Declaration { get; set; } public SyntaxNode Initializer { get; set; } public HashSet Readings { get; } = new(); public HashSet Writings { get; } = new(); public SymbolUsage(ISymbol symbol) => Symbol = symbol; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Syntax/Utilities/SymbolUsageCollector.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Syntax.Utilities; /// /// Collects all symbol usages from a class declaration. Ignores symbols whose names are not present /// in the knownSymbolNames collection for performance reasons. /// internal class SymbolUsageCollector : SafeCSharpSyntaxWalker { [Flags] private enum SymbolAccess { None = 0, Read = 1, Write = 2, ReadWrite = Read | Write } private static readonly ISet IncrementKinds = new HashSet { SyntaxKind.PostIncrementExpression, SyntaxKind.PreIncrementExpression, SyntaxKind.PostDecrementExpression, SyntaxKind.PreDecrementExpression }; private readonly Compilation compilation; private readonly HashSet knownSymbolNames; private SemanticModel model; public ISet UsedSymbols { get; } = new HashSet(); public IDictionary FieldSymbolUsages { get; } = new Dictionary(); public HashSet DebuggerDisplayValues { get; } = []; public Dictionary PropertyAccess { get; } = []; public HashSet PrivateAttributes { get; } = []; public HashSet TypesUsedWithReflection { get; } = []; public SymbolUsageCollector(Compilation compilation, IEnumerable knownSymbols) { this.compilation = compilation; knownSymbolNames = knownSymbols.Select(Name).ToHashSet(); } public override void Visit(SyntaxNode node) { model = node.EnsureCorrectSemanticModelOrDefault(model ?? compilation.GetSemanticModel(node.SyntaxTree)); if (model is not null) { if (node.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression) && knownSymbolNames.Contains(ObjectCreationFactory.Create(node).TypeAsString(model))) { UsedSymbols.UnionWith(Symbols(node)); } else if (node.IsKind(SyntaxKindEx.LocalFunctionStatement) && (LocalFunctionStatementSyntaxWrapper)node is { AttributeLists.Count: > 0 } && model.GetDeclaredSymbol(node) is IMethodSymbol localFunctionSymbol) { UsedSymbols.UnionWith(localFunctionSymbol.GetAttributes().Where(x => knownSymbolNames.Contains(x.AttributeClass.Name)).Select(x => x.AttributeClass)); } else if (node.IsKind(SyntaxKindEx.PrimaryConstructorBaseType) && knownSymbolNames.Contains(((PrimaryConstructorBaseTypeSyntaxWrapper)node).Type.GetName())) { UsedSymbols.UnionWith(Symbols(node)); } base.Visit(node); } } // TupleExpression "(a, b) = qix" // ParenthesizedVariableDesignation "var (a, b) = quix" inside a DeclarationExpression public override void VisitAssignmentExpression(AssignmentExpressionSyntax node) { var leftTupleCount = GetTupleCount(node.Left); if (leftTupleCount != 0) { var assignmentRight = node.Right; var namedTypeSymbol = model.GetSymbolInfo(assignmentRight).Symbol?.GetSymbolType(); if (namedTypeSymbol is not null) { var deconstructors = namedTypeSymbol.GetMembers("Deconstruct"); if (deconstructors.Length == 1) { UsedSymbols.Add(deconstructors.First()); } else if (deconstructors.Length > 1 && FindDeconstructor(deconstructors, leftTupleCount) is { } deconstructor) { UsedSymbols.Add(deconstructor); } } } base.VisitAssignmentExpression(node); static int GetTupleCount(ExpressionSyntax assignmentLeft) { var result = 0; if (TupleExpressionSyntaxWrapper.IsInstance(assignmentLeft)) { result = ((TupleExpressionSyntaxWrapper)assignmentLeft).Arguments.Count; } else if (DeclarationExpressionSyntaxWrapper.IsInstance(assignmentLeft) && (DeclarationExpressionSyntaxWrapper)assignmentLeft is { } leftDeclaration && ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(leftDeclaration.Designation)) { result = ((ParenthesizedVariableDesignationSyntaxWrapper)leftDeclaration.Designation).Variables.Count; } return result; } static ISymbol FindDeconstructor(IEnumerable deconstructors, int numberOfArguments) => deconstructors.FirstOrDefault(x => x.GetParameters().Count() == numberOfArguments && x.DeclaredAccessibility.IsAccessibleOutsideTheType()); } public override void VisitAttribute(AttributeSyntax node) { // Some members that seem unused might be dynamically accessed through reflection. // The DynamicallyAccessedMembersAttribute was introduced to inform tools about such uses. // The attribute is not available on NetFramework and we want to enable this mechanism for these users. // Therefore, we only check the name, but not the namespace and let the users define their own custom version. // https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute const string DynamicallyAccessedMembers = "DynamicallyAccessedMembers"; if (node.GetName() is DynamicallyAccessedMembers or $"{DynamicallyAccessedMembers}Attribute" && node is { Parent: AttributeListSyntax { Parent: BaseTypeDeclarationSyntax typeDeclaration } } && model.GetDeclaredSymbol(typeDeclaration) is { } typeSymbol) { TypesUsedWithReflection.Add(typeSymbol); } else if (model.GetSymbolInfo(node).Symbol is IMethodSymbol { MethodKind: MethodKind.Constructor, ContainingType: ITypeSymbol attribute }) { if (attribute.Is(KnownType.System_Diagnostics_DebuggerDisplayAttribute) && node.ArgumentList is not null) { var arguments = node.ArgumentList.Arguments .Where(IsValueNameOrType) .Select(x => model.GetConstantValue(x.Expression)) .Where(x => x.HasValue) .Select(x => x.Value) .OfType(); DebuggerDisplayValues.UnionWith(arguments); } else if (attribute.GetEffectiveAccessibility() == Accessibility.Private) { PrivateAttributes.Add(attribute); } } base.VisitAttribute(node); static bool IsValueNameOrType(AttributeArgumentSyntax a) => a.NameColon is null // Value || a.NameColon.Name.Identifier.ValueText == "Value" || a.NameColon.Name.Identifier.ValueText == "Name" || a.NameColon.Name.Identifier.ValueText == "Type"; } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (IsKnownIdentifier(node.Identifier)) { var symbols = Symbols(node); TryStoreFieldAccess(node, symbols); UsedSymbols.UnionWith(symbols); TryStorePropertyAccess(node, symbols); } base.VisitIdentifierName(node); } public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node) { if (knownSymbolNames.Contains(node.Type.GetName())) { UsedSymbols.UnionWith(Symbols(node)); } base.VisitObjectCreationExpression(node); } public override void VisitGenericName(GenericNameSyntax node) { if (IsKnownIdentifier(node.Identifier)) { UsedSymbols.UnionWith(Symbols(node)); } base.VisitGenericName(node); } public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) { if (node.Expression.IsKind(SyntaxKind.ThisExpression) || knownSymbolNames.Contains(node.Expression.GetIdentifier()?.ValueText) || knownSymbolNames.Contains(model.GetTypeInfo(node.Expression).Type?.Name)) { var symbols = Symbols(node); UsedSymbols.UnionWith(symbols); TryStorePropertyAccess(node, symbols); } base.VisitElementAccessExpression(node); } public override void VisitConstructorInitializer(ConstructorInitializerSyntax node) { // In this case (":base()") we cannot check at the syntax level if the constructor name is in the list // of known names so we have to check for symbols. UsedSymbols.UnionWith(Symbols(node)); base.VisitConstructorInitializer(node); } public override void VisitInitializerExpression(InitializerExpressionSyntax node) { // Collection initializers implicitly call Add() methods that don't appear in the syntax tree. if (node.IsKind(SyntaxKind.CollectionInitializerExpression) && knownSymbolNames.Contains("Add")) { UsedSymbols.UnionWith(node.Expressions.SelectMany(x => ResolveSymbols(model.GetCollectionInitializerSymbolInfo(x)))); } base.VisitInitializerExpression(node); } public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { // We are visiting a ctor with no initializer and the compiler will automatically // call the default constructor of the type if declared, or the base type if the // current type does not declare a default constructor. if (node.Initializer is null && IsKnownIdentifier(node.Identifier)) { var constructor = model.GetDeclaredSymbol(node); var implicitlyCalledConstructor = ImplicitlyCalledConstructor(constructor); if (implicitlyCalledConstructor is not null) { UsedSymbols.Add(implicitlyCalledConstructor); } } base.VisitConstructorDeclaration(node); } public override void VisitVariableDeclarator(VariableDeclaratorSyntax node) { if (IsKnownIdentifier(node.Identifier)) { var usage = FieldSymbolUsage(model.GetDeclaredSymbol(node)); usage.Declaration = node; if (node.Initializer is not null) { usage.Initializer = node; } } base.VisitVariableDeclarator(node); } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { if (node.Initializer is not null && IsKnownIdentifier(node.Identifier)) { var symbol = model.GetDeclaredSymbol(node); UsedSymbols.Add(symbol); StorePropertyAccess(symbol, AccessorAccess.Set); } base.VisitPropertyDeclaration(node); } private SymbolAccess ParentAccessType(SyntaxNode node) => node.Parent switch { // (node) ParenthesizedExpressionSyntax parenthesizedExpression => ParentAccessType(parenthesizedExpression), // node; ExpressionStatementSyntax => SymbolAccess.None, // node(_) : InvocationExpressionSyntax invocation => node == invocation.Expression ? SymbolAccess.Read : SymbolAccess.None, // _.node : node._ MemberAccessExpressionSyntax memberAccess => node == memberAccess.Name ? ParentAccessType(memberAccess) : SymbolAccess.Read, // _?.node : node?._ MemberBindingExpressionSyntax memberBinding => node == memberBinding.Name ? ParentAccessType(memberBinding) : SymbolAccess.Read, // node ??= _ : _ ??= node AssignmentExpressionSyntax assignment when assignment.IsKind(SyntaxKindEx.CoalesceAssignmentExpression) => node == assignment.Left ? SymbolAccess.ReadWrite : SymbolAccess.Read, // Ignoring distinction assignmentExpression.IsKind(SyntaxKind.SimpleAssignmentExpression) between // "node = _" and "node += _" both are considered as Write and rely on the parent to know if its read. // node = _ : _ = node AssignmentExpressionSyntax assignment => node == assignment.Left ? SymbolAccess.Write | ParentAccessType(assignment) : SymbolAccess.Read, // Invocation(node), Invocation(out node), Invocation(ref node) ArgumentSyntax argument => ArgumentAccessType(argument), // node++ ExpressionSyntax expressionSyntax when expressionSyntax.IsAnyKind(IncrementKinds) => SymbolAccess.Write | ParentAccessType(expressionSyntax), // => node ArrowExpressionClauseSyntax arrowExpressionClause when arrowExpressionClause.Parent is MethodDeclarationSyntax arrowMethod => arrowMethod.ReturnType is not null && arrowMethod.ReturnType.IsKnownType(KnownType.Void, model) ? SymbolAccess.None : SymbolAccess.Read, _ => SymbolAccess.Read }; private static SymbolAccess ArgumentAccessType(ArgumentSyntax argument) => argument.RefOrOutKeyword.Kind() switch { // out Type node : out node SyntaxKind.OutKeyword => SymbolAccess.Write, // ref node SyntaxKind.RefKeyword => SymbolAccess.ReadWrite, _ => SymbolAccess.Read }; /// /// Given a node, it tries to get the symbol or the candidate symbols (if the compiler cannot find the symbol, /// .e.g when the code cannot compile). /// /// List of symbols. private ImmutableArray Symbols(TSyntaxNode node) where TSyntaxNode : SyntaxNode => ResolveSymbols(model.GetSymbolInfo(node)).ToImmutableArray(); private static IEnumerable ResolveSymbols(SymbolInfo symbolInfo) => new[] { symbolInfo.Symbol } .Concat(symbolInfo.CandidateSymbols) .Select(OriginalDefinition) .WhereNotNull(); private static ISymbol OriginalDefinition(ISymbol candidateSymbol) => candidateSymbol is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.ReducedExtension ? methodSymbol.ReducedFrom?.OriginalDefinition : candidateSymbol?.OriginalDefinition; private void TryStorePropertyAccess(ExpressionSyntax node, IEnumerable identifierSymbols) { var propertySymbols = identifierSymbols.OfType().ToList(); if (propertySymbols.Any()) { var access = EvaluatePropertyAccesses(node); foreach (var propertySymbol in propertySymbols) { StorePropertyAccess(propertySymbol, access); } } } private void StorePropertyAccess(IPropertySymbol propertySymbol, AccessorAccess access) { if (PropertyAccess.ContainsKey(propertySymbol)) { PropertyAccess[propertySymbol] |= access; } else { PropertyAccess[propertySymbol] = access; } } private AccessorAccess EvaluatePropertyAccesses(ExpressionSyntax node) { var topmostSyntax = TopmostSyntaxWithTheSameSymbol(node); if (topmostSyntax.Parent is AssignmentExpressionSyntax assignmentExpression) { if (assignmentExpression.IsKind(SyntaxKind.SimpleAssignmentExpression)) { // Prop = value --> set // value = Prop --> get return assignmentExpression.Left == topmostSyntax ? AccessorAccess.Set : AccessorAccess.Get; } else { // Prop += value --> get/set return AccessorAccess.Both; } } else if (topmostSyntax.Parent is ArgumentSyntax argument && argument.IsInTupleAssignmentTarget()) { return AccessorAccess.Set; } else if (node.IsInNameOfArgument(model)) { // nameof(Prop) --> get/set return AccessorAccess.Both; } else { // Prop++ --> get/set return topmostSyntax.Parent.IsAnyKind(IncrementKinds) ? AccessorAccess.Both : AccessorAccess.Get; } } private bool IsKnownIdentifier(SyntaxToken identifier) => knownSymbolNames.Contains(identifier.ValueText); private void TryStoreFieldAccess(IdentifierNameSyntax node, IEnumerable symbols) { var access = ParentAccessType(node); var fieldSymbolUsagesList = FieldSymbolUsagesList(symbols); if (HasFlag(access, SymbolAccess.Read)) { foreach (var symbolUsage in fieldSymbolUsagesList) { symbolUsage.Readings.Add(node); } } if (HasFlag(access, SymbolAccess.Write)) { foreach (var symbolUsage in fieldSymbolUsagesList) { symbolUsage.Writings.Add(node); } } static bool HasFlag(SymbolAccess symbolAccess, SymbolAccess flag) => (symbolAccess & flag) != 0; } private List FieldSymbolUsagesList(IEnumerable symbols) => symbols.Select(FieldSymbolUsage).ToList(); private SymbolUsage FieldSymbolUsage(ISymbol symbol) => FieldSymbolUsages.GetOrAdd(symbol, x => new SymbolUsage(x)); private static SyntaxNode TopmostSyntaxWithTheSameSymbol(SyntaxNode identifier) => // All of the cases below could be parts of invocation or other expressions identifier.Parent switch { // this.identifier or a.identifier or ((a)).identifier, but not identifier.other MemberAccessExpressionSyntax memberAccess when memberAccess.Name == identifier => memberAccess.GetSelfOrTopParenthesizedExpression(), // this?.identifier or a?.identifier or ((a))?.identifier, but not identifier?.other MemberBindingExpressionSyntax memberBinding when memberBinding.Name == identifier => memberBinding.Parent.GetSelfOrTopParenthesizedExpression(), // identifier or ((identifier)) _ => identifier.GetSelfOrTopParenthesizedExpression() }; private static IMethodSymbol ImplicitlyCalledConstructor(IMethodSymbol constructor) => // In case there is no other explicitly called constructor in a constructor declaration // the compiler will automatically put a call to the current class' default constructor, // or if the declaration is the default constructor or there is no default constructor, // the compiler will put a call the base class' default constructor. IsDefaultConstructor(constructor) ? DefaultConstructor(constructor.ContainingType.BaseType) : DefaultConstructor(constructor.ContainingType) ?? DefaultConstructor(constructor.ContainingType.BaseType); private static IMethodSymbol DefaultConstructor(INamedTypeSymbol namedType) => // See https://github.com/SonarSource/sonar-dotnet/issues/3155 namedType?.InstanceConstructors.FirstOrDefault(IsDefaultConstructor); private static bool IsDefaultConstructor(IMethodSymbol constructor) => constructor.Parameters.Length == 0; private static string Name(ISymbol symbol) => symbol.IsConstructor() ? symbol.ContainingType.Name : symbol.Name; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Walkers/CatchLoggingInvocationWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Roslyn.Utilities.SonarAnalyzer.Shared.LoggingFrameworkMethods; namespace SonarAnalyzer.CSharp.Walkers; // The walker is used to: // - visit the catch clause (all the nested catch clauses are skipped; they will be visited independently) // - save the declared exception // - save the logging invocation that uses the exception // - find all the logging invocations and check if the exception is logged // - if the exception is logged, it will stop looking for the other invocations and set IsExceptionLogged to true // - if the exception is not logged, it will visit all the invocations public class CatchLoggingInvocationWalker(SemanticModel model) : SafeCSharpSyntaxWalker { internal readonly SemanticModel Model = model; private bool isFirstCatchClauseVisited; private bool hasWhenFilterWithDeclarations; internal ISymbol CaughtException; public bool IsExceptionLogged { get; private set; } public InvocationExpressionSyntax LoggingInvocationWithException { get; private set; } public IList LoggingInvocationsWithoutException { get; } = new List(); private static readonly ImmutableArray LoggingInvocationDescriptors = ImmutableArray.Create( new LoggingInvocationDescriptor(MicrosoftExtensionsLogging, KnownType.Microsoft_Extensions_Logging_LoggerExtensions, false), new LoggingInvocationDescriptor(CastleCoreOrCommonCore, KnownType.Castle_Core_Logging_ILogger, true), new LoggingInvocationDescriptor(CastleCoreOrCommonCore, KnownType.Common_Logging_ILog, true), new LoggingInvocationDescriptor(Log4NetILog, KnownType.log4net_ILog, true), new LoggingInvocationDescriptor(Log4NetILogExtensions, KnownType.log4net_Util_ILogExtensions, false), new LoggingInvocationDescriptor(NLogLoggingMethods, KnownType.NLog_ILogger, true), new LoggingInvocationDescriptor(NLogLoggingMethods, KnownType.NLog_ILoggerExtensions, false), new LoggingInvocationDescriptor(NLogILoggerBase, KnownType.NLog_ILoggerBase, true), new LoggingInvocationDescriptor(Serilog, KnownType.Serilog_ILogger, true), new LoggingInvocationDescriptor(Serilog, KnownType.Serilog_Log, false)); public override void VisitCatchClause(CatchClauseSyntax node) { // We want to look for logging invocations only in the main catch clause. if (isFirstCatchClauseVisited) { return; } isFirstCatchClauseVisited = true; hasWhenFilterWithDeclarations = node.Filter != null && node.Filter.DescendantNodes().Any(DeclarationPatternSyntaxWrapper.IsInstance); if (node.Declaration != null && !node.Declaration.Identifier.IsKind(SyntaxKind.None)) { CaughtException = Model.GetDeclaredSymbol(node.Declaration); } base.VisitCatchClause(node); } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (!IsExceptionLogged && IsLoggingInvocation(node, Model)) { if (GetArgumentSymbolDerivedFromException(node, Model) is { } currentException && (hasWhenFilterWithDeclarations || currentException.Equals(CaughtException))) { IsExceptionLogged = true; LoggingInvocationWithException = node; return; } else { LoggingInvocationsWithoutException.Add(node); } } base.VisitInvocationExpression(node); } public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { // Skip processing to avoid false positives. } public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { // Skip processing to avoid false positives. } private static ISymbol GetArgumentSymbolDerivedFromException(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.ArgumentList.Arguments .Where(x => semanticModel.GetTypeInfo(x.Expression).Type.DerivesFrom(KnownType.System_Exception)) .Select(x => x.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.NameIs("InnerException") ? semanticModel.GetSymbolInfo(memberAccess.Expression).Symbol : semanticModel.GetSymbolInfo(x.Expression).Symbol) .FirstOrDefault(); private static bool IsLoggingInvocation(InvocationExpressionSyntax invocation, SemanticModel model) => LoggingInvocationDescriptors.Any(x => IsLoggingInvocation(invocation, model, x.MethodNames, x.ContainingType, x.CheckDerivedTypes)); private static bool IsLoggingInvocation(InvocationExpressionSyntax invocation, SemanticModel model, ICollection methodNames, KnownType containingType, bool checkDerivedTypes) => methodNames.Contains(invocation.GetIdentifier().ToString()) && model.GetSymbolInfo(invocation).Symbol is { } invocationSymbol && invocationSymbol.HasContainingType(containingType, checkDerivedTypes); private record struct LoggingInvocationDescriptor(HashSet MethodNames, KnownType ContainingType, bool CheckDerivedTypes); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/Walkers/ParameterValidationInMethodWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Walkers; internal class ParameterValidationInMethodWalker : SafeCSharpSyntaxWalker { private static readonly ISet SubMethodEquivalents = new HashSet { SyntaxKindEx.LocalFunctionStatement, // Local function SyntaxKind.SimpleLambdaExpression, // Action SyntaxKind.ParenthesizedLambdaExpression // Func }; private readonly SemanticModel model; private readonly List argumentExceptionLocations = new(); protected bool keepWalking = true; public IEnumerable ArgumentExceptionLocations => argumentExceptionLocations; protected virtual string SecondaryMessage => null; public ParameterValidationInMethodWalker(SemanticModel model) => this.model = model; public override void Visit(SyntaxNode node) { if (keepWalking && !node.IsAnyKind(SubMethodEquivalents)) // Don't explore deeper if this node is equivalent to a method declaration { base.Visit(node); } } public override void VisitThrowStatement(ThrowStatementSyntax node) { // When throw is like `throw new XXX` where XXX derives from ArgumentException, save location if (node.Expression is not null && model.GetTypeInfo(node.Expression) is var typeInfo && typeInfo.Type.DerivesFrom(KnownType.System_ArgumentException)) { argumentExceptionLocations.Add(node.Expression.ToSecondaryLocation(SecondaryMessage)); } // there is no need to visit children } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.IsMemberAccessOnKnownType("ThrowIfNull", KnownType.System_ArgumentNullException, model)) { // "ThrowIfNull" returns void so it cannot be an argument. We can stop. argumentExceptionLocations.Add(node.ToSecondaryLocation(SecondaryMessage)); } else { // Need to check the children of this node because of the pattern (await SomeTask()).Invocation() base.VisitInvocationExpression(node); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json ================================================ { "rules-metadata-path": "../../rspec/cs/", "languages": [ "CSH" ], "latest-update": "2026-04-27T06:50:49.973042900Z", "options": { "no-language-in-filenames": true } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Common/DescriptorFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.Rspec; namespace SonarAnalyzer.CSharp.Core.Common; public static class DescriptorFactory { public static DiagnosticDescriptor Create(string id, string messageFormat, bool? isEnabledByDefault = null, bool fadeOutCode = false, bool isCompilationEnd = false) => // RuleCatalog class is created from SonarAnalyzer.SourceGenerator DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, RuleCatalog.Rules[id], messageFormat, isEnabledByDefault, fadeOutCode, isCompilationEnd); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Common/SyntaxConstants.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Common; public static class SyntaxConstants { public const string Discard = "_"; public const string Private = "private"; public const string Protected = "protected"; public const string Internal = "internal"; public const string NameOfKeywordText = "nameof"; public static readonly ExpressionSyntax NullLiteralExpression = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); public static readonly ExpressionSyntax FalseLiteralExpression = SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression); public static readonly ExpressionSyntax TrueLiteralExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/CSharpCompilationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class CSharpCompilationExtensions { public static bool IsCoalesceAssignmentSupported(this Compilation compilation) => compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp8); public static bool IsTargetTypeConditionalSupported(this Compilation compilation) => compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp9); public static bool IsLambdaDiscardParameterSupported(this Compilation compilation) => compilation.IsAtLeastLanguageVersion(LanguageVersionEx.CSharp9); public static bool IsAtLeastLanguageVersion(this Compilation compilation, LanguageVersion languageVersion) => compilation.GetLanguageVersion().IsAtLeast(languageVersion); public static LanguageVersion GetLanguageVersion(this Compilation compilation) => ((CSharpCompilation)compilation).LanguageVersion; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/IAnalyzerConfigurationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class IAnalyzerConfigurationExtensions { public static bool UseSonarCfg(this IAnalyzerConfiguration configuration) => configuration.ForceSonarCfg || !CFG.Roslyn.ControlFlowGraph.IsAvailable; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/ICompilationReportExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; // Don't change the this parameter to (this IAnalysisContext context) because it would cause boxing public static class ICompilationReportExtensions { extension(TContext context) where TContext : ICompilationReport { public void ReportIssue(DiagnosticDescriptor rule, SyntaxNode locationSyntax, params string[] messageArgs) => context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, rule, locationSyntax, messageArgs); public void ReportIssue(DiagnosticDescriptor rule, SyntaxToken locationToken, params string[] messageArgs) => context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, rule, locationToken, messageArgs); public void ReportIssue(DiagnosticDescriptor rule, Location location, params string[] messageArgs) => context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, rule, location, messageArgs); public void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations, params string[] messageArgs) => context.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, rule, primaryLocation, secondaryLocations, messageArgs); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/IFieldSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class IFieldSymbolExtensions { public static bool IsNonStaticNonPublicDisposableField(this IFieldSymbol fieldSymbol, LanguageVersion languageVersion) => fieldSymbol != null && !fieldSymbol.IsStatic && (fieldSymbol.DeclaredAccessibility == Accessibility.Protected || fieldSymbol.DeclaredAccessibility == Accessibility.Private) && IsDisposable(fieldSymbol, languageVersion); private static bool IsDisposable(this IFieldSymbol fieldSymbol, LanguageVersion languageVersion) => fieldSymbol.Type.Is(KnownType.System_IDisposable) || fieldSymbol.Type.Implements(KnownType.System_IDisposable) || fieldSymbol.Type.Is(KnownType.System_IAsyncDisposable) || fieldSymbol.Type.Implements(KnownType.System_IAsyncDisposable) || fieldSymbol.Type.IsDisposableRefStruct(languageVersion); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/ILocalSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class ILocalSymbolExtensions { private static readonly Func RefKindAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(ILocalSymbol), nameof(RefKind)); public static RefKind RefKind(this ILocalSymbol symbol) => RefKindAccessor(symbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/IMethodSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class IMethodSymbolExtensions { public static bool IsModuleInitializer(this IMethodSymbol methodSymbol) => methodSymbol.AnyAttributeDerivesFrom(KnownType.System_Runtime_CompilerServices_ModuleInitializerAttribute); public static bool IsGetTypeCall(this IMethodSymbol invokedMethod) => invokedMethod.Name == nameof(Type.GetType) && !invokedMethod.IsStatic && invokedMethod.ContainingType is not null && IsObjectOrType(invokedMethod.ContainingType); public static SyntaxNode ImplementationSyntax(this IMethodSymbol method) => (method.PartialImplementationPart ?? method).DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); private static bool IsObjectOrType(ITypeSymbol namedType) => namedType.SpecialType == SpecialType.System_Object || namedType.Is(KnownType.System_Type); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/ISymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class ISymbolExtensions { private static readonly SyntaxKind[] DeclarationsTypesWithPrimaryConstructor = { SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration }; extension(ISymbol symbol) { public SyntaxToken? FirstDeclaringReferenceIdentifier => symbol.DeclaringReferenceIdentifiers.FirstOrDefault(); public ImmutableArray DeclaringReferenceIdentifiers => symbol.DeclaringSyntaxReferences .Select(x => x.GetSyntax().GetIdentifier()) .WhereNotNull() .ToImmutableArray(); public bool IsPrimaryConstructor => symbol.IsConstructor() && symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is { } syntax && DeclarationsTypesWithPrimaryConstructor.Contains(syntax.Kind()); public IEnumerable LocationNodes(SyntaxNode node) => symbol.Locations.SelectMany(location => GetDescendantNodes(location, node)); } private static IEnumerable GetDescendantNodes(Location location, SyntaxNode invocation) { var locationRootNode = location.SourceTree?.GetRoot(); var invocationRootNode = invocation.SyntaxTree.GetRoot(); // We don't look for descendants when the location is outside the current context root if (locationRootNode != null && locationRootNode != invocationRootNode) { return Enumerable.Empty(); } // To optimise, we search first for the class constructor, then for the method declaration. // If these cannot be found (e.g. fields), we get the root of the syntax tree and search from there. var root = locationRootNode?.FindNode(location.SourceSpan) ?? invocation.FirstAncestorOrSelf() ?? invocationRootNode; return root.DescendantNodes(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/ITupleOperationWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class ITupleOperationWrapperExtensions { public static ImmutableArray AllElements(this ITupleOperationWrapper tuple) { var arrayBuilder = ImmutableArray.CreateBuilder(); CollectTupleElements(tuple); return arrayBuilder.ToImmutableArray(); void CollectTupleElements(ITupleOperationWrapper tuple) { foreach (var element in tuple.Elements) { if (ITupleOperationWrapper.IsInstance(element)) { CollectTupleElements(ITupleOperationWrapper.FromOperation(element)); } else { arrayBuilder.Add(element); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/ITypeSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class ITypeSymbolExtensions { public static bool IsDisposableRefStruct(this ITypeSymbol symbol, LanguageVersion languageVersion) => languageVersion.IsAtLeast(LanguageVersionEx.CSharp8) && IsRefStruct(symbol) && symbol.GetMembers(nameof(IDisposable.Dispose)).Any(x => x.DeclaredAccessibility == Accessibility.Public && KnownMethods.IsIDisposableDispose(x as IMethodSymbol)); public static bool IsRefStruct(this ITypeSymbol symbol) => symbol is not null && symbol.IsStruct() && symbol.IsRefLikeType(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/LanguageVersionExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; internal static class LanguageVersionExtensions { internal static bool IsAtLeast(this LanguageVersion left, LanguageVersion right) => left.CompareTo(right) >= 0; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/SonarAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class SonarAnalysisContextExtensions { extension(SonarAnalysisContext context) { public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, action, syntaxKinds); public void RegisterTreeAction(Action action) => context.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, action); public void RegisterSemanticModelAction(Action action) => context.RegisterSemanticModelAction(CSharpGeneratedCodeRecognizer.Instance, action); public void RegisterCodeBlockStartAction(Action> action) => context.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, action); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/SonarCompilationStartAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class SonarCompilationStartAnalysisContextExtensions { extension(SonarCompilationStartAnalysisContext context) { public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, action, syntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/SonarParametrizedAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class SonarParametrizedAnalysisContextExtensions { extension(SonarParametrizedAnalysisContext context) { public void RegisterTreeAction(Action action) => context.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, action); public void RegisterSemanticModelAction(Action action) => context.RegisterSemanticModelAction(CSharpGeneratedCodeRecognizer.Instance, action); public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, action, syntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Extensions/SonarSyntaxNodeReportingContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Extensions; public static class SonarSyntaxNodeReportingContextExtensions { extension(SonarSyntaxNodeReportingContext context) { public bool IsTopLevelMain => context.Node is CompilationUnitSyntax compilationUnitSyntax && compilationUnitSyntax.IsTopLevelMain() && context.ContainingSymbol.IsGlobalNamespace(); // Needed to avoid the duplicate calls from Roslyn 4.0.0 public bool IsInExpressionTree() => context.Node.IsInExpressionTree(context.Model); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Facade/CSharpFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Facade.Implementation; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Facade; public sealed class CSharpFacade : ILanguageFacade { private static readonly Lazy Singleton = new(() => new CSharpFacade()); private static readonly Lazy AssignmentFinderLazy = new(() => new CSharpAssignmentFinder()); private static readonly Lazy ExpressionNumericConverterLazy = new(() => new CSharpExpressionNumericConverter()); private static readonly Lazy> SyntaxLazy = new(() => new CSharpSyntaxFacade()); private static readonly Lazy> SyntaxKindLazy = new(() => new CSharpSyntaxKindFacade()); private static readonly Lazy> TrackerLazy = new(() => new CSharpTrackerFacade()); public static CSharpFacade Instance => Singleton.Value; public AssignmentFinder AssignmentFinder => AssignmentFinderLazy.Value; public StringComparison NameComparison => StringComparison.Ordinal; public StringComparer NameComparer => StringComparer.Ordinal; public GeneratedCodeRecognizer GeneratedCodeRecognizer => CSharpGeneratedCodeRecognizer.Instance; public IExpressionNumericConverter ExpressionNumericConverter => ExpressionNumericConverterLazy.Value; public SyntaxFacade Syntax => SyntaxLazy.Value; public ISyntaxKindFacade SyntaxKind => SyntaxKindLazy.Value; public ITrackerFacade Tracker => TrackerLazy.Value; private CSharpFacade() { } public DiagnosticDescriptor CreateDescriptor(string id, string messageFormat, bool? isEnabledByDefault = null, bool fadeOutCode = false) => DescriptorFactory.Create(id, messageFormat, isEnabledByDefault, fadeOutCode); public object FindConstantValue(SemanticModel model, SyntaxNode node) => node.FindConstantValue(model); public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, IMethodSymbol methodSymbol) => invocation switch { null => null, AttributeSyntax x => new CSharpAttributeParameterLookup(x, methodSymbol), _ => new CSharpMethodParameterLookup(invocation.ArgumentList(), methodSymbol), }; public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, SemanticModel model) => invocation?.ArgumentList() is { } argumentList ? new CSharpMethodParameterLookup(argumentList, model) : null; public string GetName(SyntaxNode expression) => expression.GetName(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Facade/Implementation/CSharpSyntaxFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using ComparisonKindEnum = SonarAnalyzer.Core.Syntax.Utilities.ComparisonKind; namespace SonarAnalyzer.CSharp.Core.Facade.Implementation; internal sealed class CSharpSyntaxFacade : SyntaxFacade { public override bool AreEquivalent(SyntaxNode firstNode, SyntaxNode secondNode) => SyntaxFactory.AreEquivalent(firstNode, secondNode); public override IEnumerable ArgumentExpressions(SyntaxNode node) => ArgumentList(node)?.OfType().Select(x => x.Expression) ?? Enumerable.Empty(); public override IReadOnlyList ArgumentList(SyntaxNode node) => node.ArgumentList()?.Arguments; public override int? ArgumentIndex(SyntaxNode argument) => Cast(argument).GetArgumentIndex(); public override SyntaxToken? ArgumentNameColon(SyntaxNode argument) => (argument as ArgumentSyntax)?.NameColon?.Name.Identifier; public override SyntaxNode AssignmentLeft(SyntaxNode assignment) => Cast(assignment).Left; public override SyntaxNode AssignmentRight(SyntaxNode assignment) => Cast(assignment).Right; public override ImmutableArray AssignmentTargets(SyntaxNode assignment) => Cast(assignment).AssignmentTargets(); public override SyntaxNode BinaryExpressionLeft(SyntaxNode binary) => Cast(binary).Left; public override SyntaxNode BinaryExpressionRight(SyntaxNode binary) => Cast(binary).Right; public override SyntaxNode CastType(SyntaxNode cast) => Cast(cast).Type; public override SyntaxNode CastExpression(SyntaxNode cast) => Cast(cast).Expression; public override ComparisonKind ComparisonKind(SyntaxNode node) => node is BinaryExpressionSyntax { OperatorToken: var token } ? token.ToComparisonKind() : ComparisonKindEnum.None; public override IEnumerable EnumMembers(SyntaxNode @enum) => @enum is null ? Enumerable.Empty() : Cast(@enum).Members; public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declaration.Variables.Select(x => x.Identifier).ToImmutableArray(); public override bool HasExactlyNArguments(SyntaxNode invocation, int count) => Cast(invocation).HasExactlyNArguments(count); public override SyntaxToken? InvocationIdentifier(SyntaxNode invocation) => invocation is null ? null : Cast(invocation).GetMethodCallIdentifier(); public override bool IsAnyKind(SyntaxNode node, ISet syntaxKinds) => node.IsAnyKind(syntaxKinds); [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public override bool IsAnyKind(SyntaxNode node, params SyntaxKind[] syntaxKinds) => node.IsAnyKind(syntaxKinds); public override bool IsAnyKind(SyntaxTrivia trivia, ISet syntaxKinds) => trivia.IsAnyKind(syntaxKinds); public override bool IsInExpressionTree(SemanticModel model, SyntaxNode node) => node.IsInExpressionTree(model); public override bool IsKind(SyntaxNode node, SyntaxKind kind) => node.IsKind(kind); public override bool IsKind(SyntaxToken token, SyntaxKind kind) => token.IsKind(kind); public override bool IsKind(SyntaxTrivia trivia, SyntaxKind kind) => trivia.IsKind(kind); public override bool IsKnownAttributeType(SemanticModel model, SyntaxNode attribute, KnownType knownType) => AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownType, model); public override bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string name, KnownType knownType, SemanticModel model) => Cast(memberAccess).IsMemberAccessOnKnownType(name, knownType, model); public override bool IsNullLiteral(SyntaxNode node) => node.IsNullLiteral(); public override bool IsPartOfBinaryNegationOrCondition(SyntaxNode node) => node.IsPartOfBinaryNegationOrCondition(); public override bool IsStatic(SyntaxNode node) => Cast(node).IsStatic(); /// public override bool IsWrittenTo(SyntaxNode expression, SemanticModel model, CancellationToken cancel) => Cast(expression).IsWrittenTo(model, cancel); public override SyntaxKind Kind(SyntaxNode node) => node.Kind(); public override string LiteralText(SyntaxNode literal) => Cast(literal).Token.ValueText; public override ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declaration.Variables.Select(x => x.Identifier).ToImmutableArray(); public override SyntaxKind[] ModifierKinds(SyntaxNode node) => (node switch { TypeDeclarationSyntax x => x.Modifiers, BaseMethodDeclarationSyntax x => x.Modifiers, _ => [], }).Select(x => x.Kind()).ToArray(); public override SyntaxNode NodeExpression(SyntaxNode node) => node switch { ArrowExpressionClauseSyntax x => x.Expression, ArgumentSyntax x => x.Expression, AttributeArgumentSyntax x => x.Expression, InterpolationSyntax x => x.Expression, InvocationExpressionSyntax x => x.Expression, LockStatementSyntax x => x.Expression, ReturnStatementSyntax x => x.Expression, MemberAccessExpressionSyntax x => x.Expression, null => null, _ => throw InvalidOperation(node, nameof(NodeExpression)) }; public override SyntaxToken? NodeIdentifier(SyntaxNode node) => node.GetIdentifier(); public override SyntaxToken? ObjectCreationTypeIdentifier(SyntaxNode objectCreation) => objectCreation is null ? null : Cast(objectCreation).GetObjectCreationTypeIdentifier(); public override SyntaxNode RemoveConditionalAccess(SyntaxNode node) => node is ExpressionSyntax expression ? expression.RemoveConditionalAccess() : node; public override SyntaxNode RemoveParentheses(SyntaxNode node) => node.RemoveParentheses(); public override string StringValue(SyntaxNode node, SemanticModel model) => node.StringValue(model); public override string InterpolatedTextValue(SyntaxNode node, SemanticModel model) => Cast(node).InterpolatedTextValue(model); public override Pair Operands(SyntaxNode invocation) => Cast(invocation).Operands(); public override SyntaxNode ParseExpression(string expression) => SyntaxFactory.ParseExpression(expression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Facade/Implementation/CSharpSyntaxKindFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Facade.Implementation; internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade { public SyntaxKind Attribute => SyntaxKind.Attribute; public SyntaxKind AttributeArgument => SyntaxKind.AttributeArgument; public SyntaxKind[] CastExpressions => new[] {SyntaxKind.CastExpression }; public SyntaxKind ClassDeclaration => SyntaxKind.ClassDeclaration; public SyntaxKind[] ClassAndRecordDeclarations => new[] { SyntaxKind.ClassDeclaration, SyntaxKindEx.RecordDeclaration, }; public SyntaxKind[] ClassAndModuleDeclarations => new[] { SyntaxKind.ClassDeclaration }; public SyntaxKind[] ComparisonKinds => new[] { SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, }; public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorDeclaration; public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.DefaultExpression, SyntaxKindEx.DefaultLiteralExpression }; public SyntaxKind EnumDeclaration => SyntaxKind.EnumDeclaration; public SyntaxKind EndOfLineTrivia => SyntaxKind.EndOfLineTrivia; public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration; public SyntaxKind IdentifierName => SyntaxKind.IdentifierName; public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken; public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression; public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression; public SyntaxKind LeftShiftAssignmentStatement => SyntaxKind.LeftShiftAssignmentExpression; public SyntaxKind LeftShiftExpression => SyntaxKind.LeftShiftExpression; public SyntaxKind LocalDeclaration => SyntaxKind.LocalDeclarationStatement; public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.MethodDeclaration }; 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 RightShiftAssignmentStatement => SyntaxKind.RightShiftAssignmentExpression; public SyntaxKind RightShiftExpression => SyntaxKind.RightShiftExpression; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentExpression; public SyntaxKind SimpleCommentTrivia => SyntaxKind.SingleLineCommentTrivia; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression, SyntaxKindEx.Utf8StringLiteralExpression }; public SyntaxKind StructDeclaration => SyntaxKind.StructDeclaration; public SyntaxKind SubtractExpression => SyntaxKind.SubtractExpression; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.EnumDeclaration, SyntaxKindEx.RecordDeclaration, SyntaxKindEx.RecordStructDeclaration, }; public SyntaxKind VariableDeclarator => SyntaxKind.VariableDeclarator; public SyntaxKind[] LocalDeclarationKinds => [ SyntaxKind.VariableDeclarator, SyntaxKindEx.SingleVariableDesignation, // x is T y, out var x, var (a,b)=..., patterns SyntaxKind.CatchDeclaration, // catch (Exception e) SyntaxKind.ForEachStatement, // foreach (var x in ...) SyntaxKind.FromClause, // from x in ... SyntaxKind.JoinClause, // join x in coll on a equals b SyntaxKind.JoinIntoClause, // join ... into x SyntaxKind.LetClause, // let x = ... SyntaxKind.QueryContinuation, // ... into x select ]; public SyntaxKind WhitespaceTrivia => SyntaxKind.WhitespaceTrivia; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Facade/Implementation/CSharpTrackerFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.CSharp.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Facade.Implementation; internal sealed class CSharpTrackerFacade : ITrackerFacade { public ArgumentTracker Argument { get; } = new CSharpArgumentTracker(); public BaseTypeTracker BaseType { get; } = new CSharpBaseTypeTracker(); public ElementAccessTracker ElementAccess { get; } = new CSharpElementAccessTracker(); public FieldAccessTracker FieldAccess { get; } = new CSharpFieldAccessTracker(); public InvocationTracker Invocation { get; } = new CSharpInvocationTracker(); public MethodDeclarationTracker MethodDeclaration { get; } = new CSharpMethodDeclarationTracker(); public ObjectCreationTracker ObjectCreation { get; } = new CSharpObjectCreationTracker(); public PropertyAccessTracker PropertyAccess { get; } = new CSharpPropertyAccessTracker(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/LiveVariableAnalysis/SonarCSharpLiveVariableAnalysis.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.CSharp.Core.LiveVariableAnalysis; public sealed class SonarCSharpLiveVariableAnalysis : LiveVariableAnalysisBase { private readonly SemanticModel model; protected override Block ExitBlock => Cfg.ExitBlock; public SonarCSharpLiveVariableAnalysis(IControlFlowGraph controlFlowGraph, ISymbol originalDeclaration, SemanticModel model, CancellationToken cancel) : base(controlFlowGraph, originalDeclaration, cancel) { this.model = model; Analyze(); } public override bool IsLocal(ISymbol symbol) { return IsLocalOrParameterSymbol() && symbol.ContainingSymbol is not null && symbol.ContainingSymbol.Equals(originalDeclaration); bool IsLocalOrParameterSymbol() => (symbol is ILocalSymbol local && local.RefKind() == RefKind.None) || (symbol is IParameterSymbol parameter && parameter.RefKind == RefKind.None); } public static bool IsOutArgument(IdentifierNameSyntax identifier) => identifier.GetFirstNonParenthesizedParent() is ArgumentSyntax argument && argument.RefOrOutKeyword.IsKind(SyntaxKind.OutKeyword); protected override IEnumerable ReversedBlocks() => Cfg.Blocks.Reverse(); protected override IEnumerable Successors(Block block) => block.SuccessorBlocks; protected override IEnumerable Predecessors(Block block) => block.PredecessorBlocks; protected override State ProcessBlock(Block block) { var ret = new SonarState(this, model); ret.ProcessBlock(block); return ret; } private sealed class SonarState : State { private readonly SonarCSharpLiveVariableAnalysis owner; private readonly SemanticModel model; public ISet AssignmentLhs { get; } = new HashSet(); public SonarState(SonarCSharpLiveVariableAnalysis owner, SemanticModel model) { this.owner = owner; this.model = model; } public void ProcessBlock(Block block) { foreach (var instruction in block.Instructions.Reverse()) { switch (instruction.Kind()) { case SyntaxKind.IdentifierName: ProcessIdentifier((IdentifierNameSyntax)instruction); break; case SyntaxKind.GenericName: ProcessGenericName((GenericNameSyntax)instruction); break; case SyntaxKind.SimpleAssignmentExpression: ProcessSimpleAssignment((AssignmentExpressionSyntax)instruction); break; case SyntaxKind.VariableDeclarator: ProcessVariableDeclarator((VariableDeclaratorSyntax)instruction); break; case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.QueryExpression: CollectAllCapturedLocal(instruction); break; } } if (block.Instructions.Any()) { return; } // Variable declaration in a foreach statement is not a VariableDeclarator, so handling it separately: if (block is BinaryBranchBlock foreachBlock && foreachBlock.BranchingNode.IsKind(SyntaxKind.ForEachStatement)) { var foreachNode = (ForEachStatementSyntax)foreachBlock.BranchingNode; ProcessVariableInForeach(foreachNode); } // Keep alive the variables declared and used in the using statement until the UsingFinalizerBlock if (block is UsingEndBlock usingFinalizerBlock) { var disposableSymbols = usingFinalizerBlock.Identifiers .Select(x => model.GetDeclaredSymbol(x.Parent) ?? model.GetSymbolInfo(x.Parent).Symbol) .WhereNotNull(); foreach (var disposableSymbol in disposableSymbols) { UsedBeforeAssigned.Add(disposableSymbol); } } } private void ProcessVariableInForeach(ForEachStatementSyntax foreachNode) { if (model.GetDeclaredSymbol(foreachNode) is { } symbol) { Assigned.Add(symbol); UsedBeforeAssigned.Remove(symbol); } } private void ProcessVariableDeclarator(VariableDeclaratorSyntax instruction) { if (model.GetDeclaredSymbol(instruction) is { } symbol) { Assigned.Add(symbol); UsedBeforeAssigned.Remove(symbol); } } private void ProcessSimpleAssignment(AssignmentExpressionSyntax assignment) { var left = assignment.Left.RemoveParentheses(); if (left.IsKind(SyntaxKind.IdentifierName) && model.GetSymbolInfo(left).Symbol is { } symbol && owner.IsLocal(symbol)) { AssignmentLhs.Add(left); Assigned.Add(symbol); UsedBeforeAssigned.Remove(symbol); } } private void ProcessIdentifier(IdentifierNameSyntax identifier) { if (!identifier.GetSelfOrTopParenthesizedExpression().IsInNameOfArgument(model) && model.GetSymbolInfo(identifier).Symbol is { } symbol) { if (owner.IsLocal(symbol)) { if (IsOutArgument(identifier)) { Assigned.Add(symbol); UsedBeforeAssigned.Remove(symbol); } else if (!AssignmentLhs.Contains(identifier)) { UsedBeforeAssigned.Add(symbol); } } if (symbol is IMethodSymbol { MethodKind: MethodKindEx.LocalFunction }) { ProcessLocalFunction(symbol); } } } private void ProcessGenericName(GenericNameSyntax genericName) { if (!genericName.GetSelfOrTopParenthesizedExpression().IsInNameOfArgument(model) && model.GetSymbolInfo(genericName).Symbol is IMethodSymbol { MethodKind: MethodKindEx.LocalFunction } method) { ProcessLocalFunction(method); } } private void ProcessLocalFunction(ISymbol symbol) { if (!ProcessedLocalFunctions.Contains(symbol) && symbol.DeclaringSyntaxReferences.Length == 1 && symbol.DeclaringSyntaxReferences.Single().GetSyntax() is { } node && LocalFunctionStatementSyntaxWrapper.IsInstance(node) && CSharpControlFlowGraph.TryGet((LocalFunctionStatementSyntaxWrapper)node, model, out var cfg)) { ProcessedLocalFunctions.Add(symbol); foreach (var block in cfg.Blocks.Reverse()) { ProcessBlock(block); } } } private void CollectAllCapturedLocal(SyntaxNode instruction) { var allCapturedSymbols = instruction.DescendantNodes() .OfType() .Select(x => model.GetSymbolInfo(x).Symbol) .Where(owner.IsLocal); // Collect captured locals // Read and write both affects liveness Captured.UnionWith(allCapturedSymbols); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.CSharp.Core.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/RegularExpressions/MessageTemplatesParser.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Core.RegularExpressions; /// /// Grammar can be found here. /// public static class MessageTemplatesParser { private const string NamePattern = "[0-9a-zA-Z_]+"; private const string PlaceholderPattern = $"(?{NamePattern})"; private const string AlignmentPattern = "(,-?[0-9]+)?"; private const string FormatPattern = @"(:[^}]+)?"; private const string HolePattern = "{" + "[@$]?" + PlaceholderPattern + AlignmentPattern + FormatPattern + "}"; private const string TextPattern = @"([^{]|{{|}})+"; private const string TemplatePattern = $"^({TextPattern}|{HolePattern})*$"; internal static readonly Regex TemplateRegex = new(TemplatePattern, RegexOptions.Compiled, TimeSpan.FromMilliseconds(300)); internal static readonly Regex InterpolatedTemplateRegex = new(TemplatePattern.Replace("{", "{{").Replace("}", "}}"), RegexOptions.Compiled, TimeSpan.FromMilliseconds(300)); /// /// Matches and extracts placeholders from a template string. /// For more info, see Message Templates. /// public static ParseResult Parse(ExpressionSyntax template) { var regex = TemplateRegex; var text = template.ToString(); if (template is InterpolatedStringExpressionSyntax interpolated) { // We cannot use SyntaxNodeExtensionsCSharp.StringValue() because it changes the length of the string. // It would be nice to use CSharpVirtualCharService but it's not accessible. Maybe we can shim it in the future. // https://github.com/dotnet/roslyn/blob/cd87803005347358c60e6d4ddb45be037cc300c8/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/EmbeddedLanguages/VirtualChars/CSharpVirtualCharService.cs // Instead we use custom logic: text = interpolated.StringStartToken + string.Join(string.Empty, interpolated.Contents.Select(ReplaceInterpolation)) + interpolated.StringEndToken; if (interpolated.StringStartToken.Text is @"$""" or @"@$""" or @"$@""") { regex = InterpolatedTemplateRegex; } } return Parse(text, regex); static string ReplaceInterpolation(InterpolatedStringContentSyntax x) => x switch { InterpolatedStringTextSyntax textSyntax => textSyntax.ToString(), // replace interpolation without changing length to preserve locations InterpolationSyntax interpolation => new string(' ', interpolation.ToString().Length) // no default case, InterpolatedStringContentSyntax has only two implementations }; } public static ParseResult Parse(string template, Regex regex) { if (regex.SafeMatch(template) is { Success: true } match) { var placeholders = match.Groups["Placeholder"].Captures .OfType() .Select(x => new Placeholder(x.Value, x.Index, x.Length)) .ToArray(); return new(true, placeholders); } else { return new(false); } } public record ParseResult(bool Success, Placeholder[] Placeholders = null); public record Placeholder(string Name, int Start, int Length); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/SonarAnalyzer.CSharp.Core.csproj ================================================  netstandard2.0 $(DefineConstants);CS NU1701 NU1605, NU1701 cs ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/AccessorDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class AccessorDeclarationSyntaxExtensions { public static bool HasBodyOrExpressionBody(this AccessorDeclarationSyntax node) => node.Body is not null || node.ExpressionBody is not null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ArgumentListSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class ArgumentListSyntaxExtensions { public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => argumentList != null && argumentList.Arguments.Count > index ? argumentList.Arguments[index].Expression.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(this ArgumentListSyntax argumentList, SemanticModel model, string parameterName) => argumentList is not null && new CSharpMethodParameterLookup(argumentList, model).TryGetSyntax(parameterName, out var expressions) ? expressions : ImmutableArray.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ArgumentSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class ArgumentSyntaxExtensions { public static int? GetArgumentIndex(this ArgumentSyntax argument) => (argument.Parent as ArgumentListSyntax)?.Arguments.IndexOf(argument); public static IEnumerable GetArgumentsOfKnownType(this SeparatedSyntaxList syntaxList, KnownType knownType, SemanticModel semanticModel) => syntaxList .Where(argument => semanticModel.GetTypeInfo(argument.Expression).Type.Is(knownType)); public static IEnumerable GetSymbolsOfKnownType(this SeparatedSyntaxList syntaxList, KnownType knownType, SemanticModel semanticModel) => syntaxList .GetArgumentsOfKnownType(knownType, semanticModel) .Select(argument => semanticModel.GetSymbolInfo(argument.Expression).Symbol); public static bool NameIs(this ArgumentSyntax argument, string name) => argument.NameColon?.Name.Identifier.Text == name; // (arg, b) = something public static bool IsInTupleAssignmentTarget(this ArgumentSyntax argument) => argument.OutermostTuple() is { SyntaxNode: { } tupleExpression } && tupleExpression.Parent is AssignmentExpressionSyntax assignment && assignment.Left == tupleExpression; public static TupleExpressionSyntaxWrapper? OutermostTuple(this ArgumentSyntax argument) => argument.Ancestors() .TakeWhile(x => x?.Kind() is SyntaxKind.Argument or SyntaxKindEx.TupleExpression) .LastOrDefault(x => x.IsKind(SyntaxKindEx.TupleExpression)) is { } outerTuple && TupleExpressionSyntaxWrapper.IsInstance(outerTuple) ? (TupleExpressionSyntaxWrapper)outerTuple : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/AssignmentExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class AssignmentExpressionSyntaxExtensions { public readonly record struct AssignmentMapping(SyntaxNode Left, SyntaxNode Right); /// /// Maps the left and the right side arguments of an . If both sides are tuples, the tuple elements are mapped. /// /// (var x, var y) = (1, 2); // [x←1, y←2] /// var (x, y) = (1, 2); // [x←1, y←2] /// (var x, (var y, var z)) = (1, (2, 3)); // [x←1, y←2, z←3] /// var x = 1; // [x←1] /// (var x, var y) = M(); // [(x,y)←M()] /// /// public static ImmutableArray MapAssignmentArguments(this AssignmentExpressionSyntax assignment) { // (var x, var y) = (1, 2) if (TupleExpressionSyntaxWrapper.IsInstance(assignment.Left) && TupleExpressionSyntaxWrapper.IsInstance(assignment.Right)) { var left = (TupleExpressionSyntaxWrapper)assignment.Left; var right = (TupleExpressionSyntaxWrapper)assignment.Right; var arrayBuilder = ImmutableArray.CreateBuilder(left.Arguments.Count); if (MapTupleElements(arrayBuilder, left, right) is NestingMatch.Handled) { return arrayBuilder.ToImmutableArray(); } } // var (x, y) = (1, 2) else if (DeclarationExpressionSyntaxWrapper.IsInstance(assignment.Left) && (DeclarationExpressionSyntaxWrapper)assignment.Left is { Designation: { } leftDesignation } && ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(leftDesignation) && TupleExpressionSyntaxWrapper.IsInstance(assignment.Right)) { var left = (ParenthesizedVariableDesignationSyntaxWrapper)leftDesignation; var right = (TupleExpressionSyntaxWrapper)assignment.Right; var arrayBuilder = ImmutableArray.CreateBuilder(left.Variables.Count); if (MapDesignationElements(arrayBuilder, left, right) is NestingMatch.Handled) { return arrayBuilder.ToImmutableArray(); } } return ImmutableArray.Create(new AssignmentMapping(assignment.Left, assignment.Right)); } /// /// Returns a list of nodes, that represent the target (left side) of an assignment. In case of tuple deconstructions, this can be more than one target. /// Nested tuple elements and any declaration expressions are flattened. /// /// (var a, var b) = x; // [a, b] /// var (a, b) = x; // [a, b] /// var (a, _) = x; // [a] ← The _ here is always a discard /// (var a, _) = x; // [a, _] ← The _ here could reference a local /// (var a, var (b, c)) = x; // [a, b, c] /// (var a, (int, int) b) = x; // [a, b] /// /// public static ImmutableArray AssignmentTargets(this AssignmentExpressionSyntax assignment) { var left = assignment.Left; if (TupleExpressionSyntaxWrapper.IsInstance(left)) { var tuple = (TupleExpressionSyntaxWrapper)left; var argumentExpressions = tuple.AllArguments().Select(x => (SyntaxNode)x.Expression); var designationsExpanded = argumentExpressions.SelectMany(x => x.IsKind(SyntaxKindEx.DeclarationExpression) ? ((DeclarationExpressionSyntaxWrapper)x).Designation.AllVariables().Select(x => (SyntaxNode)x) : new[] { x }); return designationsExpanded.ToImmutableArray(); } else if (DeclarationExpressionSyntaxWrapper.IsInstance(left)) { var declaration = (DeclarationExpressionSyntaxWrapper)left; return declaration.Designation.AllVariables().Select(x => (SyntaxNode)x).ToImmutableArray(); } else { return ImmutableArray.Create(left); } } private static NestingMatch MapTupleElements(ImmutableArray.Builder arrayBuilder, TupleExpressionSyntaxWrapper left, TupleExpressionSyntaxWrapper right) { if (left.Arguments.Count != right.Arguments.Count) { return NestingMatch.Failed; } var leftEnumerator = left.Arguments.GetEnumerator(); var rightEnumerator = right.Arguments.GetEnumerator(); while (leftEnumerator.MoveNext() && rightEnumerator.MoveNext()) { var leftArgumentExpression = leftEnumerator.Current.Expression; var rightArgumentExpression = rightEnumerator.Current.Expression; switch (HandleTupleNesting(arrayBuilder, leftArgumentExpression, rightArgumentExpression)) { case NestingMatch.Handled: break; // the switch case NestingMatch.Failed: return NestingMatch.Failed; case NestingMatch.Leaf: arrayBuilder.Add(new AssignmentMapping(leftArgumentExpression, rightArgumentExpression)); break; // the switch } } return NestingMatch.Handled; } private static NestingMatch MapDesignationElements(ImmutableArray.Builder arrayBuilder, ParenthesizedVariableDesignationSyntaxWrapper left, TupleExpressionSyntaxWrapper right) { if (left.Variables.Count != right.Arguments.Count) { return NestingMatch.Failed; } var leftEnumerator = left.Variables.GetEnumerator(); var rightEnumerator = right.Arguments.GetEnumerator(); while (leftEnumerator.MoveNext() && rightEnumerator.MoveNext()) { var leftVar = leftEnumerator.Current; var rightExpression = rightEnumerator.Current.Expression; switch (HandleDesignationNesting(arrayBuilder, leftVar, rightExpression)) { case NestingMatch.Handled: break; // the switch case NestingMatch.Failed: return NestingMatch.Failed; case NestingMatch.Leaf: arrayBuilder.Add(new AssignmentMapping(leftVar.SyntaxNode, rightExpression)); break; // the switch } } return NestingMatch.Handled; } private static NestingMatch HandleTupleNesting(ImmutableArray.Builder arrayBuilder, ExpressionSyntax leftExpression, ExpressionSyntax rightExpression) => true switch { _ when TupleExpressionSyntaxWrapper.IsInstance(leftExpression) && TupleExpressionSyntaxWrapper.IsInstance(rightExpression) => MapTupleElements(arrayBuilder, (TupleExpressionSyntaxWrapper)leftExpression, (TupleExpressionSyntaxWrapper)rightExpression), _ when DeclarationExpressionSyntaxWrapper.IsInstance(leftExpression) && (DeclarationExpressionSyntaxWrapper)leftExpression is { Designation: { } leftDesignation } => HandleDesignationNesting(arrayBuilder, leftDesignation, rightExpression), _ => NestingMatch.Leaf, }; private static NestingMatch HandleDesignationNesting(ImmutableArray.Builder arrayBuilder, VariableDesignationSyntaxWrapper leftVar, ExpressionSyntax rightExpression) => true switch { _ when ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(leftVar) && TupleExpressionSyntaxWrapper.IsInstance(rightExpression) => MapDesignationElements(arrayBuilder, (ParenthesizedVariableDesignationSyntaxWrapper)leftVar, (TupleExpressionSyntaxWrapper)rightExpression), _ => NestingMatch.Leaf, }; private enum NestingMatch { Handled, Failed, Leaf } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/AttributeListSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class AttributeListSyntaxExtensions { public static IEnumerable GetAttributes(this SyntaxList attributeLists, KnownType attributeKnownType, SemanticModel semanticModel) => attributeLists.SelectMany(x => x.Attributes).Where(x => x.IsKnownType(attributeKnownType, semanticModel)); public static IEnumerable GetAttributes(this SyntaxList attributeLists, ImmutableArray attributeKnownTypes, SemanticModel semanticModel) => attributeLists.SelectMany(list => list.Attributes).Where(x => x.IsKnownType(attributeKnownTypes, semanticModel)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/AttributeSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; internal static class AttributeSyntaxExtensions { private const int AttributeLength = 9; public static bool IsKnownType(this AttributeSyntax attribute, ImmutableArray attributeKnownTypes, SemanticModel semanticModel) { for (var i = 0; i < attributeKnownTypes.Length; i++) { if (IsKnownType(attribute, attributeKnownTypes[i], semanticModel)) { return true; } } return false; } public static bool IsKnownType(this AttributeSyntax attribute, KnownType knownType, SemanticModel semanticModel) => attribute.Name.GetName().Contains(GetShortNameWithoutAttributeSuffix(knownType)) && ((SyntaxNode)attribute).IsKnownType(knownType, semanticModel); private static string GetShortNameWithoutAttributeSuffix(KnownType knownType) => knownType.TypeName == nameof(Attribute) || !knownType.TypeName.EndsWith(nameof(Attribute)) ? knownType.TypeName : knownType.TypeName.Remove(knownType.TypeName.Length - AttributeLength); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/AwaitExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class AwaitExpressionSyntaxExtensions { public static ExpressionSyntax AwaitedExpressionWithoutConfigureAwait(this AwaitExpressionSyntax awaitExpression) => awaitExpression.Expression?.RemoveParentheses() switch { InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name.Identifier.Text: nameof(Task.ConfigureAwait), Expression: { } configuredExpression } } => configuredExpression, var x => x, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/BaseArgumentListSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class BaseArgumentListSyntaxExtensions { public static ArgumentSyntax GetArgumentByName(this BaseArgumentListSyntax list, string name) => list.Arguments.FirstOrDefault(argument => argument.NameIs(name)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/BaseMethodDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class BaseMethodDeclarationSyntaxExtensions { public static IEnumerable GetBodyDescendantNodes(this BaseMethodDeclarationSyntax method) => (method ?? throw new ArgumentNullException(nameof(method))).Body == null ? method.ExpressionBody().DescendantNodes() : method.Body.DescendantNodes(); public static bool IsStatic(this BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword); public static bool IsExtern(this BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration.Modifiers.Any(SyntaxKind.ExternKeyword); public static bool HasBodyOrExpressionBody(this BaseMethodDeclarationSyntax node) => node.GetBodyOrExpressionBody() is not null; public static SyntaxNode GetBodyOrExpressionBody(this BaseMethodDeclarationSyntax node) => (node?.Body as SyntaxNode) ?? node?.ExpressionBody()?.Expression; public static bool ContainsMethodInvocation(this BaseMethodDeclarationSyntax methodDeclarationBase, SemanticModel semanticModel, Func syntaxPredicate, Func symbolPredicate) { var childNodes = methodDeclarationBase?.Body?.DescendantNodes() ?? methodDeclarationBase?.ExpressionBody()?.DescendantNodes() ?? Enumerable.Empty(); // See issue: https://github.com/SonarSource/sonar-dotnet/issues/416 // Where clause excludes nodes that are not defined on the same SyntaxTree as the SemanticModel // (because of partial definition). // More details: https://github.com/dotnet/roslyn/issues/18730 return childNodes .OfType() .Where(syntaxPredicate) .Select(x => x.Expression.SyntaxTree.SemanticModelOrDefault(semanticModel)?.GetSymbolInfo(x.Expression).Symbol) .OfType() .Any(symbolPredicate); } public static SyntaxToken? GetIdentifierOrDefault(this BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration switch { ConstructorDeclarationSyntax constructor => (SyntaxToken?)constructor.Identifier, DestructorDeclarationSyntax destructor => (SyntaxToken?)destructor.Identifier, MethodDeclarationSyntax method => (SyntaxToken?)method.Identifier, _ => null, }; public static Location FindIdentifierLocation(this BaseMethodDeclarationSyntax methodDeclaration) => GetIdentifierOrDefault(methodDeclaration)?.GetLocation(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/BlockSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class BlockSyntaxExtensions { public static bool IsEmpty(this BlockSyntax block, bool treatCommentsAsContent = true, bool treatConditionalCompilationAsContent = true) { _ = block ?? throw new ArgumentNullException(nameof(block)); return !IsNotEmpty(); bool IsNotEmpty() => block.Statements.Any() || ((treatCommentsAsContent || treatConditionalCompilationAsContent) && (TriviaContainsCommentOrConditionalCompilation(block.OpenBraceToken.TrailingTrivia) || TriviaContainsCommentOrConditionalCompilation(block.CloseBraceToken.LeadingTrivia))); bool TriviaContainsCommentOrConditionalCompilation(SyntaxTriviaList triviaList) => triviaList.Any(x => (treatCommentsAsContent && x.Kind() is SyntaxKind.SingleLineCommentTrivia or SyntaxKind.MultiLineCommentTrivia) || (treatConditionalCompilationAsContent && x.IsKind(SyntaxKind.DisabledTextTrivia))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/CompilationUnitSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class CompilationUnitSyntaxExtensions { public static IEnumerable GetTopLevelMainBody(this CompilationUnitSyntax compilationUnit) => compilationUnit.ChildNodes() .SkipWhile(x => x.Kind() is SyntaxKind.UsingDirective or SyntaxKind.NamespaceDeclaration) .TakeWhile(x => x.IsKind(SyntaxKind.GlobalStatement)); public static IEnumerable GetMethodDeclarations(this CompilationUnitSyntax compilationUnitSyntax) => compilationUnitSyntax.GetTopLevelMainBody() .Select(x => x.ChildNodes().FirstOrDefault(y => y.IsKind(SyntaxKindEx.LocalFunctionStatement))) .Where(x => x != null) .Select(x => MethodDeclarationFactory.Create(x)); public static bool IsTopLevelMain(this CompilationUnitSyntax compilationUnit) => compilationUnit.Members.Any(x => x.IsKind(SyntaxKind.GlobalStatement)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ExpressionSyntaxExtensions.Roslyn.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.CSharp.Extensions; // Copied from Roslyn // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs [ExcludeFromCodeCoverage] public static class ExpressionSyntaxExtensions { /// /// Returns if represents a node where a value is written to, like on the left side of an assignment expression. This method /// also returns for potentially writeable expressions, like parameters. /// See also . /// /// /// Copied from /// public static bool IsWrittenTo( this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { if (expression == null) return false; expression = GetExpressionToAnalyzeForWrites(expression); if (expression.IsOnlyWrittenTo()) return true; if (expression.IsInRefContext(out var refParent)) { // most cases of `ref x` will count as a potential write of `x`. An important exception is: // `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not // be a write of 'x'. if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: { } variableDeclarationType } } }) { if (ScopedTypeSyntaxWrapper.IsInstance(variableDeclarationType) && (ScopedTypeSyntaxWrapper)variableDeclarationType is { } scopedType) { variableDeclarationType = scopedType.Type; } if (RefTypeSyntaxWrapper.IsInstance(variableDeclarationType) && ((RefTypeSyntaxWrapper)variableDeclarationType).ReadOnlyKeyword != default) { return false; } } return true; } // Similar to `ref x`, `&x` allows reads and write of the value, meaning `x` may be (but is not definitely) // written to. if (expression.Parent.IsKind(SyntaxKind.AddressOfExpression)) return true; // We're written if we're used in a ++, or -- expression. if (expression.IsOperandOfIncrementOrDecrementExpression()) return true; if (expression.IsLeftSideOfAnyAssignExpression()) return true; // An extension method invocation with a ref-this parameter can write to an expression. if (expression.Parent is MemberAccessExpressionSyntax memberAccess && expression == memberAccess.Expression) { var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol; if (symbol is IMethodSymbol { MethodKind: MethodKind.ReducedExtension, ReducedFrom.Parameters: { Length: > 0 } parameters, } && parameters[0].RefKind == RefKind.Ref) { return true; } } return false; } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L221 private static ExpressionSyntax GetExpressionToAnalyzeForWrites(ExpressionSyntax? expression) { if (expression.IsRightSideOfDotOrArrow()) { expression = (ExpressionSyntax)expression.Parent; } expression = (ExpressionSyntax)expression.WalkUpParentheses(); return expression; } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L63 public static bool IsRightSideOfDotOrArrow(this ExpressionSyntax name) => IsAnyMemberAccessExpressionName(name) || IsRightSideOfQualifiedName(name); // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L41 public static bool IsAnyMemberAccessExpressionName(this ExpressionSyntax expression) { if (expression == null) return false; return expression == (expression.Parent as MemberAccessExpressionSyntax)?.Name || expression.IsMemberBindingExpressionName(); } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L50 public static bool IsMemberBindingExpressionName(this ExpressionSyntax expression) => expression?.Parent is MemberBindingExpressionSyntax memberBinding && memberBinding.Name == expression; // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L54 public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression) => expression?.Parent is QualifiedNameSyntax qualifiedName && qualifiedName.Right == expression; // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L233 public static bool IsOnlyWrittenTo(this ExpressionSyntax expression) { expression = GetExpressionToAnalyzeForWrites(expression); if (expression != null) { if (expression.IsInOutContext()) { return true; } if (expression.Parent != null) { if (expression.IsLeftSideOfAssignExpression()) { return true; } if (expression.IsAttributeNamedArgumentIdentifier()) { return true; } } if (IsExpressionOfArgumentInDeconstruction(expression)) { return true; } } return false; } /// /// If this declaration or identifier is part of a deconstruction, find the deconstruction. /// If found, returns either an assignment expression or a foreach variable statement. /// Returns null otherwise. /// /// copied from SyntaxExtensions.GetContainingDeconstruction. /// /// /// Copied from /// private static bool IsExpressionOfArgumentInDeconstruction(ExpressionSyntax expr) { if (!expr.IsParentKind(SyntaxKind.Argument)) { return false; } while (true) { var parent = expr.Parent; if (parent == null) { return false; } switch (parent.Kind()) { case SyntaxKind.Argument: if (parent.Parent?.Kind() == SyntaxKindEx.TupleExpression) { expr = (ExpressionSyntax)parent.Parent; continue; } return false; case SyntaxKind.SimpleAssignmentExpression: if (((AssignmentExpressionSyntax)parent).Left == expr) { return true; } return false; case SyntaxKindEx.ForEachVariableStatement: if (((ForEachVariableStatementSyntaxWrapper)parent).Variable == expr) { return true; } return false; default: return false; } } } // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L190 public static bool IsInOutContext(this ExpressionSyntax expression) => expression?.Parent is ArgumentSyntax { RefOrOutKeyword: SyntaxToken { RawKind: (int)SyntaxKind.OutKeyword } } argument && argument.Expression == expression; // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L383 public static bool IsAttributeNamedArgumentIdentifier(this ExpressionSyntax expression) { var nameEquals = expression?.Parent as NameEqualsSyntax; return nameEquals.IsParentKind(SyntaxKind.AttributeArgument); } // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L194 public static bool IsInRefContext(this ExpressionSyntax expression) => IsInRefContext(expression, out _); /// /// Returns true if this expression is in some ref keyword context. If then /// will be the node containing the keyword. /// /// /// Copied from /// public static bool IsInRefContext(this ExpressionSyntax expression, out SyntaxNode refParent) { while (expression?.Parent is ParenthesizedExpressionSyntax or PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression }) expression = (ExpressionSyntax)expression.Parent; if (expression?.Parent switch { ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.RefKeyword } => true, var x when RefExpressionSyntaxWrapper.IsInstance(x) => true, _ => false, }) { refParent = expression.Parent; return true; } refParent = null; return false; } // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs#L389 public static bool IsOperandOfIncrementOrDecrementExpression(this ExpressionSyntax expression) { if (expression?.Parent is SyntaxNode parent) { switch (parent.Kind()) { case SyntaxKind.PostIncrementExpression: case SyntaxKind.PreIncrementExpression: case SyntaxKind.PostDecrementExpression: case SyntaxKind.PreDecrementExpression: return true; } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Extensions; namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class ExpressionSyntaxExtensions { private static readonly HashSet LiteralSyntaxKinds = [ SyntaxKind.CharacterLiteralExpression, SyntaxKind.FalseLiteralExpression, SyntaxKind.NullLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.StringLiteralExpression, SyntaxKind.TrueLiteralExpression ]; private static readonly HashSet EqualsOrNotEquals = [ SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression ]; public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => (ExpressionSyntax)((SyntaxNode)expression).RemoveParentheses(); public static bool CanBeNull(this ExpressionSyntax expression, SemanticModel semanticModel) => semanticModel.GetTypeInfo(expression).Type is { } expressionType && (expressionType.IsReferenceType || expressionType.Is(KnownType.System_Nullable_T)); public static ExpressionSyntax RemoveConditionalAccess(this ExpressionSyntax node) { var whenNotNull = node.RemoveParentheses(); while (whenNotNull is ConditionalAccessExpressionSyntax conditionalAccess) { whenNotNull = conditionalAccess.WhenNotNull.RemoveParentheses(); } return whenNotNull; } /// /// Given an expression: /// - verify if it contains a comparison against the 'null' literal. /// - if it does contain a comparison, extract the expression which is compared against 'null' and whether the comparison is affirmative or negative. /// /// /// The caller is expected to verify unary negative expression before. For example, for "!(x == null)", the caller should extract what is inside the parenthesis. /// /// Expression expected to contain a comparison to null. /// The variable/expression which gets compared to null. /// True if the check is affirmative ("== null", "is null"), false if negative ("!= null", "is not null") /// True if the expression contains a comparison to null. public static bool TryGetExpressionComparedToNull(this ExpressionSyntax expression, out ExpressionSyntax compared, out bool isAffirmative) { compared = null; isAffirmative = false; if (expression.RemoveParentheses() is BinaryExpressionSyntax binary && EqualsOrNotEquals.Contains(binary.Kind())) { isAffirmative = binary.IsKind(SyntaxKind.EqualsExpression); var binaryLeft = binary.Left.RemoveParentheses(); var binaryRight = binary.Right.RemoveParentheses(); if (CSharpEquivalenceChecker.AreEquivalent(binaryLeft, SyntaxConstants.NullLiteralExpression)) { compared = binaryRight; return true; } else if (CSharpEquivalenceChecker.AreEquivalent(binaryRight, SyntaxConstants.NullLiteralExpression)) { compared = binaryLeft; return true; } } if (IsPatternExpressionSyntaxWrapper.IsInstance(expression.RemoveParentheses())) { var isPatternWrapper = (IsPatternExpressionSyntaxWrapper)expression.RemoveParentheses(); if (isPatternWrapper.IsNotNull()) { isAffirmative = false; compared = isPatternWrapper.Expression; return true; } else if (isPatternWrapper.IsNull()) { isAffirmative = true; compared = isPatternWrapper.Expression; return true; } } return false; } /// /// Returns the expression, representing the left side of the dot. This is useful for finding the expression of an invoked expression.
/// For the expression of the invocation M() in the expression this.A.B.M() the member access this.A.B is returned and
/// for this.A?.B?.M() the member binding .B is returned. ///
/// The expression to start the search of. Should be an MemberAccess or a MemberBinding. /// The expression left of the dot or question mark dot. public static ExpressionSyntax GetLeftOfDot(this ExpressionSyntax expression) => expression switch { MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression, MemberBindingExpressionSyntax memberBindingExpression => memberBindingExpression.GetParentConditionalAccessExpression()?.Expression, _ => null, }; public static ExpressionSyntax GetSelfOrTopParenthesizedExpression(this ExpressionSyntax node) => (ExpressionSyntax)SyntaxNodeExtensionsCSharp.GetSelfOrTopParenthesizedExpression((SyntaxNode)node); public static bool IsOnThis(this ExpressionSyntax expression) => IsOn(expression, SyntaxKind.ThisExpression); public static bool IsInNameOfArgument(this ExpressionSyntax expression, SemanticModel semanticModel) { var parentInvocation = expression.FirstAncestorOrSelf(); return parentInvocation is not null && parentInvocation.IsNameof(semanticModel); } public static bool IsStringEmpty(this ExpressionSyntax expression, SemanticModel semanticModel) { if (!expression.IsKind(SyntaxKind.SimpleMemberAccessExpression) && !expression.IsKind(SyntaxKind.PointerMemberAccessExpression)) { return false; } var nameSymbolInfo = semanticModel.GetSymbolInfo(((MemberAccessExpressionSyntax)expression).Name); return nameSymbolInfo.Symbol is not null && nameSymbolInfo.Symbol.IsInType(KnownType.System_String) && nameSymbolInfo.Symbol.Name == nameof(string.Empty); } public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) => expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) is not null; public static bool IsLeftSideOfAssignment(this ExpressionSyntax expression) { var topParenthesizedExpression = expression.GetSelfOrTopParenthesizedExpression(); return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression) && topParenthesizedExpression.Parent is AssignmentExpressionSyntax assignment && assignment.Left == topParenthesizedExpression; } /// /// Extracts all that can be bound to fields, methods, parameters, locals and so on from the . /// /// a + b returns [IdentifierName(a), IdentifierName(b)] /// a.b returns [MemberAccess(a, b)] /// a is b returns [IdentifierName(a)]. Note: b can bind to a type or a constant member. The constant member bind is ignored. /// (b)a returns [IdentifierName(a)]. Note: b can only bind to a type and is not returned. /// a is { b.c: d e } returns [IdentifierName(a)] Patterns are not visited. /// a switch { b c => d } returns [IdentifierName(a), IdentifierName(d)] Patterns are not visited. /// /// public static IReadOnlyCollection ExtractMemberIdentifier(this ExpressionSyntax expression) => expression switch { IdentifierNameSyntax identifier => [identifier], // {Prop} -> Prop MemberAccessExpressionSyntax memberAccess => [memberAccess], // {Prop.Something} -> Prop.Something InvocationExpressionSyntax { ArgumentList.Arguments: { } arguments } invocation => [invocation, .. arguments.SelectMany(x => ExtractMemberIdentifier(x.Expression))], // {Method(a)} -> Method(), a ElementAccessExpressionSyntax { ArgumentList.Arguments: { } arguments } elementAccess => [elementAccess, .. arguments.SelectMany(x => ExtractMemberIdentifier(x.Expression))], ConditionalAccessExpressionSyntax conditional => [conditional.GetRootConditionalAccessExpression()], AwaitExpressionSyntax { Expression: { } awaited } => ExtractMemberIdentifier(awaited), // {await Prop} _> Prop PrefixUnaryExpressionSyntax { Operand: { } operand } => ExtractMemberIdentifier(operand), // {++Prop} -> Prop PostfixUnaryExpressionSyntax { Operand: { } operand } => ExtractMemberIdentifier(operand), // {Prop++} -> Prop CastExpressionSyntax { Expression: { } operand } => ExtractMemberIdentifier(operand), // {(Type)Prop} -> Prop BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AsExpression or (int)SyntaxKind.IsExpression, Left: { } left } => ExtractMemberIdentifier(left), // {Prop as Type} or {Prop is Type} -> Prop, {Prop is Constant} is ignored BinaryExpressionSyntax { Left: { } left, Right: { } right } => [.. ExtractMemberIdentifier(left), .. ExtractMemberIdentifier(right)], // {Prop1 + Prop2} -> Prop1, Prop2 ParenthesizedExpressionSyntax { Expression: { } parenthesized } => ExtractMemberIdentifier(parenthesized), // {(Prop)} -> Prop InterpolatedStringExpressionSyntax { Contents: { } contents } => [.. contents.OfType().SelectMany(x => ExtractMemberIdentifier(x.Expression))], // {Cond ? WhenTrue : WhenFalse} -> Cond, WhenTrue, WhenFalse ConditionalExpressionSyntax { Condition: { } condition, WhenTrue: { } whenTrue, WhenFalse: { } whenFalse } => [.. ExtractMemberIdentifier(condition), .. ExtractMemberIdentifier(whenTrue), .. ExtractMemberIdentifier(whenFalse)], // {Prop switch { Arm1 => InArm1, _ => InDefault }} -> Prop, InArm1, InDefault { } switchExpression when SwitchExpressionSyntaxWrapper.IsInstance(switchExpression) && (SwitchExpressionSyntaxWrapper)switchExpression is { } wrapped => [.. ExtractMemberIdentifier(wrapped.GoverningExpression), .. wrapped.Arms.SelectMany(x => ExtractMemberIdentifier(x.Expression))], { } isPattern when IsPatternExpressionSyntaxWrapper.IsInstance(isPattern) && (IsPatternExpressionSyntaxWrapper)isPattern is { } wrapped => ExtractMemberIdentifier(wrapped.Expression), _ => [], }; /// /// On member access like operations, like a.b, c>a.b(), or a[b], the most left hand /// member (a) is returned. is skipped, so this.a returns a. /// public static ExpressionSyntax LeftMostInMemberAccess(this ExpressionSyntax expression) => expression switch { IdentifierNameSyntax identifier => identifier, // Prop MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax identifier } => identifier, // Prop.Something -> Prop MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax, Name: IdentifierNameSyntax identifier } => identifier, // this.Prop -> Prop MemberAccessExpressionSyntax { Expression: PredefinedTypeSyntax predefinedType } => predefinedType, // int.MaxValue -> int MemberAccessExpressionSyntax { Expression: { } left } => LeftMostInMemberAccess(left), // Prop.Something.Something -> Prop MemberBindingExpressionSyntax memberBindingExpression => memberBindingExpression.GetRootConditionalAccessExpression()?.Expression.LeftMostInMemberAccess(), // C#14 a?.b -> a InvocationExpressionSyntax { Expression: { } left } => LeftMostInMemberAccess(left), // Method() -> Method, also this.Method() and Method().Something ElementAccessExpressionSyntax { Expression: { } left } => LeftMostInMemberAccess(left), // a[b] -> a ConditionalAccessExpressionSyntax conditional => LeftMostInMemberAccess(conditional.GetRootConditionalAccessExpression().Expression), // a?.b -> a ParenthesizedExpressionSyntax { Expression: { } inner } => LeftMostInMemberAccess(inner), // (a.b).c -> a PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression } nullSuppression => LeftMostInMemberAccess(nullSuppression.Operand), // a! -> a ExpressionSyntax { RawKind: (int)SyntaxKindEx.FieldExpression } fieldExpression => fieldExpression, // C#14 field keyword _ => null, }; /// /// Returns if the expression is a default literal or a null supressed default literal, i.e. default or default!. /// It returns for other expressions, including default(object) or default(object)!. /// /// The potential default literal. /// Returns if the expressions is a default literal or a supressed null literal. public static bool IsDefaultLiteral(this ExpressionSyntax expression) => expression?.RemoveParentheses() is { } innerExpression && (innerExpression.IsKind(SyntaxKindEx.DefaultLiteralExpression) || (innerExpression is PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression, Operand: { } supressionOperand } && supressionOperand.RemoveParentheses() is { RawKind: (int)SyntaxKindEx.DefaultLiteralExpression })); private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind) => expression switch { InvocationExpressionSyntax invocation => IsOn(invocation.Expression, onKind), // Following statement is a simplification as we don't check where the method is defined (so this could be this or base) AliasQualifiedNameSyntax or GenericNameSyntax or IdentifierNameSyntax or QualifiedNameSyntax => true, MemberAccessExpressionSyntax memberAccess => memberAccess.Expression.RemoveParentheses().IsKind(onKind), ConditionalAccessExpressionSyntax conditionalAccess => conditionalAccess.Expression.RemoveParentheses().IsKind(onKind), _ => false, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/InterpolatedStringExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class InterpolatedStringExpressionSyntaxExtensions { public static string ContentsText(this InterpolatedStringExpressionSyntax interpolatedStringExpression) => interpolatedStringExpression.Contents.JoinStr(null, x => x.ToString()); public static string InterpolatedTextValue(this InterpolatedStringExpressionSyntax interpolatedStringExpression, SemanticModel model) => CSharpStringInterpolationConstantValueResolver.Instance.InterpolatedTextValue(interpolatedStringExpression, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/InvocationExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class InvocationExpressionSyntaxExtensions { public static bool IsMemberAccessOnKnownType(this InvocationExpressionSyntax invocation, string identifierName, KnownType knownType, SemanticModel model) => invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.IsMemberAccessOnKnownType(identifierName, knownType, model); public static IEnumerable GetArgumentSymbolsOfKnownType(this InvocationExpressionSyntax invocation, KnownType knownType, SemanticModel model) => invocation.ArgumentList.Arguments.GetSymbolsOfKnownType(knownType, model); public static bool HasExactlyNArguments(this InvocationExpressionSyntax invocation, int count) => invocation is not null && invocation.ArgumentList.Arguments.Count == count; public static bool IsGetTypeCall(this InvocationExpressionSyntax invocation, SemanticModel model) => invocation is not null && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsGetTypeCall(); public static bool IsOnBase(this InvocationExpressionSyntax invocation) => (invocation.Expression as MemberAccessExpressionSyntax)?.Expression is BaseExpressionSyntax; public static bool IsEqualTo(this InvocationExpressionSyntax first, InvocationExpressionSyntax second, SemanticModel model) => Pair.From(model.GetSymbolInfo(first).Symbol, model.GetSymbolInfo(second).Symbol) switch { // the nameof(someVariable) is considered an Invocation Expression, but it is not a method call, and GetSymbolInfo returns null for it. (null, null) when first.GetName() == "nameof" && second.GetName() == "nameof" => model.GetConstantValue(first).Equals(model.GetConstantValue(second)), ({ } firstSymbol, { } secondSymbol) => firstSymbol.Equals(secondSymbol), _ => false }; public static Pair Operands(this InvocationExpressionSyntax invocation) => invocation switch { { Expression: MemberAccessExpressionSyntax access } => new Pair(access.Expression, access.Name), { Expression: MemberBindingExpressionSyntax binding } => new(invocation.GetParentConditionalAccessExpression()?.Expression, binding.Name), _ => default }; public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) => invocation?.Expression.GetIdentifier(); public static bool IsNameof(this InvocationExpressionSyntax expression, SemanticModel semanticModel) => expression is not null && expression.Expression is IdentifierNameSyntax identifierNameSyntax && identifierNameSyntax.Identifier.ValueText == Common.SyntaxConstants.NameOfKeywordText && semanticModel.GetSymbolInfo(expression).Symbol?.Kind != SymbolKind.Method; public static bool IsMethodInvocation(this InvocationExpressionSyntax invocation, KnownType type, string methodName, SemanticModel semanticModel) => invocation.Expression.NameIs(methodName) && semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsInType(type); public static bool IsMethodInvocation(this InvocationExpressionSyntax invocation, ImmutableArray types, string methodName, SemanticModel semanticModel) => invocation.Expression.NameIs(methodName) && semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsInType(types); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/LocalFunctionStatementSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class LocalFunctionStatementSyntaxWrapperExtensions { public static bool IsTopLevel(this LocalFunctionStatementSyntaxWrapper localFunction) => localFunction is { SyntaxNode.Parent: GlobalStatementSyntax { Parent: CompilationUnitSyntax } }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/MemberAccessExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class MemberAccessExpressionSyntaxExtensions { public static bool IsMemberAccessOnKnownType(this MemberAccessExpressionSyntax memberAccess, string name, KnownType knownType, SemanticModel semanticModel) => memberAccess.NameIs(name) && semanticModel.GetSymbolInfo(memberAccess).Symbol is {} symbol && symbol.ContainingType.DerivesFrom(knownType); public static bool IsPropertyInvocation(this MemberAccessExpressionSyntax expression, ImmutableArray types, string propertyName, SemanticModel semanticModel) => expression.NameIs(propertyName) && semanticModel.GetSymbolInfo(expression).Symbol is IPropertySymbol propertySymbol && propertySymbol.IsInType(types); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/MethodDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class MethodDeclarationSyntaxExtensions { /// /// Returns true if the method throws exceptions or returns null. /// public static bool ThrowsOrReturnsNull(this MethodDeclarationSyntax syntaxNode) => syntaxNode.DescendantNodes().OfType().Any() || syntaxNode.DescendantNodes().OfType().Any(expression => expression.IsKind(SyntaxKindEx.ThrowExpression)) || syntaxNode.DescendantNodes().OfType().Any(returnStatement => returnStatement.Expression.IsKind(SyntaxKind.NullLiteralExpression)) || // For simplicity this returns true for any method witch contains a NullLiteralExpression but this could be a source of FNs syntaxNode.DescendantNodes().OfType().Any(expression => expression.IsKind(SyntaxKind.NullLiteralExpression)); public static bool IsExtensionMethod(this BaseMethodDeclarationSyntax methodDeclaration) => methodDeclaration.ParameterList.Parameters.Count > 0 && methodDeclaration.ParameterList.Parameters[0].Modifiers.Any(s => s.IsKind(SyntaxKind.ThisKeyword)); public static bool HasReturnTypeVoid(this MethodDeclarationSyntax methodDeclaration) => methodDeclaration.ReturnType is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.VoidKeyword } }; public static bool IsDeconstructor(this MethodDeclarationSyntax methodDeclaration) { return methodDeclaration.HasReturnTypeVoid() && (methodDeclaration.IsExtensionMethod() || !methodDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword)) && methodDeclaration.Identifier.Value.Equals("Deconstruct") && AllParametersHaveModifierOut(methodDeclaration); static bool AllParametersHaveModifierOut(MethodDeclarationSyntax methodDeclaration) => (methodDeclaration.IsExtensionMethod() ? methodDeclaration.ParameterList.Parameters.Skip(1) : methodDeclaration.ParameterList.Parameters) .All(x => x.Modifiers.Any(y => y.IsKind(SyntaxKind.OutKeyword))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ObjectCreationExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class ObjectCreationExpressionSyntaxExtensions { public static bool IsKnownType(this ObjectCreationExpressionSyntax objectCreation, KnownType knownType, SemanticModel semanticModel) => objectCreation.Type.GetName().EndsWith(knownType.TypeName) && ((SyntaxNode)objectCreation).IsKnownType(knownType, semanticModel); public static SyntaxToken? GetObjectCreationTypeIdentifier(this ObjectCreationExpressionSyntax objectCreation) => objectCreation?.Type.GetIdentifier(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/ParameterSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class ParameterSyntaxExtensions { /// /// Returns true if the parameter is of type string. For performance reasons the check is done at the syntax level. /// public static bool IsString(this ParameterSyntax parameterSyntax) => IsString(parameterSyntax.Type.ToString()); private static bool IsString(string parameterTypeName) => parameterTypeName == "string" || parameterTypeName == "String" || parameterTypeName == "System.String"; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/PropertyDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class PropertyDeclarationSyntaxExtensions { public static bool IsAutoProperty(this PropertyDeclarationSyntax propertyDeclaration) => propertyDeclaration.AccessorList != null && propertyDeclaration.AccessorList.Accessors .All(accessor => accessor.Body == null && accessor.ExpressionBody == null); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/StatementSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class StatementSyntaxExtensions { extension(StatementSyntax statement) { /// /// Returns the statement before the statement given as input. /// public StatementSyntax PrecedingStatement => statement.SiblingStatements() .OfType() .TakeWhile(x => x != statement) .LastOrDefault(); /// /// Returns the statement after the statement given as input. /// public StatementSyntax FollowingStatement => statement.SiblingStatements() .OfType() .SkipWhile(x => x != statement) .Skip(1) .FirstOrDefault(); public StatementSyntax FirstNonBlockStatement { get { var current = statement; while (current is BlockSyntax { } block) { current = block.Statements.FirstOrDefault(); } return current; } } private IEnumerable SiblingStatements() => statement.Parent is GlobalStatementSyntax ? statement.SyntaxTree .GetCompilationUnitRoot() .ChildNodes() .OfType() .Select(x => x.Statement) : statement.Parent.ChildNodes(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SwitchExpressionSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SwitchExpressionSyntaxWrapperExtensions { public static bool HasDiscardPattern(this SwitchExpressionSyntaxWrapper switchExpression) => switchExpression.Arms.Any(arm => DiscardPatternSyntaxWrapper.IsInstance(arm.Pattern.SyntaxNode)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SwitchStatementSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SwitchStatementSyntaxExtensions { public static bool HasDefaultLabel(this SwitchStatementSyntax node) => GetDefaultLabelSectionIndex(node) >= 0; public static int GetDefaultLabelSectionIndex(this SwitchStatementSyntax node) => node.Sections.IndexOf(x => x.Labels.AnyOfKind(SyntaxKind.DefaultSwitchLabel)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SyntaxNodeExtensions.Roslyn.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.CSharp.Extensions; [ExcludeFromCodeCoverage] public static class SyntaxNodeExtensions { /// /// Returns the left hand side of a conditional access expression. Returns c in case like a?.b?[0].c?.d.e?.f if d is passed. /// /// Copied from /// Roslyn SyntaxNodeExtensions public static ConditionalAccessExpressionSyntax GetParentConditionalAccessExpression(this SyntaxNode node) { // Walk upwards based on the grammar/parser rules around ?. expressions (can be seen in // LanguageParser.ParseConsequenceSyntax). // These are the parts of the expression that the ?... expression can end with. Specifically: // // 1. x?.y.M() // invocation // 2. x?.y[...]; // element access // 3. x?.y.z // member access // 4. x?.y // member binding // 5. x?[y] // element binding var current = node; if ((current.IsParentKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess) && memberAccess.Name == current) || (current.IsParentKind(SyntaxKind.MemberBindingExpression, out MemberBindingExpressionSyntax memberBinding) && memberBinding.Name == current)) { current = current.Parent; } // Effectively, if we're on the RHS of the ? we have to walk up the RHS spine first until we hit the first // conditional access. while ((current.Kind() is SyntaxKind.InvocationExpression or SyntaxKind.ElementAccessExpression or SyntaxKind.SimpleMemberAccessExpression or SyntaxKind.MemberBindingExpression or SyntaxKind.ElementBindingExpression // Optional exclamations might follow the conditional operation. For example: a.b?.$$c!!!!() or SyntaxKindEx.SuppressNullableWarningExpression) && current.Parent is not ConditionalAccessExpressionSyntax) { current = current.Parent; } // Two cases we have to care about: // // 1. a?.b.$$c.d and // 2. a?.b.$$c.d?.e... // // Note that `a?.b.$$c.d?.e.f?.g.h.i` falls into the same bucket as two. i.e. the parts after `.e` are // lower in the tree and are not seen as we walk upwards. // // // To get the root ?. (the one after the `a`) we have to potentially consume the first ?. on the RHS of the // right spine (i.e. the one after `d`). Once we do this, we then see if that itself is on the RHS of a // another conditional, and if so we hten return the one on the left. i.e. for '2' this goes in this direction: // // a?.b.$$c.d?.e // it will do: // -----> // <--------- // // Note that this only one CAE consumption on both sides. GetRootConditionalAccessExpression can be used to // get the root parent in a case like: // // x?.y?.z?.a?.b.$$c.d?.e.f?.g.h.i // it will do: // -----> // <--------- // <--- // <--- // <--- if (current.IsParentKind(SyntaxKind.ConditionalAccessExpression, out ConditionalAccessExpressionSyntax conditional) && conditional.Expression == current) { current = conditional; } if (current.IsParentKind(SyntaxKind.ConditionalAccessExpression, out conditional) && conditional.WhenNotNull == current) { current = conditional; } return current as ConditionalAccessExpressionSyntax; } /// /// Call on the `.y` part of a `x?.y` to get the entire `x?.y` conditional access expression. This also works /// when there are multiple chained conditional accesses. For example, calling this on '.y' or '.z' in /// `x?.y?.z` will both return the full `x?.y?.z` node. This can be used to effectively get 'out' of the RHS of /// a conditional access, and commonly represents the full standalone expression that can be operated on /// atomically. /// /// Copied from Roslyn SyntaxNodeExtensions. public static ConditionalAccessExpressionSyntax GetRootConditionalAccessExpression(this SyntaxNode node) { // Once we've walked up the entire RHS, now we continually walk up the conditional accesses until we're at // the root. For example, if we have `a?.b` and we're on the `.b`, this will give `a?.b`. Similarly with // `a?.b?.c` if we're on either `.b` or `.c` this will result in `a?.b?.c` (i.e. the root of this CAE // sequence). var current = node.GetParentConditionalAccessExpression(); while (current.IsParentKind(SyntaxKind.ConditionalAccessExpression, out ConditionalAccessExpressionSyntax conditional) && conditional.WhenNotNull == current) { current = conditional; } return current; } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L347 public static bool IsLeftSideOfAssignExpression(this SyntaxNode node) => node?.Parent is AssignmentExpressionSyntax { RawKind: (int)SyntaxKind.SimpleAssignmentExpression } assignment && assignment.Left == node; // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L43C1-L45C1 public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind) => Microsoft.CodeAnalysis.CSharpExtensions.IsKind(node?.Parent, kind); // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L46 public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind, out T result) where T : SyntaxNode { if (node?.Parent?.IsKind(kind) is true && node.Parent is T t) { result = t; return true; } result = null; return false; } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L351 public static bool IsLeftSideOfAnyAssignExpression(this SyntaxNode node) { return node?.Parent != null && node.Parent.IsAnyAssignExpression() && ((AssignmentExpressionSyntax)node.Parent).Left == node; } // Copy of // https://github.com/dotnet/roslyn/blob/575bc42589145ba18b4f1cc2267d02695f861d8f/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L323 public static bool IsAnyAssignExpression(this SyntaxNode node) => SyntaxFacts.IsAssignmentExpression(node.Kind()); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SyntaxNodeExtensionsCSharp.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CSharp.Core.Trackers; #pragma warning disable T0046 // Move Extensions to dedicated class namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SyntaxNodeExtensionsCSharp { private static readonly ControlFlowGraphCache CfgCache = new(); private static readonly HashSet PerformanceSensitiveSyntaxes = [ SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.PropertyDeclaration, SyntaxKindEx.LocalFunctionStatement ]; private static readonly HashSet EnclosingScopeSyntaxKinds = [ SyntaxKind.AddAccessorDeclaration, SyntaxKind.AnonymousMethodExpression, SyntaxKind.BaseConstructorInitializer, SyntaxKind.CompilationUnit, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.EnumMemberDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKind.GroupClause, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.JoinClause, SyntaxKind.LetClause, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.MethodDeclaration, SyntaxKind.OrderByClause, SyntaxKind.OperatorDeclaration, SyntaxKind.Parameter, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKindEx.PrimaryConstructorBaseType, SyntaxKind.PropertyDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.QueryContinuation, SyntaxKind.SelectClause, SyntaxKind.SetAccessorDeclaration, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ThisConstructorInitializer, SyntaxKind.WhereClause ]; private static readonly SyntaxKind[] NegationOrConditionEnclosingSyntaxKinds = [ SyntaxKind.AnonymousMethodExpression, SyntaxKind.BitwiseNotExpression, SyntaxKind.ConditionalExpression, SyntaxKind.IfStatement, SyntaxKind.MethodDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.WhileStatement]; public static ControlFlowGraph CreateCfg(this SyntaxNode node, SemanticModel model, CancellationToken cancel) => CfgCache.FindOrCreate(node, model, cancel); public static bool ContainsConditionalConstructs(this SyntaxNode node) => node is not null && node.DescendantNodes().Any(x => x.Kind() is SyntaxKind.IfStatement or SyntaxKind.ConditionalExpression or SyntaxKind.CoalesceExpression or SyntaxKind.SwitchStatement or SyntaxKindEx.SwitchExpression or SyntaxKindEx.CoalesceAssignmentExpression); public static object FindConstantValue(this SyntaxNode node, SemanticModel semanticModel) => new CSharpConstantValueFinder(semanticModel).FindConstant(node); public static string FindStringConstant(this SyntaxNode node, SemanticModel semanticModel) => FindConstantValue(node, semanticModel) as string; public static bool IsPartOfBinaryNegationOrCondition(this SyntaxNode node) { if (node.Parent is not MemberAccessExpressionSyntax) { return false; } var topNode = node.Parent.GetSelfOrTopParenthesizedExpression(); if (topNode.Parent?.IsKind(SyntaxKind.BitwiseNotExpression) ?? false) { return true; } var current = topNode; while (current.Parent is not null && !NegationOrConditionEnclosingSyntaxKinds.Contains(current.Parent.Kind())) { current = current.Parent; } return current.Parent switch { IfStatementSyntax ifStatement => ifStatement.Condition == current, WhileStatementSyntax whileStatement => whileStatement.Condition == current, ConditionalExpressionSyntax condExpr => condExpr.Condition == current, _ => false }; } public static string GetDeclarationTypeName(this SyntaxNode node) => node.Kind() switch { SyntaxKind.ClassDeclaration => "class", SyntaxKind.ConstructorDeclaration => "constructor", SyntaxKind.DelegateDeclaration => "delegate", SyntaxKind.DestructorDeclaration => "destructor", SyntaxKind.EnumDeclaration => "enum", SyntaxKind.EnumMemberDeclaration => "enum", SyntaxKind.EventDeclaration => "event", SyntaxKind.EventFieldDeclaration => "event", SyntaxKind.FieldDeclaration => "field", SyntaxKind.IndexerDeclaration => "indexer", SyntaxKind.InterfaceDeclaration => "interface", SyntaxKindEx.LocalFunctionStatement => "local function", SyntaxKind.MethodDeclaration => "method", SyntaxKind.PropertyDeclaration => "property", SyntaxKindEx.RecordDeclaration => "record", SyntaxKindEx.RecordStructDeclaration => "record struct", SyntaxKind.StructDeclaration => "struct", #if DEBUG _ => throw new UnexpectedValueException("node.Kind()", node.Kind().ToString()) #else _ => "type" #endif }; /// /// Returns the that directly owns, or that can be reached /// via a single unambiguous property path (e.g. CatchClauseSyntax → Declaration → Type). /// Does not unwrap nullable, array, pointer, or alias layers. /// Returns if the node has no associated type. /// public static TypeSyntax TypeSyntax(this SyntaxNode node) => node switch { // Member declarations and function return types BaseFieldDeclarationSyntax { Declaration: { } x } => x.TypeSyntax(), BasePropertyDeclarationSyntax x => x.Type, ConversionOperatorDeclarationSyntax x => x.Type, DelegateDeclarationSyntax x => x.ReturnType, { RawKind: (int)SyntaxKindEx.LocalFunctionStatement } x => ((LocalFunctionStatementSyntaxWrapper)x).ReturnType, MethodDeclarationSyntax x => x.ReturnType, OperatorDeclarationSyntax x => x.ReturnType, ParenthesizedLambdaExpressionSyntax x => x.ReturnType, SimpleLambdaExpressionSyntax x => x.Parameter.TypeSyntax(), // Named typed slots CatchDeclarationSyntax x => x.Type, ParameterSyntax x => x.Type, VariableDeclarationSyntax x => x.Type, // Statements with a typed declaration CatchClauseSyntax { Declaration: { } x } => x.TypeSyntax(), FixedStatementSyntax { Declaration: { } x } => x.TypeSyntax(), ForEachStatementSyntax x => x.Type, ForStatementSyntax { Declaration: { } x } => x.TypeSyntax(), LocalDeclarationStatementSyntax { Declaration: { } x } => x.TypeSyntax(), UsingStatementSyntax { Declaration: { } x } => x.TypeSyntax(), // Expressions with a type operand BinaryExpressionSyntax { RawKind: (int)SyntaxKind.IsExpression or (int)SyntaxKind.AsExpression, Right: TypeSyntax x } => x, CastExpressionSyntax x => x.Type, DefaultExpressionSyntax x => x.Type, ObjectCreationExpressionSyntax x => x.Type, RefValueExpressionSyntax x => x.Type, SizeOfExpressionSyntax x => x.Type, StackAllocArrayCreationExpressionSyntax x => x.Type, TypeOfExpressionSyntax x => x.Type, // Type-referencing constructs AttributeSyntax x => x.Name, BaseTypeSyntax x => x.Type, TypeConstraintSyntax x => x.Type, UsingDirectiveSyntax x => x.NamespaceOrType, // Query clauses FromClauseSyntax x => x.Type, JoinClauseSyntax x => x.Type, // Wrapper types (shim layer — C# 9+ nodes) { RawKind: (int)SyntaxKindEx.DeclarationExpression } x => ((DeclarationExpressionSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.DeclarationPattern } x => ((DeclarationPatternSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.FunctionPointerParameter } x => ((FunctionPointerParameterSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.RecursivePattern } x => ((RecursivePatternSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.RefType } x => ((RefTypeSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.ScopedType } x => ((ScopedTypeSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.TupleElement } x => ((TupleElementSyntaxWrapper)x).Type, { RawKind: (int)SyntaxKindEx.TypePattern } x => ((TypePatternSyntaxWrapper)x).Type, // Identity / fallback TypeSyntax x => x, _ => null }; // Extracts the expression body from an arrow-bodied syntax node. public static ArrowExpressionClauseSyntax ArrowExpressionBody(this SyntaxNode node) => node switch { MethodDeclarationSyntax a => a.ExpressionBody, ConstructorDeclarationSyntax b => b.ExpressionBody(), OperatorDeclarationSyntax c => c.ExpressionBody, AccessorDeclarationSyntax d => d.ExpressionBody, ConversionOperatorDeclarationSyntax e => e.ExpressionBody, IndexerDeclarationSyntax f => f.ExpressionBody, PropertyDeclarationSyntax g => g.ExpressionBody, _ => null }; public static SyntaxNode RemoveParentheses(this SyntaxNode expression) { var current = expression; while (current is { } && current.Kind() is SyntaxKind.ParenthesizedExpression or SyntaxKindEx.ParenthesizedPattern) { current = current.IsKind(SyntaxKindEx.ParenthesizedPattern) ? ((ParenthesizedPatternSyntaxWrapper)current).Pattern : ((ParenthesizedExpressionSyntax)current).Expression; } return current; } public static SyntaxNode WalkUpParentheses(this SyntaxNode node) { while (node is not null && node.IsKind(SyntaxKind.ParenthesizedExpression)) { node = node.Parent; } return node; } public static SyntaxToken? GetIdentifier(this SyntaxNode node) => node switch { AliasQualifiedNameSyntax { Name.Identifier: var identifier } => identifier, ArgumentSyntax { NameColon.Name.Identifier: var identifier } => identifier, ArrayTypeSyntax { ElementType: { } elementType } => GetIdentifier(elementType), AttributeArgumentSyntax { NameColon.Name.Identifier: var identifier } => identifier, AttributeArgumentSyntax { NameEquals.Name.Identifier: var identifier } => identifier, AttributeSyntax { Name: { } name } => GetIdentifier(name), BaseTypeDeclarationSyntax { Identifier: var identifier } => identifier, CatchDeclarationSyntax { Identifier: var identifier } => identifier, ConditionalAccessExpressionSyntax { WhenNotNull: var rightSide } => GetIdentifier(rightSide), ConstructorDeclarationSyntax { Identifier: var identifier } => identifier, ConstructorInitializerSyntax { ThisOrBaseKeyword: var keyword } => keyword, ConversionOperatorDeclarationSyntax { Type: { } type } => GetIdentifier(type), DelegateDeclarationSyntax { Identifier: var identifier } => identifier, DestructorDeclarationSyntax { Identifier: var identifier } => identifier, EnumMemberDeclarationSyntax { Identifier: var identifier } => identifier, EventDeclarationSyntax { Identifier: var identifier } => identifier, ForEachStatementSyntax { Identifier: var identifier } => identifier, FromClauseSyntax { Identifier: var identifier } => identifier, IndexerDeclarationSyntax { ThisKeyword: var thisKeyword } => thisKeyword, InvocationExpressionSyntax { Expression: not InvocationExpressionSyntax // We don't want to recurse into nested invocations like: fun()() } invocation => GetIdentifier(invocation.Expression), JoinClauseSyntax { Identifier: var identifier } => identifier, JoinIntoClauseSyntax { Identifier: var identifier } => identifier, LetClauseSyntax { Identifier: var identifier } => identifier, MemberAccessExpressionSyntax { Name.Identifier: var identifier } => identifier, MemberBindingExpressionSyntax { Name.Identifier: var identifier } => identifier, MethodDeclarationSyntax { Identifier: var identifier } => identifier, NameColonSyntax nameColon => nameColon.Name.Identifier, NamespaceDeclarationSyntax { Name: { } name } => GetIdentifier(name), NullableTypeSyntax { ElementType: { } elementType } => GetIdentifier(elementType), ObjectCreationExpressionSyntax { Type: var type } => GetIdentifier(type), OperatorDeclarationSyntax { OperatorToken: var operatorToken } => operatorToken, ParameterSyntax { Identifier: var identifier } => identifier, ParenthesizedExpressionSyntax { Expression: { } expression } => GetIdentifier(expression), PropertyDeclarationSyntax { Identifier: var identifier } => identifier, PointerTypeSyntax { ElementType: { } elementType } => GetIdentifier(elementType), PredefinedTypeSyntax { Keyword: var keyword } => keyword, QualifiedNameSyntax { Right.Identifier: var identifier } => identifier, QueryContinuationSyntax { Identifier: var identifier } => identifier, SimpleBaseTypeSyntax { Type: { } type } => GetIdentifier(type), SimpleNameSyntax { Identifier: var identifier } => identifier, TypeParameterConstraintClauseSyntax { Name.Identifier: var identifier } => identifier, TypeParameterSyntax { Identifier: var identifier } => identifier, PrefixUnaryExpressionSyntax { Operand: { } operand } => GetIdentifier(operand), PostfixUnaryExpressionSyntax { Operand: { } operand } => GetIdentifier(operand), UsingDirectiveSyntax { Alias.Name: { } name } => GetIdentifier(name), VariableDeclaratorSyntax { Identifier: var identifier } => identifier, { } fileScoped when FileScopedNamespaceDeclarationSyntaxWrapper.IsInstance(fileScoped) && ((FileScopedNamespaceDeclarationSyntaxWrapper)fileScoped).Name is { } name => GetIdentifier(name), { } localFunction when LocalFunctionStatementSyntaxWrapper.IsInstance(localFunction) => ((LocalFunctionStatementSyntaxWrapper)localFunction).Identifier, { } singleVar when SingleVariableDesignationSyntaxWrapper.IsInstance(singleVar) => ((SingleVariableDesignationSyntaxWrapper)singleVar).Identifier, { } implicitNew when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(implicitNew) => ((ImplicitObjectCreationExpressionSyntaxWrapper)implicitNew).NewKeyword, { } primary when PrimaryConstructorBaseTypeSyntaxWrapper.IsInstance(primary) && ((PrimaryConstructorBaseTypeSyntaxWrapper)primary).Type is { } type => GetIdentifier(type), { } refType when RefTypeSyntaxWrapper.IsInstance(refType) => GetIdentifier(((RefTypeSyntaxWrapper)refType).Type), { } subPattern when SubpatternSyntaxWrapper.IsInstance(subPattern) && ((SubpatternSyntaxWrapper)subPattern).ExpressionColon is { SyntaxNode: not null } expressionColon => GetIdentifier(expressionColon.Expression), _ => null }; /// /// Finds the syntactic complementing of an assignment with tuples. /// /// var (a, b) = (1, 2); // if node is "a", "1" is returned and vice versa. /// (var a, var b) = (1, 2); // if node is "2", "var b" is returned and vice versa. /// a = 1; // if node is "a", "1" is returned and vice versa. /// t = (1, 2); // if node is "t", "(1, 2)" is returned, if node is "1", "null" is returned. /// /// must be an of a tuple or some variable designation of a . /// /// /// The on the other side of the assignment or if is not /// a direct child of the assignment, not part of a tuple, not part of a designation, or no corresponding /// can be found on the other side. /// public static SyntaxNode FindAssignmentComplement(this SyntaxNode node) { if (node is { Parent: AssignmentExpressionSyntax assigment }) { return OtherSideOfAssignment(node, assigment); } // can be either outermost tuple, or DeclarationExpression if 'node' is SingleVariableDesignationExpression var outermostParenthesesExpression = node.AncestorsAndSelf() .TakeWhile(x => x?.Kind() is SyntaxKind.Argument or SyntaxKindEx.TupleExpression or SyntaxKindEx.SingleVariableDesignation or SyntaxKindEx.ParenthesizedVariableDesignation or SyntaxKindEx.DiscardDesignation or SyntaxKindEx.DeclarationExpression) .LastOrDefault(x => x.Kind() is SyntaxKindEx.DeclarationExpression or SyntaxKindEx.TupleExpression); if ((TupleExpressionSyntaxWrapper.IsInstance(outermostParenthesesExpression) || DeclarationExpressionSyntaxWrapper.IsInstance(outermostParenthesesExpression)) && outermostParenthesesExpression.Parent is AssignmentExpressionSyntax assignment) { var otherSide = OtherSideOfAssignment(outermostParenthesesExpression, assignment); if (TupleExpressionSyntaxWrapper.IsInstance(otherSide) || DeclarationExpressionSyntaxWrapper.IsInstance(otherSide)) { var stackFromNodeToOutermost = GetNestingPathFromNodeToOutermost(node); return FindMatchingNestedNode(stackFromNodeToOutermost, otherSide); } else { return null; } } return null; static ExpressionSyntax OtherSideOfAssignment(SyntaxNode oneSide, AssignmentExpressionSyntax assignment) => assignment switch { { Left: { } left, Right: { } right } when left.Equals(oneSide) => right, { Left: { } left, Right: { } right } when right.Equals(oneSide) => left, _ => null, }; static Stack GetNestingPathFromNodeToOutermost(SyntaxNode node) { Stack pathFromNodeToTheTop = new(); while (TupleExpressionSyntaxWrapper.IsInstance(node?.Parent) || ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(node?.Parent) || DeclarationExpressionSyntaxWrapper.IsInstance(node?.Parent)) { if (DeclarationExpressionSyntaxWrapper.IsInstance(node?.Parent) && node is { Parent.Parent: ArgumentSyntax { } argument }) { node = argument; } node = node switch { ArgumentSyntax tupleArgument when TupleExpressionSyntaxWrapper.IsInstance(node.Parent) => PushPathPositionForTuple(pathFromNodeToTheTop, (TupleExpressionSyntaxWrapper)node.Parent, tupleArgument), _ when VariableDesignationSyntaxWrapper.IsInstance(node) && ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(node.Parent) => PushPathPositionForParenthesizedDesignation(pathFromNodeToTheTop, (ParenthesizedVariableDesignationSyntaxWrapper)node.Parent, (VariableDesignationSyntaxWrapper)node), _ => null, }; } return pathFromNodeToTheTop; } static SyntaxNode FindMatchingNestedNode(Stack pathFromOutermostToGivenNode, SyntaxNode outermostParenthesesToMatch) { var matchedNestedNode = outermostParenthesesToMatch; while (matchedNestedNode is not null && pathFromOutermostToGivenNode.Count > 0) { if (DeclarationExpressionSyntaxWrapper.IsInstance(matchedNestedNode)) { matchedNestedNode = ((DeclarationExpressionSyntaxWrapper)matchedNestedNode).Designation; } var expectedPathPosition = pathFromOutermostToGivenNode.Pop(); matchedNestedNode = matchedNestedNode switch { _ when TupleExpressionSyntaxWrapper.IsInstance(matchedNestedNode) => StepDownInTuple((TupleExpressionSyntaxWrapper)matchedNestedNode, expectedPathPosition), _ when ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(matchedNestedNode) => StepDownInParenthesizedVariableDesignation((ParenthesizedVariableDesignationSyntaxWrapper)matchedNestedNode, expectedPathPosition), _ => null, }; } return matchedNestedNode; } static SyntaxNode PushPathPositionForTuple(Stack pathPositions, TupleExpressionSyntaxWrapper tuple, ArgumentSyntax argument) { pathPositions.Push(new(tuple.Arguments.IndexOf(argument), tuple.Arguments.Count)); return tuple.SyntaxNode.Parent; } static SyntaxNode PushPathPositionForParenthesizedDesignation(Stack pathPositions, ParenthesizedVariableDesignationSyntaxWrapper parenthesizedDesignation, VariableDesignationSyntaxWrapper variable) { pathPositions.Push(new(parenthesizedDesignation.Variables.IndexOf(variable), parenthesizedDesignation.Variables.Count)); return parenthesizedDesignation.SyntaxNode; } static SyntaxNode StepDownInParenthesizedVariableDesignation(ParenthesizedVariableDesignationSyntaxWrapper parenthesizedVariableDesignation, PathPosition expectedPathPosition) => parenthesizedVariableDesignation.Variables.Count == expectedPathPosition.TupleLength ? (SyntaxNode)parenthesizedVariableDesignation.Variables[expectedPathPosition.Index] : null; static SyntaxNode StepDownInTuple(TupleExpressionSyntaxWrapper tupleExpression, PathPosition expectedPathPosition) => tupleExpression.Arguments.Count == expectedPathPosition.TupleLength ? (SyntaxNode)tupleExpression.Arguments[expectedPathPosition.Index].Expression : null; } // This is a refactored version of internal Roslyn SyntaxNodeExtensions.IsInExpressionTree public static bool IsInExpressionTree(this SyntaxNode node, SemanticModel model) { return node.AncestorsAndSelf().Any(x => IsExpressionLambda(x) || IsExpressionSelectOrOrder(x) || IsExpressionQuery(x)); bool IsExpressionLambda(SyntaxNode node) => node is LambdaExpressionSyntax && model.GetTypeInfo(node).ConvertedType.DerivesFrom(KnownType.System_Linq_Expressions_Expression); bool IsExpressionSelectOrOrder(SyntaxNode node) => node is SelectOrGroupClauseSyntax or OrderingSyntax && TakesExpressionTree(model.GetSymbolInfo(node)); bool IsExpressionQuery(SyntaxNode node) => node is QueryClauseSyntax queryClause && model.GetQueryClauseInfo(queryClause) is var info && (TakesExpressionTree(info.CastInfo) || TakesExpressionTree(info.OperationInfo)); static bool TakesExpressionTree(SymbolInfo info) { var symbols = info.Symbol is null ? info.CandidateSymbols : ImmutableArray.Create(info.Symbol); return symbols.Any(x => x is IMethodSymbol method && method.Parameters.Length > 0 && method.Parameters[0].Type.DerivesFrom(KnownType.System_Linq_Expressions_Expression)); } } // based on Type="BaseArgumentListSyntax" in https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml public static BaseArgumentListSyntax ArgumentList(this SyntaxNode node) => node switch { ObjectCreationExpressionSyntax creation => creation.ArgumentList, InvocationExpressionSyntax invocation => invocation.ArgumentList, ConstructorInitializerSyntax constructorInitializer => constructorInitializer.ArgumentList, ElementAccessExpressionSyntax x => x.ArgumentList, ElementBindingExpressionSyntax x => x.ArgumentList, null => null, _ when PrimaryConstructorBaseTypeSyntaxWrapper.IsInstance(node) => ((PrimaryConstructorBaseTypeSyntaxWrapper)node).ArgumentList, _ when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(node) => ((ImplicitObjectCreationExpressionSyntaxWrapper)node).ArgumentList, _ => throw new InvalidOperationException($"The {nameof(node)} of kind {node.Kind()} does not have an {nameof(ArgumentList)}."), }; public static ParameterListSyntax ParameterList(this SyntaxNode node) => node switch { BaseMethodDeclarationSyntax method => method.ParameterList, TypeDeclarationSyntax type => type.ParameterList(), { RawKind: (int)SyntaxKindEx.LocalFunctionStatement } localFunction => ((LocalFunctionStatementSyntaxWrapper)localFunction).ParameterList, ParenthesizedLambdaExpressionSyntax lambda => lambda.ParameterList, AnonymousMethodExpressionSyntax anonymous => anonymous.ParameterList, DelegateDeclarationSyntax delegateDeclaration => delegateDeclaration.ParameterList, _ => default, }; public static BlockSyntax GetBody(this SyntaxNode node) => node switch { BaseMethodDeclarationSyntax method => method.Body, AccessorDeclarationSyntax accessor => accessor.Body, _ when LocalFunctionStatementSyntaxWrapper.IsInstance(node) => ((LocalFunctionStatementSyntaxWrapper)node).Body, _ => null, }; public static SyntaxNode GetInitializer(this SyntaxNode node) => node switch { VariableDeclaratorSyntax { Initializer: { } initializer } => initializer, PropertyDeclarationSyntax { Initializer: { } initializer } => initializer, _ => null }; public static SyntaxTokenList GetModifiers(this SyntaxNode node) => node switch { AccessorDeclarationSyntax accessor => accessor.Modifiers, MemberDeclarationSyntax member => member.Modifiers, _ => default, }; public static bool IsTrue(this SyntaxNode node) => node switch { { RawKind: (int)SyntaxKind.TrueLiteralExpression } => true, // true { RawKind: (int)SyntaxKind.LogicalNotExpression } => IsFalse(((PrefixUnaryExpressionSyntax)node).Operand), // !false { RawKind: (int)SyntaxKindEx.ConstantPattern } => IsTrue(((ConstantPatternSyntaxWrapper)node).Expression), // is true { RawKind: (int)SyntaxKindEx.NotPattern } => IsFalse(((UnaryPatternSyntaxWrapper)node).Pattern), // is not false { RawKind: (int)SyntaxKind.ParenthesizedExpression } => IsTrue(((ParenthesizedExpressionSyntax)node).Expression), // (true) { RawKind: (int)SyntaxKindEx.ParenthesizedPattern } => IsTrue(((ParenthesizedPatternSyntaxWrapper)node).Pattern), // is (true) { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression } => IsTrue(((PostfixUnaryExpressionSyntax)node).Operand), // true! _ => false, }; public static bool IsFalse(this SyntaxNode node) => node switch { { RawKind: (int)SyntaxKind.FalseLiteralExpression } => true, // false { RawKind: (int)SyntaxKind.LogicalNotExpression } => IsTrue(((PrefixUnaryExpressionSyntax)node).Operand), // !true { RawKind: (int)SyntaxKindEx.ConstantPattern } => IsFalse(((ConstantPatternSyntaxWrapper)node).Expression), // is false { RawKind: (int)SyntaxKindEx.NotPattern } => IsTrue(((UnaryPatternSyntaxWrapper)node).Pattern), // is not true { RawKind: (int)SyntaxKind.ParenthesizedExpression } => IsFalse(((ParenthesizedExpressionSyntax)node).Expression), // (false) { RawKind: (int)SyntaxKindEx.ParenthesizedPattern } => IsFalse(((ParenthesizedPatternSyntaxWrapper)node).Pattern), // is (false) { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression } => IsFalse(((PostfixUnaryExpressionSyntax)node).Operand), // false! _ => false, }; public static bool IsDynamic(this SyntaxNode node, SemanticModel model) => model.GetTypeInfo(node) is { Type.Kind: SymbolKind.DynamicType }; public static SyntaxNode EnclosingScope(this SyntaxNode node) => node.AncestorsAndSelf().FirstOrDefault(x => x.IsAnyKind(EnclosingScopeSyntaxKinds)); public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => node.AncestorsAndSelf().LastOrDefault(x => x is BaseMethodDeclarationSyntax || x is PropertyDeclarationSyntax); public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) { var current = node; while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) { current = current.Parent; } return current; } public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => node.GetSelfOrTopParenthesizedExpression().Parent; public static bool HasAncestor(this SyntaxNode node, SyntaxKind syntaxKind) => node.Ancestors().Any(x => x.IsKind(syntaxKind)); public static bool HasAncestor(this SyntaxNode node, SyntaxKind kind1, SyntaxKind kind2) => node.Ancestors().Any(x => x.IsKind(kind1) || x.IsKind(kind2)); public static bool HasAncestor(this SyntaxNode node, ISet syntaxKinds) => node.Ancestors().Any(x => x.IsAnyKind(syntaxKinds)); public static bool IsNullLiteral(this SyntaxNode node) => node is not null && node.IsKind(SyntaxKind.NullLiteralExpression); [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public static bool IsAnyKind(this SyntaxNode node, params SyntaxKind[] syntaxKinds) => node is not null && syntaxKinds.Contains((SyntaxKind)node.RawKind); public static bool IsAnyKind(this SyntaxNode node, ISet syntaxKinds) => node is not null && syntaxKinds.Contains((SyntaxKind)node.RawKind); public static string GetName(this SyntaxNode node) => node.GetIdentifier()?.ValueText ?? string.Empty; public static bool NameIs(this SyntaxNode node, string name) => node.GetName().Equals(name, StringComparison.Ordinal); public static bool NameIs(this SyntaxNode node, string name, params string[] orNames) => node.GetName() is { } nodeName && (nodeName.Equals(name, StringComparison.Ordinal) || Array.Exists(orNames, x => nodeName.Equals(x, StringComparison.Ordinal))); public static string StringValue(this SyntaxNode node, SemanticModel model) => node switch { LiteralExpressionSyntax literal when literal.Kind() is SyntaxKind.StringLiteralExpression or SyntaxKindEx.Utf8StringLiteralExpression => literal.Token.ValueText, InterpolatedStringExpressionSyntax expression => expression.InterpolatedTextValue(model) ?? expression.ContentsText(), _ => null }; public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => nodes.Any(x => x.RawKind == (int)kind); public static AttributeData PerformanceSensitiveAttribute(this SyntaxNode node, SemanticModel model) => node? .AncestorsAndSelf() .Where(x => x.IsAnyKind(PerformanceSensitiveSyntaxes) || x is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } }) .Select(x => (model.GetSymbolInfo(x).Symbol ?? model.GetDeclaredSymbol(x))?.GetAttributes().FirstOrDefault(y => y.HasName(nameof(PerformanceSensitiveAttribute)))) .FirstOrDefault(x => x is not null); /// /// Replaces the syntax element with and returns a speculative semantic model. /// This model can then be used to analyze the new syntax element within the context of the new tree. Use the /// and the returned for semantic queries. /// /// The node type to replace. /// The node of the tree associated with the . /// The replacement, constructed via or similar methods. /// The of the . /// The speculative semantic model of the tree with the replacement. /// The inside the tree with the replacement. Use this node along with to /// query for semantic information inside the replacement node. public static T ChangeSyntaxElement(this T originalNode, T newNode, SemanticModel originalModel, out SemanticModel newModel) where T : SyntaxNode { newModel = null; if (originalNode.AncestorsAndSelf().FirstOrDefault(x => x is BaseMethodDeclarationSyntax or AccessorDeclarationSyntax or EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax } } } or EqualsValueClauseSyntax { Parent: PropertyDeclarationSyntax } or EqualsValueClauseSyntax { Parent: ParameterSyntax } or ArrowExpressionClauseSyntax or ConstructorInitializerSyntax or AttributeSyntax) is { } enclosingNode) { var annotation = new SyntaxAnnotation(); var annotated = newNode.WithAdditionalAnnotations(annotation); var replaced = enclosingNode.ReplaceNode(originalNode, annotated); if (replaced switch { BaseMethodDeclarationSyntax baseMethod => originalModel.TryGetSpeculativeSemanticModelForMethodBody(originalNode.SpanStart, baseMethod, out newModel), AccessorDeclarationSyntax accessor => originalModel.TryGetSpeculativeSemanticModelForMethodBody(originalNode.SpanStart, accessor, out newModel), EqualsValueClauseSyntax field => originalModel.TryGetSpeculativeSemanticModel(originalNode.SpanStart, field, out newModel), ArrowExpressionClauseSyntax arrow => originalModel.TryGetSpeculativeSemanticModel(originalNode.SpanStart, arrow, out newModel), ConstructorInitializerSyntax initializer => originalModel.TryGetSpeculativeSemanticModel(originalNode.SpanStart, initializer, out newModel), AttributeSyntax attribute => originalModel.TryGetSpeculativeSemanticModel(originalNode.SpanStart, attribute, out newModel), _ => throw new NotSupportedException("Unreachable case. Make sure to handle all node kinds from the ancestors if."), }) { return replaced.GetAnnotatedNodes(annotation).FirstOrDefault() as T; } } return null; } private readonly record struct PathPosition(int Index, int TupleLength); private sealed class ControlFlowGraphCache : ControlFlowGraphCacheBase { protected override bool IsLocalFunction(SyntaxNode node) => node.IsKind(SyntaxKindEx.LocalFunctionStatement); protected override bool HasNestedCfg(SyntaxNode node) => node.Kind() is SyntaxKindEx.LocalFunctionStatement or SyntaxKind.SimpleLambdaExpression or SyntaxKind.AnonymousMethodExpression or SyntaxKind.ParenthesizedLambdaExpression; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SyntaxTokenExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SyntaxTokenExtensions { [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public static bool IsAnyKind(this SyntaxToken token, params SyntaxKind[] syntaxKinds) => syntaxKinds.Contains((SyntaxKind)token.RawKind); public static bool IsAnyKind(this SyntaxToken token, ISet syntaxKinds) => syntaxKinds.Contains((SyntaxKind)token.RawKind); public static bool AnyOfKind(this IEnumerable tokens, SyntaxKind kind) => tokens.Any(x => x.RawKind == (int)kind); public static ComparisonKind ToComparisonKind(this SyntaxToken token) => token.Kind() switch { SyntaxKind.EqualsEqualsToken => ComparisonKind.Equals, SyntaxKind.ExclamationEqualsToken => ComparisonKind.NotEquals, SyntaxKind.LessThanToken => ComparisonKind.LessThan, SyntaxKind.LessThanEqualsToken => ComparisonKind.LessThanOrEqual, SyntaxKind.GreaterThanToken => ComparisonKind.GreaterThan, SyntaxKind.GreaterThanEqualsToken => ComparisonKind.GreaterThanOrEqual, _ => ComparisonKind.None, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SyntaxTokenListExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SyntaxTokenListExtensions { public static SyntaxToken? Find(this SyntaxTokenList tokenList, SyntaxKind kind) => tokenList.IndexOf(kind) is var index and >= 0 ? tokenList[index] : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/SyntaxTriviaExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class SyntaxTriviaExtensions { private static readonly HashSet CommentKinds = [ SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.SingleLineDocumentationCommentTrivia, SyntaxKind.MultiLineDocumentationCommentTrivia ]; public static bool IsAnyKind(this SyntaxTrivia syntaxTravia, ISet syntaxKinds) => syntaxKinds.Contains((SyntaxKind)syntaxTravia.RawKind); public static bool IsComment(this SyntaxTrivia trivia) => trivia.IsAnyKind(CommentKinds); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/TupleExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class TupleExpressionSyntaxExtensions { public static ImmutableArray AllArguments(this TupleExpressionSyntaxWrapper tupleExpression) { var builder = ImmutableArray.CreateBuilder(tupleExpression.Arguments.Count); CollectTupleElements(tupleExpression.Arguments); return builder.ToImmutableArray(); void CollectTupleElements(SeparatedSyntaxList arguments) { foreach (var argument in arguments) { if (TupleExpressionSyntaxWrapper.IsInstance(argument.Expression)) { CollectTupleElements(((TupleExpressionSyntaxWrapper)argument.Expression).Arguments); } else { builder.Add(argument); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/TypeDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class TypeDeclarationSyntaxExtensions { /// /// Returns a union of all the methods and local functions from a given type declaration. /// public static IEnumerable GetMethodDeclarations(this TypeDeclarationSyntax typeDeclaration) => typeDeclaration.Members .OfType() .SelectMany(method => GetLocalFunctions(method).Union(new List { MethodDeclarationFactory.Create(method) })); private static IEnumerable GetLocalFunctions(MethodDeclarationSyntax methodDeclaration) => methodDeclaration.DescendantNodes() .Where(member => member.IsKind(SyntaxKindEx.LocalFunctionStatement)) .Select(member => MethodDeclarationFactory.Create(member)); public static IMethodSymbol PrimaryConstructor(this TypeDeclarationSyntax typeDeclaration, SemanticModel semanticModel) { if (ParameterList(typeDeclaration) is { } parameterList) { return parameterList is { Parameters: { Count: > 0 } parameters } && parameters[0] is { Identifier.RawKind: not (int)SyntaxKind.ArgListKeyword } parameter0 ? semanticModel.GetDeclaredSymbol(parameter0)?.ContainingSymbol as IMethodSymbol : semanticModel.GetDeclaredSymbol(typeDeclaration).GetMembers(".ctor").OfType().FirstOrDefault(m => m is { MethodKind: MethodKind.Constructor, Parameters.Length: 0, }); } return null; } public static ParameterListSyntax ParameterList(this TypeDeclarationSyntax typeDeclaration) => // In earlier versions, the ParameterList was only available on the derived RecordDeclarationSyntax (starting from version 3.7) // To work with version 3.7 to version 4.6 we need to special case the record declaration and access // the parameter list from the derived RecordDeclarationSyntax. typeDeclaration.Kind() switch { SyntaxKind.ClassDeclaration => ClassDeclarationSyntaxShimExtensions.get_ParameterList((ClassDeclarationSyntax)typeDeclaration), SyntaxKind.StructDeclaration => StructDeclarationSyntaxShimExtensions.get_ParameterList((StructDeclarationSyntax)typeDeclaration), SyntaxKindEx.RecordDeclaration or SyntaxKindEx.RecordStructDeclaration => ((RecordDeclarationSyntaxWrapper)typeDeclaration).ParameterList, SyntaxKindEx.ExtensionBlockDeclaration => ((ExtensionBlockDeclarationSyntaxWrapper)typeDeclaration).ParameterList, _ => TypeDeclarationSyntaxShimExtensions.get_ParameterList(typeDeclaration), }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/TypeSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class TypeSyntaxExtensions { /// /// Recursively unwraps array, pointer, nullable, ref, and scoped type modifiers, /// returning the innermost base . /// Types that are returned as-is include , /// , , /// , and . /// public static TypeSyntax Unwrap(this TypeSyntax type) => type switch { ArrayTypeSyntax x => x.ElementType.Unwrap(), NullableTypeSyntax x => x.ElementType.Unwrap(), PointerTypeSyntax x => x.ElementType.Unwrap(), { RawKind: (int)SyntaxKindEx.RefType } x => ((RefTypeSyntaxWrapper)x).Type.Unwrap(), { RawKind: (int)SyntaxKindEx.ScopedType } x => ((ScopedTypeSyntaxWrapper)x).Type.Unwrap(), _ => type }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Extensions/VariableDesignationSyntaxWrapperExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; public static class VariableDesignationSyntaxWrapperExtensions { /// /// Returns all of the designation. Nested designations are flattened and /// only identifiers are included in the result (discards are skipped). For a designation like (a, (_, b)) /// the method returns [a, b]. /// public static ImmutableArray AllVariables(this VariableDesignationSyntaxWrapper variableDesignation) { var builder = ImmutableArray.CreateBuilder(); CollectVariables(variableDesignation); return builder.ToImmutableArray(); void CollectVariables(VariableDesignationSyntaxWrapper variableDesignation) { if (ParenthesizedVariableDesignationSyntaxWrapper.IsInstance(variableDesignation)) { foreach (var variable in ((ParenthesizedVariableDesignationSyntaxWrapper)variableDesignation).Variables) { CollectVariables(variable); } } else if (SingleVariableDesignationSyntaxWrapper.IsInstance(variableDesignation)) { builder.Add((SingleVariableDesignationSyntaxWrapper)variableDesignation); } // DiscardDesignationSyntaxWrapper is skipped } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpAttributeParameterLookup.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; internal class CSharpAttributeParameterLookup(AttributeSyntax attribute, IMethodSymbol methodSymbol) : MethodParameterLookupBase(attribute.ArgumentList?.Arguments ?? default, methodSymbol) { protected override SyntaxNode Expression(AttributeArgumentSyntax argument) => argument.Expression; protected override SyntaxToken? GetNameColonIdentifier(AttributeArgumentSyntax argument) => argument.NameColon?.Name.Identifier; protected override SyntaxToken? GetNameEqualsIdentifier(AttributeArgumentSyntax argument) => argument.NameEquals?.Name.Identifier; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpEquivalenceChecker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public static class CSharpEquivalenceChecker { public static bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) => EquivalenceChecker.AreEquivalent(node1, node2, NodeComparator); public static bool AreEquivalent(SyntaxList nodeList1, SyntaxList nodeList2) => EquivalenceChecker.AreEquivalent(nodeList1, nodeList2, NodeComparator); private static bool NodeComparator(SyntaxNode node1, SyntaxNode node2) => NullCheckState(node1, true) is { } nullCheck1 && NullCheckState(node2, true) is { } nullCheck2 && nullCheck1.IsPositive == nullCheck2.IsPositive ? SyntaxFactory.AreEquivalent(nullCheck1.Expression, nullCheck2.Expression) : SyntaxFactory.AreEquivalent(node1, node2); /// Flag indicating that current chain of null checks is positive '== null'. It's flipped for each `!` operator. '!(x != null)' is equal to 'x == null'. private static NullCheck NullCheckState(SyntaxNode node, bool isPositive) { if (node is PrefixUnaryExpressionSyntax unary && unary.IsKind(SyntaxKind.LogicalNotExpression)) { return NullCheckState(unary.Operand.RemoveParentheses(), !isPositive); } else if (node is BinaryExpressionSyntax binary && binary.Kind() is SyntaxKind.EqualsExpression or SyntaxKind.NotEqualsExpression) { if (binary.IsKind(SyntaxKind.NotEqualsExpression)) { isPositive = !isPositive; } return NullCheckExpression(binary) is { } expression ? new NullCheck(expression, isPositive) : null; } else if (node.IsKind(SyntaxKindEx.IsPatternExpression)) { var isPattern = (IsPatternExpressionSyntaxWrapper)node; return NullCheckPattern(isPattern.Expression, isPattern.Pattern.SyntaxNode, isPositive); } else { return null; } } private static SyntaxNode NullCheckExpression(BinaryExpressionSyntax binary) { if (binary.Left.IsKind(SyntaxKind.NullLiteralExpression)) { return binary.Right; } else if (binary.Right.IsKind(SyntaxKind.NullLiteralExpression)) { return binary.Left; } else { return null; } } /// Flag indicating that current chain of null checks is positive 'is null'. It's flipped for each `not` operator. 'is not not null' is equal to 'is null'. private static NullCheck NullCheckPattern(SyntaxNode expression, SyntaxNode pattern, bool isPositive) { if (pattern.IsKind(SyntaxKindEx.ConstantPattern) && ((ConstantPatternSyntaxWrapper)pattern).Expression.IsKind(SyntaxKind.NullLiteralExpression)) { return new NullCheck(expression, isPositive); } else if (pattern.IsKind(SyntaxKindEx.NotPattern)) { return NullCheckPattern(expression, ((UnaryPatternSyntaxWrapper)pattern).Pattern.SyntaxNode, !isPositive); } else { return null; } } private class NullCheck { public readonly SyntaxNode Expression; public readonly bool IsPositive; public NullCheck(SyntaxNode expression, bool isPositive) { Expression = expression; IsPositive = isPositive; } } } public class CSharpSyntaxNodeEqualityComparer : IEqualityComparer, IEqualityComparer> where T : SyntaxNode { public bool Equals(T x, T y) => CSharpEquivalenceChecker.AreEquivalent(x, y); public bool Equals(SyntaxList x, SyntaxList y) => CSharpEquivalenceChecker.AreEquivalent(x, y); public int GetHashCode(T obj) => obj.GetType().FullName.GetHashCode(); public int GetHashCode(SyntaxList obj) => (obj.Count + obj.Select(x => x.GetType().FullName).Distinct().JoinStr(", ")).GetHashCode(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpExpressionNumericConverter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class CSharpExpressionNumericConverter : ExpressionNumericConverterBase { private static readonly ISet SupportedOperatorTokens = new HashSet { SyntaxKind.MinusToken, SyntaxKind.PlusToken }; protected override object TokenValue(LiteralExpressionSyntax literalExpression) => literalExpression.Token.Value; protected override SyntaxNode Operand(PrefixUnaryExpressionSyntax unaryExpression) => unaryExpression.Operand; protected override bool IsSupportedOperator(PrefixUnaryExpressionSyntax unaryExpression) => SupportedOperatorTokens.Contains(unaryExpression.OperatorToken.Kind()); protected override bool IsMinusOperator(PrefixUnaryExpressionSyntax unaryExpression) => unaryExpression.OperatorToken.IsKind(SyntaxKind.MinusToken); protected override SyntaxNode RemoveParentheses(SyntaxNode expression) => expression.RemoveParentheses(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpGeneratedCodeRecognizer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class CSharpGeneratedCodeRecognizer : GeneratedCodeRecognizer { #region Singleton implementation private CSharpGeneratedCodeRecognizer() { } private static readonly Lazy Lazy = new Lazy(() => new CSharpGeneratedCodeRecognizer()); public static CSharpGeneratedCodeRecognizer Instance => Lazy.Value; #endregion Singleton implementation protected override bool IsTriviaComment(SyntaxTrivia trivia) => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) || trivia.IsKind(SyntaxKind.MultiLineCommentTrivia); protected override string GetAttributeName(SyntaxNode node) => node.IsKind(SyntaxKind.Attribute) ? ((AttributeSyntax)node).Name.ToString() : string.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpMethodParameterLookup.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class CSharpMethodParameterLookup : MethodParameterLookupBase { public CSharpMethodParameterLookup(InvocationExpressionSyntax invocation, SemanticModel semanticModel) : this(invocation.ArgumentList, semanticModel) { } public CSharpMethodParameterLookup(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) : this(invocation.ArgumentList, methodSymbol) { } public CSharpMethodParameterLookup(BaseArgumentListSyntax argumentList, SemanticModel semanticModel) : base(argumentList.Arguments, semanticModel.GetSymbolInfo(argumentList.Parent)) { } public CSharpMethodParameterLookup(BaseArgumentListSyntax argumentList, IMethodSymbol methodSymbol) : base(argumentList.Arguments, methodSymbol) { } protected override SyntaxNode Expression(ArgumentSyntax argument) => argument.Expression; protected override SyntaxToken? GetNameColonIdentifier(ArgumentSyntax argument) => argument.NameColon?.Name.Identifier; protected override SyntaxToken? GetNameEqualsIdentifier(ArgumentSyntax argument) => null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpRemovableDeclarationCollector.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class CSharpRemovableDeclarationCollector : RemovableDeclarationCollectorBase { public CSharpRemovableDeclarationCollector(INamedTypeSymbol namedType, Compilation compilation) : base(namedType, compilation) { } public override IEnumerable> RemovableFieldLikeDeclarations(ISet kinds, Accessibility maxAccessibility) { var fieldLikeNodes = TypeDeclarations.SelectMany(x => MatchingDeclarations(x, kinds).Select(node => new NodeAndModel((BaseFieldDeclarationSyntax)node, x.Model))); return fieldLikeNodes.SelectMany(x => x.Node.Declaration.Variables.Select(variable => CreateNodeSymbolAndModel(variable, x.Model)).Where(tuple => IsRemovable(tuple.Symbol, maxAccessibility))); } public override BaseTypeDeclarationSyntax OwnerOfSubnodes(BaseTypeDeclarationSyntax node) => node; public static bool IsNodeContainerTypeDeclaration(SyntaxNode node) => IsNodeStructOrClassOrRecordDeclaration(node) || node.IsKind(SyntaxKind.InterfaceDeclaration); protected override IEnumerable MatchingDeclarations(NodeAndModel container, ISet kinds) => container.Node.DescendantNodes(IsNodeContainerTypeDeclaration).Where(x => kinds.Contains(x.Kind())); private static bool IsNodeStructOrClassOrRecordDeclaration(SyntaxNode node) => node?.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKindEx.RecordDeclaration or SyntaxKindEx.RecordStructDeclaration; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpStringInterpolationConstantValueResolver.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class CSharpStringInterpolationConstantValueResolver : StringInterpolationConstantValueResolver { private static readonly Lazy Singleton = new(() => new CSharpStringInterpolationConstantValueResolver()); public static CSharpStringInterpolationConstantValueResolver Instance => Singleton.Value; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IEnumerable Contents(InterpolatedStringExpressionSyntax interpolatedStringExpression) => interpolatedStringExpression.Contents; protected override SyntaxToken TextToken(InterpolatedStringTextSyntax interpolatedStringText) => interpolatedStringText.TextToken; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/CSharpSyntaxClassifier.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Syntax.Utilities; namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public sealed class CSharpSyntaxClassifier : SyntaxClassifierBase { private static CSharpSyntaxClassifier instance; public static CSharpSyntaxClassifier Instance => instance ??= new(); private CSharpSyntaxClassifier() { } public override SyntaxNode MemberAccessExpression(SyntaxNode node) => (node as MemberAccessExpressionSyntax)?.Expression; protected override bool IsStatement(SyntaxNode node) => node is StatementSyntax; protected override SyntaxNode ParentLoopCondition(SyntaxNode node) => node.Parent switch { DoStatementSyntax doStatement => doStatement.Condition, ForStatementSyntax forStatement => forStatement.Condition, ForEachStatementSyntax forEachStatement => forEachStatement.Expression, WhileStatementSyntax whileStatement => whileStatement.Condition, _ when node.Parent.IsKind(SyntaxKindEx.ForEachVariableStatement) => ((ForEachVariableStatementSyntaxWrapper)node.Parent).Expression, _ => null }; protected override bool IsCfgBoundary(SyntaxNode node) => node is LambdaExpressionSyntax || node.IsKind(SyntaxKindEx.LocalFunctionStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/MutedSyntaxWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; /// /// This class find syntax cases that are not properly supported by current CFG/SE/LVA and we mute all issues related to these scenarios. /// public class MutedSyntaxWalker : CSharpSyntaxWalker { // All kinds that SonarAnalysisContextExtensions.RegisterExplodedGraphBasedAnalysis registers for private static readonly HashSet RootKinds = [ SyntaxKind.AddAccessorDeclaration, SyntaxKind.AnonymousMethodExpression, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKindEx.InitAccessorDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.PropertyDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKind.SimpleLambdaExpression ]; private readonly SemanticModel model; private readonly SyntaxNode root; private readonly ISymbol[] symbols; private bool isMuted; public MutedSyntaxWalker(SemanticModel model, SyntaxNode node) : this(model, node, node.DescendantNodesAndSelf().OfType().Select(x => model.GetSymbolInfo(x).Symbol).WhereNotNull().ToArray()) { } public MutedSyntaxWalker(SemanticModel model, SyntaxNode node, params ISymbol[] symbols) { this.model = model; this.symbols = symbols; root = node.Ancestors().FirstOrDefault(x => x.IsAnyKind(RootKinds)); } public bool IsMuted() { if (symbols.Any() && root is not null) { Visit(root); } return isMuted; } public override void Visit(SyntaxNode node) { if (!isMuted) // Performance optimization, we can stop visiting once we know the answer { base.Visit(node); } } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (Array.Find(symbols, x => node.NameIs(x.Name) && x.Equals(model.GetSymbolInfo(node).Symbol)) is { } symbol) { isMuted = IsInTupleAssignmentTarget() || IsUsedInLocalFunction(symbol) || IsInUnsupportedExpression(); } base.VisitIdentifierName(node); bool IsInTupleAssignmentTarget() => node.Parent is ArgumentSyntax argument && argument.IsInTupleAssignmentTarget(); bool IsUsedInLocalFunction(ISymbol symbol) => // We don't mute it if it's declared and used in local function !(symbol.ContainingSymbol is IMethodSymbol containingSymbol && containingSymbol.MethodKind == MethodKindEx.LocalFunction) && node.HasAncestor(SyntaxKindEx.LocalFunctionStatement); bool IsInUnsupportedExpression() => node.FirstAncestorOrSelf(x => x?.Kind() is SyntaxKindEx.IndexExpression or SyntaxKindEx.RangeExpression) is not null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Syntax/Utilities/SafeCSharpSyntaxWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Syntax.Utilities; public class SafeCSharpSyntaxWalker : CSharpSyntaxWalker, ISafeSyntaxWalker { protected SafeCSharpSyntaxWalker() { } protected SafeCSharpSyntaxWalker(SyntaxWalkerDepth depth) : base(depth) { } public bool SafeVisit(SyntaxNode syntaxNode) { try { Visit(syntaxNode); return true; } catch (InsufficientExecutionStackException) { // Roslyn walker overflows the stack when the depth of the call is around 2050. // See https://github.com/SonarSource/sonar-dotnet/issues/2115 return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpArgumentTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; internal sealed class CSharpArgumentTracker : ArgumentTracker { protected override SyntaxKind[] TrackedSyntaxKinds => [ SyntaxKind.AttributeArgument, SyntaxKind.Argument, ]; protected override ILanguageFacade Language => CSharpFacade.Instance; protected override IReadOnlyCollection ArgumentList(SyntaxNode argumentNode) => argumentNode switch { AttributeArgumentSyntax { Parent: AttributeArgumentListSyntax { Arguments: { } list } } => list, ArgumentSyntax { Parent: BaseArgumentListSyntax { Arguments: { } list } } => list, _ => null, }; protected override int? Position(SyntaxNode argumentNode) => argumentNode is ArgumentSyntax { NameColon: not null } or AttributeArgumentSyntax { NameColon: not null } or AttributeArgumentSyntax { NameEquals: not null } ? null : ArgumentList(argumentNode).IndexOf(x => x == argumentNode); protected override RefKind? ArgumentRefKind(SyntaxNode argumentNode) => argumentNode switch { ArgumentSyntax { RefOrOutKeyword: { } refOrOut } => refOrOut.Kind() switch { SyntaxKind.OutKeyword => RefKind.Out, SyntaxKind.RefKeyword => RefKind.Ref, SyntaxKind.InKeyword => RefKindEx.In, _ => RefKind.None }, AttributeArgumentSyntax => null, // RefKind is not supported for attributes and there is no way to specify such a constraint for Attributes in ArgumentDescriptor. _ => null, }; protected override bool InvocationMatchesMemberKind(SyntaxNode invokedExpression, MemberKind memberKind) => memberKind switch { MemberKind.Method => invokedExpression is InvocationExpressionSyntax, MemberKind.Constructor => invokedExpression is ObjectCreationExpressionSyntax or ConstructorInitializerSyntax || ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(invokedExpression), MemberKind.Indexer => invokedExpression is ElementAccessExpressionSyntax or ElementBindingExpressionSyntax, MemberKind.Attribute => invokedExpression is AttributeSyntax, _ => false, }; protected override bool InvokedMemberMatches(SemanticModel model, SyntaxNode invokedExpression, MemberKind memberKind, Func invokedMemberNameConstraint) => memberKind switch { MemberKind.Method => invokedMemberNameConstraint(invokedExpression.GetName()), MemberKind.Constructor => invokedExpression switch { ObjectCreationExpressionSyntax { Type: { } typeName } => invokedMemberNameConstraint(typeName.GetName()), ConstructorInitializerSyntax x => FindClassNameFromConstructorInitializerSyntax(x) is not string name || invokedMemberNameConstraint(name), { } x when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(x) => invokedMemberNameConstraint(model.GetSymbolInfo(x).Symbol?.ContainingType?.Name), _ => false, }, MemberKind.Indexer => invokedExpression switch { ElementAccessExpressionSyntax { Expression: { } accessedExpression } => invokedMemberNameConstraint(accessedExpression.GetName()), ElementBindingExpressionSyntax binding => binding.GetParentConditionalAccessExpression() is { Expression: { } accessedExpression } && invokedMemberNameConstraint(accessedExpression.GetName()), _ => false, }, MemberKind.Attribute => invokedExpression is AttributeSyntax { Name: { } typeName } && invokedMemberNameConstraint(typeName.GetName()), _ => false, }; private static string FindClassNameFromConstructorInitializerSyntax(ConstructorInitializerSyntax initializerSyntax) => initializerSyntax.ThisOrBaseKeyword.Kind() switch { SyntaxKind.ThisKeyword => initializerSyntax is { Parent: ConstructorDeclarationSyntax { Identifier.ValueText: { } typeName } } ? typeName : null, SyntaxKind.BaseKeyword => initializerSyntax is { Parent: ConstructorDeclarationSyntax { Parent: BaseTypeDeclarationSyntax { BaseList.Types: { Count: > 0 } baseListTypes } } } ? baseListTypes.First().GetName() // Get the class name of the called constructor from the base types list of the type declaration : null, _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpAssignmentFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpAssignmentFinder : AssignmentFinder { protected override SyntaxNode GetTopMostContainingMethod(SyntaxNode node) => node.GetTopMostContainingMethod(); /// 'true' will find any AssignmentExpressionSyntax like =, +=, -=, &=. 'false' will find only '=' SimpleAssignmentExpression. protected override bool IsAssignmentToIdentifier(SyntaxNode node, string identifierName, bool anyAssignmentKind, out SyntaxNode rightExpression) { if (node.IsKind(SyntaxKind.GlobalStatement)) { node = ((GlobalStatementSyntax)node).Statement; } if (node is ExpressionStatementSyntax statement) { node = statement.Expression; } if ((anyAssignmentKind || node.IsKind(SyntaxKind.SimpleAssignmentExpression)) && IdentifierMutation(node, identifierName) is { } mutation) { rightExpression = mutation; return true; } rightExpression = null; return false; } protected override bool IsIdentifierDeclaration(SyntaxNode node, string identifierName, out SyntaxNode initializer) { if (node.IsKind(SyntaxKind.GlobalStatement)) { node = ((GlobalStatementSyntax)node).Statement; } if (node is LocalDeclarationStatementSyntax declarationStatement && declarationStatement.Declaration.Variables.SingleOrDefault(x => x.Identifier.ValueText == identifierName) is { } declaration) { initializer = declaration.Initializer?.Value; return true; } initializer = null; return false; } protected override bool IsLoop(SyntaxNode node) => node?.Kind() is SyntaxKind.ForStatement or SyntaxKind.ForEachStatement or SyntaxKind.WhileStatement or SyntaxKind.DoStatement; /// /// If is mutated inside then /// the expression representing the new value is returned. The returned expression might be /// the reference to the identifier itself, e.g. in a case like i++;. /// private static SyntaxNode IdentifierMutation(SyntaxNode mutation, string identifierName) => mutation switch { AssignmentExpressionSyntax assignment when assignment.MapAssignmentArguments().FirstOrDefault(x => x.Left.NameIs(identifierName)) is { Right: { } right } => right, PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.PostIncrementExpression or (int)SyntaxKind.PostDecrementExpression, Operand: { } operand, } when operand.NameIs(identifierName) => operand, PrefixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.PreIncrementExpression or (int)SyntaxKind.PreDecrementExpression or (int)SyntaxKind.AddressOfExpression, Operand: { } operand, } when operand.NameIs(identifierName) => operand, // Passing by ref or out is likely mutating the argument so we assume it is assigned a value in the called method. ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.RefKeyword or (int)SyntaxKind.OutKeyword, Expression: { } argumentExpression } when argumentExpression.NameIs(identifierName) => argumentExpression, _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpBaseTypeTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpBaseTypeTracker : BaseTypeTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.BaseList }; protected override IEnumerable GetBaseTypeNodes(SyntaxNode contextNode) => ((BaseListSyntax)contextNode)?.Types.Select(t => t.Type).ToArray(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpBuilderPatternCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpBuilderPatternCondition : BuilderPatternCondition { public CSharpBuilderPatternCondition(bool constructorIsSafe, params BuilderPatternDescriptor[] descriptors) : base(constructorIsSafe, descriptors, new CSharpAssignmentFinder()) { } protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxNode GetExpression(InvocationExpressionSyntax node) => node.Expression; protected override string GetIdentifierName(InvocationExpressionSyntax node) => node.Expression.GetName(); protected override bool IsMemberAccess(SyntaxNode node, out SyntaxNode memberAccessExpression) { if (node is MemberAccessExpressionSyntax memberAccess) { memberAccessExpression = memberAccess.Expression; return true; } memberAccessExpression = null; return false; } protected override bool IsObjectCreation(SyntaxNode node) => node?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression; protected override bool IsIdentifier(SyntaxNode node, out string identifierName) { if (node is IdentifierNameSyntax identifier) { identifierName = identifier.Identifier.ValueText; return true; } identifierName = null; return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpConstantValueFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpConstantValueFinder : ConstantValueFinder { public CSharpConstantValueFinder(SemanticModel semanticModel) : base(semanticModel, new CSharpAssignmentFinder(), (int)SyntaxKind.NullLiteralExpression) { } protected override string IdentifierName(IdentifierNameSyntax node) => node.Identifier.ValueText; protected override SyntaxNode InitializerValue(VariableDeclaratorSyntax node) => node.Initializer?.Value; protected override VariableDeclaratorSyntax VariableDeclarator(SyntaxNode node) => node as VariableDeclaratorSyntax; protected override bool IsPtrZero(SyntaxNode node) => node is MemberAccessExpressionSyntax memberAccess && memberAccess.IsPtrZero(Model); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpElementAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpElementAccessTracker : ElementAccessTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = [SyntaxKind.ElementAccessExpression, SyntaxKind.ElementBindingExpression]; public override object AssignedValue(ElementAccessContext context) => context.Node.Ancestors().FirstOrDefault(x => x.IsKind(SyntaxKind.SimpleAssignmentExpression)) is AssignmentExpressionSyntax assignment ? assignment.Right.FindConstantValue(context.Model) : null; public override Condition ArgumentAtIndexEquals(int index, string value) { return context => ArgumentList(context.Node) is { } arguments && index < arguments.Count && arguments[index].Expression.FindStringConstant(context.Model) == value; static SeparatedSyntaxList? ArgumentList(SyntaxNode node) => node switch { ElementAccessExpressionSyntax elementAccess => elementAccess.ArgumentList.Arguments, ElementBindingExpressionSyntax elementBinding => elementBinding.ArgumentList.Arguments, _ => null }; } public override Condition MatchSetter() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment(); public override Condition MatchProperty(MemberDescriptor member) => context => context.Node switch { ElementAccessExpressionSyntax { Expression: MemberAccessExpressionSyntax { RawKind: (int)SyntaxKind.SimpleMemberAccessExpression } memberAccessExpression } => member.IsMatch(memberAccessExpression.Name.Identifier.ValueText, context.Model.GetTypeInfo(memberAccessExpression.Expression).Type, Language.NameComparison), ElementAccessExpressionSyntax { Expression: MemberBindingExpressionSyntax memberBindingExpression } => member.IsMatch(memberBindingExpression.Name.Identifier.ValueText, context.Model.GetSymbolInfo(memberBindingExpression).Symbol?.ContainingType, Language.NameComparison), ElementBindingExpressionSyntax { Parent.Parent: ConditionalAccessExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccessExpression } } => member.IsMatch(memberAccessExpression.Name.Identifier.ValueText, context.Model.GetTypeInfo(memberAccessExpression.Expression).Type, Language.NameComparison), _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpFieldAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpFieldAccessTracker : FieldAccessTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.MemberBindingExpression, SyntaxKind.IdentifierName }; public override Condition WhenRead() => context => !((ExpressionSyntax)context.Node).IsLeftSideOfAssignment(); public override Condition MatchSet() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment(); public override Condition AssignedValueIsConstant() => context => { var assignment = (AssignmentExpressionSyntax)context.Node.Ancestors().FirstOrDefault(ancestor => ancestor.IsKind(SyntaxKind.SimpleAssignmentExpression)); return assignment != null && assignment.Right.HasConstantValue(context.Model); }; protected override bool IsIdentifierWithinMemberAccess(SyntaxNode expression) => expression.IsKind(SyntaxKind.IdentifierName) && expression.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpInvocationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpInvocationTracker : InvocationTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.InvocationExpression }; public override Condition ArgumentAtIndexIsStringConstant(int index) => ArgumentAtIndexConformsTo(index, (argument, model) => argument.Expression.FindStringConstant(model) is not null); public override Condition ArgumentAtIndexIsAny(int index, params string[] values) => ArgumentAtIndexConformsTo(index, (argument, model) => values.Contains(argument.Expression.FindStringConstant(model))); public override Condition ArgumentAtIndexIs(int index, Func predicate) => ArgumentAtIndexConformsTo(index, (argument, model) => predicate(argument, model)); public override Condition MatchProperty(MemberDescriptor member) => context => ((InvocationExpressionSyntax)context.Node).Expression is MemberAccessExpressionSyntax methodMemberAccess && methodMemberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) && methodMemberAccess.Expression is MemberAccessExpressionSyntax propertyMemberAccess && propertyMemberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) && context.Model.GetTypeInfo(propertyMemberAccess.Expression) is TypeInfo enclosingClassType && member.IsMatch(propertyMemberAccess.Name.Identifier.ValueText, enclosingClassType.Type, Language.NameComparison); public override object ConstArgumentForParameter(InvocationContext context, string parameterName) { var argumentList = ((InvocationExpressionSyntax)context.Node).ArgumentList; var values = argumentList.ArgumentValuesForParameter(context.Model, parameterName); return values.Length == 1 && values[0] is ExpressionSyntax valueSyntax ? valueSyntax.FindConstantValue(context.Model) : null; } protected override SyntaxToken? ExpectedExpressionIdentifier(SyntaxNode expression) => ((ExpressionSyntax)expression).GetIdentifier(); private static Condition ArgumentAtIndexConformsTo(int index, Func predicate) => context => context.Node is InvocationExpressionSyntax { ArgumentList.Arguments: { } arguments } && index < arguments.Count && arguments[index] is { } argument && predicate(argument, context.Model); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpMethodDeclarationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpMethodDeclarationTracker : MethodDeclarationTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; public override Condition ParameterAtIndexIsUsed(int index) => context => { var parameterSymbol = context.MethodSymbol.Parameters.ElementAtOrDefault(0); if (parameterSymbol == null) { return false; } var methodInfo = GetMethodInfo(context); if (methodInfo?.DescendantNodes == null) { return false; } return methodInfo.DescendantNodes.Any( node => node.IsKind(SyntaxKind.IdentifierName) && ((IdentifierNameSyntax)node).Identifier.ValueText == parameterSymbol.Name && parameterSymbol.Equals(methodInfo.SemanticModel.GetSymbolInfo(node).Symbol)); }; private static MethodInfo GetMethodInfo(MethodDeclarationContext context) { if (context.MethodSymbol.IsTopLevelMain()) { var declaration = context.MethodSymbol .DeclaringSyntaxReferences .Select(r => r.GetSyntax()) .OfType() .First(); return new MethodInfo(context.GetSemanticModel(declaration), declaration.GetTopLevelMainBody().SelectMany(x => x.DescendantNodes())); } else { var declaration = context.MethodSymbol .DeclaringSyntaxReferences .Select(r => r.GetSyntax()) .OfType() .FirstOrDefault(declaration => declaration.HasBodyOrExpressionBody()); if (declaration == null) { return null; } return new MethodInfo( context.GetSemanticModel(declaration), declaration.Body?.DescendantNodes() ?? declaration.ExpressionBody()?.DescendantNodes() ?? Enumerable.Empty()); } } protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) => methodDeclaration switch { MethodDeclarationSyntax method => method.Identifier, ConstructorDeclarationSyntax constructor => constructor.Identifier, DestructorDeclarationSyntax destructor => destructor.Identifier, OperatorDeclarationSyntax op => op.OperatorToken, _ => methodDeclaration?.Parent.Parent switch // Accessors { EventDeclarationSyntax e => e.Identifier, PropertyDeclarationSyntax p => p.Identifier, IndexerDeclarationSyntax i => i.ThisKeyword, _ => null } }; private sealed class MethodInfo { public SemanticModel SemanticModel { get; } public IEnumerable DescendantNodes { get; } public MethodInfo(SemanticModel model, IEnumerable descendantNodes) { SemanticModel = model; DescendantNodes = descendantNodes; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpObjectCreationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpObjectCreationTracker : ObjectCreationTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; public override Condition ArgumentAtIndexIsConst(int index) => context => ObjectCreationFactory.Create(context.Node).ArgumentList is { } argumentList && argumentList.Arguments.Count > index && argumentList.Arguments[index].Expression.HasConstantValue(context.Model); public override object ConstArgumentForParameter(ObjectCreationContext context, string parameterName) => ObjectCreationFactory.TryCreate(context.Node, out var objectCreation) && objectCreation.ArgumentList.ArgumentValuesForParameter(context.Model, parameterName) is { Length: 1 } values && values[0] is ExpressionSyntax valueSyntax ? valueSyntax.FindConstantValue(context.Model) : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpObjectInitializationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Trackers; /// /// Verifies the initialization of an object, whether one or more properties have been correctly set when the object was initialized. /// /// A correct initialization could consist of: /// - EITHER invoking the constructor with specific parameters /// - OR invoking the constructor and then setting some specific properties on the created object public class CSharpObjectInitializationTracker { /// /// By default, the constructor arguments are ignored. /// private const int DefaultTrackedConstructorArgumentIndex = -1; private static readonly Func DefaultIsAllowedObject = (s, node, model) => false; /// /// Given the value of a literal (e.g. enum or boolean), returns true if it is an allowed value. /// private readonly Predicate isAllowedConstantValue; /// /// Given the symbol of an object, the expression used to populate the value and the semantic model, returns true if it is allowed. /// private readonly Func isAllowedObject; /// /// Given the name of a property, returns true if it is of interest for the rule verdict. /// private readonly Predicate isTrackedPropertyName; /// /// An array of types for which this class tracks initializations. /// private readonly ImmutableArray trackedTypes; /// /// The index of a constructor argument that corresponds to a tracked property. It should be -1 if it should be ignored. /// private readonly int trackedConstructorArgumentIndex; public CSharpObjectInitializationTracker(Predicate isAllowedConstantValue, ImmutableArray trackedTypes, Predicate isTrackedPropertyName, Func isAllowedObject = null, int trackedConstructorArgumentIndex = DefaultTrackedConstructorArgumentIndex) { this.isAllowedConstantValue = isAllowedConstantValue; this.trackedTypes = trackedTypes; this.isTrackedPropertyName = isTrackedPropertyName; this.isAllowedObject = isAllowedObject ?? DefaultIsAllowedObject; this.trackedConstructorArgumentIndex = trackedConstructorArgumentIndex; } public bool ShouldBeReported(IObjectCreation objectCreation, SemanticModel model, bool isDefaultConstructorSafe) => IsTrackedType(objectCreation.Expression, model) && !ObjectCreatedWithAllowedValue(objectCreation, model, isDefaultConstructorSafe) && !IsLaterAssignedWithAllowedValue(objectCreation, model); public bool ShouldBeReported(AssignmentExpressionSyntax assignment, SemanticModel model) { var assignmentMap = assignment.MapAssignmentArguments(); // Ignore assignments within object initializers, they are reported in the ObjectCreationExpression handler return assignment.FirstAncestorOrSelf() is null && assignmentMap.Any(x => IsTrackedPropertyName(x.Left) && IsPropertyOnTrackedType(x.Left, model) && !IsAllowedValue(x.Right, model)); } /// /// Tests if the provided is equal to an allowed constant (literal) value. /// private bool IsAllowedConstantValue(object constantValue) => isAllowedConstantValue(constantValue); private bool IsTrackedType(ExpressionSyntax expression, SemanticModel model) => model.GetTypeInfo(expression).Type.IsAny(trackedTypes); /// /// Tests if the expression is an allowed value. The implementation of checks is provided by the derived class. /// /// True if the expression is an allowed value, otherwise false. private bool IsAllowedValue(SyntaxNode expression, SemanticModel model) { if (expression is null) { return false; } else if (expression.IsKind(SyntaxKind.NullLiteralExpression)) { return true; } else if (expression.FindConstantValue(model) is { } constantValue) { return IsAllowedConstantValue(constantValue); } else if (model.GetSymbolInfo(expression).Symbol is { } symbol) { return isAllowedObject(symbol, expression, model); } else { return false; } } /// /// Verifies that the properties are assigned with allowed values. Otherwise, verifies the constructor invocation. /// /// There are multiple ways of verifying: /// - implicit constructor with property setting /// var x = new X { Prop = new Y() }; /// - constructor with passing a list of arguments, where we care about a specific argument /// /// Currently we do not handle the situation with default and named arguments. /// private bool ObjectCreatedWithAllowedValue(IObjectCreation objectCreation, SemanticModel model, bool isDefaultConstructorSafe) { var trackedPropertyAssignments = InitializerExpressions(objectCreation.Initializer) .OfType() .Where(x => IsTrackedPropertyName(x.Left)) .ToList(); if (trackedPropertyAssignments.Any()) { return trackedPropertyAssignments.All(x => IsAllowedValue(x.Right, model)); } else if (trackedConstructorArgumentIndex != -1) { var argumentList = objectCreation.ArgumentList; return argumentList is null || argumentList.Arguments.Count != trackedConstructorArgumentIndex + 1 || IsAllowedValue(argumentList.Arguments[trackedConstructorArgumentIndex].Expression, model); } else { // if no tracked properties are being explicitly set or passed as arguments, check if the default constructor is safe return isDefaultConstructorSafe; } } /// /// Returns true if is the name of a tracked property. /// private bool IsTrackedPropertyName(string propertyName) => isTrackedPropertyName(propertyName); /// /// Returns true if the has the name of a tracked property. /// private bool IsTrackedPropertyName(SyntaxNode expression) { var identifier = (expression as MemberAccessExpressionSyntax)?.Name?.Identifier ?? (expression as IdentifierNameSyntax)?.Identifier ?? (expression as MemberBindingExpressionSyntax)?.Name.Identifier; return identifier.HasValue && IsTrackedPropertyName(identifier.Value.ValueText); } /// /// Returns true if the provided expression is a member of a tracked type. /// private bool IsPropertyOnTrackedType(SyntaxNode expression, SemanticModel model) { var targetExpression = expression switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Expression, ConditionalAccessExpressionSyntax conditionalAccess => conditionalAccess.Expression, MemberBindingExpressionSyntax memberBinding => (memberBinding.Parent.Parent as ConditionalAccessExpressionSyntax)?.Expression, _ => null }; return targetExpression is not null && IsTrackedType(targetExpression, model); } private bool IsLaterAssignedWithAllowedValue(IObjectCreation objectCreation, SemanticModel model) { var statement = objectCreation.Expression.FirstAncestorOrSelf(); if (statement is null) { return false; } var variableSymbol = AssignedVariableSymbol(objectCreation, model); var nextStatements = NextStatements(statement); var innerStatements = InnerStatements(statement); return variableSymbol is not null && nextStatements.Union(innerStatements) .OfType() .Select(x => x.Expression) .OfType() .Any(TrackedPropertySetWithAllowedValue); bool TrackedPropertySetWithAllowedValue(AssignmentExpressionSyntax assignment) { var assignmentMap = assignment.MapAssignmentArguments(); return assignmentMap.Any(x => IsTrackedPropertyName(x.Left) && variableSymbol.Equals(AssignedVariableSymbol(x.Left, model)) && IsAllowedValue(x.Right, model)); } } private static IEnumerable InitializerExpressions(InitializerExpressionSyntax initializer) => initializer?.Expressions ?? Enumerable.Empty(); private static ISymbol AssignedVariableSymbol(IObjectCreation objectCreation, SemanticModel model) { if (objectCreation.Expression.FirstAncestorOrSelf()?.Left is { } variable) { return model.GetSymbolInfo(variable).Symbol; } return objectCreation.Expression.FirstAncestorOrSelf() is { } identifier ? model.GetDeclaredSymbol(identifier) : null; } private static ISymbol AssignedVariableSymbol(SyntaxNode node, SemanticModel model) { var identifier = node is MemberAccessExpressionSyntax memberAccess ? memberAccess.Expression : node as IdentifierNameSyntax; return model.GetSymbolInfo(identifier).Symbol; } private static IEnumerable NextStatements(StatementSyntax statement) => statement.Parent.ChildNodes().OfType().SkipWhile(x => x != statement).Skip(1); private static IEnumerable InnerStatements(StatementSyntax statement) => statement.DescendantNodes().OfType(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Trackers/CSharpPropertyAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers; public class CSharpPropertyAccessTracker : PropertyAccessTracker { protected override ILanguageFacade Language => CSharpFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.MemberBindingExpression, SyntaxKind.IdentifierName }; public override object AssignedValue(PropertyAccessContext context) => context.Node.Ancestors().FirstOrDefault(ancestor => ancestor.IsKind(SyntaxKind.SimpleAssignmentExpression)) is AssignmentExpressionSyntax assignment ? assignment.Right.FindConstantValue(context.Model) : null; public override Condition MatchGetter() => context => !((ExpressionSyntax)context.Node).IsLeftSideOfAssignment(); public override Condition MatchSetter() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment(); public override Condition AssignedValueIsConstant() => context => AssignedValue(context) != null; protected override bool IsIdentifierWithinMemberAccess(SyntaxNode expression) => expression.IsKind(SyntaxKind.IdentifierName) && IsSimpleMemberWithinMemberAccess(expression.Parent); private static bool IsSimpleMemberWithinMemberAccess(SyntaxNode node) => node.IsKind(SyntaxKind.SimpleMemberAccessExpression) || (node.IsKind(SyntaxKind.MemberBindingExpression) && node.Parent.IsKind(SyntaxKind.SimpleAssignmentExpression) && node.Parent.Parent.IsKind(SyntaxKind.ConditionalAccessExpression)); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Wrappers/IMethodDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Wrappers; public interface IMethodDeclaration { BlockSyntax Body { get; } ArrowExpressionClauseSyntax ExpressionBody { get; } SyntaxToken Identifier { get; } ParameterListSyntax ParameterList { get; } public TypeParameterListSyntax TypeParameterList { get; } bool HasImplementation { get; } bool IsLocal { get; } TypeSyntax ReturnType { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Wrappers/MethodDeclarationFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Wrappers; public static class MethodDeclarationFactory { public static IMethodDeclaration Create(SyntaxNode node) { if (node is null) { throw new ArgumentNullException(nameof(node)); } else if (LocalFunctionStatementSyntaxWrapper.IsInstance(node)) { return new LocalFunctionStatementAdapter((LocalFunctionStatementSyntaxWrapper)node); } else if (node is MethodDeclarationSyntax method) { return new MethodDeclarationSyntaxAdapter(method); } else { throw new InvalidOperationException("Unexpected type: " + node.GetType().Name); } } private sealed class LocalFunctionStatementAdapter : IMethodDeclaration { private readonly LocalFunctionStatementSyntaxWrapper syntaxWrapper; public BlockSyntax Body => syntaxWrapper.Body; public ArrowExpressionClauseSyntax ExpressionBody => syntaxWrapper.ExpressionBody; public SyntaxToken Identifier => syntaxWrapper.Identifier; public ParameterListSyntax ParameterList => syntaxWrapper.ParameterList; public TypeParameterListSyntax TypeParameterList => syntaxWrapper.TypeParameterList; public bool HasImplementation => Body is not null || ExpressionBody is not null; public bool IsLocal => true; public TypeSyntax ReturnType => syntaxWrapper.ReturnType; public LocalFunctionStatementAdapter(LocalFunctionStatementSyntaxWrapper syntaxWrapper) => this.syntaxWrapper = syntaxWrapper; } private sealed class MethodDeclarationSyntaxAdapter : IMethodDeclaration { private readonly MethodDeclarationSyntax declarationSyntax; public BlockSyntax Body => declarationSyntax.Body; public ArrowExpressionClauseSyntax ExpressionBody => declarationSyntax.ExpressionBody; public SyntaxToken Identifier => declarationSyntax.Identifier; public ParameterListSyntax ParameterList => declarationSyntax.ParameterList; public TypeParameterListSyntax TypeParameterList => declarationSyntax.TypeParameterList; public bool HasImplementation => Body is not null || ExpressionBody is not null; public bool IsLocal => false; public TypeSyntax ReturnType => declarationSyntax.ReturnType; public MethodDeclarationSyntaxAdapter(MethodDeclarationSyntax declarationSyntax) => this.declarationSyntax = declarationSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/Wrappers/ObjectCreationFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Wrappers; public interface IObjectCreation { InitializerExpressionSyntax Initializer { get; } ArgumentListSyntax ArgumentList { get; } ExpressionSyntax Expression { get; } IEnumerable InitializerExpressions { get; } bool IsKnownType(KnownType knownType, SemanticModel semanticModel); string TypeAsString(SemanticModel semanticModel); ITypeSymbol TypeSymbol(SemanticModel semanticModel); IMethodSymbol MethodSymbol(SemanticModel semanticModel); } public class ObjectCreationFactory { public static IObjectCreation Create(SyntaxNode node) => node switch { null => throw new ArgumentNullException(nameof(node)), ObjectCreationExpressionSyntax objectCreation => new ObjectCreation(objectCreation), { } when ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(node) => new ImplicitObjectCreation((ImplicitObjectCreationExpressionSyntaxWrapper)node), _ => throw new InvalidOperationException("Unexpected type: " + node.GetType().Name) }; public static IObjectCreation TryCreate(SyntaxNode node) => node?.Kind() is SyntaxKind.ObjectCreationExpression or SyntaxKindEx.ImplicitObjectCreationExpression ? Create(node) : null; public static bool TryCreate(SyntaxNode node, out IObjectCreation objectCreation) { objectCreation = TryCreate(node); return objectCreation is not null; } private sealed class ObjectCreation : IObjectCreation { private readonly ObjectCreationExpressionSyntax objectCreation; public InitializerExpressionSyntax Initializer => objectCreation.Initializer; public ArgumentListSyntax ArgumentList => objectCreation.ArgumentList; public ExpressionSyntax Expression => objectCreation; public IEnumerable InitializerExpressions => objectCreation.Initializer?.Expressions; public ObjectCreation(ObjectCreationExpressionSyntax objectCreationExpressionSyntax) => objectCreation = objectCreationExpressionSyntax; public bool IsKnownType(KnownType knownType, SemanticModel semanticModel) => objectCreation.Type.GetName().EndsWith(knownType.TypeName) && objectCreation.IsKnownType(knownType, semanticModel); public string TypeAsString(SemanticModel semanticModel) => objectCreation.Type.ToString(); public ITypeSymbol TypeSymbol(SemanticModel semanticModel) => semanticModel.GetTypeInfo(objectCreation).Type; public IMethodSymbol MethodSymbol(SemanticModel semanticModel) => semanticModel.GetSymbolInfo(objectCreation).Symbol as IMethodSymbol; } private sealed class ImplicitObjectCreation : IObjectCreation { private readonly ImplicitObjectCreationExpressionSyntaxWrapper objectCreation; public InitializerExpressionSyntax Initializer => objectCreation.Initializer; public ArgumentListSyntax ArgumentList => objectCreation.ArgumentList; public ExpressionSyntax Expression => objectCreation.SyntaxNode; public IEnumerable InitializerExpressions => objectCreation.Initializer?.Expressions; public ImplicitObjectCreation(ImplicitObjectCreationExpressionSyntaxWrapper wrapper) => objectCreation = wrapper; public bool IsKnownType(KnownType knownType, SemanticModel semanticModel) => semanticModel.GetTypeInfo(objectCreation).Type.Is(knownType); // Return null if TypeSymbol returns null to avoid AD0001 due to this issue: https://github.com/dotnet/roslyn/issues/70041 public string TypeAsString(SemanticModel semanticModel) { var typeSymbol = TypeSymbol(semanticModel); return typeSymbol is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T } namedTypeSymbol ? namedTypeSymbol.TypeArguments[0].Name : typeSymbol?.Name; } public ITypeSymbol TypeSymbol(SemanticModel semanticModel) => semanticModel.GetTypeInfo(objectCreation).ConvertedType; public IMethodSymbol MethodSymbol(SemanticModel semanticModel) => semanticModel.GetSymbolInfo(objectCreation).Symbol as IMethodSymbol; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Core/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Direct", "requested": "[1.0.27, )", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Common/DescriptorFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Analyzers; namespace SonarAnalyzer.CSharp.Styling.Common; internal static class DescriptorFactory { public static DiagnosticDescriptor Create(string id, string messageFormat, SourceScope scope = SourceScope.All, bool isCompilationEnd = false) => DiagnosticDescriptorFactory.Create( AnalyzerLanguage.CSharp, new RuleDescriptor(id, $"Internal Styling Rule {id}", "CODE_SMELL", "Major", null, scope, true, null), messageFormat, true, false, isCompilationEnd); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Common/OrderDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Common; internal sealed record OrderDescriptor(int Value, string Description); ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Common/StylingAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Analyzers; namespace SonarAnalyzer.CSharp.Styling.Common; public abstract class StylingAnalyzer : SonarDiagnosticAnalyzer { public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected DiagnosticDescriptor Rule { get; } protected StylingAnalyzer(string id, string messageFormat, SourceScope scope = SourceScope.All) => Rule = DescriptorFactory.Create(id, messageFormat, scope); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Extensions/LambdaExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Extensions; public static class LambdaExpressionSyntaxExtensions { public static bool IsAnalysisContextAction(this LambdaExpressionSyntax node, SemanticModel model) => model.GetSymbolInfo(node).Symbol is IMethodSymbol { ReturnsVoid: true } symbol && IsAnalysisContextName(symbol.Parameters.Single().Type.Name); private static bool IsAnalysisContextName(string name) => name.EndsWith("AnalysisContext") || (name.StartsWith("Sonar") && name.EndsWith("ReportingContext")); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Extensions/MemberDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Extensions; internal static class MemberDeclarationSyntaxExtensions { public static OrderDescriptor ComputeOrder(this MemberDeclarationSyntax member) { if (member.Modifiers.Any(SyntaxKind.ProtectedKeyword)) // protected, protected internal or private protected { return new(3, "protected"); } else if (member.Modifiers.Any(SyntaxKind.PublicKeyword)) { return new(1, "public"); } else if (member.Modifiers.Any(SyntaxKind.InternalKeyword)) { return new(2, "internal"); } else // private or unspecified { return new(4, "private"); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Extensions/SyntaxNodeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Extensions; internal static class SyntaxNodeExtensions { public static bool HasSameStartLineAs(this SyntaxNode first, SyntaxNode second) => first.GetLocation().StartLine() == second.GetLocation().StartLine(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AllArgumentsOnSameLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AllArgumentsOnSameLine : StylingAnalyzer { public AllArgumentsOnSameLine() : base("T0028", "All arguments should be on the same line or on separate lines.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Verify(c, ((BaseArgumentListSyntax)c.Node).Arguments), SyntaxKind.ArgumentList); context.RegisterNodeAction(c => Verify(c, ((BracketedArgumentListSyntax)c.Node).Arguments), SyntaxKind.BracketedArgumentList); context.RegisterNodeAction(c => Verify(c, ((TypeArgumentListSyntax)c.Node).Arguments), SyntaxKind.TypeArgumentList); context.RegisterNodeAction(c => Verify(c, ((AttributeArgumentListSyntax)c.Node).Arguments), SyntaxKind.AttributeArgumentList); } private void Verify(SonarSyntaxNodeReportingContext context, IEnumerable arguments) { var args = arguments.ToArray(); if (args.Length < 2) { return; } var multiline = args[0].GetLocation().StartLine() != args.Last().GetLocation().StartLine(); foreach (var arg in args) { if (IsOnNewLine(arg) != multiline && arg.ChildNodes().All(x => !IsIgnored(context.Model, x))) { context.ReportIssue(Rule, arg); } } } private static bool IsOnNewLine(SyntaxNode node) => node.GetLocation().StartLine() != node.GetFirstToken().GetPreviousToken().GetLocation().StartLine(); private static bool IsIgnored(SemanticModel model, SyntaxNode node) => node switch { LambdaExpressionSyntax lambda => lambda.IsAnalysisContextAction(model), InterpolatedStringExpressionSyntax interpolated => interpolated.StringStartToken.Line() != interpolated.StringEndToken.Line() && interpolated.StringEndToken.IsKind(SyntaxKind.InterpolatedRawStringEndToken), LiteralExpressionSyntax literal => literal.GetFirstToken().IsKind(SyntaxKind.MultiLineRawStringLiteralToken), _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AllParametersBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; public abstract class AllParametersBase : StylingAnalyzer { protected abstract void Verify(SonarSyntaxNodeReportingContext context, SyntaxNode[] parameters); protected AllParametersBase(string id, string messageFormat) : base(id, messageFormat) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => Verify(c, Parameters(c.Node).ToArray()), SyntaxKind.ParameterList, SyntaxKind.BracketedParameterList, SyntaxKind.TypeParameterList, SyntaxKind.FunctionPointerParameterList); private static IEnumerable Parameters(SyntaxNode node) => node switch { FunctionPointerParameterListSyntax list => list.Parameters, TypeParameterListSyntax list => list.Parameters, BaseParameterListSyntax list => list.Parameters, _ => throw new UnexpectedValueException(nameof(node), node?.GetType().ToString()) }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AllParametersOnSameColumn.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AllParametersOnSameColumn : AllParametersBase { public AllParametersOnSameColumn() : base("T0022", "Parameters should start on the same column.") { } protected override void Verify(SonarSyntaxNodeReportingContext context, SyntaxNode[] parameters) { foreach (var parameter in parameters.Skip(1)) { if (!parameters[0].HasSameStartLineAs(parameter) && !IsSameColumn(parameters[0], parameter)) { context.ReportIssue(Rule, parameter); } } } private static bool IsSameColumn(SyntaxNode first, SyntaxNode second) => first.GetLocation().StartColumn() == second.GetLocation().StartColumn(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AllParametersOnSameLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AllParametersOnSameLine : AllParametersBase { public AllParametersOnSameLine() : base("T0023", "Parameters should be on the same line or all on separate lines.") { } protected override void Verify(SonarSyntaxNodeReportingContext context, SyntaxNode[] parameters) { if (parameters.Length < 3) { return; } var isSameLine = parameters[0].HasSameStartLineAs(parameters[1]); for (var i = 2; i < parameters.Length; i++) { if (isSameLine != parameters[i - 1].HasSameStartLineAs(parameters[i])) { context.ReportIssue(Rule, parameters[i]); return; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/ArrowLocation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ArrowLocation : StylingAnalyzer { public ArrowLocation() : base("T0002", "Place the arrow at the end of the previous line.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Verify(c, ((LambdaExpressionSyntax)c.Node).ArrowToken), SyntaxKind.SimpleLambdaExpression); context.RegisterNodeAction(c => Verify(c, ((LambdaExpressionSyntax)c.Node).ArrowToken), SyntaxKind.ParenthesizedLambdaExpression); context.RegisterNodeAction(c => Verify(c, ((ArrowExpressionClauseSyntax)c.Node).ArrowToken), SyntaxKind.ArrowExpressionClause); context.RegisterNodeAction(c => Verify(c, ((SwitchExpressionArmSyntax)c.Node).EqualsGreaterThanToken), SyntaxKind.SwitchExpressionArm); } private void Verify(SonarSyntaxNodeReportingContext context, SyntaxToken arrowToken) { if (!arrowToken.IsMissing && arrowToken.IsFirstTokenOnLine()) { context.ReportIssue(Rule, arrowToken); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidArrangeActAssertComment.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidArrangeActAssertComment : StylingAnalyzer { private static readonly Regex WordRegex = new(@"\b(\w+)\b", RegexOptions.None, Constants.DefaultRegexTimeout); private static readonly HashSet ForbiddenComments = ["Arrange", "Act", "Assert"]; public AvoidArrangeActAssertComment() : base("T0044", "Remove this Arrange, Act, Assert comment.", SourceScope.Tests) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var comments = c.Node .DescendantTrivia() .Where(x => x.IsComment() && !ExtractWords(x).Except(ForbiddenComments, StringComparer.OrdinalIgnoreCase).Any()); foreach (var comment in comments) { c.ReportIssue(Rule, comment.GetLocation()); } }, SyntaxKind.MethodDeclaration); private static string[] ExtractWords(SyntaxTrivia comment) => WordRegex.SafeMatches(comment.ToString()) .Cast() .Where(x => x.Success) .Select(x => x.Groups[1].Value) .ToArray(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidDeconstruction.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.Syntax.Utilities; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidDeconstruction : StylingAnalyzer { private const string DiscardMessage = "It's pointless."; private const string VariableMessage = "Reference the member from the instance instead."; public AvoidDeconstruction() : base("T0035", "Don't use this deconstruction. {0}") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var varPattern = (VarPatternSyntax)c.Node; if (varPattern.Parent is not IsPatternExpressionSyntax and not SwitchExpressionArmSyntax) { if (varPattern.Designation is DiscardDesignationSyntax) { c.ReportIssue(Rule, c.Node, DiscardMessage); } else if (varPattern.Designation is SingleVariableDesignationSyntax variable && !IsUsedEnough(c, variable)) { c.ReportIssue(Rule, c.Node, VariableMessage); } } }, SyntaxKind.VarPattern); private static bool IsUsedEnough(SonarSyntaxNodeReportingContext context, SingleVariableDesignationSyntax variable) { if (context.Node.Ancestors().FirstOrDefault(x => x is BlockSyntax or ArrowExpressionClauseSyntax or LambdaExpressionSyntax) is { } root && context.Model.GetDeclaredSymbol(variable) is { } symbol) { var walker = new IdentifierWalker(context.Model, symbol); walker.SafeVisit(root); return walker.IsUsedEnough; } else { return false; } } } file sealed class IdentifierWalker : SafeCSharpSyntaxWalker { private const int MinUsageCount = 3; private readonly SemanticModel model; private readonly ISymbol symbol; private int usageCount; public bool IsUsedEnough => usageCount >= MinUsageCount; public IdentifierWalker(SemanticModel model, ISymbol symbol) { this.model = model; this.symbol = symbol; } public override void Visit(SyntaxNode node) { if (!IsUsedEnough) // Performance: Stop searching once we know the answer { base.Visit(node); } } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (node.Identifier.ValueText == symbol.Name && model.GetSymbolInfo(node).Symbol is { } nodeSymbol && symbol.Equals(nodeSymbol, SymbolEqualityComparer.Default)) { usageCount++; } base.VisitIdentifierName(node); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidGet.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidGet : StylingAnalyzer { public AvoidGet() : base("T0000", "Do not use 'Get' prefix. Just don't.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Process(c, ((MethodDeclarationSyntax)c.Node).Identifier), SyntaxKind.MethodDeclaration); context.RegisterNodeAction(c => Process(c, ((LocalFunctionStatementSyntax)c.Node).Identifier), SyntaxKind.LocalFunctionStatement); } private void Process(SonarSyntaxNodeReportingContext context, SyntaxToken identifier) { if (HasGetPrefix(identifier.ValueText) && !IgnoreMethod((IMethodSymbol)context.ContainingSymbol)) { context.ReportIssue(Rule, identifier); } } private static bool HasGetPrefix(string name) => name.Length > 4 && name.StartsWith("Get") && !char.IsLower(name[3]); private static bool IgnoreMethod(IMethodSymbol method) => method.ReturnsVoid || method.IsOverride || method.ExplicitOrImplicitInterfaceImplementations().Any(); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidListForEach.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidListForEach : StylingAnalyzer { public AvoidListForEach() : base("T0011", "Use 'foreach' iteration instead of 'List.ForEach'.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var name = ((MemberAccessExpressionSyntax)c.Node).Name; if (name.Identifier.ValueText == nameof(List.ForEach) && c.Model.GetSymbolInfo(name).Symbol is IMethodSymbol method && method.Is(KnownType.System_Collections_Generic_List_T, nameof(List.ForEach))) { c.ReportIssue(Rule, name); } }, SyntaxKind.SimpleMemberAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidNullable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidNullable : StylingAnalyzer { public AvoidNullable() : base("T0036", "Do not use nullable context.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (((NullableDirectiveTriviaSyntax)c.Node).SettingToken.IsKind(SyntaxKind.EnableKeyword)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.NullableDirectiveTrivia); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidPrimaryConstructor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AvoidPrimaryConstructor : StylingAnalyzer { public AvoidPrimaryConstructor() : base("T0043", "Do not use primary constructors.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node.Parent is ClassDeclarationSyntax or StructDeclarationSyntax) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.ParameterList); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidUnusedInterpolation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidUnusedInterpolation : StylingAnalyzer { private const string ReduceMessage = "Reduce the number of $ in this string."; private const string RemoveMessage = "Remove unused interpolation from this string."; public AvoidUnusedInterpolation() : base("T0040", "{0}") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var interpolated = (InterpolatedStringExpressionSyntax)c.Node; if (interpolated.Contents.OfType().IsEmpty()) { c.ReportIssue(Rule, interpolated.StringStartToken, RemoveMessage); } else if (DollarCount(interpolated) > CurlyBraceCount(interpolated) + 1) { c.ReportIssue(Rule, interpolated.StringStartToken, ReduceMessage); } }, SyntaxKind.InterpolatedStringExpression); private static int DollarCount(InterpolatedStringExpressionSyntax interpolated) => interpolated.StringStartToken.ValueText.Count(x => x == '$'); private static int CurlyBraceCount(InterpolatedStringExpressionSyntax interpolated) { var max = 0; foreach (var text in interpolated.Contents.OfType()) { max = Math.Max(max, CurlyBraceCount(text.TextToken.ValueText)); } return max; } private static int CurlyBraceCount(string text) { var max = 0; var i = 0; while (i < text.Length) { max = Math.Max(max, Count('{')); max = Math.Max(max, Count('}')); if (i < text.Length && text[i] != '{') { i++; } } return max; int Count(char c) { var count = 0; while (i < text.Length && text[i] == c) { count++; i++; } return count; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidValueTuple.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidValueTuple : StylingAnalyzer { public AvoidValueTuple() : base("T0003", "Do not use ValueTuple in the production code due to missing System.ValueTuple.dll.", SourceScope.Main) { } // It's not a problem in UTs protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => c.ReportIssue(Rule, c.Node), SyntaxKind.TupleType, SyntaxKind.TupleExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/AvoidVarPattern.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class AvoidVarPattern : StylingAnalyzer { public AvoidVarPattern() : base("T0034", "Avoid embedding this var pattern. Declare it in var statement instead.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node.Parent is IsPatternExpressionSyntax && CanBeExtracted(c.Node.Parent)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.VarPattern); private static bool CanBeExtracted(SyntaxNode node) { if (node.Parent is BinaryExpressionSyntax { Parent: not BinaryExpressionSyntax } binaryLast && binaryLast.Right == node) { node = node.Parent; } else { while (node.Parent is BinaryExpressionSyntax binaryFirst && binaryFirst.Left == node) { node = node.Parent; } } return node.Parent is IfStatementSyntax { Parent: not ElseClauseSyntax } or WhileStatementSyntax or ForStatementSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/FieldOrdering.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FieldOrdering : StylingAnalyzer { public FieldOrdering() : base("T0013", "Move this static field above the {0} instance ones.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ValidateMembers, SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.StructDeclaration); private void ValidateMembers(SonarSyntaxNodeReportingContext context) { var fields = ((TypeDeclarationSyntax)context.Node).Members.OfType().Where(x => !x.Modifiers.Any(SyntaxKind.ConstKeyword)); foreach (var visibilityGroup in fields.GroupBy(x => x.ComputeOrder())) { ValidateMembers(context, visibilityGroup.Key, visibilityGroup); } } private void ValidateMembers(SonarSyntaxNodeReportingContext context, OrderDescriptor order, IEnumerable members) { var hasInstance = false; foreach (var member in members) { if (member.Modifiers.Any(SyntaxKind.StaticKeyword)) { if (hasInstance) { context.ReportIssue(Rule, member.Declaration, order.Description); } } else { hasInstance = true; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/FileScopeNamespace.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class FileScopeNamespace : StylingAnalyzer { public FileScopeNamespace() : base("T0001", "Use file-scoped namespace.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => c.ReportIssue(Rule, ((NamespaceDeclarationSyntax)c.Node).Name), SyntaxKind.NamespaceDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/HelperInTypeName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class HelperInTypeName : StylingAnalyzer { public HelperInTypeName() : base("T0006", "Do not use 'Helper' in type names.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { if (c.Node.GetIdentifier() is { } identifier && identifier.ValueText.Contains("Helper")) { c.ReportIssue(Rule, identifier); } }, SyntaxKind.UsingDirective, SyntaxKind.ClassDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.InterfaceDeclaration); context.RegisterNodeAction(c => { var name = c.Node switch { NamespaceDeclarationSyntax ns => ns.Name, FileScopedNamespaceDeclarationSyntax ns => ns.Name }; if (name.ToString().Contains("Helper")) { c.ReportIssue(Rule, name); } }, SyntaxKind.NamespaceDeclaration, SyntaxKind.FileScopedNamespaceDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentArgument.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentArgument : IndentBase { public IndentArgument() : base("T0029", "argument") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (ExpectedPosition(c.Node) is { } expected) { Verify(c, expected, c.Node.GetFirstToken(), c.Node); if (c.Node is ArgumentSyntax argument && argument.Expression is LambdaExpressionSyntax lambda && (lambda.Body ?? lambda.ExpressionBody) is { } expressionOrBody) { Verify(c, expected, expressionOrBody.GetFirstToken(), expressionOrBody); } } }, SyntaxKind.Argument, SyntaxKind.ExpressionElement); protected override SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) => current switch { ForStatementSyntax => node.Ancestors().OfType().FirstOrDefault(), InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } when FirstMemberOnLine(memberAccess) is { } member => member, ObjectCreationExpressionSyntax or BaseObjectCreationExpressionSyntax when current.GetFirstToken().IsFirstTokenOnLine() => current, CollectionExpressionSyntax when node is ExpressionElementSyntax && !current.GetFirstToken().IsFirstTokenOnLine() && FirstMemberOnLine(current) is { } member => member, { Parent: ConditionalExpressionSyntax ternary } when ternary.WhenTrue == current || ternary.WhenFalse == current => current, _ => base.NodeRoot(node, current), }; private static SyntaxNode FirstMemberOnLine(SyntaxNode node) => node switch { ArgumentSyntax { Parent.Parent: { } grandParent } => FirstMemberOnLine(grandParent), CollectionExpressionSyntax { Parent: { } parent } => FirstMemberOnLine(parent), InvocationExpressionSyntax { Expression: { } expression } => FirstMemberOnLine(expression), MemberAccessExpressionSyntax { Name: { } name } memberAccess when memberAccess.OperatorToken.IsFirstTokenOnLine() => name, MemberAccessExpressionSyntax { Expression: { } expression } => FirstMemberOnLine(expression), _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Styling.Rules; public abstract class IndentBase : StylingAnalyzer { protected IndentBase(string id, string expressionName) : base(id, $$"""Indent this {{expressionName}} at line position {0}.""") { } protected void Verify(SonarSyntaxNodeReportingContext context, int expected, SyntaxToken token, SyntaxNode reportingLocationExpression = null) { if (token.IsFirstTokenOnLine() // Raise only when the line starts with this token to avoid collisions with T0024, T0027, etc. && token.GetLocation().GetLineSpan().StartLinePosition.Character != expected) { context.ReportIssue(Rule, Location.Create(token.SyntaxTree, TextSpan.FromBounds(token.SpanStart, (reportingLocationExpression?.Span ?? token.Span).End)), (expected + 1).ToString()); } } protected virtual int Offset(SyntaxNode node, SyntaxNode root) => 4; protected int? ExpectedPosition(SyntaxNode node) => StatementRoot(node) is { } root ? ExpectedPosition(root.GetLocation().GetLineSpan().StartLinePosition.Character, Offset(node, root)) : null; protected static int ExpectedPosition(int character, int offset) => (character + offset) / 4 * 4; // Nearest next tab distance protected virtual SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) { if (current is ForStatementSyntax) { return node; // Root from the original node itself (ternary, binary, ...) } else if (current is StatementSyntax or AssignmentExpressionSyntax or SwitchExpressionArmSyntax || current is ExpressionSyntax { Parent: IfStatementSyntax or WhileStatementSyntax } || current.Parent is ArrowExpressionClauseSyntax or LambdaExpressionSyntax || (current is InvocationExpressionSyntax or CollectionExpressionSyntax && current.GetFirstToken().IsFirstTokenOnLine())) { return current; } else { return null; } } protected virtual bool IsIgnored(SyntaxNode node) => false; private SyntaxNode StatementRoot(SyntaxNode node) { var current = node; while (current is not null) { if (IsIgnored(current)) { return null; } else if (NodeRoot(node, current) is { } result) { return result; } else { current = current.Parent; } } return null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentInvocation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentInvocation : IndentBase { public IndentInvocation() : base("T0026", "member access") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var memberAccess = (MemberAccessExpressionSyntax)c.Node; if (ExpectedPosition(memberAccess) is { } expected) { Verify(c, expected, memberAccess.OperatorToken, memberAccess.Name); } }, SyntaxKind.SimpleMemberAccessExpression); context.RegisterNodeAction(c => { var memberBinding = (MemberBindingExpressionSyntax)c.Node; if (ExpectedPosition(memberBinding) is { } expected) { Verify(c, expected, memberBinding.OperatorToken, memberBinding.Name); } }, SyntaxKind.MemberBindingExpression); } protected override SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) { if (current is InvocationExpressionSyntax { Parent: ConditionalAccessExpressionSyntax }) { return null; } else if (current.Parent is ConditionalExpressionSyntax ternary && (ternary.WhenTrue == current || ternary.WhenFalse == current)) { return current; } else if (current is BinaryExpressionSyntax binary && binary.OperatorToken.IsFirstTokenOnLine()) { return binary; } else { return base.NodeRoot(node, current); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentOperator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentOperator : IndentBase { public IndentOperator() : base("T0019", "operator") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var binary = (BinaryExpressionSyntax)c.Node; if (ExpectedPosition(binary) is { } expected) { Verify(c, expected, binary.OperatorToken, binary.Right); } }, SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression, SyntaxKind.BitwiseAndExpression, SyntaxKind.BitwiseOrExpression, SyntaxKind.ExclusiveOrExpression, SyntaxKind.CoalesceExpression, SyntaxKind.AddExpression, SyntaxKind.LeftShiftExpression, SyntaxKind.RightShiftExpression, SyntaxKind.UnsignedRightShiftExpression, SyntaxKind.SubtractExpression, SyntaxKind.MultiplyExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.AsExpression, SyntaxKind.IsExpression); context.RegisterNodeAction(c => { var binary = (BinaryPatternSyntax)c.Node; if (ExpectedPosition(binary) is { } expected) { Verify(c, expected, binary.OperatorToken, binary.Right); } }, SyntaxKind.AndPattern, SyntaxKind.OrPattern); context.RegisterNodeAction(c => { var isPattern = (IsPatternExpressionSyntax)c.Node; if (ExpectedPosition(isPattern) is { } expected) { Verify(c, expected, isPattern.IsKeyword); } }, SyntaxKind.IsPatternExpression); context.RegisterNodeAction(c => { var range = (RangeExpressionSyntax)c.Node; if (ExpectedPosition(range) is { } expected) { Verify(c, expected, range.OperatorToken, range.RightOperand); } }, SyntaxKind.RangeExpression); } protected override int Offset(SyntaxNode node, SyntaxNode root) => CoalesceRoot(node) == root ? 3 // When rooted from the same expression, align itself to the same expression from the previous line : 4; // Otherwise force one tab, like in the base class protected override SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) { if (current is ArrowExpressionClauseSyntax { Parent: AccessorDeclarationSyntax }) { return current.Parent; } else if (current is ArrowExpressionClauseSyntax or LambdaExpressionSyntax or ConditionalExpressionSyntax { Parent: not ReturnStatementSyntax } or ForStatementSyntax) { return CoalesceRoot(node); } else if (current is IfStatementSyntax { Parent: ElseClauseSyntax }) { return null; } else if (current is ElseClauseSyntax) { return current; } else if (current is StatementSyntax or AssignmentExpressionSyntax or SwitchExpressionArmSyntax || (current is InvocationExpressionSyntax or CollectionExpressionSyntax && current.GetFirstToken().IsFirstTokenOnLine())) { return current; } else if (current is ParenthesizedExpressionSyntax) { return current.GetSelfOrTopParenthesizedExpression(); } else { return null; } } private static SyntaxNode CoalesceRoot(SyntaxNode node) { // Coalesce expressions have right-associative tree, so the trick of returning "node" itself as the starting point doesn't work. We need to find the first in 'first ?? node ?? last' while (node.Parent.IsKind(SyntaxKind.CoalesceExpression)) { node = node.Parent; } return node; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentRawString.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentRawString : IndentBase { public IndentRawString() : base("T0042", "raw string literal") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var token = c.Node.GetLastToken(); if (token.Kind() is SyntaxKind.MultiLineRawStringLiteralToken or SyntaxKind.InterpolatedRawStringEndToken or SyntaxKind.Utf8MultiLineRawStringLiteralToken && c.Node.GetLocation() is var location && location.StartLine() != location.EndLine() && ExpectedPosition(c.Node) is { } expected) { var line = c.Node.SyntaxTree.GetText().Lines[token.GetLocation().GetLineSpan().EndLinePosition.Line]; var actual = line.ToString().IndexOf(@""""); if (actual != expected) // This does not support TAB characters { c.ReportIssue(Rule, Location.Create(token.SyntaxTree, new TextSpan(line.Start + actual, 3)), (expected + 1).ToString()); } } }, SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression, SyntaxKind.Utf8StringLiteralExpression); protected override bool IsIgnored(SyntaxNode node) => node is LambdaExpressionSyntax or IfStatementSyntax or WhileStatementSyntax or ForStatementSyntax; protected override SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) { if (current is StatementSyntax or ObjectCreationExpressionSyntax { Parent: ArrowExpressionClauseSyntax } or ImplicitObjectCreationExpressionSyntax { Parent: ArrowExpressionClauseSyntax } or AssignmentExpressionSyntax or SwitchExpressionArmSyntax) { return current; } else if (current is MemberAccessExpressionSyntax memberAccessOnRawString && memberAccessOnRawString.Expression == node) { return null; // We don't want anything, when the invocation is on the raw string itself } else if (current is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax invokedMemberAccessOnRawString } && invokedMemberAccessOnRawString.Expression == node) { return null; // We don't want anything, when the invocation is on the raw string itself } else if (current is InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccess } && memberAccess.OperatorToken.IsFirstTokenOnLine()) { return memberAccess.Name; // Off by one due to the dot } else if (current is ArrowExpressionClauseSyntax) { return current.Parent; } else if (current.Parent is ConditionalExpressionSyntax ternary && (ternary.WhenTrue == current || ternary.WhenFalse == current)) { return current; // This assumes that the '? """' or ': """' is positioned correctly, and indents from that } else if (current is not ArgumentSyntax and not ExpressionElementSyntax and not LiteralExpressionSyntax and not InterpolatedStringExpressionSyntax && current.GetFirstToken().IsFirstTokenOnLine()) { return current; } else { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/IndentTernary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class IndentTernary : IndentBase { public IndentTernary() : base("T0025", "ternary") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var ternary = (ConditionalExpressionSyntax)c.Node; if (ExpectedPosition(ternary) is { } expected) { Verify(c, expected, ternary.QuestionToken, ternary.WhenTrue); Verify(c, expected, ternary.ColonToken, ternary.WhenFalse); } }, SyntaxKind.ConditionalExpression); protected override SyntaxNode NodeRoot(SyntaxNode node, SyntaxNode current) => current == node && current.GetFirstToken().IsFirstTokenOnLine() ? current : base.NodeRoot(node, current); private int? ExpectedPosition(ConditionalExpressionSyntax ternary) { if (ternary.Condition is BinaryExpressionSyntax binary && binary.OperatorToken.IsFirstTokenOnLine()) { return ExpectedPosition(binary.OperatorToken.GetLocation().GetLineSpan().StartLinePosition.Character, 4); } else if (ternary.Condition.GetLocation() is var location && location.StartLine() != location.EndLine()) { return null; // Unexpected multiline condition => not known, not supported to avoid FPs } else { return base.ExpectedPosition(ternary); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/InitializerLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class InitializerLine : StylingAnalyzer { private const int MaxLineLength = 200; // According to S103 public InitializerLine() : base("T0030", "Move this {0} to the previous line.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { foreach (var declarator in ((FieldDeclarationSyntax)c.Node).Declaration.Variables.Where(x => x.Initializer is not null)) { Verify(c, declarator.Initializer.Value, declarator.GetLocation().StartLine(), "initializer"); } }, SyntaxKind.FieldDeclaration); context.RegisterNodeAction(c => { var property = (PropertyDeclarationSyntax)c.Node; if (property.Initializer is not null) { Verify(c, property.Initializer.Value, property.Identifier.GetLocation().StartLine(), "initializer"); } if (property.ExpressionBody is not null) { Verify(c, property.ExpressionBody.Expression, property.Identifier.GetLocation().StartLine(), "expression"); } }, SyntaxKind.PropertyDeclaration); context.RegisterNodeAction(c => { var accessor = (AccessorDeclarationSyntax)c.Node; if (accessor.ExpressionBody is not null) { Verify(c, accessor.ExpressionBody.Expression, accessor.Keyword.GetLocation().StartLine(), "expression"); } }, SyntaxKind.AddAccessorDeclaration, SyntaxKind.GetAccessorDeclaration, SyntaxKind.InitAccessorDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.SetAccessorDeclaration); } private void Verify(SonarSyntaxNodeReportingContext context, ExpressionSyntax value, int expectedLine, string message) { if (MissplacedLocation(value, expectedLine) is { } location && context.Node.SyntaxTree.GetText().Lines[expectedLine].Span.Length + location.SourceSpan.Length + 1 < MaxLineLength) { context.ReportIssue(Rule, location, message); } } private static Location MissplacedLocation(ExpressionSyntax value, int expectedLine) { var valueLocation = value.GetLocation(); if (valueLocation.StartLine() == expectedLine) { return null; } else if (value is ObjectCreationExpressionSyntax objectCreation) { return Location.Create(value.SyntaxTree, TextSpan.FromBounds(objectCreation.NewKeyword.SpanStart, objectCreation.ArgumentList?.Span.End ?? objectCreation.Type.Span.End)); } else if (value is ImplicitObjectCreationExpressionSyntax implicitObjectCreation) { return Location.Create(value.SyntaxTree, TextSpan.FromBounds(implicitObjectCreation.NewKeyword.SpanStart, implicitObjectCreation.ArgumentList.Span.End)); } else if (valueLocation.StartLine() != valueLocation.EndLine()) { return null; } else { return value.GetLocation(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/LambdaParameterName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LambdaParameterName : StylingAnalyzer { public LambdaParameterName() : base("T0010", "Use 'x' for the lambda parameter name.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node is SimpleLambdaExpressionSyntax { Parameter: { Identifier.ValueText: not ("x" or "_") } parameter, Parent: { } parent } lambda && parent.FirstAncestorOrSelf() is null && !lambda.IsAnalysisContextAction(c.Model) && !MatchesDelegateParameterName(c.Model, lambda)) { c.ReportIssue(Rule, parameter); } }, SyntaxKind.SimpleLambdaExpression); private static bool MatchesDelegateParameterName(SemanticModel model, SimpleLambdaExpressionSyntax lambda) => model.GetTypeInfo(lambda).ConvertedType is INamedTypeSymbol { TypeKind: TypeKind.Delegate, DelegateInvokeMethod.Parameters: { Length: 1 } parameters } delegateType && !IsFuncOrAction(delegateType) && parameters[0] is { Name: { } parameterName } && parameterName == lambda.Parameter.Identifier.ValueText; private static bool IsFuncOrAction(INamedTypeSymbol delegateType) => delegateType.IsAny(KnownType.System_Func_T_TResult, KnownType.System_Action_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/LocalFunctionLocation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class LocalFunctionLocation : StylingAnalyzer { public LocalFunctionLocation() : base("T0015", "This local function should be at the end of the method.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var function = (LocalFunctionStatementSyntax)c.Node; if (!IsInTopLevelBlock(function) || !IsLastStatement(function)) { c.ReportIssue(Rule, function.Identifier); } }, SyntaxKind.LocalFunctionStatement); private static bool IsInTopLevelBlock(LocalFunctionStatementSyntax function) => function.Parent is GlobalStatementSyntax or BlockSyntax { Parent: BaseMethodDeclarationSyntax or BasePropertyDeclarationSyntax or AccessorDeclarationSyntax }; private static bool IsLastStatement(LocalFunctionStatementSyntax function) => function is { FollowingStatement: null or LocalFunctionStatementSyntax }; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/MemberAccessLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberAccessLine : StylingAnalyzer { public MemberAccessLine() : base("T0027", "Move this member access to the next line.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var memberAccess = (MemberAccessExpressionSyntax)c.Node; if (!memberAccess.HasSameStartLineAs(memberAccess.Name) && memberAccess.OperatorToken.GetPreviousToken().GetLocation().EndLine() == memberAccess.Name.GetLocation().StartLine() && FindStartLineMemberAccess(memberAccess) is { } startLineMemberAccess && !IsIgnored(startLineMemberAccess, memberAccess)) { c.ReportIssue(Rule, Location.Create(c.Tree, TextSpan.FromBounds(memberAccess.OperatorToken.SpanStart, memberAccess.Span.End))); } }, SyntaxKind.SimpleMemberAccessExpression); private static MemberAccessExpressionSyntax FindStartLineMemberAccess(SyntaxNode node) => node switch { MemberAccessExpressionSyntax memberAccess when memberAccess.OperatorToken.Line() != memberAccess.OperatorToken.GetPreviousToken().Line() => memberAccess, MemberAccessExpressionSyntax memberAccess => FindStartLineMemberAccess(memberAccess.Expression), InvocationExpressionSyntax invocation => FindStartLineMemberAccess(invocation.Expression), ElementAccessExpressionSyntax elementAccess => FindStartLineMemberAccess(elementAccess.Expression), _ => null }; private static bool IsIgnored(MemberAccessExpressionSyntax startLine, MemberAccessExpressionSyntax current) => (startLine.Name is IdentifierNameSyntax { Identifier.ValueText: "And" or "Which" or "Subject" } && startLine.OperatorToken.IsFirstTokenOnLine()) || (current.Expression is InvocationExpressionSyntax invocation && invocation.NameIs("Should")); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/MemberVisibilityOrdering.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MemberVisibilityOrdering : StylingAnalyzer { public MemberVisibilityOrdering() : base("T0009", "Move this {0} {1} above the {2} ones.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ValidateMembers, SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.ExtensionBlockDeclaration); private void ValidateMembers(SonarSyntaxNodeReportingContext context) { var type = (TypeDeclarationSyntax)context.Node; var members = new Dictionary>(); foreach (var member in type.Members) { if (Category(member) is { } category && ReportingLocation(member) is { } location) { members.GetOrAdd(category, x => []).Add(new(location, member.ComputeOrder())); } } foreach (var category in members.Keys) { OrderDescriptor maxOrder = null; SecondaryLocation secondary = null; foreach (var member in members[category]) { if (member.Order.Value < maxOrder?.Value) { context.ReportIssue(Rule, member.Location, [secondary], member.Order.Description, category, maxOrder.Description); } if (maxOrder is null || member.Order.Value > maxOrder.Value) { maxOrder = member.Order; secondary = member.Location.ToSecondary(); } } } } private static string Category(MemberDeclarationSyntax member) => member switch { _ when member.Modifiers.Any(SyntaxKind.AbstractKeyword) => "Abstract Member", FieldDeclarationSyntax when member.Modifiers.Any(SyntaxKind.ConstKeyword) => "Constant", EnumDeclarationSyntax => "Enum", FieldDeclarationSyntax => "Field", DelegateDeclarationSyntax => "Delegate", EventFieldDeclarationSyntax => "Event", PropertyDeclarationSyntax => "Property", IndexerDeclarationSyntax => "Indexer", ConstructorDeclarationSyntax => "Constructor", MethodDeclarationSyntax => "Method", _ => null }; private static Location ReportingLocation(SyntaxNode node) => node switch { EventFieldDeclarationSyntax eventField => eventField.Declaration.GetLocation(), FieldDeclarationSyntax field => field.Declaration.GetLocation(), _ => node.GetIdentifier()?.GetLocation() }; private sealed record MemberInfo(Location Location, OrderDescriptor Order); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/MethodExpressionBodyLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MethodExpressionBodyLine : StylingAnalyzer { public MethodExpressionBodyLine() : base("T0032", "Move this expression body to the next line.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var method = (BaseMethodDeclarationSyntax)c.Node; if (method.ExpressionBody?.Expression is { } expression && method.GetLocation().StartLine() == expression.GetLocation().StartLine() && expression.DescendantTokens().Skip(1).Any() && !IsStubThrow((IMethodSymbol)c.ContainingSymbol, expression)) { c.ReportIssue(Rule, expression); } }, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration); private static bool IsStubThrow(IMethodSymbol method, ExpressionSyntax expression) => expression is ThrowExpressionSyntax { Expression: ObjectCreationExpressionSyntax { Type: IdentifierNameSyntax { Identifier.ValueText: nameof(NotImplementedException) or nameof(NotSupportedException) } } } && (method.IsOverride || method.ExplicitOrImplicitInterfaceImplementations().Any()); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/MoveMethodToDedicatedExtensionClass.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class MoveMethodToDedicatedExtensionClass : StylingAnalyzer { public MoveMethodToDedicatedExtensionClass() : base("T0046", "Move this extension method to the {0} class.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var method = (IMethodSymbol)c.ContainingSymbol; if (method.IsExtension && ExpectedContainingType(method) is { } expected) { c.ReportIssue(Rule, ((MethodDeclarationSyntax)c.Node).Identifier, expected); } }, SyntaxKind.MethodDeclaration); private static string ExpectedContainingType(IMethodSymbol method) { var parameter = method.AssociatedExtensionImplementation is null ? method.Parameters[0].Type : method.ContainingType.ExtensionParameter.Type; var expectedPrefixes = parameter switch { ITypeParameterSymbol typeParameter => typeParameter.ConstraintTypes.Where(x => x.IsType).Select(x => x.Name), INamedTypeSymbol namedType => ValidTypeArguments(namedType).Select(x => x.Name), _ => [parameter.Name] }; var containingType = method.AssociatedExtensionImplementation is null ? method.ContainingType : method.ContainingType.ContainingType; if (!expectedPrefixes.Any() || expectedPrefixes.All(string.IsNullOrWhiteSpace)) { return "ObjectExtensions"; } else if (expectedPrefixes.Any(x => containingType.Name.Equals($"{x}Extensions"))) { return null; } else { return expectedPrefixes.Select(x => $"{x}Extensions").JoinOr(); } } private static IEnumerable ValidTypeArguments(INamedTypeSymbol namedType) { var validTypes = new HashSet(SymbolEqualityComparer.Default) { namedType }; foreach (var type in namedType.TypeArguments.OfType()) { validTypes.AddRange(ValidTypeArguments(type)); } return validTypes; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/NamespaceName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NamespaceName : StylingAnalyzer { public NamespaceName() : base("T0037", "Use {0} namespace.", SourceScope.Tests) { } // IDE0030 is aligning namespace with folder for most of the cases protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (FileScopedNamespaceDeclarationSyntax)c.Node; var removableUsing = declaration.Name.ToString().Replace(".Test.", "."); if (c.Tree.GetRoot().ChildNodes().OfType().Any(x => x.Name.ToString() == removableUsing)) { c.ReportIssue(Rule, declaration.Name, removableUsing + ".Test"); } }, SyntaxKind.FileScopedNamespaceDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/NullPatternMatching.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NullPatternMatching : StylingAnalyzer { public NullPatternMatching() : base("T0007", "Use 'is {0}null' pattern matching.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Validate(c, null), SyntaxKind.EqualsExpression); context.RegisterNodeAction(c => Validate(c, "not "), SyntaxKind.NotEqualsExpression); context.RegisterNodeAction(c => { if (((RecursivePatternSyntax)c.Node) is { Designation: null, PropertyPatternClause.Subpatterns.Count: 0 }) { c.ReportIssue(Rule, c.Node, "not "); } }, SyntaxKind.RecursivePattern); } private void Validate(SonarSyntaxNodeReportingContext context, string messageInfix) { var binary = (BinaryExpressionSyntax)context.Node; if ((binary.Left.IsKind(SyntaxKind.NullLiteralExpression) || binary.Right.IsKind(SyntaxKind.NullLiteralExpression)) && !context.IsInExpressionTree()) { context.ReportIssue(Rule, binary, messageInfix); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/OperatorLocation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class OperatorLocation : StylingAnalyzer { public OperatorLocation() : base("T0018", "The '{0}' operator should not be at the end of the line.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => Validate(c, ((BinaryExpressionSyntax)c.Node).OperatorToken), SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression, SyntaxKind.BitwiseAndExpression, SyntaxKind.BitwiseOrExpression, SyntaxKind.ExclusiveOrExpression, SyntaxKind.CoalesceExpression, SyntaxKind.AddExpression, SyntaxKind.LeftShiftExpression, SyntaxKind.RightShiftExpression, SyntaxKind.UnsignedRightShiftExpression, SyntaxKind.SubtractExpression, SyntaxKind.MultiplyExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.AsExpression, SyntaxKind.IsExpression); context.RegisterNodeAction(c => Validate(c, ((BinaryPatternSyntax)c.Node).OperatorToken), SyntaxKind.AndPattern, SyntaxKind.OrPattern); context.RegisterNodeAction(c => Validate(c, ((IsPatternExpressionSyntax)c.Node).IsKeyword), SyntaxKind.IsPatternExpression); context.RegisterNodeAction(c => Validate(c, ((RangeExpressionSyntax)c.Node).OperatorToken), SyntaxKind.RangeExpression); context.RegisterNodeAction(c => { var conditional = (ConditionalExpressionSyntax)c.Node; Validate(c, conditional.QuestionToken); Validate(c, conditional.ColonToken); }, SyntaxKind.ConditionalExpression); context.RegisterNodeAction(c => Validate(c, ((MemberAccessExpressionSyntax)c.Node).OperatorToken), SyntaxKind.SimpleMemberAccessExpression); context.RegisterNodeAction(c => Validate(c, ((MemberBindingExpressionSyntax)c.Node).OperatorToken), SyntaxKind.MemberBindingExpression); context.RegisterNodeAction(c => Validate(c, ((QualifiedNameSyntax)c.Node).DotToken), SyntaxKind.QualifiedName); } private void Validate(SonarSyntaxNodeReportingContext context, SyntaxToken token) { if (token.GetLocation().StartLine() != token.GetNextToken().GetLocation().StartLine()) { context.ReportIssue(Rule, token, token.ToString()); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/PropertyOrdering.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PropertyOrdering : StylingAnalyzer { public PropertyOrdering() : base("T0014", "Move this static property above the {0} instance ones.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ValidateMembers, SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.StructDeclaration); private void ValidateMembers(SonarSyntaxNodeReportingContext context) { foreach (var visibilityGroup in ((TypeDeclarationSyntax)context.Node).Members.OfType().GroupBy(x => x.ComputeOrder())) { ValidateMembers(context, visibilityGroup.Key, visibilityGroup); } } private void ValidateMembers(SonarSyntaxNodeReportingContext context, OrderDescriptor order, IEnumerable members) { var hasInstance = false; foreach (var member in members) { if (member.Modifiers.Any(SyntaxKind.StaticKeyword)) { if (hasInstance) { context.ReportIssue(Rule, member.Identifier, order.Description); } } else { hasInstance = true; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/ProtectedFieldsCase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class ProtectedFieldsCase : StylingAnalyzer { public ProtectedFieldsCase() : base("T0039", "Protected field should start with lower case letter.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var field = (FieldDeclarationSyntax)c.Node; if (field.Modifiers.Count == 2 && field.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) && field.Modifiers.Any(SyntaxKind.ProtectedKeyword)) { foreach (var variable in field.Declaration.Variables.Where(x => char.IsUpper(x.Identifier.ValueText[0]))) { c.ReportIssue(Rule, variable.Identifier); } } }, SyntaxKind.FieldDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/SeparateDeclarations.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class SeparateDeclarations : StylingAnalyzer { public SeparateDeclarations() : base("T0016", "Add an empty line before this declaration.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( ValidateSeparatedMember, SyntaxKind.ClassDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.EnumDeclaration, SyntaxKind.MethodDeclaration, SyntaxKind.NamespaceDeclaration, SyntaxKind.OperatorDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.StructDeclaration); context.RegisterNodeAction( ValidatePossibleSingleLineMember, SyntaxKind.EventDeclaration, SyntaxKind.EventFieldDeclaration, SyntaxKind.DelegateDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.IndexerDeclaration, SyntaxKind.PropertyDeclaration); } private void ValidatePossibleSingleLineMember(SonarSyntaxNodeReportingContext context) { var firstToken = context.Node.GetFirstToken(); if (firstToken.Line() != context.Node.GetLastToken().Line() || IsStandaloneCloseBrace(firstToken.GetPreviousToken()) || PreviousDeclarationKind() != context.Node.Kind()) { ValidateSeparatedMember(context); } SyntaxKind PreviousDeclarationKind() => context.Node.Parent.ChildNodes().TakeWhile(x => x != context.Node).LastOrDefault() is { } preceding ? preceding.Kind() : SyntaxKind.None; static bool IsStandaloneCloseBrace(SyntaxToken token) => token.IsKind(SyntaxKind.CloseBraceToken) && token.Line() != token.GetPreviousToken().Line(); } private void ValidateSeparatedMember(SonarSyntaxNodeReportingContext context) { var firstToken = context.Node.GetFirstToken(); if (!context.Node.Parent.IsKind(SyntaxKind.InterfaceDeclaration) && !context.Node.GetModifiers().Any(SyntaxKind.AbstractKeyword) && !firstToken.GetPreviousToken().IsKind(SyntaxKind.OpenBraceToken) && !ContainsEmptyLine(firstToken.LeadingTrivia)) { var firstComment = firstToken.LeadingTrivia.FirstOrDefault(x => x.IsComment()); context.ReportIssue(Rule, firstComment == default ? firstToken.GetLocation() : firstComment.GetLocation()); } } private static bool ContainsEmptyLine(SyntaxTriviaList trivia) { var previousLine = -1; foreach (var trivium in trivia.Where(x => !x.IsKind(SyntaxKind.WhitespaceTrivia))) { if (trivium.IsKind(SyntaxKind.EndOfLineTrivia)) { if (previousLine != trivium.GetLocation().StartLine()) { return true; } } else { previousLine = trivium.GetLocation().EndLine(); } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/TermaryLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TernaryLine : StylingAnalyzer { public TernaryLine() : base("T0024", "Place branches of the multiline ternary on a separate line.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var ternary = (ConditionalExpressionSyntax)c.Node; var location = ternary.GetLocation(); if (location.StartLine() != location.EndLine()) { Verify(c, ternary.QuestionToken, ternary.WhenTrue); Verify(c, ternary.ColonToken, ternary.WhenFalse); } }, SyntaxKind.ConditionalExpression); private void Verify(SonarSyntaxNodeReportingContext context, SyntaxToken token, ExpressionSyntax expression) { if (!token.IsFirstTokenOnLine()) { context.ReportIssue(Rule, Location.Create(expression.SyntaxTree, TextSpan.FromBounds(token.SpanStart, expression.Span.End))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/TypeMemberOrdering.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class TypeMemberOrdering : StylingAnalyzer { private const string NestedTypes = "Nested Types"; private static readonly MemberKind Constant = new(1, "Constants"); private static readonly MemberKind AbstractMember = new(10, "Abstract Members"); private static readonly MemberKind AbstractType = new(40, "Abstract Types"); private static readonly Dictionary MemberKinds = new() { // Order 1: Constants are FieldDeclaration and are handled separately in the code {SyntaxKind.EnumDeclaration, new(2, "Enums") }, {SyntaxKind.FieldDeclaration, new(3, "Fields") }, // Order 10: Abstract members are handled separately in the code {SyntaxKind.DelegateDeclaration, new(20, "Delegates") }, {SyntaxKind.EventFieldDeclaration, new(21, "Events") }, {SyntaxKind.PropertyDeclaration, new(22, "Properties") }, {SyntaxKind.IndexerDeclaration, new(23, "Indexers") }, {SyntaxKind.ConstructorDeclaration, new(24, "Constructors") }, {SyntaxKind.DestructorDeclaration, new(25, "Destructor") }, {SyntaxKind.MethodDeclaration, new(30, "Methods") }, {SyntaxKind.ConversionOperatorDeclaration, new(31, "Operators") }, {SyntaxKind.OperatorDeclaration, new(31, "Operators") }, // Order 40: Abstract types are handled separtely in the code {SyntaxKind.ClassDeclaration, new(40, NestedTypes) }, {SyntaxKind.InterfaceDeclaration, new(40, NestedTypes) }, {SyntaxKind.RecordDeclaration, new(40, NestedTypes) }, {SyntaxKind.StructDeclaration, new(40, NestedTypes) }, }; public TypeMemberOrdering() : base("T0008", "Move {0} {1}.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( ValidateMembers, SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.RecordStructDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.ExtensionBlockDeclaration); private void ValidateMembers(SonarSyntaxNodeReportingContext context) { var secondaries = new Dictionary(); var type = (TypeDeclarationSyntax)context.Node; var members = new List(); foreach (var member in type.Members) { if (ReportingLocation(member) is { } location && Kind(member) is { } kind) { members.Add(new(member, location, kind.Order, kind.Description)); if (!secondaries.ContainsKey(kind)) { secondaries.Add(kind, location.ToSecondary("Move the declaration before this one.")); } } } var maxOrder = 0; var availableKinds = members.GroupBy(x => x.Order).OrderBy(x => x.Key).Select(x => new MemberKind(x.Key, x.First().Description)).ToArray(); foreach (var member in members) { if (member.Order < maxOrder) { var before = availableKinds.First(x => x.Order > member.Order); context.ReportIssue(Rule, member.Location, [secondaries[before]], member.Description, ExpectedLocation(availableKinds, member.Order, before)); } maxOrder = Math.Max(maxOrder, member.Order); } } private static MemberKind Kind(MemberDeclarationSyntax member) { if (member.IsKind(SyntaxKind.FieldDeclaration) && member.Modifiers.Any(SyntaxKind.ConstKeyword)) { return Constant; } else if (member.Modifiers.Any(SyntaxKind.AbstractKeyword)) { return member is BaseTypeDeclarationSyntax ? AbstractType : AbstractMember; } else if (MemberKinds.TryGetValue(member.Kind(), out var kind)) { return kind; } else { return null; } } private static Location ReportingLocation(SyntaxNode node) => node switch { EventFieldDeclarationSyntax eventField => eventField.Declaration.GetLocation(), FieldDeclarationSyntax field => field.Declaration.GetLocation(), _ => node.GetIdentifier()?.GetLocation() }; private static string ExpectedLocation(MemberKind[] availableKinds, int order, MemberKind before) { var after = availableKinds.LastOrDefault(x => x.Order < order) is { } previous ? $"after {previous.Description}, " : null; return $"{after}before {before.Description}"; } private sealed record MemberInfo(MemberDeclarationSyntax Member, Location Location, int Order, string Description); private sealed record MemberKind(int Order, string Description); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseDifferentMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class UseDifferentMember : StylingAnalyzer { public UseDifferentMember() : base("T0047", "Use '{0}' instead of '{1}'.{2}") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var identifier = (IdentifierNameSyntax)c.Node; if (identifier.Identifier.Text is nameof(IMethodSymbol.IsExtensionMethod) && c.Model.GetSymbolInfo(identifier) is { Symbol: IPropertySymbol property } && property.IsInType(KnownType.Microsoft_CodeAnalysis_IMethodSymbol)) { c.ReportIssue(Rule, c.Node, "IsExtension", nameof(IMethodSymbol.IsExtensionMethod), " It also covers extension methods defined in extension blocks."); } }, SyntaxKind.IdentifierName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.Accessibility; namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseField : StylingAnalyzer { public UseField() : base("T0038", "Use field instead of this {0} auto-property.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var property = (PropertyDeclarationSyntax)c.Node; if (c.ContainingSymbol is IPropertySymbol { DeclaredAccessibility: Private or Protected or ProtectedAndInternal, IsAbstract: false, IsVirtual: false, IsOverride: false } && property.AccessorList is { } accessors && accessors.Accessors.All(x => x.Body is null && x.ExpressionBody is null)) { c.ReportIssue(Rule, property, property.Modifiers.Where(x => x.Kind() is SyntaxKind.PrivateKeyword or SyntaxKind.ProtectedKeyword).JoinStr(" ", x => x.ValueText)); } }, SyntaxKind.PropertyDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseInnermostRegistrationContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class UseInnermostRegistrationContext : StylingAnalyzer { public UseInnermostRegistrationContext() : base("T0005", "Use inner-most registration context '{0}' instead of '{1}'.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => { const string contextNameSpace = "SonarAnalyzer.Core.AnalysisContext."; var allContextTypes = new[] { nameof(SonarAnalysisContext), nameof(SonarCompilationStartAnalysisContext), $"{nameof(SonarCodeBlockStartAnalysisContext)}`1", nameof(SonarSymbolStartAnalysisContext), nameof(SonarParametrizedAnalysisContext), nameof(IReport), }.Select(x => compilationStart.Compilation.GetTypeByMetadataName($"{contextNameSpace}{x}")).WhereNotNull().ToImmutableArray(); if (allContextTypes.IsEmpty) { return; } var registrationContextTypes = allContextTypes.Where(x => x.Name != nameof(IReport)).ToImmutableArray(); compilationStart.RegisterNodeAction(c => { if (c.Node is ParameterSyntax outterParameter && outterParameter.GetName() is { } outterParameterName && RegistrationContextParameterSymbol(c.Model, registrationContextTypes, outterParameter) is { } outterParameterSymbol) { // Find all inner registrations by looking for other parameters of a context type (IReport or one of the registration context types) // Inside the scopes of such parameters, outterParameter isn't allowed to be used. var innerContextParameters = outterParameter.Parent.EnclosingScope() .DescendantNodes(x => !(outterParameter.Parent is ParameterListSyntax && x == outterParameter.Parent)) // Don't consider other parameters of the same method as inner parameters .OfType() .Where(x => x != outterParameter) // SimpleLambda parameter are not excluded by the DescendantNodes filter above .Select(x => new NodeAndSymbol(x, RegistrationOrReportingContextParameterSymbol(c.Model, allContextTypes, x))) .Where(x => x.Symbol is not null) .ToImmutableArray(); var violationNodes = ViolationsInInnerScopes(c.Model, outterParameterSymbol, innerContextParameters); foreach (var violation in violationNodes) { c.ReportIssue(Rule, violation.Node, violation.Symbol.Name, outterParameterName); } } }, SyntaxKind.Parameter); }); private static IEnumerable ViolationsInInnerScopes(SemanticModel model, IParameterSymbol outterParameterSymbol, ImmutableArray innerContextParameters) { Dictionary violations = []; foreach (var innerContextParameter in innerContextParameters) { var innerScope = innerContextParameter.Node.Parent.EnclosingScope(); // outterParameter should not be used inside these scopes foreach (var node in innerScope.DescendantNodes().OfType().Where(x => x.GetName() == outterParameterSymbol.Name && model.GetSymbolInfo(x).Symbol is IParameterSymbol usedParameter && SymbolEqualityComparer.Default.Equals(usedParameter, outterParameterSymbol))) { // There might be mutiple inner context parameters we pass until we find a violation, // but only the last one we have seen is the most inner one. The dictionaries add or // replace logic solves this for us. violations[node] = innerContextParameter.Symbol; } } return violations.Select(x => new NodeAndSymbol(x.Key, x.Value)); } private static IParameterSymbol RegistrationContextParameterSymbol(SemanticModel model, ImmutableArray registrationContextTypes, ParameterSyntax parameter) { // Syntax based check to exclude parameters with a Type node that are known to be not registration context types if (parameter.Type is { } type && type.GetName() is { } typeName && !registrationContextTypes.Any(x => x.Name == typeName)) { return null; } return model.GetDeclaredSymbol(parameter) is IParameterSymbol parameterSymbol && IsRegistrationParameter(registrationContextTypes, parameterSymbol) ? parameterSymbol : null; } private static bool IsRegistrationParameter(ImmutableArray registrationContextTypes, IParameterSymbol parameterSymbol) => registrationContextTypes.Any(parameterSymbol.Type.DerivesFrom); private static IParameterSymbol RegistrationOrReportingContextParameterSymbol(SemanticModel model, ImmutableArray allContextTypes, ParameterSyntax parameter) => model.GetDeclaredSymbol(parameter) is IParameterSymbol parameterSymbol && allContextTypes.Any(parameterSymbol.Type.DerivesOrImplements) ? parameterSymbol : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseLinqExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseLinqExtensions : StylingAnalyzer { public UseLinqExtensions() : base("T0021", "Use IEnumerable extensions instead of the query syntax.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => c.ReportIssue(Rule, c.Node), SyntaxKind.QueryExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseNullInsteadOfDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseNullInsteadOfDefault : StylingAnalyzer { public UseNullInsteadOfDefault() : base("T0012", "Use 'null' instead of 'default' for reference types.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (IsReferenceType(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.DefaultLiteralExpression, SyntaxKind.DefaultExpression); private static bool IsReferenceType(SyntaxNode node, SemanticModel model) { var type = model.GetTypeInfo(node).Type; return (type.IsReferenceType && type is not IErrorTypeSymbol) || type.Is(KnownType.System_Nullable_T); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UsePositiveLogic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UsePositiveLogic : StylingAnalyzer { public UsePositiveLogic() : base("T0033", "Swap the branches and use positive condition.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var ifStatement = (IfStatementSyntax)c.Node; if (ifStatement.Else is { Statement: not IfStatementSyntax }) { Validate(c, ifStatement.Condition); } }, SyntaxKind.IfStatement); context.RegisterNodeAction(c => Validate(c, ((ConditionalExpressionSyntax)c.Node).Condition), SyntaxKind.ConditionalExpression); } private void Validate(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) { if (IsNegative(expression, null)) { context.ReportIssue(Rule, expression); } } private static bool IsNegative(ExpressionSyntax expression, Operator outerAndOr) => expression switch { PrefixUnaryExpressionSyntax prefix => prefix.IsKind(SyntaxKind.LogicalNotExpression), BinaryExpressionSyntax binary => IsNegative(binary, outerAndOr), ParenthesizedExpressionSyntax parenthesized => IsNegative(parenthesized.Expression, outerAndOr), IsPatternExpressionSyntax isPattern => IsNegative(isPattern.Pattern, outerAndOr), _ => false }; private static bool IsNegative(BinaryExpressionSyntax binary, Operator outerAndOr) => binary.IsKind(SyntaxKind.NotEqualsExpression) || (CheckAndOr(binary, outerAndOr) is { } currentandOr && IsNegative(binary.Left, currentandOr) && IsNegative(binary.Right, currentandOr)); private static bool IsNegative(PatternSyntax pattern, Operator outerAndOr) => pattern is UnaryPatternSyntax { RawKind: (int)SyntaxKind.NotPattern } || (pattern is BinaryPatternSyntax { } binary && CheckAndOr(binary, outerAndOr) is { } currentAndOr && IsNegative(binary.Left, currentAndOr) && IsNegative(binary.Right, currentAndOr)); private static Operator CheckAndOr(SyntaxNode node, Operator outerAndOr) => (outerAndOr ?? Operator.From((SyntaxKind)node.RawKind)) is { } currentAndOr && currentAndOr.IsMatch((SyntaxKind)node.RawKind) ? currentAndOr : null; private sealed class Operator { private static readonly Operator And = new(SyntaxKind.LogicalAndExpression, SyntaxKind.AndPattern); private static readonly Operator Or = new(SyntaxKind.LogicalOrExpression, SyntaxKind.OrPattern); private readonly SyntaxKind logical; private readonly SyntaxKind pattern; private Operator(SyntaxKind logical, SyntaxKind pattern) { this.logical = logical; this.pattern = pattern; } public static Operator From(SyntaxKind kind) { if (kind == And.logical || kind == And.pattern) { return And; } else if (kind == Or.logical || kind == Or.pattern) { return Or; } else { return null; } } public bool IsMatch(SyntaxKind kind) => kind == logical || kind == pattern; } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseRawString.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseRawString : StylingAnalyzer { public UseRawString() : base("T0041", "Use raw string literal for multiline strings.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var location = c.Node.GetLocation(); if (location.StartLine() != location.EndLine() && c.Node.GetFirstToken().Kind() is not SyntaxKind.MultiLineRawStringLiteralToken and not SyntaxKind.InterpolatedMultiLineRawStringStartToken) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.StringLiteralExpression, SyntaxKind.InterpolatedStringExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseRegexSafeIsMatch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseRegexSafeIsMatch : StylingAnalyzer { public UseRegexSafeIsMatch() : base("T0004", "Use '{0}{1}' instead.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { var regexExtensions = c.Compilation.GetSymbolsWithName("RegexExtensions", SymbolFilter.Type).OfType().ToArray(); Verify(c, "IsMatch", "SafeIsMatch", regexExtensions); Verify(c, "Match", "SafeMatch", regexExtensions); Verify(c, "Matches", "SafeMatches", regexExtensions); }); private void Verify( SonarCompilationStartAnalysisContext context, string methodName, string replacementName, IEnumerable regexExtensions) { if (regexExtensions.Any(x => x.GetMembers(replacementName).Any())) { context.RegisterNodeAction(c => { var memberAccess = (MemberAccessExpressionSyntax)c.Node; if (memberAccess.NameIs(methodName) && c.Model.GetSymbolInfo(memberAccess).Symbol is IMethodSymbol method && method.ContainingType.Is(KnownType.System_Text_RegularExpressions_Regex)) { c.ReportIssue( Rule, method.IsStatic ? memberAccess.Expression : memberAccess.Name, method.IsStatic ? "SafeRegex." : "Safe", method.Name); } }, SyntaxKind.SimpleMemberAccessExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseShortName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseShortName : StylingAnalyzer { private static readonly RenameInfo[] RenameCandidates = [ new("CancellationToken", "cancellationToken", "cancel"), new("CancellationToken", "CancellationToken", "Cancel"), new("DiagnosticDescriptor", "diagnosticDescriptor", "descriptor"), new("DiagnosticDescriptor", "DiagnosticDescriptor", "Descriptor"), new("SyntaxNode", "syntaxNode", "node"), new("SyntaxNode", "SyntaxNode", "Node"), new("SyntaxToken", "syntaxToken", "token"), new("SyntaxToken", "SyntaxToken", "Token"), new("SyntaxTree", "syntaxTree", "tree"), new("SyntaxTree", "SyntaxTree", "Tree"), new("SyntaxTrivia", "syntaxTrivia", "trivia"), new("SyntaxTrivia", "SyntaxTrivia", "Trivia"), new("SemanticModel", "semanticModel", "model"), new("SemanticModel", "SemanticModel", "Model") ]; public UseShortName() : base("T0017", "Use short name '{0}'.") { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => ValidateDeclaration(c, ((VariableDeclaratorSyntax)c.Node).Identifier), SyntaxKind.VariableDeclarator); context.RegisterNodeAction(c => ValidateDeclaration(c, ((PropertyDeclarationSyntax)c.Node).Identifier), SyntaxKind.PropertyDeclaration); context.RegisterNodeAction(c => { if (!FollowsPredefinedName(c.ContainingSymbol)) { ValidateDeclaration(c, ((ParameterSyntax)c.Node).Identifier); } }, SyntaxKind.Parameter); } private void ValidateDeclaration(SonarSyntaxNodeReportingContext context, SyntaxToken identifier) { if (FindRename(identifier.ValueText) is { } name && context.Model.GetDeclaredSymbol(context.Node).GetSymbolType() is { } type && type.Name == name.TypeName) { context.ReportIssue(Rule, identifier, identifier.ValueText.Replace(name.UsedName, name.SuggestedName)); } } private static RenameInfo FindRename(string name) => Array.Find(RenameCandidates, x => name.Contains(x.UsedName)); private static bool FollowsPredefinedName(ISymbol symbol) => symbol is IMethodSymbol method && (symbol.IsOverride || symbol.InterfaceMembers().Any() || method.PartialDefinitionPart is not null); private sealed record RenameInfo(string TypeName, string UsedName, string SuggestedName); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/Rules/UseVar.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class UseVar : StylingAnalyzer { public UseVar() : base("T0045", "Use var.") { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node is VariableDeclarationSyntax { Type: not IdentifierNameSyntax { Identifier.ValueText: "var" }, Variables.Count: 1, Parent: LocalDeclarationStatementSyntax } declaration && declaration.Variables[0] is { Initializer.Value: ImplicitObjectCreationExpressionSyntax or IdentifierNameSyntax or InvocationExpressionSyntax or MemberAccessExpressionSyntax }) { c.ReportIssue(Rule, declaration.Type); } }, SyntaxKind.VariableDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/SonarAnalyzer.CSharp.Styling.csproj ================================================  Internal.SonarAnalyzer.CSharp.Styling netstandard2.0 true Internal Sonar styling analyzer for C# ================================================ FILE: analyzers/src/SonarAnalyzer.CSharp.Styling/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Fody": { "type": "Direct", "requested": "[6.9.3, )", "resolved": "6.9.3", "contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA==" }, "ILMerge.Fody": { "type": "Direct", "requested": "[1.24.0, )", "resolved": "1.24.0", "contentHash": "Jg13T22kxyVUCP1lAfjSDKkL01XzMIM+GWd5kv9Hohi/9L2dhHZK55KQe2Yu/sHoQsYvRToeCDuIBKrNOJza7A==", "dependencies": { "Fody": "6.6.4" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[5.0.0, )", "resolved": "5.0.0", "contentHash": "Al/Q8B+yO8odSqGVpSvrShMFDvlQdIBU//F3E6Rb0YdiLSALE9wh/pvozPNnfmh5HDnvU+mkmSjpz4hQO++jaA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.CSharp": "[5.0.0]", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "owmu2Cr3IQ8yQiBleBHlGk8dSQ12oaF2e7TpzwJKEl4m84kkZJjEY1n33L67Y3zM5jPOjmmbdHjbfiL0RqcMRQ==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "3.11.0", "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "ZbUmIvT6lqTNKiv06Jl5wf0MTMi1vQ1oH7ou4CLcs2C/no/L7EhP3T8y3XXvn9VbqMcJaJnEsNA1jwYUMgc5jg==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.IO.Pipelines": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "eA3cinogwaNB4jdjQHOP3Z3EuyiDII7MT35jgtnsA4vkn0LUrrSHsU0nzHTzFzmaFYeKV7MYyMxOocFzsBHpTw==", "dependencies": { "System.Buffers": "4.5.1", "System.Memory": "4.5.5", "System.Threading.Tasks.Extensions": "4.5.4" } }, "System.Memory": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==", "dependencies": { "System.Buffers": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "t+SoieZsRuEyiw/J+qXUbolyO219tKQQI0+2/YI+Qv7YdGValA6WiuokrNKqjrTNsy5ABWU11bdKOzUdheteXg==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", "dependencies": { "System.Collections.Immutable": "9.0.0", "System.Memory": "4.5.5" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.1.0", "contentHash": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg==" }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Threading.Channels": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/IAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public interface IAnalysisContext { Compilation Compilation { get; } AnalyzerOptions Options { get; } SonarAnalysisContext AnalysisContext { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/IReport.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public interface IReport { ReportingContext CreateReportingContext(Diagnostic diagnostic); } /// /// Interface for reporting contexts that are executed on a known Tree. The decisions about generated code and unchanged files are taken during action registration. /// public interface ITreeReport : IReport { SyntaxTree Tree { get; } void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs); [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] void ReportIssue(Diagnostic diagnostic); } /// /// Base class for reporting contexts that are common for the entire compilation. Specific tree is not known before the action is executed. /// public interface ICompilationReport : IReport { void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, params string[] messageArgs); [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, Diagnostic diagnostic); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/IReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public interface IReportingContext { SyntaxTree Tree { get; } Diagnostic Diagnostic { get; } void ReportDiagnostic(Diagnostic diagnostic); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/IssueReporter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.CFG.Common; namespace SonarAnalyzer.Core.AnalysisContext; public static class IssueReporter { private static readonly ImmutableHashSet ExcludedFromDesignTimeRuleIds = ImmutableHashSet.Create( "S108", "S1481", "S927", "S4487", "S2696", "S2259", "S1144", "S2325", "S1117", "S1481", "S1871"); private static readonly Regex VbNetErrorPattern = new Regex(@"\s+error(\s+\S+)?\s*:", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, Constants.DefaultRegexTimeout); // Minimum supported version for Razor IDE is Visual Studio 17.9/Roslyn 4.9.2 // https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support?view=vs-2022 private static Version minimumDesignTimeRoslynVersion = new("4.9.2"); public static void ReportIssueCore(Compilation compilation, Func hasMatchingScope, Func createReportingContext, DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs) { _ = rule ?? throw new ArgumentNullException(nameof(rule)); secondaryLocations ??= []; properties ??= ImmutableDictionary.Empty; secondaryLocations = secondaryLocations.Where(x => x.Location.IsValid(compilation)).ToArray(); properties = properties.AddRange(secondaryLocations.Select((x, index) => new KeyValuePair(index.ToString(), x.Message))); var diagnostic = Diagnostic.Create(rule, primaryLocation, secondaryLocations.Select(x => x.Location), properties, messageArgs); ReportIssueCore(hasMatchingScope, createReportingContext, diagnostic); } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public static void ReportIssueCore(Func hasMatchingScope, Func createReportingContext, Diagnostic diagnostic) { if (ShouldRaiseOnRazorFile(ref diagnostic) && hasMatchingScope(diagnostic.Descriptor) && SonarAnalysisContext.LegacyIsRegisteredActionEnabled(diagnostic.Descriptor, diagnostic.Location?.SourceTree)) { var reportingContext = createReportingContext(diagnostic); if (!diagnostic.Location.IsValid(reportingContext.Compilation)) { Debug.Fail("Primary location should be part of the compilation. An AD0001 is raised if this is not the case."); return; } // This is the current way SonarLint will handle how and what to report. if (SonarAnalysisContext.ReportDiagnostic is not null) { Debug.Assert(SonarAnalysisContext.ShouldDiagnosticBeReported is null, "Not expecting SonarLint to set both the old and the new delegates."); SonarAnalysisContext.ReportDiagnostic(reportingContext); return; } // Standalone NuGet, Scanner run and SonarLint < 4.0 used with latest NuGet if (!IsTriggeringVbcError(reportingContext.Diagnostic) && (SonarAnalysisContext.ShouldDiagnosticBeReported?.Invoke(reportingContext.Tree, reportingContext.Diagnostic) ?? true)) { reportingContext.ReportDiagnostic(reportingContext.Diagnostic); } } } internal static void SetMinimumDesignTimeRoslynVersion(Version version) => minimumDesignTimeRoslynVersion = version; internal static Version GetMinimumDesignTimeRoslynVersion() => minimumDesignTimeRoslynVersion; internal static bool IsTextMatchingVbcErrorPattern(string text) => text is not null && VbNetErrorPattern.SafeIsMatch(text); private static bool ShouldRaiseOnRazorFile(ref Diagnostic diagnostic) { // On build time, if the diagnostic has a mapped location, we do the mapping ourselves and raise there. if (GeneratedCodeRecognizer.IsBuildTimeRazorGeneratedFile(diagnostic.Location.SourceTree)) { if (diagnostic.Location.GetMappedLineSpan().HasMappedPath) { // we want to map the locations of razor files exclusively during compile time, and not design time (IDE). // The reason is that for IDE, the correct way to raise issues is directly on the generated files. // source: https://github.com/dotnet/razor/issues/9308#issuecomment-1883869224 diagnostic = MapDiagnostic(diagnostic); return true; } else { return false; } } // On design time, we only raise on generated .ide.g.cs files if the diagnostic has a mapped location. else if (GeneratedCodeRecognizer.IsDesignTimeRazorGeneratedFile(diagnostic.Location.SourceTree)) { return diagnostic.Location.GetMappedLineSpan().HasMappedPath && !ExcludedFromDesignTimeRuleIds.Contains(diagnostic.Id) && !RoslynVersion.IsVersionLessThan(minimumDesignTimeRoslynVersion); } // If it is not a build/design-time generated file, it is a normal file. else { return true; } } private static Diagnostic MapDiagnostic(Diagnostic diagnostic) { var mappedLocation = diagnostic.Location.EnsureMappedLocation(); var descriptor = new DiagnosticDescriptor( diagnostic.Descriptor.Id, diagnostic.Descriptor.Title, diagnostic.GetMessage(), diagnostic.Descriptor.Category, diagnostic.Descriptor.DefaultSeverity, diagnostic.Descriptor.IsEnabledByDefault, diagnostic.Descriptor.Description, diagnostic.Descriptor.HelpLinkUri, diagnostic.Descriptor.CustomTags.ToArray()); return Diagnostic.Create(descriptor, mappedLocation, diagnostic.AdditionalLocations.Select(x => x.EnsureMappedLocation()).ToImmutableList(), diagnostic.Properties); } /// /// VB.Net Complier (VBC) post-process issues and will fail if the line contains the . /// /// /// This helper method is intended to be used only while waiting for the bug to be fixed on Microsoft side. /// . /// /// /// The diagnostic to test. /// /// /// Returns true when reporting the diagnostic will trigger a VBC post-process error and false otherwise. /// private static bool IsTriggeringVbcError(Diagnostic diagnostic) { if (diagnostic.Location is null || diagnostic.Location.SourceTree?.GetRoot().Language != LanguageNames.VisualBasic) { return false; } var text = diagnostic.Location.SourceTree.GetText(); var lineNumber = diagnostic.Location.LineNumberToReport(); return IsTextMatchingVbcErrorPattern(text.Lines[lineNumber - 1].ToString()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/ReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public class ReportingContext : IReportingContext { private readonly Action roslynReportDiagnostic; public SyntaxTree Tree { get; } public Diagnostic Diagnostic { get; } public Compilation Compilation { get; } public ReportingContext(SonarSyntaxNodeReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } public ReportingContext(SonarSyntaxTreeReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } public ReportingContext(SonarCompilationReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, diagnostic.Location?.SourceTree) { } public ReportingContext(SonarSymbolReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, diagnostic.Location?.SourceTree) { } public ReportingContext(SonarCodeBlockReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } public ReportingContext(SonarSemanticModelReportingContext context, Diagnostic diagnostic) : this(diagnostic, context.Context.ReportDiagnostic, context.Compilation, context.Tree) { } internal ReportingContext(Diagnostic diagnostic, Action roslynReportDiagnostic, Compilation compilation, SyntaxTree tree) { Diagnostic = diagnostic; this.roslynReportDiagnostic = roslynReportDiagnostic; Compilation = compilation; Tree = tree; } public void ReportDiagnostic(Diagnostic diagnostic) => roslynReportDiagnostic(diagnostic); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/ShouldAnalyzeTreeCache.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Roslyn.Utilities; namespace SonarAnalyzer.Core.AnalysisContext; internal class ShouldAnalyzeTreeCache { private enum ShouldAnalyzeTree { NotComputed, True, False } private readonly HashSet rulesDisabledForRazor = [ "S103", "S104", "S109", "S113", "S1147", "S1192", "S1451", ]; private SyntaxTree Tree { get; } private bool IsRazorFile { get; } private ShouldAnalyzeTree ShouldAnalyzeTreeResult { get; set; } public ShouldAnalyzeTreeCache(SyntaxTree tree) { Tree = tree; IsRazorFile = GeneratedCodeRecognizer.IsRazorGeneratedFile(tree); } /// /// For each action registered on context we need to do some pre-processing before actually calling the rule. /// SonarAnalysisContext.Execute ensures that the rule does apply to the current scope (main vs test source), so we do not need to check this here. /// We call an external delegate (set by legacy SonarLint for VS) to ensure the rule should be run (usually /// the decision is made on based on whether the project contains the analyzer as NuGet). /// We also make sure that Razor files are not analyzed by rules that are not supported for Razor. /// [PerformanceSensitive("")] internal bool ShouldAnalyze(TSonarContext context, GeneratedCodeRecognizer generatedCodeRecognizer) where TSonarContext : IAnalysisContext { if (ShouldAnalyzeTreeResult == ShouldAnalyzeTree.NotComputed) { // We assume that we can ignore the generatedCodeRecognizer in the future. We only use it for the very first time we do the computation // and assume all other generatedCodeRecognizer we could see here are returning the same result. // We do not care about race conditions when setting ShouldAnalyzeTreeResult because // we will set from NotComputed to True or False. Any concurrency happening here is safe. ShouldAnalyzeTreeResult = context.ShouldAnalyzeTree(Tree, generatedCodeRecognizer) ? ShouldAnalyzeTree.True : ShouldAnalyzeTree.False; } var supportedDiagnostics = context.AnalysisContext.SupportedDiagnostics; return ShouldAnalyzeTreeResult == ShouldAnalyzeTree.True && LegacyIsRegisteredActionEnabled(supportedDiagnostics) && ShouldAnalyzeRazorFile(supportedDiagnostics); } [PerformanceSensitive("")] private bool LegacyIsRegisteredActionEnabled(ImmutableArray supportedDiagnostics) => SonarAnalysisContext.ShouldExecuteRegisteredAction is null // Box supportedDiagnostics only, if really needed || SonarAnalysisContext.LegacyIsRegisteredActionEnabled(supportedDiagnostics, Tree); // We can not change the signature for compatibility reasons [PerformanceSensitive("")] private bool ShouldAnalyzeRazorFile(ImmutableArray supportedDiagnostics) => !IsRazorFile || !supportedDiagnostics.Any(x => (x.CustomTags.Count() == 1 && x.CustomTags.Contains(DiagnosticDescriptorFactory.TestSourceScopeTag)) || rulesDisabledForRazor.Contains(x.Id)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using RoslynAnalysisContext = Microsoft.CodeAnalysis.Diagnostics.AnalysisContext; namespace SonarAnalyzer.Core.AnalysisContext; public class SonarAnalysisContext { private readonly RoslynAnalysisContext analysisContext; /// /// This delegate is called on all specific contexts, after the registration to the , to /// control whether or not the action should be executed. /// /// /// This delegate is set by old SonarLint (from v4.0 to v5.5) when the project has the NuGet package installed to avoid /// duplicated analysis and issues. When both the NuGet and the VSIX are available, NuGet will take precedence and VSIX /// will be inhibited. /// This delegate was removed from SonarLint v6.0. /// public static Func, SyntaxTree, bool> ShouldExecuteRegisteredAction { get; set; } /// /// This delegates control whether or not a diagnostic should be reported to Roslyn. /// /// /// Currently this delegate is set by SonarLint (older than v4.0) to provide a suppression mechanism (i.e. specific issues turned off on the bound SonarQube). /// public static Func ShouldDiagnosticBeReported { get; set; } /// /// This delegate is used to supersede the default reporting action. /// When this delegate is set, the delegate set for is ignored. /// /// /// Currently this delegate is set by SonarLint (4.0+) to control how the diagnostic should be reported to Roslyn (including not being reported). /// public static Action ReportDiagnostic { get; set; } internal ImmutableArray SupportedDiagnostics { get; } internal SonarAnalysisContext(RoslynAnalysisContext analysisContext, ImmutableArray supportedDiagnostics) { this.analysisContext = analysisContext ?? throw new ArgumentNullException(nameof(analysisContext)); SupportedDiagnostics = supportedDiagnostics; } private protected SonarAnalysisContext(SonarAnalysisContext context) : this(context.analysisContext, context.SupportedDiagnostics) { } public bool TryGetValue(SourceText text, SourceTextValueProvider valueProvider, out TValue value) => analysisContext.TryGetValue(text, valueProvider, out value); public void RegisterCodeBlockStartAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action> action) where TSyntaxKind : struct => RegisterCompilationStartAction( x => x.RegisterCodeBlockStartAction(generatedCodeRecognizer, action)); public void RegisterCompilationAction(Action action) => analysisContext.RegisterCompilationAction( x => Execute(new(this, x), action)); public virtual void RegisterCompilationStartAction(Action action) => analysisContext.RegisterCompilationStartAction( x => Execute(new(this, x), action)); public void RegisterSymbolAction(Action action, params SymbolKind[] symbolKinds) => RegisterCompilationStartAction( x => x.RegisterSymbolAction(action, symbolKinds)); public void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) => RegisterCompilationStartAction( x => x.RegisterSymbolStartAction(action, symbolKind)); public void RegisterNodeAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action, params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct => RegisterCompilationStartAction( x => x.RegisterNodeAction(generatedCodeRecognizer, action, syntaxKinds)); public void RegisterSemanticModelAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action) => RegisterCompilationStartAction( x => x.RegisterSemanticModelAction(generatedCodeRecognizer, action)); public void RegisterTreeAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action) => RegisterCompilationStartAction( x => x.RegisterTreeAction(generatedCodeRecognizer, action)); /// /// Register action for a SyntaxNode that is executed unconditionally: /// /// For all non-generated code. /// For all generated code. /// For all unchanged files under PR analysis. /// /// This should NOT be used for actions that report issues. /// public void RegisterNodeActionInAllFiles(Action action, params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct => analysisContext.RegisterSyntaxNodeAction(x => action(new(this, x)), syntaxKinds); /// /// Legacy API for backward compatibility with SonarLint v4.0 - v5.5. See . /// internal static bool LegacyIsRegisteredActionEnabled(IEnumerable diagnostics, SyntaxTree tree) => ShouldExecuteRegisteredAction is null || tree is null || ShouldExecuteRegisteredAction(diagnostics, tree); /// /// Legacy API for backward compatibility with SonarLint v4.0 - v5.5. See . /// internal static bool LegacyIsRegisteredActionEnabled(DiagnosticDescriptor diagnostic, SyntaxTree tree) => ShouldExecuteRegisteredAction is null || tree is null || ShouldExecuteRegisteredAction(new[] { diagnostic }, tree); private void Execute(TSonarContext context, Action action) where TSonarContext : IAnalysisContext // Generic specialization: The JIT emmits a specialized version of Execute() for each struct TSonarContext, which means it gets called without boxing. { // For each action registered on context we need to do some pre-processing before actually calling the rule. // We need to ensure the rule does apply to the current scope (main vs test source). // Further checks are done in SonarCompilationStartAnalysisContext for registrations that have a syntax tree. if (context.HasMatchingScope(SupportedDiagnostics)) { action(context); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarAnalysisContextBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public abstract class SonarAnalysisContextBase : IAnalysisContext { public abstract Compilation Compilation { get; } public abstract AnalyzerOptions Options { get; } public abstract CancellationToken Cancel { get; } public SonarAnalysisContext AnalysisContext { get; } public TContext Context { get; } protected SonarAnalysisContextBase(SonarAnalysisContext analysisContext, TContext context) { AnalysisContext = analysisContext ?? throw new ArgumentNullException(nameof(analysisContext)); Context = context; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarCodeBlockReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public readonly record struct SonarCodeBlockReportingContext(SonarAnalysisContext AnalysisContext, CodeBlockAnalysisContext Context) : ITreeReport, IAnalysisContext { public SyntaxTree Tree => Context.CodeBlock.SyntaxTree; public Compilation Compilation => Context.SemanticModel.Compilation; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public SyntaxNode CodeBlock => Context.CodeBlock; public ISymbol OwningSymbol => Context.OwningSymbol; public SemanticModel Model => Context.SemanticModel; public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs) { var @this = this; IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, properties, messageArgs); } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(Diagnostic diagnostic) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarCodeBlockStartAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public sealed class SonarCodeBlockStartAnalysisContext : SonarAnalysisContextBase> where TSyntaxKind : struct { public override Compilation Compilation => Context.SemanticModel.Compilation; public override AnalyzerOptions Options => Context.Options; public override CancellationToken Cancel => Context.CancellationToken; public SyntaxNode CodeBlock => Context.CodeBlock; public ISymbol OwningSymbol => Context.OwningSymbol; public SemanticModel Model => Context.SemanticModel; internal SonarCodeBlockStartAnalysisContext(SonarAnalysisContext analysisContext, CodeBlockStartAnalysisContext context) : base(analysisContext, context) { } public void RegisterNodeAction(Action action, params TSyntaxKind[] symbolKinds) => Context.RegisterSyntaxNodeAction(x => action(new(AnalysisContext, x)), symbolKinds); public void RegisterCodeBlockEndAction(Action action) => Context.RegisterCodeBlockEndAction(x => action(new(AnalysisContext, x))); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarCodeFixContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.AnalysisContext; public readonly struct SonarCodeFixContext { private readonly CodeFixContext context; public readonly CancellationToken Cancel => context.CancellationToken; public readonly Document Document => context.Document; public readonly ImmutableArray Diagnostics => context.Diagnostics; public readonly TextSpan Span => context.Span; public SonarCodeFixContext(CodeFixContext context) => this.context = context; public void RegisterCodeFix(string title, Func> createChangedDocument, ImmutableArray diagnostics) => context.RegisterCodeFix(CodeAction.Create(title, createChangedDocument, title), diagnostics); public void RegisterCodeFix(string title, Func> createChangedDocument, ImmutableArray diagnostics) => context.RegisterCodeFix(CodeAction.Create(title, createChangedDocument, title), diagnostics); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarCompilationReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.AnalysisContext; public readonly record struct SonarCompilationReportingContext(SonarAnalysisContext AnalysisContext, CompilationAnalysisContext Context) : ICompilationReport, IAnalysisContext { // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments#development-and-launchsettingsjson private const string LaunchSettingsFileName = "launchSettings.json"; private static readonly TimeSpan FileNameTimeout = TimeSpan.FromMilliseconds(100); private static readonly Regex WebConfigRegex = new(@"[\\\/]web\.([^\\\/]+\.)?config$", RegexOptions.IgnoreCase, FileNameTimeout); private static readonly Regex AppSettingsRegex = new(@"[\\\/]appsettings\.([^\\\/]+\.)?json$", RegexOptions.IgnoreCase, FileNameTimeout); public Compilation Compilation => Context.Compilation; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public IEnumerable WebConfigFiles() { return this.ProjectConfiguration().FilesToAnalyze.FindFiles(WebConfigRegex).Where(ShouldProcess); static bool ShouldProcess(string path) => !Path.GetFileName(path).Equals("web.debug.config", StringComparison.OrdinalIgnoreCase); } public IEnumerable AppSettingsFiles() { return this.ProjectConfiguration().FilesToAnalyze.FindFiles(AppSettingsRegex).Where(ShouldProcess); static bool ShouldProcess(string path) => !Path.GetFileName(path).Equals("appsettings.development.json", StringComparison.OrdinalIgnoreCase); } public IEnumerable LaunchSettingsFiles() => this.ProjectConfiguration().FilesToAnalyze.FindFiles(LaunchSettingsFileName); public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, params string[] messageArgs) { if (this.ShouldAnalyzeTree(primaryLocation?.SourceTree, generatedCodeRecognizer)) { var @this = this; secondaryLocations = secondaryLocations?.Where(x => x.Location.IsValid(@this.Compilation)).ToArray(); IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, ImmutableDictionary.Empty, messageArgs); } } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, Diagnostic diagnostic) { if (this.ShouldAnalyzeTree(diagnostic.Location.SourceTree, generatedCodeRecognizer)) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarCompilationStartAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using SonarAnalyzer.ShimLayer.AnalysisContext; namespace SonarAnalyzer.Core.AnalysisContext; public sealed class SonarCompilationStartAnalysisContext : SonarAnalysisContextBase { private static readonly SyntaxTreeValueProvider ShouldAnalyzeValueProvider = new(x => new ShouldAnalyzeTreeCache(x)); public override Compilation Compilation => Context.Compilation; public override AnalyzerOptions Options => Context.Options; public override CancellationToken Cancel => Context.CancellationToken; internal SonarCompilationStartAnalysisContext(SonarAnalysisContext analysisContext, CompilationStartAnalysisContext context) : base(analysisContext, context) { } /// public bool TryGetValue(SourceText text, SourceTextValueProvider valueProvider, out TValue value) => Context.TryGetValue(text, valueProvider, out value); /// public bool TryGetValue(SyntaxTree tree, SyntaxTreeValueProvider valueProvider, out TValue value) => Context.TryGetValue(tree, valueProvider, out value); public void RegisterCodeBlockStartAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action> action) where TSyntaxKind : struct => Context.RegisterCodeBlockStartAction(x => Execute(new(AnalysisContext, x), action, x.CodeBlock.SyntaxTree, generatedCodeRecognizer)); public void RegisterSymbolAction(Action action, params SymbolKind[] symbolKinds) => Context.RegisterSymbolAction(x => action(new(AnalysisContext, x)), symbolKinds); public void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) => Context.RegisterSymbolStartAction(x => action(new(AnalysisContext, x)), symbolKind); public void RegisterCompilationEndAction(Action action) => Context.RegisterCompilationEndAction(x => action(new(AnalysisContext, x))); public void RegisterSemanticModelActionInAllFiles(Action action) => Context.RegisterSemanticModelAction(x => action(new(AnalysisContext, x))); public void RegisterSemanticModelAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action) => Context.RegisterSemanticModelAction(x => Execute(new(AnalysisContext, x), action, x.SemanticModel.SyntaxTree, generatedCodeRecognizer)); public void RegisterTreeAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action) => Context.RegisterSyntaxTreeAction(x => Execute(new(AnalysisContext, x, Context.Compilation), action, x.Tree, generatedCodeRecognizer)); /// public void RegisterNodeActionInAllFiles(Action action, params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct => Context.RegisterSyntaxNodeAction(x => action(new(AnalysisContext, x)), syntaxKinds); #pragma warning disable HAA0303, HAA0302, HAA0301, HAA0502 [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8406", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] public void RegisterNodeAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action, params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct => Context.RegisterSyntaxNodeAction( x => Execute( new(AnalysisContext, x), // Critical hot-path: The newed SonarSyntaxNodeReportingContext is a struct and we need to ensure it is not boxed. // This is achieved by generic specialization of "Execute" and under test by the SonarSyntaxNodeReportingContextMemoryTest UTs. action, x.Node.SyntaxTree, generatedCodeRecognizer), syntaxKinds); private void Execute(TSonarContext context, Action action, SyntaxTree sourceTree, GeneratedCodeRecognizer generatedCodeRecognizer = null) where TSonarContext : IAnalysisContext // Generic specialization: The JIT emmits a specialized version of Execute() for each struct TSonarContext, which means it gets called without boxing. { Debug.Assert(context.HasMatchingScope(AnalysisContext.SupportedDiagnostics), "SonarAnalysisContext.Execute does this check. It should never be needed here."); if (!TryGetValue(sourceTree, ShouldAnalyzeValueProvider, out var shouldAnalyzeTree)) { shouldAnalyzeTree = new ShouldAnalyzeTreeCache(sourceTree); } if (shouldAnalyzeTree.ShouldAnalyze(context, generatedCodeRecognizer)) { action(context); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarParametrizedAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public sealed class SonarParametrizedAnalysisContext : SonarAnalysisContext { private readonly List> postponedActions = new(); internal SonarParametrizedAnalysisContext(SonarAnalysisContext context) : base(context) { } /// /// Register CompilationStart action that will be executed once rule parameters are set. /// public override void RegisterCompilationStartAction(Action action) => postponedActions.Add(action); /// /// Execution of postponed registration actions. This should be called once all rule parameters are set. /// public void ExecutePostponedActions(SonarCompilationStartAnalysisContext context) { foreach (var action in postponedActions) { action(context); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarSemanticModelReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public readonly record struct SonarSemanticModelReportingContext(SonarAnalysisContext AnalysisContext, SemanticModelAnalysisContext Context) : ITreeReport, IAnalysisContext { public SyntaxTree Tree => Model.SyntaxTree; public Compilation Compilation => Context.SemanticModel.Compilation; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public SemanticModel Model => Context.SemanticModel; public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs) { var @this = this; IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, properties, messageArgs); } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(Diagnostic diagnostic) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarSymbolReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; public readonly record struct SonarSymbolReportingContext(SonarAnalysisContext AnalysisContext, SymbolAnalysisContext Context) : ICompilationReport, IAnalysisContext { public Compilation Compilation => Context.Compilation; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public ISymbol Symbol => Context.Symbol; public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, params string[] messageArgs) { if (this.ShouldAnalyzeTree(primaryLocation?.SourceTree, generatedCodeRecognizer)) { var @this = this; secondaryLocations = secondaryLocations?.Where(x => x.Location.IsValid(@this.Compilation)).ToArray(); IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, ImmutableDictionary.Empty, messageArgs); } } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(GeneratedCodeRecognizer generatedCodeRecognizer, Diagnostic diagnostic) { if (this.ShouldAnalyzeTree(diagnostic.Location.SourceTree, generatedCodeRecognizer)) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarSymbolStartAnalysisContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.ShimLayer.AnalysisContext; namespace SonarAnalyzer.Core.AnalysisContext; public sealed class SonarSymbolStartAnalysisContext : SonarAnalysisContextBase { public override Compilation Compilation => Context.Compilation; public override AnalyzerOptions Options => Context.Options; public override CancellationToken Cancel => Context.CancellationToken; public ISymbol Symbol => Context.Symbol; internal SonarSymbolStartAnalysisContext(SonarAnalysisContext analysisContext, SymbolStartAnalysisContextWrapper context) : base(analysisContext, context) { } public void RegisterCodeBlockAction(Action action) => Context.RegisterCodeBlockAction(x => action(new(AnalysisContext, x))); public void RegisterCodeBlockStartAction(Action> action) where TLanguageKindEnum : struct => Context.RegisterCodeBlockStartAction(x => action(new(AnalysisContext, x))); public void RegisterOperationAction(Action action, ImmutableArray operationKinds) => // https://github.com/SonarSource/sonar-dotnet/issues/8878 throw new NotImplementedException("SonarOperationAnalysisContext wrapper type not implemented."); public void RegisterOperationBlockAction(Action action) => // https://github.com/SonarSource/sonar-dotnet/issues/8878 throw new NotImplementedException("SonarOperationBlockAnalysisContext wrapper type not implemented."); public void RegisterOperationBlockStartAction(Action action) => // https://github.com/SonarSource/sonar-dotnet/issues/8878 throw new NotImplementedException("SonarOperationBlockStartAnalysisContext wrapper type not implemented."); public void RegisterSymbolEndAction(Action action) => Context.RegisterSymbolEndAction(x => action(new(AnalysisContext, x))); public void RegisterSyntaxNodeAction(Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct => Context.RegisterSyntaxNodeAction(x => action(new(AnalysisContext, x)), syntaxKinds); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarSyntaxNodeReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; // Performance note: this struct is used in the Roslyn analysis context and should be as lightweight as possible. It should not be boxed on the hot-path in the // registration (e.g. in SonarCompilationStartAnalysisContext.RegisterNodeAction) which is called for all matching syntax kinds of all syntax trees. // It is okay to box during issue reporting because reporting is rare in comparission. public readonly record struct SonarSyntaxNodeReportingContext(SonarAnalysisContext AnalysisContext, SyntaxNodeAnalysisContext Context) : ITreeReport, IAnalysisContext { public SyntaxTree Tree => Context.Node.SyntaxTree; public Compilation Compilation => Context.Compilation; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public SyntaxNode Node => Context.Node; public SemanticModel Model => Context.SemanticModel; public ISymbol ContainingSymbol => Context.ContainingSymbol; /// /// Roslyn invokes the analyzer twice for positional records. The first invocation is for the class declaration and the second for the ctor represented by the positional parameter list. /// This behavior has been fixed since the Roslyn version 4.2.0 but we still need this for the proper support of Roslyn 4.0.0. /// /// /// Returns for the invocation on the class declaration and for the ctor invocation. /// /// /// record R(int i); /// /// public bool IsRedundantPositionalRecordContext() => Context.ContainingSymbol.Kind == SymbolKind.Method; /// /// Roslyn invokes the analyzer twice for PrimaryConstructorBaseType. The ContainingSymbol is first the type and second the constructor. This check filters can be used to filter /// the first invocation. See also #Roslyn/70488. /// /// /// Returns for the invocation with PrimaryConstructorBaseType and ContainingSymbol being and /// for everything else. /// public bool IsRedundantPrimaryConstructorBaseTypeContext() => Context is { Node.RawKind: (int)SyntaxKindEx.PrimaryConstructorBaseType, Compilation.Language: LanguageNames.CSharp, ContainingSymbol.Kind: SymbolKind.NamedType, }; public bool IsAzureFunction() => AzureFunctionMethod() is not null; public IMethodSymbol AzureFunctionMethod() => Context.ContainingSymbol is IMethodSymbol method && method.HasAttribute(KnownType.Microsoft_Azure_WebJobs_FunctionNameAttribute) ? method : null; public bool IsRazorAnalysisEnabled() => AnalysisContext.IsRazorAnalysisEnabled(Options, Compilation); public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs) { var @this = this; // This boxes this struct in the capture below, but that is okay because reporting is rare and not on the hot-path. IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, properties, messageArgs); } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(Diagnostic diagnostic) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarSyntaxTreeReportingContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; // SyntaxTreeAnalysisContext doesn't hold a Compilation reference, we need to provide it from CompilationStart context via constructor public readonly record struct SonarSyntaxTreeReportingContext(SonarAnalysisContext AnalysisContext, SyntaxTreeAnalysisContext Context, Compilation Compilation) : ITreeReport, IAnalysisContext { public SyntaxTree Tree => Context.Tree; public AnalyzerOptions Options => Context.Options; public CancellationToken Cancel => Context.CancellationToken; public ReportingContext CreateReportingContext(Diagnostic diagnostic) => new(this, diagnostic); public void ReportIssue(DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations = null, ImmutableDictionary properties = null, params string[] messageArgs) { var @this = this; IssueReporter.ReportIssueCore( Compilation, x => @this.HasMatchingScope(x), CreateReportingContext, rule, primaryLocation, secondaryLocations, properties, messageArgs); } [Obsolete("Use another overload of ReportIssue, without calling Diagnostic.Create")] public void ReportIssue(Diagnostic diagnostic) { var @this = this; IssueReporter.ReportIssueCore( x => @this.HasMatchingScope(x), CreateReportingContext, diagnostic); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/DiagnosticDescriptorFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Analyzers; public static class DiagnosticDescriptorFactory { public static readonly string SonarWayTag = "SonarWay"; public static readonly string UtilityTag = "Utility"; public static readonly string MainSourceScopeTag = "MainSourceScope"; public static readonly string TestSourceScopeTag = "TestSourceScope"; /// /// Indicates that the diagnostic is a compilation end diagnostic reported from a compilation end action. /// /// /// See also: /// /// /// WellKnownDiagnosticTags.cs /// /// /// RS1037 /// /// /// public static readonly string CompilationEnd = nameof(CompilationEnd); public static DiagnosticDescriptor CreateUtility(string diagnosticId, string title) => new(diagnosticId, title, string.Empty, string.Empty, DiagnosticSeverity.Warning, isEnabledByDefault: true, customTags: BuildUtilityTags()); public static DiagnosticDescriptor Create(AnalyzerLanguage language, RuleDescriptor rule, string messageFormat, bool? isEnabledByDefault, bool fadeOutCode, bool isCompilationEnd) => new(rule.Id, rule.Title, messageFormat, rule.Category, fadeOutCode ? DiagnosticSeverity.Info : DiagnosticSeverity.Warning, rule.IsHotspot || (isEnabledByDefault ?? rule.SonarWay), rule.Description, null, BuildTags(language, rule, fadeOutCode, isCompilationEnd)); private static string[] BuildTags(AnalyzerLanguage language, RuleDescriptor rule, bool fadeOutCode, bool isCompilationEnd) { var tags = new List { language.LanguageName }; tags.AddRange(rule.Scope.ToTags()); Add(rule.SonarWay, SonarWayTag); Add(fadeOutCode, WellKnownDiagnosticTags.Unnecessary); Add(isCompilationEnd, CompilationEnd); return tags.ToArray(); void Add(bool condition, string tag) { if (condition) { tags.Add(tag); } } } private static IEnumerable ToTags(this SourceScope sourceScope) => sourceScope switch { SourceScope.Main => new[] { MainSourceScopeTag }, SourceScope.Tests => new[] { TestSourceScopeTag }, SourceScope.All => new[] { MainSourceScopeTag, TestSourceScopeTag }, _ => throw new NotSupportedException($"{sourceScope} is not supported 'SourceScope' value."), }; private static string[] BuildUtilityTags() => SourceScope.All.ToTags().Concat(new[] { UtilityTag }) #if !DEBUG // Allow to configure the analyzers in debug mode only. This allows to run test selectively (for example to test only one rule) .Union(new[] { WellKnownDiagnosticTags.NotConfigurable }) #endif .ToArray(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/HotspotDiagnosticAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Analyzers; public abstract class HotspotDiagnosticAnalyzer : SonarDiagnosticAnalyzer { protected IAnalyzerConfiguration Configuration { get; } protected HotspotDiagnosticAnalyzer(IAnalyzerConfiguration configuration) => Configuration = configuration; protected bool IsEnabled(AnalyzerOptions options) { Configuration.Initialize(options); return SupportedDiagnostics.Any(x => Configuration.IsEnabled(x.Id)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/ParametrizedDiagnosticAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Configuration; namespace SonarAnalyzer.Core.Analyzers; public abstract class ParametrizedDiagnosticAnalyzer : SonarDiagnosticAnalyzer { protected abstract void Initialize(SonarParametrizedAnalysisContext context); protected sealed override void Initialize(SonarAnalysisContext context) { var parameterContext = new SonarParametrizedAnalysisContext(context); Initialize(parameterContext); context.RegisterCompilationStartAction( c => { ParameterLoader.SetParameterValues(this, c.SonarLintXml()); parameterContext.ExecutePostponedActions(c); }); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/SonarCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CodeFixes; namespace SonarAnalyzer.Core.Analyzers; public abstract class SonarCodeFix : CodeFixProvider { protected abstract Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context); public override FixAllProvider GetFixAllProvider() => DocumentBasedFixAllProvider.Instance; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { if (context.Document.SupportsSyntaxTree) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); /// This only disables code-fixes when different versions are loaded /// In case of analyzers, is sufficient, because Roslyn only /// creates a single instance from each assembly-version, so we can disable the VSIX analyzers /// In case of code fix providers Roslyn creates multiple instances of the code fix providers. Which means that /// we can only disable one of them if they are created from different assembly-versions. /// If the VSIX and the NuGet has the same version, then code fixes show up multiple times, this ticket will fix this problem: https://github.com/dotnet/roslyn/issues/4030 if (SonarAnalysisContext.LegacyIsRegisteredActionEnabled([], syntaxRoot.SyntaxTree)) { await RegisterCodeFixesAsync(syntaxRoot, new(context)).ConfigureAwait(false); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/SonarDiagnosticAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using RoslynAnalysisContext = Microsoft.CodeAnalysis.Diagnostics.AnalysisContext; namespace SonarAnalyzer.Core.Analyzers; #pragma warning disable T0038 // Use field instead of protected auto-property. We want these class to provide uniform experience. public abstract class SonarDiagnosticAnalyzer : DiagnosticAnalyzer { public static readonly string EnableConcurrentExecutionVariable = "SONAR_DOTNET_ENABLE_CONCURRENT_EXECUTION"; protected abstract void Initialize(SonarAnalysisContext context); protected virtual bool EnableConcurrentExecution => IsConcurrentExecutionEnabled(); public sealed override void Initialize(RoslynAnalysisContext context) { // The default values are Analyze | ReportDiagnostics. We do this call to make sure it will be still enabled even if the default values changed. (Needed for the razor analysis) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); if (EnableConcurrentExecution) { context.EnableConcurrentExecution(); } Initialize(new SonarAnalysisContext(context, SupportedDiagnostics)); } protected static bool IsConcurrentExecutionEnabled() { var value = Environment.GetEnvironmentVariable(EnableConcurrentExecutionVariable); return value is null || !bool.TryParse(value, out var result) || result; } } public abstract class SonarDiagnosticAnalyzer : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected abstract string MessageFormat { get; } protected abstract ILanguageFacade Language { get; } public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected DiagnosticDescriptor Rule { get; } protected SonarDiagnosticAnalyzer(string diagnosticId) => Rule = Language.CreateDescriptor(diagnosticId, MessageFormat); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Analyzers/TrackerHotspotDiagnosticAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Analyzers; public abstract class TrackerHotspotDiagnosticAnalyzer : HotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected abstract ILanguageFacade Language { get; } protected abstract void Initialize(TrackerInput input); public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected DiagnosticDescriptor Rule { get; } protected TrackerHotspotDiagnosticAnalyzer(IAnalyzerConfiguration configuration, string diagnosticId, string messageFormat) : base(configuration) => Rule = Language.CreateDescriptor(diagnosticId, messageFormat); protected override void Initialize(SonarAnalysisContext context) => Initialize(new TrackerInput(context, Configuration, Rule)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/AnalyzerAdditionalFile.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Common; public sealed class AnalyzerAdditionalFile : AdditionalText { public AnalyzerAdditionalFile(string path) { Path = path; } public override string Path { get; } public override SourceText GetText(CancellationToken cancel = default) => SourceText.From(File.ReadAllText(Path)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/AnalyzerConfiguration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public class AnalyzerConfiguration { /// /// Hotspot rules are not configurable (from ruleset) to prevent them from appearing in SonarLint. /// They are enabled by default and we check if SonarLint.xml contains the rule key on CompilationStart /// to determine whether to run the analysis or not. /// SonarLint.xml is added by both SonarScanner for .NET and by SonarLint, however there are differences: /// - SonarLint only uses it to pass parameters to rules /// - SonarScanner uses it to pass parameters and to enable security hotspots (which should only run in batch mode) /// public static IAnalyzerConfiguration Hotspot { get; } = new HotspotConfiguration(new RuleLoader()); public static IAnalyzerConfiguration AlwaysEnabled { get; } = new AlwaysEnabledConfiguration(false); public static IAnalyzerConfiguration AlwaysEnabledWithSonarCfg { get; } = new AlwaysEnabledConfiguration(true); private class AlwaysEnabledConfiguration : IAnalyzerConfiguration { public bool ForceSonarCfg { get; } public AlwaysEnabledConfiguration(bool forceSonarCfg) => ForceSonarCfg = forceSonarCfg; public void Initialize(AnalyzerOptions options) { // Ignore options because we always return true for IsEnabled } public bool IsEnabled(string ruleKey) => true; } /// /// Singleton to hold the configuration for hotspot rules. /// internal /* for tests */ class HotspotConfiguration : IAnalyzerConfiguration { private readonly IRuleLoader ruleLoader; // Hotspot configuration is cached at the assembly level and the MsBuild process // can reuse the already loaded assembly when multiple projects are analyzed one after the other. // Due to this we have to check the current configuration path to see if a reload is needed. private string loadedSonarLintXmlPath; private bool isInitialized; private ISet enabledRules = new HashSet(); /// /// There could be many rules that check if they are enabled simultaneously and since /// the XML loading is an IO operation (e.g. slow) we lock until it completes to prevent /// rules from wrongly deciding that they are disabled while the XML is loaded. /// private static readonly object IsInitializedGate = new object(); public bool ForceSonarCfg => false; public HotspotConfiguration(IRuleLoader ruleLoader) => this.ruleLoader = ruleLoader; public bool IsEnabled(string ruleKey) => // Initialize can be called multiple times, and the `enabledRules` can change between initializations, // so here we have a race condition when a second initialization happens. // We would need to lock here as well, or even better, make IsEnabled and Initialize atomic. // https://github.com/SonarSource/sonar-dotnet/issues/4139 isInitialized ? enabledRules.Contains(ruleKey) : throw new InvalidOperationException("Call Initialize() before calling IsEnabled()."); public void Initialize(AnalyzerOptions options) { var currentSonarLintXmlFile = options.SonarLintXml(); if (isInitialized && loadedSonarLintXmlPath == currentSonarLintXmlFile?.Path) { return; } lock (IsInitializedGate) { if (isInitialized && loadedSonarLintXmlPath == currentSonarLintXmlFile?.Path) { return; } loadedSonarLintXmlPath = currentSonarLintXmlFile?.Path; if (loadedSonarLintXmlPath == null) { isInitialized = true; return; } // we assume the returned set is not null var sonarLintXml = currentSonarLintXmlFile.GetText().ToString(); enabledRules = ruleLoader.GetEnabledRules(sonarLintXml); isInitialized = true; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/AnalyzerLanguage.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.Core.Common; public sealed class AnalyzerLanguage { public static readonly AnalyzerLanguage CSharp = new(LanguageNames.CSharp, ".cs"); public static readonly AnalyzerLanguage VisualBasic = new(LanguageNames.VisualBasic, ".vb"); public string LanguageName { get; } public string FileExtension { get; } private AnalyzerLanguage(string languageName, string fileExtension) { LanguageName = languageName; FileExtension = fileExtension; } public override string ToString() => LanguageName; public static AnalyzerLanguage FromName(string name) => name switch { LanguageNames.CSharp => CSharp, LanguageNames.VisualBasic => VisualBasic, _ => throw new NotSupportedException("Unsupported language name: " + name) }; public static AnalyzerLanguage FromPath(string path) { var comparer = StringComparer.OrdinalIgnoreCase; return Path.GetExtension(path) switch { { } ext when comparer.Equals(ext, ".cs") || comparer.Equals(ext, ".razor") || comparer.Equals(ext, ".cshtml") => CSharp, { } ext when comparer.Equals(ext, ".vb") => VisualBasic, _ => throw new NotSupportedException("Unsupported file extension: " + Path.GetExtension(path)) }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/BidirectionalDictionary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; internal class BidirectionalDictionary { private readonly IDictionary aToB = new Dictionary(); private readonly IDictionary bToA = new Dictionary(); public ICollection AKeys => aToB.Keys; public ICollection BKeys => bToA.Keys; public void Add(TA a, TB b) { if (aToB.ContainsKey(a) || bToA.ContainsKey(b)) { throw new ArgumentException("An element with the same key already exists in the BidirectionalDictionary."); } aToB.Add(a, b); bToA.Add(b, a); } public TB GetByA(TA a) => aToB[a]; public TA GetByB(TB b) => bToA[b]; public bool ContainsKeyByA(TA a) => aToB.ContainsKey(a); public bool ContainsKeyByB(TB b) => bToA.ContainsKey(b); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/Constants.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public static class Constants { internal static readonly string[] LineTerminators = { "\r\n", "\n", "\r" }; public static TimeSpan DefaultRegexTimeout => TimeSpan.FromMilliseconds(100); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/Conversions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public static class Conversions { public static int? ToInt(object value) => ConvertWith(value, Convert.ToInt32); public static double? ToDouble(object value) { if (value is char) // 'char' needs to roundtrip char -> int -> double, can't go char -> double { value = Convert.ToInt32(value); } return ConvertWith(value, Convert.ToDouble) is { } doubleValue && !double.IsInfinity(doubleValue) ? doubleValue : null; } public static T? ConvertWith(object value, Func converter) where T : struct { try { return converter(value); } catch (Exception ex) when (ex is FormatException || ex is OverflowException || ex is InvalidCastException) { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/DisjointSets.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; /// /// Data structure for working with disjoint sets of strings, to perform union-find operations with equality semantics: /// i.e. reflexivity, symmetry and transitivity. /// /// See https://en.wikipedia.org/wiki/Disjoint-set_data_structure for an introduction to the data structure and /// https://www.geeksforgeeks.org/introduction-to-disjoint-set-data-structure-or-union-find-algorithm/ for examples of /// its use. /// /// An example of use is to build undirected connected components of dependencies, where each node is the identifier. /// /// Uses a dictionary of strings as a backing store. The dictionary represents a forest of trees, where each node is /// a string and each tree is a set of nodes. /// public class DisjointSets { private readonly Dictionary parents; public DisjointSets(IEnumerable elements) => parents = elements.ToDictionary(x => x, x => x); public void Union(string from, string to) => parents[FindRoot(from)] = FindRoot(to); public string FindRoot(string element) => parents[element] is var root && root != element ? FindRoot(root) : root; // Set elements are sorted in ascending order. Sets are sorted in ascending order by their first element. public List> GetAllSets() => [.. parents.GroupBy(x => FindRoot(x.Key), x => x.Key).Select(x => x.OrderBy(x => x).ToList()).OrderBy(x => x[0])]; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/DocumentBasedFixAllProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; namespace SonarAnalyzer.Core.Common; public class DocumentBasedFixAllProvider : FixAllProvider { #region Singleton implementation private DocumentBasedFixAllProvider() { } private static readonly Lazy Lazy = new(() => new DocumentBasedFixAllProvider()); public static DocumentBasedFixAllProvider Instance => Lazy.Value; #endregion Singleton implementation private const string TitleSolutionPattern = "Fix all '{0}' in Solution"; private const string TitleScopePattern = "Fix all '{0}' in '{1}'"; private const string TitleFixAll = "Fix all '{0}'"; private static string GetFixAllTitle(FixAllContext fixAllContext) { var diagnosticIds = fixAllContext.DiagnosticIds; var diagnosticId = string.Join(",", diagnosticIds.ToArray()); switch (fixAllContext.Scope) { case FixAllScope.Document: return string.Format(TitleScopePattern, diagnosticId, fixAllContext.Document.Name); case FixAllScope.Project: return string.Format(TitleScopePattern, diagnosticId, fixAllContext.Project.Name); case FixAllScope.Solution: return string.Format(TitleSolutionPattern, diagnosticId); default: return TitleFixAll; } } public override Task GetFixAsync(FixAllContext fixAllContext) { var title = GetFixAllTitle(fixAllContext); switch (fixAllContext.Scope) { case FixAllScope.Document: return Task.FromResult(CodeAction.Create(title, async ct => fixAllContext.Document.WithSyntaxRoot( await GetFixedDocumentAsync(fixAllContext, fixAllContext.Document).ConfigureAwait(false)))); case FixAllScope.Project: return Task.FromResult(CodeAction.Create(title, ct => GetFixedProjectAsync(fixAllContext, fixAllContext.Project))); case FixAllScope.Solution: return Task.FromResult(CodeAction.Create(title, ct => GetFixedSolutionAsync(fixAllContext))); default: return Task.FromResult(null); } } private static async Task GetFixedSolutionAsync(FixAllContext fixAllContext) { var newSolution = fixAllContext.Solution; foreach (var projectId in newSolution.ProjectIds) { newSolution = await GetFixedProjectAsync(fixAllContext, newSolution.GetProject(projectId)) .ConfigureAwait(false); } return newSolution; } private static async Task GetFixedProjectAsync(FixAllContext fixAllContext, Project project) { var solution = project.Solution; var newDocuments = project.Documents.ToDictionary(d => d.Id, d => GetFixedDocumentAsync(fixAllContext, d)); await Task.WhenAll(newDocuments.Values).ConfigureAwait(false); foreach (var newDoc in newDocuments) { solution = solution.WithDocumentSyntaxRoot(newDoc.Key, newDoc.Value.Result); } return solution; } private static async Task GetFixedDocumentAsync(FixAllContext fixAllContext, Document document) { var annotationKind = Guid.NewGuid().ToString(); var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); var elementDiagnosticPairs = diagnostics .Select(d => new KeyValuePair(GetReportedElement(d, root), d)) .Where(n => !n.Key.IsMissing) .GroupBy(n => n.Key) .ToDictionary(g => g.Key, g => g.First().Value); diagnostics = elementDiagnosticPairs.Values.ToImmutableArray(); // Continue with unique winners var diagnosticAnnotationPairs = new BidirectionalDictionary(); CreateAnnotationForDiagnostics(diagnostics, annotationKind, diagnosticAnnotationPairs); root = GetRootWithAnnotatedElements(root, elementDiagnosticPairs, diagnosticAnnotationPairs); var currentDocument = document.WithSyntaxRoot(root); var annotatedElements = root.GetAnnotatedNodesAndTokens(annotationKind).ToList(); while (annotatedElements.Any()) { var element = annotatedElements.First(); var annotation = element.GetAnnotations(annotationKind).First(); var diagnostic = diagnosticAnnotationPairs.GetByB(annotation); var location = root.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().GetLocation(); if (location == null) { // annotation is already removed from the tree continue; } var newDiagnostic = Diagnostic.Create( diagnostic.Descriptor, location, diagnostic.AdditionalLocations, diagnostic.Properties); var fixes = new List(); var context = new CodeFixContext(currentDocument, newDiagnostic, (a, d) => { lock (fixes) { fixes.Add(a); } }, fixAllContext.CancellationToken); await fixAllContext.CodeFixProvider.RegisterCodeFixesAsync(context).ConfigureAwait(false); var action = fixes.FirstOrDefault(fix => fix.EquivalenceKey == fixAllContext.CodeActionEquivalenceKey); if (action != null) { var operations = await action.GetOperationsAsync(fixAllContext.CancellationToken).ConfigureAwait(false); var solution = operations.OfType().Single().ChangedSolution; currentDocument = solution.GetDocument(document.Id); root = await currentDocument.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); } root = RemoveAnnotationIfExists(root, annotation); currentDocument = document.WithSyntaxRoot(root); annotatedElements = root.GetAnnotatedNodesAndTokens(annotationKind).ToList(); } return await currentDocument.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false); } private static SyntaxNodeOrToken GetReportedElement(Diagnostic diagnostic, SyntaxNode root) { var token = root.FindToken(diagnostic.Location.SourceSpan.Start); var exactMatch = token.Span == diagnostic.Location.SourceSpan; return exactMatch ? (SyntaxNodeOrToken)token : root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); } private static SyntaxNode RemoveAnnotationIfExists(SyntaxNode root, SyntaxAnnotation annotation) { var element = root.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault(); if (element == default) { return root; } if (element.IsNode) { var node = element.AsNode(); return root.ReplaceNode( node, node.WithoutAnnotations(annotation)); } var token = element.AsToken(); return root.ReplaceToken( token, token.WithoutAnnotations(annotation)); } private static SyntaxNode GetRootWithAnnotatedElements(SyntaxNode root, Dictionary elementDiagnosticPairs, BidirectionalDictionary diagnosticAnnotationPairs) { var nodes = elementDiagnosticPairs.Keys.Where(k => k.IsNode).Select(k => k.AsNode()); var tokens = elementDiagnosticPairs.Keys.Where(k => k.IsToken).Select(k => k.AsToken()); return root.ReplaceSyntax( nodes, (original, rewritten) => { var annotation = diagnosticAnnotationPairs.GetByA(elementDiagnosticPairs[original]); return rewritten.WithAdditionalAnnotations(annotation); }, tokens, (original, rewritten) => { var annotation = diagnosticAnnotationPairs.GetByA(elementDiagnosticPairs[original]); return rewritten.WithAdditionalAnnotations(annotation); }, null, null); } private static void CreateAnnotationForDiagnostics(System.Collections.Immutable.ImmutableArray diagnostics, string annotationKind, BidirectionalDictionary diagnosticAnnotationPairs) { foreach (var diagnostic in diagnostics) { diagnosticAnnotationPairs.Add(diagnostic, new SyntaxAnnotation(annotationKind)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/HashCode.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; using Roslyn.Utilities; namespace SonarAnalyzer.Core.Common; public static class HashCode // Replacement for System.HashCode that is available from .NET Standard 2.1 { private const uint Seed = 374761393U; private const uint PreMultiplier = 3266489917U; private const uint PostMultiplier = 668265263U; private const int RotateOffset = 17; private const int IntSeed = 393241; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/pull/7012", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] public static int DictionaryContentHash(ImmutableDictionary dictionary) { // Performance: Make sure this method is allocation free: // * Don't use IDictionary as parameter, because we will not get the struct ImmutableDictionary.Enumerator but the boxed version of it // * Don't use Linq for the same reason. if (dictionary.IsEmpty) { return 0; } var seed = IntSeed; foreach (var kvp in dictionary) { seed ^= Combine(kvp.Key, kvp.Value); } return seed; } /// /// Calculates a hash for the enumerable based on the content. The same values in a different order produce the same hash-code. /// public static int EnumerableUnorderedContentHash(IEnumerable enumerable) => enumerable.Aggregate(IntSeed, (seed, x) => seed ^ (x?.GetHashCode() ?? 0)); /// /// Calculates a hash for the enumerable based on the content. The same values in a different order produce different hash-codes. /// public static int EnumerableOrderedContentHash(IEnumerable enumerable) => enumerable.Aggregate(IntSeed, Combine); public static int Combine(T1 a, T2 b) => (int)Seed.AddHash(a?.GetHashCode()).AddHash(b?.GetHashCode()); public static int Combine(T1 a, T2 b, T3 c) => (int)Combine(a, b).AddHash(c?.GetHashCode()); public static int Combine(T1 a, T2 b, T3 c, T4 d) => (int)Combine(a, b, c).AddHash(d?.GetHashCode()); public static int Combine(T1 a, T2 b, T3 c, T4 d, T5 e) => (int)Combine(a, b, c, d).AddHash(e?.GetHashCode()); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint AddHash(this int hash, int? value) => ((uint)hash).AddHash(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint AddHash(this uint hash, int? value) => RotateLeft(hash + (uint)(value ?? 0) * PreMultiplier) * PostMultiplier; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateLeft(uint value) => (value << RotateOffset) | (value >> (sizeof(int) - RotateOffset)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/IAnalyzerConfiguration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public interface IAnalyzerConfiguration { // Force the use of Sonar Cfg in rules that support both Roslyn and Sonar CFGs bool ForceSonarCfg { get; } bool IsEnabled(string ruleKey); void Initialize(AnalyzerOptions options); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/IRuleLoader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public interface IRuleLoader { ISet GetEnabledRules(string content); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/ISafeSyntaxWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public interface ISafeSyntaxWalker { public bool SafeVisit(SyntaxNode syntaxNode); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/IsExternalInit.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.ComponentModel; namespace System.Runtime.CompilerServices; // This empty class needs to exist when C# 9 init-only setters are used in project targeting .NET Framework. // https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.isexternalinit?view=net-6.0 // It is used only by compiler to track metadata. It does not affect MSIL, CLR nor runtime. // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init#metadata-encoding [EditorBrowsable(EditorBrowsableState.Never)] public static class IsExternalInit { } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/MultiValueDictionary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public sealed class MultiValueDictionary : Dictionary> { public static MultiValueDictionary Create() where TUnderlying : ICollection, new() => new MultiValueDictionary { UnderlyingCollectionFactory = () => new TUnderlying() }; private Func> UnderlyingCollectionFactory { get; set; } = () => new List(); public void Add(TKey key, TValue value) => AddWithKey(key, value); public void AddWithKey(TKey key, TValue value) { if (!TryGetValue(key, out var values)) { values = UnderlyingCollectionFactory(); Add(key, values); } values.Add(value); } public void AddRangeWithKey(TKey key, IEnumerable addedValues) { if (!TryGetValue(key, out var values)) { values = UnderlyingCollectionFactory(); Add(key, values); } foreach (var addedValue in addedValues) { values.Add(addedValue); } } } #region Extensions public static class MultiValueDictionaryExtensions { public static MultiValueDictionary ToMultiValueDictionary(this IEnumerable source, Func> elementSelector) => source.ToMultiValueDictionary(x => x, elementSelector); public static MultiValueDictionary ToMultiValueDictionary(this IEnumerable source, Func keySelector, Func> elementSelector) { var dictionary = new MultiValueDictionary(); foreach (var item in source) { dictionary.Add(keySelector(item), elementSelector(item)); } return dictionary; } } #endregion Extensions ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/NaturalLanguageDetector.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.Text; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Common; // Java implementation: https://github.com/SonarSource/sonar-java/blob/master/java-checks/src/main/java/org/sonar/java/checks/helpers/LatinAlphabetLanguagesHelper.java public static class NaturalLanguageDetector { private static readonly double[] WordFirstLetterFrequency = [2.6590433d, 1.2620203d, 1.5564419d, 0.9035879d, 0.7042806d, 1.1847413d, 0.4955763d, 0.9510469d, 1.7106735d, 0.2684641d, 0.2196475d, 0.7503985d, 1.087598d, 0.6504237d, 1.7798191d, 1.2322421d, 0.0468438d, 0.9674179d, 1.8778546d, 3.5174797d, 0.4200321d, 0.2624742d, 1.2915608d, 0.0095785d, 0.1583926d, 0.0323609d]; private static readonly double[] WordLastLetterFrequency = [0.5738782d, 0.0514104d, 0.2813366d, 2.509574d, 4.8288358d, 1.0461689d, 0.7732504d, 0.7288072d, 0.1805434d, 0.0053191d, 0.2896774d, 0.9860451d, 0.5045335d, 2.8780559d, 1.0164436d, 0.183349d, 0.0039651d, 1.6346942d, 3.5859066d, 1.9681061d, 0.0870041d, 0.0362143d, 0.207057d, 0.0560339d, 1.5560091d, 0.0277812d]; private static readonly double[][] CharacterPairFrequency = [ [0.0929348d, 1.3457961d, 2.7854923d, 2.5539888d, 0.2209547d, 0.6705441d, 1.8441841d, 0.2415183d, 2.1081316d, 0.1513539d, 0.7217594d, 8.0951704d, 3.0899456d, 14.1108051d, 0.0792523d, 1.3738109d, 0.0387682d, 8.5247815d, 6.357247d, 8.850255d, 1.0730911d, 1.0915589d, 0.4722228d, 0.1239866d, 1.5762482d, 0.218569d], [1.9067869d, 0.1279563d, 0.0739743d, 0.0265155d, 3.4357286d, 0.0049029d, 0.005839d, 0.0312368d, 0.956677d, 0.0743913d, 0.0039669d, 1.3367944d, 0.0346314d, 0.0176708d, 1.5688768d, 0.0101957d, 0.0006587d, 1.1051669d, 0.3112683d, 0.0468363d, 1.321757d, 0.0127821d, 0.0117435d, 0.0007594d, 1.2868484d, 0.0017814d], [4.0719631d, 0.0205519d, 0.4552517d, 0.0478322d, 4.0359256d, 0.0136253d, 0.0141062d, 4.2610277d, 1.9324629d, 0.0027702d, 1.1371642d, 1.4239825d, 0.0214459d, 0.0160944d, 5.2439283d, 0.0142073d, 0.0346805d, 1.0777674d, 0.2821967d, 2.8990383d, 1.0071517d, 0.0049062d, 0.0070608d, 0.0009994d, 0.2640629d, 0.0263652d], [1.4407028d, 0.0366525d, 0.0727675d, 0.3063419d, 5.3874701d, 0.0432824d, 0.1899634d, 0.0710289d, 3.8700951d, 0.0319036d, 0.0064268d, 0.1858389d, 0.1456839d, 0.0833305d, 1.2408384d, 0.0215672d, 0.0132501d, 0.6165595d, 0.7798563d, 0.0405733d, 1.0407546d, 0.1140672d, 0.1005016d, 0.0014863d, 0.2886946d, 0.0112261d], [4.4101634d, 0.5402931d, 3.1511903d, 8.335849d, 2.1665083d, 1.0040715d, 0.9092963d, 0.1880728d, 1.0110292d, 0.0358556d, 0.188317d, 3.8583031d, 2.2997606d, 8.5360484d, 0.7791784d, 1.1289852d, 0.2515411d, 14.4775826d, 8.3971751d, 2.7876921d, 0.3437904d, 1.4431433d, 0.9862942d, 1.1282521d, 0.8072609d, 0.0804047d], [0.9463967d, 0.0087502d, 0.0314099d, 0.0487887d, 1.5768827d, 0.7325095d, 0.0113305d, 0.0027475d, 2.0741465d, 0.0032058d, 0.0049733d, 0.4309108d, 0.0223647d, 0.0049622d, 3.2088246d, 0.0047891d, 0.0004224d, 1.5815529d, 0.0435306d, 0.5887487d, 0.4870926d, 0.0019864d, 0.0038661d, 0.0018069d, 0.0444506d, 0.0011643d], [1.4170025d, 0.0481224d, 0.0082768d, 0.0816686d, 3.0209535d, 0.0149393d, 0.1457067d, 1.2637355d, 1.121449d, 0.0042659d, 0.006808d, 0.5162312d, 0.0464436d, 0.4306755d, 0.8720109d, 0.0092219d, 0.0014534d, 1.302087d, 0.3605589d, 0.1215897d, 0.9230535d, 0.003158d, 0.0317547d, 0.0021631d, 0.2001351d, 0.0043706d], [4.3917104d, 0.0536482d, 0.0181969d, 0.0394497d, 16.2490394d, 0.0149868d, 0.0059029d, 0.0085796d, 4.4951994d, 0.0023695d, 0.012801d, 0.1408159d, 0.1344758d, 0.270349d, 3.0645262d, 0.0150515d, 0.0061781d, 0.5387961d, 0.2133521d, 0.6162165d, 0.5563874d, 0.0095012d, 0.0915231d, 0.0005191d, 0.3276425d, 0.0056028d], [3.0618075d, 0.5378794d, 5.3672662d, 1.7146727d, 2.6249932d, 0.9268649d, 1.6588638d, 0.0298823d, 0.1653869d, 0.0423041d, 0.4355309d, 3.3780001d, 1.6210354d, 16.2047698d, 5.3259079d, 0.8335975d, 0.06607d, 2.5575842d, 7.7396368d, 6.8953537d, 0.1660788d, 1.8670839d, 0.0228681d, 0.1719965d, 0.0319517d, 0.3727173d], [0.5384459d, 0.0018035d, 0.0037464d, 0.0049227d, 0.3791862d, 0.0016495d, 0.0030904d, 0.0047405d, 0.1010139d, 0.0030284d, 0.0051238d, 0.0023837d, 0.0052344d, 0.0054429d, 0.5759392d, 0.0419588d, 0.0001699d, 0.0199578d, 0.0076412d, 0.0024995d, 0.511387d, 0.0018288d, 0.0015663d, 0.0003011d, 0.0024199d, 0.0015624d], [0.4584126d, 0.0151268d, 0.0066457d, 0.0105469d, 1.4778732d, 0.0156612d, 0.0235217d, 0.0926333d, 0.9247234d, 0.0043772d, 0.028332d, 0.1271601d, 0.0365927d, 0.2679984d, 0.2570398d, 0.0171023d, 0.0008536d, 0.0805852d, 0.6105992d, 0.0285942d, 0.115693d, 0.007477d, 0.0280535d, 0.0013893d, 0.1124803d, 0.0013102d], [4.548621d, 0.2915819d, 0.089763d, 1.4893018d, 6.3515348d, 0.2311593d, 0.0921435d, 0.0322694d, 5.0245197d, 0.0092016d, 0.1856222d, 3.8105199d, 0.4655274d, 0.0369741d, 2.6739741d, 0.153297d, 0.0018632d, 0.0689171d, 1.1608595d, 0.8001751d, 0.9675034d, 0.2130048d, 0.102301d, 0.0016705d, 2.2223913d, 0.0112197d], [4.4732575d, 0.9971554d, 0.0892385d, 0.0154109d, 5.1403143d, 0.0175541d, 0.0107624d, 0.0132742d, 2.3275322d, 0.0027971d, 0.0088211d, 0.0391983d, 0.8021489d, 0.0887556d, 2.0619624d, 1.4747935d, 0.0008712d, 0.0401555d, 0.5991706d, 0.0220131d, 0.881698d, 0.0076061d, 0.0126008d, 0.0026432d, 0.3033358d, 0.0023306d], [3.6077607d, 0.0975166d, 2.589403d, 7.9063088d, 4.6470058d, 0.3556239d, 6.2425962d, 0.1073503d, 3.0557149d, 0.0788384d, 0.6186266d, 0.3370293d, 0.17844d, 0.8651743d, 2.8184623d, 0.0480307d, 0.0223127d, 0.102047d, 3.1470283d, 6.0322931d, 0.6582686d, 0.2595533d, 0.0651263d, 0.0103653d, 0.6055506d, 0.0792284d], [0.5652844d, 0.5958594d, 1.2994182d, 1.1115993d, 0.2776038d, 6.3941577d, 0.7715881d, 0.2678748d, 0.5253778d, 0.0947828d, 0.4635273d, 2.920816d, 3.7278997d, 11.6017554d, 1.4753292d, 1.8177215d, 0.0118906d, 8.8419322d, 1.7595924d, 2.4753308d, 4.5261994d, 1.4178813d, 1.8557399d, 0.1644457d, 0.2754933d, 0.0487071d], [2.5624505d, 0.0202675d, 0.0204994d, 0.022034d, 2.9595845d, 0.015402d, 0.0525791d, 0.7732213d, 1.0678211d, 0.0022877d, 0.0096737d, 1.8234707d, 0.0814874d, 0.0208135d, 2.2662701d, 0.7738026d, 0.0008108d, 2.6371155d, 0.3885394d, 0.5976453d, 0.7912125d, 0.0032943d, 0.0076619d, 0.0009594d, 0.0841922d, 0.0031912d], [0.0116363d, 0.0014528d, 0.00085d, 0.0004046d, 0.0011631d, 0.0009863d, 0.0001834d, 0.0003922d, 0.0148139d, 0.0001364d, 0.0001327d, 0.0016952d, 0.0008495d, 0.0003971d, 0.0012619d, 0.0004823d, 0.0007202d, 0.0008395d, 0.0016322d, 0.0009446d, 0.781532d, 0.0008461d, 0.0007344d, 0.0002711d, 0.0001503d, 0.0001024d], [5.2664628d, 0.2722724d, 1.0315216d, 1.5041745d, 11.4452395d, 0.2187558d, 0.8034347d, 0.1197433d, 5.9004085d, 0.0136782d, 0.7869179d, 0.7888557d, 1.1905663d, 1.5408916d, 5.2636683d, 0.2840431d, 0.0162732d, 0.8714239d, 3.073065d, 3.0935018d, 1.0043785d, 0.4845634d, 0.1116324d, 0.0059307d, 1.9104828d, 0.0298333d], [1.3958189d, 0.1084548d, 1.3997172d, 0.0477502d, 5.5977636d, 0.0961754d, 0.0285751d, 2.4672552d, 3.9516833d, 0.0077479d, 0.339737d, 0.4491616d, 0.3986528d, 0.1207062d, 2.8901855d, 1.1973049d, 0.080366d, 0.0717654d, 2.3862707d, 7.8045342d, 1.7449085d, 0.0408148d, 0.1931512d, 0.0020589d, 0.350114d, 0.0198438d], [4.0046636d, 0.2315084d, 0.6360105d, 0.0243227d, 8.9176277d, 0.0526605d, 0.0200005d, 18.3086482d, 7.9891653d, 0.0062372d, 0.0151697d, 0.6619201d, 0.1460394d, 0.0806787d, 6.2468781d, 0.0274042d, 0.0007526d, 2.9306096d, 2.1799955d, 1.1239766d, 1.616373d, 0.1039086d, 0.5300224d, 0.003287d, 1.5511418d, 0.0753694d], [1.0405339d, 0.7757457d, 1.0559389d, 0.6735502d, 1.0636866d, 0.1190306d, 0.7986363d, 0.0290528d, 0.7019287d, 0.0192097d, 0.1602448d, 1.8289351d, 1.1563737d, 3.2297811d, 0.0582932d, 0.8142665d, 0.0061515d, 3.2935746d, 3.1995976d, 2.5388821d, 0.0101856d, 0.0400647d, 0.0116986d, 0.0440218d, 0.0426452d, 0.0412904d], [0.9870774d, 0.0037721d, 0.006448d, 0.017297d, 4.6552622d, 0.0133728d, 0.0130077d, 0.0038715d, 2.1772529d, 0.0008623d, 0.0029271d, 0.0134182d, 0.0028136d, 0.0068546d, 0.5066435d, 0.0041575d, 0.0002258d, 0.0206034d, 0.0273123d, 0.0044648d, 0.0203117d, 0.002107d, 0.0015298d, 0.0005471d, 0.0621332d, 0.0006573d], [2.9790214d, 0.0193429d, 0.0157836d, 0.018724d, 1.8652788d, 0.0161439d, 0.004503d, 1.5224859d, 2.2852698d, 0.0017198d, 0.0207637d, 0.0770983d, 0.0136184d, 0.6167433d, 1.3048155d, 0.018365d, 0.0009061d, 0.2921267d, 0.250538d, 0.0269223d, 0.0164429d, 0.0029288d, 0.0208577d, 0.0010678d, 0.0402552d, 0.0011097d], [0.1665908d, 0.0055061d, 0.086935d, 0.0017397d, 0.1209231d, 0.0230238d, 0.0004892d, 0.0240804d, 0.2232723d, 0.0004185d, 0.0003742d, 0.006829d, 0.0051386d, 0.0011599d, 0.0317656d, 0.2459899d, 0.0005172d, 0.0012081d, 0.0033771d, 0.3847311d, 0.0254768d, 0.0063257d, 0.0036148d, 0.0100832d, 0.0172085d, 0.0002967d], [0.3162593d, 0.054578d, 0.1219273d, 0.0766018d, 0.7890763d, 0.0112568d, 0.021848d, 0.0089087d, 0.1617439d, 0.0032816d, 0.0146214d, 0.2275842d, 0.1906941d, 0.1630151d, 0.6414934d, 0.1588797d, 0.0004128d, 0.1039721d, 0.4814257d, 0.1023695d, 0.0593967d, 0.0103587d, 0.0387865d, 0.0029518d, 0.005733d, 0.0142095d], [0.2393676d, 0.0081167d, 0.0035083d, 0.0052527d, 0.3775166d, 0.0012409d, 0.0050274d, 0.0330923d, 0.1787957d, 0.0006392d, 0.006042d, 0.0129057d, 0.0072316d, 0.0071939d, 0.1036998d, 0.0025679d, 0.0009882d, 0.0047487d, 0.0052224d, 0.005546d, 0.0411031d, 0.0031899d, 0.0047273d, 0.0007615d, 0.0223349d, 0.0495398d], ]; private static readonly Regex Pattern = new("[a-z]+|[A-Z][a-z]+|[A-Za-z]+", RegexOptions.None, Constants.DefaultRegexTimeout); /** * @param text to analyze * @return 1.0 in average for random string, and above if it looks like language (ex: could be compared using > 1.5d) */ public static double HumanLanguageScore(string text) { if (string.IsNullOrEmpty(text)) { return 0.0d; } text = StripAccents(text); var totalScore = 0.0d; var totalWeight = 0.0d; var camelCaseSeparator = 0; var lastFindingEnd = -1; var allWorldLength = 0; var matches = Pattern.SafeMatches(text); foreach (Match match in matches) { if (match.Index == lastFindingEnd) { camelCaseSeparator++; } var word = match.Value.ToUpperInvariant(); allWorldLength += word.Length; double weight = word.Length; totalScore += WordScore(word) * weight; totalWeight += weight; lastFindingEnd = match.Index + word.Length; } if (totalWeight < 0.01d) { return 1.0d; } var separatorCount = text.Length - allWorldLength + camelCaseSeparator; var expectedSeparatorCount = text.Length / 6; var unexpectedSeparatorCount = Math.Abs(separatorCount - expectedSeparatorCount); return (((totalScore / totalWeight) * allWorldLength) + (0.1d * unexpectedSeparatorCount)) / (allWorldLength + unexpectedSeparatorCount); } private static int Index(char ch) => ch - 'A'; private static double WordScore(string word) { if (word.Length == 1) { // returning the one letter statistics gave too high a score to 'a', // so we decided to just always return a constant return 0.1d; } var firstCharScore = WordFirstLetterFrequency[Index(word[0])]; var lastCharScore = WordLastLetterFrequency[Index(word[word.Length - 1])]; var pairsScore = word.Zip(word.Skip(1), (a, b) => CharacterPairFrequency[Index(a)][Index(b)]).Sum(); return (firstCharScore + lastCharScore + pairsScore) / (word.Length + 1); } private static string StripAccents(string input) => string.Concat(input.Normalize(NormalizationForm.FormD).Where(x => CharUnicodeInfo.GetUnicodeCategory(x) != UnicodeCategory.NonSpacingMark)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/NodeAndModel.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public readonly record struct NodeAndModel(TSyntax Node, SemanticModel Model) where TSyntax : SyntaxNode; ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/NodeAndSymbol.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public readonly record struct NodeAndSymbol(TSyntaxNode Node, TSymbol Symbol) where TSyntaxNode : SyntaxNode where TSymbol : ISymbol; public readonly record struct NodeAndSymbol(SyntaxNode Node, ISymbol Symbol); ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/NodeSymbolAndModel.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public readonly record struct NodeSymbolAndModel(TSyntax Node, TSymbol Symbol, SemanticModel Model) where TSyntax : SyntaxNode where TSymbol : ISymbol; ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/Pair.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public readonly record struct Pair(TLeft Left, TRight Right); public static class Pair { public static Pair From(TLeft left, TRight right) => new(left, right); public static void Swap(ref T left, ref T right) { var tmp = left; left = right; right = tmp; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/PropertyType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; /// /// sonar-plugin-api / RuleParamType also supports lists with single/multi selection. /// We don't have a way how to annotate those on .NET side. /// See sonar-plugin-api / RuleParamTypeTest.java for format an usages. /// public enum PropertyType { String, Text, Boolean, Integer, Float, RegularExpression, // This will be translated to String by RuleParamType.parse() on the API side } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/RuleDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public record RuleDescriptor(string Id, string Title, string Type, string DefaultSeverity, string Status, SourceScope Scope, bool SonarWay, string Description) { public string Category => $"{DefaultSeverity} {ReadableType}"; private string ReadableType => Type switch { "BUG" => "Bug", "CODE_SMELL" => "Code Smell", "VULNERABILITY" => "Vulnerability", "SECURITY_HOTSPOT" => "Security Hotspot", _ => throw new UnexpectedValueException(nameof(Type), Type) }; public bool IsHotspot => Type == "SECURITY_HOTSPOT"; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/RuleLoader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Common; public class RuleLoader : IRuleLoader { public ISet GetEnabledRules(string content) => XDocument.Parse(content) .Descendants("Rule") .Select(r => r.Element("Key")?.Value) .WhereNotNull() .ToHashSet(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/RuleParameterAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; namespace SonarAnalyzer.Core.Common; [AttributeUsage(AttributeTargets.Property)] public sealed class RuleParameterAttribute : Attribute { public string Key { get; } public string Description { get; } public PropertyType Type { get; } public string DefaultValue { get; } public RuleParameterAttribute(string key, PropertyType type, string description, string defaultValue) { Key = key; Description = description; Type = type; DefaultValue = defaultValue; } public RuleParameterAttribute(string key, PropertyType type, string description, int defaultValue) : this(key, type, description, defaultValue.ToString(CultureInfo.InvariantCulture)) { } public RuleParameterAttribute(string key, PropertyType type, string description, double defaultValue) : this(key, type, description, defaultValue.ToString(CultureInfo.InvariantCulture)) { } public RuleParameterAttribute(string key, PropertyType type, string description) : this(key, type, description, null) { } public RuleParameterAttribute(string key, PropertyType type) : this(key, type, null, null) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/SecondaryLocation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public record SecondaryLocation(Location Location, string Message); ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/ShannonEntropy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public static class ShannonEntropy { // See https://en.wikipedia.org/wiki/Entropy_(information_theory) for more details. public static double Calculate(string input) { if (string.IsNullOrEmpty(input)) { return 0; } var length = input.Length; return input .GroupBy(x => x) .ToDictionary(x => x.Key, x => x.Count()) .Values .Select(x => (double)x / length) .Select(probability => -probability * Math.Log(probability, 2)) .Sum(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/SourceScope.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public enum SourceScope { Main, Tests, All } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/TokenAndModel.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public readonly record struct TokenAndModel(SyntaxToken Token, SemanticModel Model); ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/TopLevelStatements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public static class TopLevelStatements { public const string MainMethodImplicitName = "
$"; public static readonly ImmutableHashSet ProgramClassImplicitName = ImmutableHashSet.Create("Program", "$"); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/UnexpectedLanguageException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public sealed class UnexpectedLanguageException : Exception { public UnexpectedLanguageException(AnalyzerLanguage language) : this(language.LanguageName) { } public UnexpectedLanguageException(string language) : base($"Unexpected language: {language}") { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Common/UnexpectedValueException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Common; public sealed class UnexpectedValueException : Exception { public UnexpectedValueException(string name, object value) : base($"Unexpected {name} value: {value}") { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/AnalysisConfig.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Configuration; /// /// Data class to describe an analysis configuration. /// /// /// This class is the counterpart of SonarScanner.MSBuild.Common.AnalysisConfig. /// This class should not be used directly in this codebase. To get configuration properties, use . /// internal sealed class AnalysisConfig { public static readonly AnalysisConfig Empty = new(); public string SonarQubeVersion { get; } public ConfigSetting[] AdditionalConfig { get; } public AnalysisConfig(XDocument document) { var xmlns = XNamespace.Get("http://www.sonarsource.com/msbuild/integration/2015/1"); if (document.Root.Name != xmlns + "AnalysisConfig") { throw new InvalidOperationException("Unexpected Root node: " + document.Root.Name); } SonarQubeVersion = document.Root.Element(xmlns + "SonarQubeVersion")?.Value ?? string.Empty; AdditionalConfig = document.Root.Element(xmlns + "AdditionalConfig")?.Elements(xmlns + "ConfigSetting").Select(x => new ConfigSetting(x)).ToArray() ?? []; } private AnalysisConfig() { SonarQubeVersion = string.Empty; AdditionalConfig = []; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/AnalysisConfigReader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Xml.Linq; namespace SonarAnalyzer.Core.Configuration; /// /// This class reads and encapsulates , exposing only the configuration our analyzers need. /// public class AnalysisConfigReader { private readonly AnalysisConfig analysisConfig; public bool IsCloud => analysisConfig.SonarQubeVersion.StartsWith("8.0.0"); // This analyzer will never be backported to Server 8.0, so we don't care about release version public AnalysisConfigReader(string analysisConfigPath) { if (File.Exists(analysisConfigPath)) { try { analysisConfig = new(XDocument.Load(analysisConfigPath)); } catch (Exception e) { throw new InvalidOperationException($"File '{analysisConfigPath}' could not be parsed.", e); } } else { analysisConfig = AnalysisConfig.Empty; } } public string[] UnchangedFiles() => ConfigValue("UnchangedFilesPath") is { } unchangedFilesPath ? File.ReadAllLines(unchangedFilesPath) : []; private string ConfigValue(string id) => analysisConfig.AdditionalConfig.FirstOrDefault(x => x.Id == id)?.Value; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ConfigSetting.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Configuration; /// /// Data class to describe an analysis setting. /// /// /// This class is the counterpart of SonarScanner.MSBuild.Common.ConfigSetting. /// internal sealed record ConfigSetting(string Id, string Value) { public ConfigSetting(XElement element) : this(element.Attribute("Id").Value, element.Attribute("Value")?.Value) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/FilesToAnalyzeProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Configuration; public class FilesToAnalyzeProvider { private readonly IEnumerable allFiles; public FilesToAnalyzeProvider(string filePath) => allFiles = ReadLines(filePath); public IEnumerable FindFiles(string fileName, bool onlyExistingFiles = true) => allFiles.Where(x => FilterByFileName(x, fileName) && (!onlyExistingFiles || File.Exists(x))); public IEnumerable FindFiles(Regex fullPathRegex, bool onlyExistingFiles = true) => allFiles.Where(x => fullPathRegex.SafeIsMatch(x) && (!onlyExistingFiles || File.Exists(x))); private static IEnumerable ReadLines(string filePath) { if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath)) { return Enumerable.Empty(); } try { return File.ReadAllLines(filePath); } catch { // cannot log exception return Enumerable.Empty(); } } private static bool FilterByFileName(string fullPath, string fileName) { try { return Path.GetFileName(fullPath).Equals(fileName, StringComparison.OrdinalIgnoreCase); } catch (ArgumentException) { return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ParameterLoader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.Reflection; namespace SonarAnalyzer.Core.Configuration; internal static class ParameterLoader { /** * At each compilation, parse the configuration file and set the rule parameters on * * There is no caching mechanism because inside the IDE, the configuration file can change when the user: * - changes something inside the configuration file * - loads a different solution in the IDE * * If caching needs to be done in the future, it should take into account: * - diffing the contents of the configuration file * - associating the file with a unique identifier for the build project */ internal static void SetParameterValues(ParametrizedDiagnosticAnalyzer parameteredAnalyzer, SonarLintXmlReader sonarLintXml) { if (!sonarLintXml.ParametrizedRules.Any()) { return; } var propertyParameterPairs = parameteredAnalyzer.GetType() .GetRuntimeProperties() .Select(x => new { Property = x, Descriptor = x.GetCustomAttributes().SingleOrDefault() }) .Where(x => x.Descriptor is not null); var ids = new HashSet(parameteredAnalyzer.SupportedDiagnostics.Select(x => x.Id)); foreach (var propertyParameterPair in propertyParameterPairs) { var parameter = sonarLintXml.ParametrizedRules.FirstOrDefault(x => ids.Contains(x.Key)); var parameterValue = parameter?.Parameters.FirstOrDefault(x => x.Key == propertyParameterPair.Descriptor.Key); if (TryConvertToParameterType(parameterValue?.Value, propertyParameterPair.Descriptor.Type, out var value)) { propertyParameterPair.Property.SetValue(parameteredAnalyzer, value); } } } private static bool TryConvertToParameterType(string parameter, PropertyType type, out object result) { if (parameter is null) { result = null; return false; } switch (type) { case PropertyType.Text: case PropertyType.String: result = parameter; return true; case PropertyType.Integer when int.TryParse(parameter, NumberStyles.None, CultureInfo.InvariantCulture, out var parsedInt): result = parsedInt; return true; case PropertyType.Boolean when bool.TryParse(parameter, out var parsedBool): result = parsedBool; return true; default: result = null; return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ProjectConfig.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Configuration; /// /// Data class to describe a single project configuration for our analyzers. /// /// /// This class is the counterpart of SonarScanner.MSBuild.Common.ProjectConfig, and is used for easy deserialization. /// This class should not be used directly in this codebase. To get configuration properties, use . /// internal class ProjectConfig { public static readonly ProjectConfig Empty = new(nameof(Configuration.ProjectType.Unknown)); /// /// Full path to the SonarQubeAnalysisConfig.xml file. /// public string AnalysisConfigPath { get; set; } /// /// The full name and path of the project file. /// public string ProjectPath { get; set; } /// /// The full name and path of the text file containing all files to analyze. /// public string FilesToAnalyzePath { get; set; } /// /// Root of the project-specific output directory. Analyzer should write protobuf and other files there. /// public string OutPath { get; set; } /// /// The kind of the project. /// public string ProjectType { get; set; } /// /// MSBuild target framework for the current build. /// public string TargetFramework { get; set; } public ProjectConfig(XDocument document) { var xmlns = XNamespace.Get("http://www.sonarsource.com/msbuild/analyzer/2021/1"); if (document.Root.Name != xmlns + "SonarProjectConfig") { throw new InvalidOperationException("Unexpected Root: " + document.Root.Name); } AnalysisConfigPath = Read(nameof(AnalysisConfigPath)); ProjectPath = Read(nameof(ProjectPath)); FilesToAnalyzePath = Read(nameof(FilesToAnalyzePath)); OutPath = Read(nameof(OutPath)); ProjectType = Read(nameof(ProjectType)); TargetFramework = Read(nameof(TargetFramework)); string Read(string name) => document.Root.Element(xmlns + name)?.Value; } private ProjectConfig(string projectType) => ProjectType = projectType; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ProjectConfigReader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Configuration; /// /// This class reads and encapsulates , exposing only the configuration our analyzers need. /// public class ProjectConfigReader { public static readonly ProjectConfigReader Empty = new(null); private readonly ProjectConfig projectConfig; private readonly Lazy projectType; private readonly Lazy filesToAnalyze; private readonly Lazy analysisConfig; public bool IsScannerRun => !string.IsNullOrEmpty(projectConfig.OutPath); public string AnalysisConfigPath => projectConfig.AnalysisConfigPath; public string FilesToAnalyzePath => projectConfig.FilesToAnalyzePath; public string OutPath => projectConfig.OutPath; public string ProjectPath => projectConfig.ProjectPath; public string TargetFramework => projectConfig.TargetFramework; public ProjectType ProjectType => projectType.Value; public FilesToAnalyzeProvider FilesToAnalyze => filesToAnalyze.Value; public AnalysisConfigReader AnalysisConfig => analysisConfig.Value; public ProjectConfigReader(SourceText sonarProjectConfig) { projectConfig = sonarProjectConfig is null ? ProjectConfig.Empty : ReadContent(sonarProjectConfig); projectType = new Lazy(ParseProjectType); filesToAnalyze = new Lazy(() => new FilesToAnalyzeProvider(FilesToAnalyzePath)); analysisConfig = new(() => new(AnalysisConfigPath)); } private static ProjectConfig ReadContent(SourceText sonarProjectConfig) { try { return new(XDocument.Parse(sonarProjectConfig.ToString())); } catch (Exception e) { throw new InvalidOperationException($"{nameof(sonarProjectConfig)} could not be parsed.", e); } } private ProjectType ParseProjectType() => Enum.TryParse(projectConfig.ProjectType, out var result) ? result : ProjectType.Product; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ProjectType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Configuration; /// /// Possible types of project. Note that we expect only the string format to be passed from the Scanner. /// public enum ProjectType { Unknown, Product, Test } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/ProjectTypeCache.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; namespace SonarAnalyzer.Core.Configuration; internal static class ProjectTypeCache { // This list is duplicated in sonar-scanner-msbuild and sonar-security and should be manually synchronized after each change. public /* for testing */ static readonly ISet TestAssemblyNames = new HashSet { "dotMemory.Unit", "Microsoft.VisualStudio.TestPlatform.TestFramework", // Name inside https://nuget.info/packages/MSTest.TestFramework/3.11.0 "Microsoft.VisualStudio.QualityTools.UnitTestFramework", "MSTest.TestFramework", // Name inside https://nuget.info/packages/MSTest.TestFramework/4.0.0 "Machine.Specifications", "nunit.framework", "nunitlite", "TechTalk.SpecFlow", "xunit", // Legacy Xunit (v1.x) "xunit.core", "xunit.v3.core", // Assertion "FluentAssertions", "Shouldly", // Mock "FakeItEasy", "Moq", "NSubstitute", "Rhino.Mocks", "Telerik.JustMock" }; private static readonly ConditionalWeakTable Cache = new ConditionalWeakTable(); // Should only be used by SonarAnalysisContext public static bool IsTest(this Compilation compilation) => // We can't detect references => it's MAIN compilation is not null && Cache.GetValue(compilation, x => new IsTestWrapper(x)).Value; private sealed class IsTestWrapper { public readonly bool Value; public IsTestWrapper(Compilation compilation) => Value = compilation.ReferencedAssemblyNames.Any(x => TestAssemblyNames.Contains(x.Name)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/SonarLintXml.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Serialization; namespace SonarAnalyzer.Core.Configuration; /// /// Data class to represent the SonarLint.xml for our analyzers. /// /// /// This class should not be used in this codebase. To get SonarLint.xml properties, use . /// [XmlRoot(ElementName = "AnalysisInput")] public class SonarLintXml { public static readonly SonarLintXml Empty = new(); [XmlArray("Settings")] [XmlArrayItem("Setting")] public List Settings { get; set; } [XmlArray("Rules")] [XmlArrayItem("Rule")] public List Rules { get; set; } } public class SonarLintXmlRule { [XmlElement("Key")] public string Key { get; set; } [XmlArray("Parameters")] [XmlArrayItem("Parameter")] public List Parameters { get; set; } } public class SonarLintXmlKeyValuePair { public string Key { get; set; } public string Value { get; set; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Configuration/SonarLintXmlReader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Xml.Serialization; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.RegularExpressions; namespace SonarAnalyzer.Core.Configuration; public class SonarLintXmlReader { public static readonly SonarLintXmlReader Empty = new(null); private readonly bool ignoreHeaderCommentsCS; private readonly bool ignoreHeaderCommentsVB; private readonly bool analyzeGeneratedCodeCS; private readonly bool analyzeGeneratedCodeVB; private readonly bool analyzeRazorCodeCS; public string[] Exclusions { get; } public string[] Inclusions { get; } public string[] GlobalExclusions { get; } public string[] TestExclusions { get; } public string[] TestInclusions { get; } public string[] GlobalTestExclusions { get; } public List ParametrizedRules { get; } public SonarLintXmlReader(SourceText sonarLintXmlText) { var sonarLintXml = sonarLintXmlText is null ? SonarLintXml.Empty : ParseContent(sonarLintXmlText); var settings = sonarLintXml.Settings?.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.First().Value) ?? new Dictionary(); Exclusions = ReadArray("sonar.exclusions"); Inclusions = ReadArray("sonar.inclusions"); GlobalExclusions = ReadArray("sonar.global.exclusions"); TestExclusions = ReadArray("sonar.test.exclusions"); TestInclusions = ReadArray("sonar.test.inclusions"); GlobalTestExclusions = ReadArray("sonar.global.test.exclusions"); ParametrizedRules = ReadRuleParameters(); ignoreHeaderCommentsCS = ReadBoolean("sonar.cs.ignoreHeaderComments"); ignoreHeaderCommentsVB = ReadBoolean("sonar.vbnet.ignoreHeaderComments"); analyzeGeneratedCodeCS = ReadBoolean("sonar.cs.analyzeGeneratedCode"); analyzeGeneratedCodeVB = ReadBoolean("sonar.vbnet.analyzeGeneratedCode"); analyzeRazorCodeCS = ReadBoolean("sonar.cs.analyzeRazorCode", true); string[] ReadArray(string key) => settings.GetValueOrDefault(key) is { } value && !string.IsNullOrEmpty(value) ? value.Split(',') : Array.Empty(); bool ReadBoolean(string key, bool defaultValue = false) => settings.TryGetValue(key, out var value) ? bool.TryParse(value, out var boolValue) && boolValue : defaultValue; List ReadRuleParameters() => sonarLintXml.Rules?.Where(x => x.Parameters.Any()).ToList() ?? new(); } public bool IgnoreHeaderComments(string language) => language switch { LanguageNames.CSharp => ignoreHeaderCommentsCS, LanguageNames.VisualBasic => ignoreHeaderCommentsVB, _ => throw new UnexpectedLanguageException(language) }; public bool AnalyzeGeneratedCode(string language) => language switch { LanguageNames.CSharp => analyzeGeneratedCodeCS, LanguageNames.VisualBasic => analyzeGeneratedCodeVB, _ => throw new UnexpectedLanguageException(language) }; public bool AnalyzeRazorCode(string language) => language switch { LanguageNames.CSharp => analyzeRazorCodeCS, LanguageNames.VisualBasic => false, _ => throw new UnexpectedLanguageException(language) }; public bool IsFileIncluded(string filePath, bool isTestProject) => isTestProject ? IsFileIncluded(TestInclusions, TestExclusions, GlobalTestExclusions, filePath) : IsFileIncluded(Inclusions, Exclusions, GlobalExclusions, filePath); private static bool IsFileIncluded(string[] inclusions, string[] exclusions, string[] globalExclusions, string filePath) => IsIncluded(inclusions, filePath) && !IsExcluded(exclusions, filePath) && !IsExcluded(globalExclusions, filePath); private static bool IsIncluded(string[] inclusions, string filePath) => inclusions.Length == 0 || inclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath, true)); private static bool IsExcluded(string[] exclusions, string filePath) => exclusions.Any(x => WildcardPatternMatcher.IsMatch(x, filePath, false)); private static SonarLintXml ParseContent(SourceText sonarLintXml) { try { var serializer = new XmlSerializer(typeof(SonarLintXml)); using var sr = new StringReader(sonarLintXml.ToString()); return (SonarLintXml)serializer.Deserialize(sr); } catch { return SonarLintXml.Empty; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/AnalyzerOptionsExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Roslyn.Utilities; using SonarAnalyzer.Core.Configuration; namespace SonarAnalyzer.Core.Extensions; public static class AnalyzerOptionsExtensions { private static readonly SourceTextValueProvider SonarLintXmlProvider = new(x => new SonarLintXmlReader(x)); public static SonarLintXmlReader SonarLintXml(this AnalyzerOptions options, SonarAnalysisContext context) { if (options.SonarLintXml() is { } sonarLintXml) { return sonarLintXml.GetText() is { } sourceText && context.TryGetValue(sourceText, SonarLintXmlProvider, out var sonarLintXmlReader) ? sonarLintXmlReader : throw new InvalidOperationException($"File '{Path.GetFileName(sonarLintXml.Path)}' has been added as an AdditionalFile but could not be read and parsed."); } else { return SonarLintXmlReader.Empty; } } public static AdditionalText SonarLintXml(this AnalyzerOptions options) => options.AdditionalFile("SonarLint.xml"); public static AdditionalText SonarProjectConfig(this AnalyzerOptions options) => options.AdditionalFile("SonarProjectConfig.xml"); public static AdditionalText ProjectOutFolderPath(this AnalyzerOptions options) => options.AdditionalFile("ProjectOutFolderPath.txt"); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7440", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private static AdditionalText AdditionalFile(this AnalyzerOptions options, string fileName) { // HotPath: This code path needs to be allocation free. Don't use Linq. foreach (var additionalText in options.AdditionalFiles) // Uses the struct enumerator of ImmutableArray { // Don't use Path.GetFilename. It allocates a string. if (additionalText.Path is { } path && path.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) { // The character before the filename (if there is a character) must be a directory separator var separatorPosition = path.Length - fileName.Length - 1; if (separatorPosition < 0 || IsDirectorySeparator(path[separatorPosition])) { return additionalText; } } } return null; static bool IsDirectorySeparator(char c) => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/CompilationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; internal static class CompilationExtensions { public static INamedTypeSymbol GetTypeByMetadataName(this Compilation compilation, KnownType knownType) => compilation.GetTypeByMetadataName(knownType.MetadataName); public static IMethodSymbol SpecialTypeMethod(this Compilation compilation, SpecialType type, string methodName) => (IMethodSymbol)compilation.GetSpecialType(type).GetMembers(methodName).SingleOrDefault(); public static bool IsNetFrameworkTarget(this Compilation compilation) => // There's no direct way of checking compilation target framework yet (09/2020). // See https://github.com/dotnet/roslyn/issues/3798 compilation.ObjectType.ContainingAssembly.Name == "mscorlib"; public static bool ReferencesAny(this Compilation compilation, params KnownAssembly[] assemblies) => assemblies.Any() ? Array.Exists(assemblies, x => compilation.References(x)) : throw new ArgumentException("Assemblies argument needs to be non-empty"); public static bool ReferencesAll(this Compilation compilation, params KnownAssembly[] assemblies) => Array.TrueForAll(assemblies, x => compilation.References(x)); public static bool References(this Compilation compilation, KnownAssembly assembly) => assembly.IsReferencedBy(compilation); public static bool IsMemberAvailable(this Compilation compilation, KnownType type, string memberName, Func memberCheck = null) where TMemberType : ISymbol { var containingType = compilation.GetTypeByMetadataName(type); if (containingType is null) { return false; } var memberSymbols = containingType.GetMembers(memberName).OfType(); return memberCheck is null ? memberSymbols.Any() : memberSymbols.Any(memberCheck); } public static bool ReferencesNetCoreControllers(this Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Mvc_Controller) is not null || compilation.GetTypeByMetadataName(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase) is not null; public static bool ReferencesNetFrameworkControllers(this Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.System_Web_Mvc_Controller) is not null; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/DiagnosticDescriptorExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; public static class DiagnosticDescriptorExtensions { public static bool IsEnabled(this DiagnosticDescriptor descriptor, SonarSyntaxNodeReportingContext context) { if (context.HasMatchingScope(descriptor)) { // Roslyn calls an analyzer if any of the diagnostics is active. We need to remove deactivated rules from execution to improve overall performance. // This is a reproduction of Roslyn activation logic: // https://github.com/dotnet/roslyn/blob/0368609e1467563247e9b5e4e3fe8bff533d59b6/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs#L1316-L1327 var options = CompilationOptionsWrapper.FromObject(context.Compilation.Options).SyntaxTreeOptionsProvider; var severity = options.Instance is not null && (options.TryGetDiagnosticValue(context.Tree, descriptor.Id, default, out var severityFromOptions) || options.TryGetGlobalDiagnosticValue(descriptor.Id, default, out severityFromOptions)) ? severityFromOptions // .editorconfig for a specific tree : descriptor.GetEffectiveSeverity(context.Compilation.Options); // RuleSet file or .globalconfig return severity switch { ReportDiagnostic.Default => descriptor.IsEnabledByDefault, ReportDiagnostic.Suppress => false, _ => true }; } else { return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/DictionaryExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; public static class DictionaryExtensions { public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key) => dictionary.GetValueOrDefault(key, default); public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key, TValue defaultValue) => dictionary.TryGetValue(key, out var result) ? result : defaultValue; public static TValue GetOrAdd(this IDictionary dictionary, TKey key, Func factory) { if (!dictionary.TryGetValue(key, out var value)) { value = factory(key); dictionary.Add(key, value); } return value; } public static bool DictionaryEquals(this IDictionary dict1, IDictionary dict2) => dict1 == dict2 || (EqualityComparer.Default is var valueComparer && dict1 is not null && dict2 is not null && dict1.Count == dict2.Count && dict1.All(x => dict2.TryGetValue(x.Key, out var value2) && valueComparer.Equals(x.Value, value2))); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/HashSetExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; public static class HashSetExtensions { public static void AddRange(this HashSet hashset, IEnumerable values) { foreach (var value in values) { hashset.Add(value); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/IAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using System.Runtime.CompilerServices; using Roslyn.Utilities; using SonarAnalyzer.Core.Configuration; using static SonarAnalyzer.Core.Analyzers.DiagnosticDescriptorFactory; namespace SonarAnalyzer.Core.Extensions; public static class IAnalysisContextExtensions { private const string RazorGeneratedFileSuffix = "_razor.g.cs"; private static readonly ConditionalWeakTable> UnchangedFilesCache = new(); private static readonly ConditionalWeakTable> FileInclusionCache = new(); /// /// Reads the properties from the SonarLint.xml file and caches the result for the scope of this analysis. /// public static SonarLintXmlReader SonarLintXml(this T context) where T : IAnalysisContext => // Don't change to (this IAnalysisContext context) because this would cause boxing context.Options.SonarLintXml(context.AnalysisContext); public static bool IsRazorAnalysisEnabled(this T context) where T : IAnalysisContext => // Don't change to (this IAnalysisContext context) because this would cause boxing context.AnalysisContext.IsRazorAnalysisEnabled(context.Options, context.Compilation); public static bool IsTestProject(this T context) where T : IAnalysisContext { var projectType = context.ProjectConfiguration().ProjectType; return projectType == ProjectType.Unknown ? context.Compilation.IsTest() // SonarLint, NuGet or Scanner <= 5.0 : projectType == ProjectType.Test; // Scanner >= 5.1 does authoritative decision that we follow } /// /// Reads configuration from SonarProjectConfig.xml file and caches the result for scope of this analysis. /// public static ProjectConfigReader ProjectConfiguration(this T context) where T : IAnalysisContext => // Don't change to (this IAnalysisContext context) because this would cause boxing context.AnalysisContext.ProjectConfiguration(context.Options); public static bool HasMatchingScope(this T context, ImmutableArray descriptors) where T : IAnalysisContext // Don't change to (this IAnalysisContext context) because this would cause boxing { // Performance: Don't use descriptors.Any(HasMatchingScope), the delegate creation allocates too much memory. https://github.com/SonarSource/sonar-dotnet/issues/7438 foreach (var descriptor in descriptors) { if (context.HasMatchingScope(descriptor)) { return true; } } return false; } public static bool HasMatchingScope(this T context, DiagnosticDescriptor descriptor) where T : IAnalysisContext // Don't change to (this IAnalysisContext context) because this would cause boxing { // MMF-2297: Test Code as 1st Class Citizen is not ready on server side yet. // ScannerRun: Only utility rules and rules with TEST-ONLY scope are executed for test projects for now. // SonarLint & Standalone NuGet & Internal styling rules Txxxx: Respect the scope as before. return context.IsTestProject() ? ContainsTag(TestSourceScopeTag) && !(descriptor.Id.StartsWith("S") && context.ProjectConfiguration().IsScannerRun && ContainsTag(MainSourceScopeTag) && !ContainsTag(UtilityTag)) : ContainsTag(MainSourceScopeTag); bool ContainsTag(string tag) => descriptor.CustomTags.Contains(tag); } /// Tree to decide on. Can be null for Symbol-based and Compilation-based scenarios. And we want to analyze those too. /// When set, generated trees are analyzed only when language-specific 'analyzeGeneratedCode' configuration property is also set. public static bool ShouldAnalyzeTree(this T context, SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) where T : IAnalysisContext => // Don't change to (this IAnalysisContext context) because this would cause boxing context.SonarLintXml() is var sonarLintXml && (generatedCodeRecognizer is null || sonarLintXml.AnalyzeGeneratedCode(context.Compilation.Language) || !tree.IsConsideredGenerated(generatedCodeRecognizer, context.IsRazorAnalysisEnabled())) && (tree is null || (!context.IsUnchanged(tree) && !context.IsExcluded(sonarLintXml, tree.FilePath))); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7439", AllowCaptures = true, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] public static bool IsUnchanged(this T context, SyntaxTree tree) where T : IAnalysisContext // Don't change to (this IAnalysisContext context) because this would cause boxing { // Hot path: Use TryGetValue to prevent the allocation of the GetValue factory delegate in the common case var unchangedFiles = UnchangedFilesCache.TryGetValue(context.Compilation, out var unchangedFilesFromCache) ? unchangedFilesFromCache : UnchangedFilesCache.GetValue(context.Compilation, _ => CreateUnchangedFilesHashSet(context.ProjectConfiguration())); return unchangedFiles.Contains(MapFilePath(tree)); } [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7439", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] private static bool IsExcluded(this T context, SonarLintXmlReader sonarLintXml, string filePath) where T : IAnalysisContext // Don't change to (this IAnalysisContext context) because this would cause boxing { // If ProjectType is not 'Unknown' it means we are in S4NET context and all files are analyzed. // If ProjectType is 'Unknown' then we are in SonarLint or NuGet context and we need to check if the file has been excluded from analysis through SonarLint.xml. if (context.ProjectConfiguration().ProjectType == ProjectType.Unknown) { var fileInclusionCache = FileInclusionCache.GetOrCreateValue(context.Compilation); // Hot path: Don't use GetOrAdd with the value factory parameter. It allocates a delegate which causes GC pressure. var isIncluded = fileInclusionCache.TryGetValue(filePath, out var result) ? result : fileInclusionCache.GetOrAdd(filePath, sonarLintXml.IsFileIncluded(filePath, context.IsTestProject())); return !isIncluded; } return false; } private static ImmutableHashSet CreateUnchangedFilesHashSet(ProjectConfigReader config) => ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase, config.AnalysisConfig?.UnchangedFiles() ?? []); private static string MapFilePath(SyntaxTree tree) => // Currently only .razor file hashes are stored in the cache. // // For a file like `Pages\Component.razor`, the compiler generated path has the following pattern: // Microsoft.NET.Sdk.Razor.SourceGenerators\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\Pages_Component_razor.g.cs // In order to avoid rebuilding the original file path we have to read it from the pragma directive. // // This should be updated for .cshtml files as well once https://github.com/SonarSource/sonar-dotnet/issues/8032 is done. tree.FilePath.EndsWith(RazorGeneratedFileSuffix, StringComparison.OrdinalIgnoreCase) ? tree.GetOriginalFilePath() : tree.FilePath; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/ICompilationReportExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext; // Don't change the this parameter to (this IAnalysisContext context) because it would cause boxing public static class ICompilationReportExtensions { public static void ReportIssue(this T context, GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, SyntaxNode locationSyntax, params string[] messageArgs) where T : ICompilationReport => context.ReportIssue(generatedCodeRecognizer, rule, locationSyntax.GetLocation(), messageArgs); public static void ReportIssue(this T context, GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, SyntaxToken locationToken, params string[] messageArgs) where T : ICompilationReport => context.ReportIssue(generatedCodeRecognizer, rule, locationToken.GetLocation(), messageArgs); public static void ReportIssue(this T context, GeneratedCodeRecognizer generatedCodeRecognizer, DiagnosticDescriptor rule, Location location, params string[] messageArgs) where T : ICompilationReport => context.ReportIssue(generatedCodeRecognizer, rule, location, [], messageArgs); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/IEnumerableExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.Core.Extensions; public static class IEnumerableExtensions { public static HashSet ToHashSet(this IEnumerable enumerable, IEqualityComparer equalityComparer = null) { enumerable ??= []; return equalityComparer is null ? new(enumerable) : new(enumerable, equalityComparer); } /// /// Compares each element between two collections. The elements needs in the same order to be considered equal. /// public static bool Equals(this IEnumerable first, IEnumerable second, Func predicate) { var enum1 = first.GetEnumerator(); var enum2 = second.GetEnumerator(); var enum1HasNext = enum1.MoveNext(); var enum2HasNext = enum2.MoveNext(); while (enum1HasNext && enum2HasNext) { if (!predicate(enum1.Current, enum2.Current)) { return false; } enum1HasNext = enum1.MoveNext(); enum2HasNext = enum2.MoveNext(); } return enum1HasNext == enum2HasNext; } public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : class => enumerable.Where(x => x is not null); public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : struct => enumerable.Where(x => x.HasValue).Select(x => x.Value); public static bool IsEmpty(this IEnumerable enumerable) => !enumerable.Any(); /// /// Applies a specified function to the corresponding elements of two sequences, /// producing a sequence of the results. If the collections have different length /// default(T) will be passed in the operation function for the corresponding items that /// do not exist. /// /// The type of the elements of the first input sequence. /// The type of the elements of the second input sequence. /// The type of the elements of the result sequence. /// The first sequence to merge. /// The second sequence to merge. /// A function that specifies how to merge the elements from the two sequences. /// An System.Collections.Generic.IEnumerable`1 that contains merged elements of /// two input sequences. public static IEnumerable Merge(this IEnumerable first, IEnumerable second, Func operation) { using var iter1 = first.GetEnumerator(); using var iter2 = second.GetEnumerator(); while (iter1.MoveNext()) { yield return operation(iter1.Current, iter2.MoveNext() ? iter2.Current : default); } while (iter2.MoveNext()) { yield return operation(default, iter2.Current); } } public static int IndexOf(this IEnumerable enumerable, Func predicate) => enumerable.Select((item, index) => new { item, index }).FirstOrDefault(x => predicate(x.item))?.index ?? -1; /// /// This is as extension. It concatenates the members of the collection using the specified between each member. /// is used to convert to for concatenation. /// public static string JoinStr(this IEnumerable enumerable, string separator, Func selector) => string.Join(separator, enumerable.Select(x => selector(x))); /// /// This is as extension. It concatenates the members of the collection using the specified between each member. /// is used to convert to for concatenation. /// public static string JoinStr(this IEnumerable enumerable, string separator, Func selector) => string.Join(separator, enumerable.Select(x => selector(x))); /// /// This is as extension. It concatenates the members of the collection using the specified between each member. /// public static string JoinStr(this IEnumerable enumerable, string separator) => JoinStr(enumerable, separator, x => x); /// /// This is as extension. It concatenates the members of the collection using the specified between each member. /// public static string JoinStr(this IEnumerable enumerable, string separator) => JoinStr(enumerable, separator, x => x); /// /// Concatenates the members of a collection using the specified between each member. /// Any whitespace or null member of the collection will be ignored. /// public static string JoinNonEmpty(this IEnumerable enumerable, string separator) => string.Join(separator, enumerable.Where(x => !string.IsNullOrWhiteSpace(x))); /// /// Concatenates the members of using "and" as the last separator and using a /// serial comma. /// /// [a, b, c] => "a, b, and c" /// [a, b] => "a and b" /// [a] => "a" /// [] or null => "" /// /// public static string JoinAnd(this IEnumerable values) => values.JoinWith("and"); /// /// Concatenates the members of using "or" as the last separator and using a /// serial comma. /// /// [a, b, c] => "a, b, or c" /// [a, b] => "a or b" /// [a] => "a" /// [] or null => "" /// /// public static string JoinOr(this IEnumerable values) => values.JoinWith("or"); public static IEnumerable ToSecondary(this IEnumerable locations, string message = null, params string[] messageArgs) => locations.Select(x => x.ToSecondary(message, messageArgs)); public static IEnumerable ToSecondaryLocations(this IEnumerable nodes, string message = null, params string[] messageArgs) => nodes.Select(x => x.ToSecondaryLocation(message, messageArgs)); public static IEnumerable ToSecondaryLocations(this IEnumerable nodes, string message = null, params string[] messageArgs) => nodes.Select(x => x.ToSecondaryLocation(message, messageArgs)); public static string ToSentence(this IEnumerable words, bool quoteWords = false) { var wordCollection = words as ICollection ?? words.ToList(); var singleQuoteOrBlank = quoteWords ? "'" : string.Empty; return wordCollection.Count switch { 0 => null, 1 => string.Concat(singleQuoteOrBlank, wordCollection.First(), singleQuoteOrBlank), _ => new StringBuilder(singleQuoteOrBlank) .Append(string.Join($"{singleQuoteOrBlank}, {singleQuoteOrBlank}", wordCollection.Take(wordCollection.Count - 1))) .Append(singleQuoteOrBlank) .Append(" and ") .Append(singleQuoteOrBlank) .Append(wordCollection.Last()) .Append(singleQuoteOrBlank) .ToString(), }; } private static string JoinWith(this IEnumerable values, string with) => values?.Select(x => x?.ToString()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList() switch { { Count: > 2 } serial => $"{serial.Take(serial.Count - 1).JoinStr(", ")}, {with} {serial[serial.Count - 1]}", { Count: 2 } pair => $"{pair[0]} {with} {pair[1]}", { Count: 1 } single => single[0], _ => string.Empty, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/ITreeReportExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; // Don't change the this parameter to (this IAnalysisContext context) because it would cause boxing public static class ITreeReportExtensions { public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxNode locationSyntax, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, locationSyntax.GetLocation(), messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxNode locationSyntax, ImmutableDictionary properties, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, locationSyntax.GetLocation(), properties, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxNode primaryLocationSyntax, IEnumerable secondaryLocations, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, primaryLocationSyntax.GetLocation(), secondaryLocations, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxToken locationToken, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, locationToken.GetLocation(), messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxToken locationToken, ImmutableDictionary properties, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, locationToken.GetLocation(), properties, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, SyntaxToken primaryLocationToken, IEnumerable secondaryLocations, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, primaryLocationToken.GetLocation(), secondaryLocations, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, Location location, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, location, [], ImmutableDictionary.Empty, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, Location location, ImmutableDictionary properties, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, location, [], properties, messageArgs); public static void ReportIssue(this T context, DiagnosticDescriptor rule, Location primaryLocation, IEnumerable secondaryLocations, params string[] messageArgs) where T : ITreeReport => context.ReportIssue(rule, primaryLocation, secondaryLocations, ImmutableDictionary.Empty, messageArgs); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/IXmlLineInfoExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml; using System.Xml.Linq; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Extensions; internal static class IXmlLineInfoExtensions { public static Location CreateLocation(this IXmlLineInfo startPos, string path, XName name, XElement closestElement) { // IXmlLineInfo is 1-based, whereas Roslyn is zero-based if (startPos.HasLineInfo()) { var prefix = closestElement.GetPrefixOfNamespace(name.Namespace); var length = name.LocalName.Length; if (prefix is not null) { length += prefix.Length + 1; // prefix:LocalName - +1 for ':' } var start = new LinePosition(startPos.LineNumber - 1, startPos.LinePosition - 1); var end = new LinePosition(startPos.LineNumber - 1, startPos.LinePosition - 1 + length); // We cannot properly compute the TextSpan as we only have the line information. // It should not break the analysis nor the reporting as we still have the LinePositionSpan. // Manual test has shown that the Sarif contains the correct information with only the LinePositionSpan. return Location.Create(path, new TextSpan(start.Line, length), new LinePositionSpan(start, end)); } else { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/RegexExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Extensions; public static class RegexExtensions { private static readonly MatchCollection EmptyMatchCollection = Regex.Matches(string.Empty, "a", RegexOptions.None, Constants.DefaultRegexTimeout); /// /// Matches the input to the regex. Returns in case of an . /// public static Match SafeMatch(this Regex regex, string input) { try { return regex.Match(input); } catch (RegexMatchTimeoutException) { return Match.Empty; } } /// /// Matches the input to the regex. Returns in case of an . /// public static bool SafeIsMatch(this Regex regex, string input) => regex.SafeIsMatch(input, false); /// /// Matches the input to the regex. Returns in case of an . /// public static bool SafeIsMatch(this Regex regex, string input, bool timeoutFallback) { try { return regex.IsMatch(input); } catch (RegexMatchTimeoutException) { return timeoutFallback; } } /// /// Matches the input to the regex. Returns an empty in case of an . /// public static MatchCollection SafeMatches(this Regex regex, string input) { try { var res = regex.Matches(input); _ = res.Count; // MatchCollection is lazy. Accessing "Count" executes the regex and caches the result return res; } catch (RegexMatchTimeoutException) { return EmptyMatchCollection; } } /// /// Replaces with in . /// Returns the original in case of an . /// public static string SafeReplace(this Regex regex, string input, string replacement) { try { return regex.Replace(input, replacement); } catch (RegexMatchTimeoutException) { return input; } } } public static class SafeRegex { /// /// Matches the input to the regex. Returns (false by default) in case of an . /// public static bool IsMatch(string input, string pattern, bool timeoutFallback = false) => IsMatch(input, pattern, RegexOptions.None, timeoutFallback); /// public static bool IsMatch(string input, string pattern, RegexOptions options, bool timeoutFallback = false) => IsMatch(input, pattern, options, Constants.DefaultRegexTimeout, timeoutFallback); /// public static bool IsMatch(string input, string pattern, RegexOptions options, TimeSpan matchTimeout, bool timeoutFallback = false) { try { return Regex.IsMatch(input, pattern, options, matchTimeout); } catch (RegexMatchTimeoutException) { return timeoutFallback; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/SonarAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.Configuration; namespace SonarAnalyzer.Core.Extensions; public static class SonarAnalysisContextExtensions { private static readonly SourceTextValueProvider ProjectConfigProvider = new(x => new ProjectConfigReader(x)); public static ProjectConfigReader ProjectConfiguration(this SonarAnalysisContext context, AnalyzerOptions options) { if (options.SonarProjectConfig() is { } sonarProjectConfig) { return sonarProjectConfig.GetText() is { } sourceText // TryGetValue catches all exceptions from SourceTextValueProvider and returns false when exception is thrown && context.TryGetValue(sourceText, ProjectConfigProvider, out var cachedProjectConfigReader) ? cachedProjectConfigReader : throw new InvalidOperationException($"File '{Path.GetFileName(sonarProjectConfig.Path)}' has been added as an AdditionalFile but could not be read and parsed."); } else { return ProjectConfigReader.Empty; } } public static bool IsRazorAnalysisEnabled(this SonarAnalysisContext context, AnalyzerOptions options, Compilation compilation) => options.SonarLintXml(context).AnalyzeRazorCode(compilation.Language); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/StackExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Extensions; public static class StackExtensions { public static bool TryPop(this Stack stack, out T result) { if (stack.Count > 0) { result = stack.Pop(); return true; } result = default; return false; } public static void Push(this Stack stack, IEnumerable items) { foreach (var item in items) { stack.Push(item); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/StringExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Extensions; public static class StringExtensions { public static XDocument ParseXDocument(this string text) { try { return XDocument.Parse(text, LoadOptions.SetLineInfo); } catch { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/XAttributeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Extensions; public static class XAttributeExtensions { public static Location CreateLocation(this XAttribute attribute, string path) => attribute.CreateLocation(path, attribute.Name, attribute.Parent); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Extensions/XElementExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Extensions; public static class XElementExtensions { public static XAttribute GetAttributeIfBoolValueIs(this XElement element, string attributeName, bool value) => element.Attribute(attributeName) is { } attribute && attribute.Value.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) ? attribute : null; public static Location CreateLocation(this XElement element, string path) => element.CreateLocation(path, element.Name, element); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Facade/ILanguageFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Facade; public interface ILanguageFacade { AssignmentFinder AssignmentFinder { get; } StringComparison NameComparison { get; } StringComparer NameComparer { get; } GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } IExpressionNumericConverter ExpressionNumericConverter { get; } DiagnosticDescriptor CreateDescriptor(string id, string messageFormat, bool? isEnabledByDefault = null, bool fadeOutCode = false); object FindConstantValue(SemanticModel model, SyntaxNode node); IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, IMethodSymbol methodSymbol); IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, SemanticModel model); string GetName(SyntaxNode expression); } public interface ILanguageFacade : ILanguageFacade where TSyntaxKind : struct { SyntaxFacade Syntax { get; } ISyntaxKindFacade SyntaxKind { get; } ITrackerFacade Tracker { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Facade/ISyntaxKindFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Facade; public interface ISyntaxKindFacade where TSyntaxKind : struct { abstract TSyntaxKind Attribute { get; } abstract TSyntaxKind AttributeArgument { get; } abstract TSyntaxKind[] CastExpressions { get; } abstract TSyntaxKind ClassDeclaration { get; } abstract TSyntaxKind[] ClassAndRecordDeclarations { get; } abstract TSyntaxKind[] ClassAndModuleDeclarations { get; } abstract TSyntaxKind[] ComparisonKinds { get; } abstract TSyntaxKind ConstructorDeclaration { get; } abstract TSyntaxKind[] DefaultExpressions { get; } abstract TSyntaxKind EndOfLineTrivia { get; } abstract TSyntaxKind EnumDeclaration { get; } abstract TSyntaxKind FieldDeclaration { get; } abstract TSyntaxKind IdentifierName { get; } abstract TSyntaxKind IdentifierToken { get; } abstract TSyntaxKind InvocationExpression { get; } abstract TSyntaxKind InterpolatedStringExpression { get; } abstract TSyntaxKind LeftShiftAssignmentStatement { get; } abstract TSyntaxKind LeftShiftExpression { get; } abstract TSyntaxKind LocalDeclaration { get; } abstract TSyntaxKind[] MethodDeclarations { get; } abstract TSyntaxKind[] ObjectCreationExpressions { get; } abstract TSyntaxKind Parameter { get; } abstract TSyntaxKind RefKeyword { get; } abstract TSyntaxKind RightShiftExpression { get; } abstract TSyntaxKind RightShiftAssignmentStatement { get; } abstract TSyntaxKind ParameterList { get; } abstract TSyntaxKind ReturnStatement { get; } abstract TSyntaxKind SimpleAssignment { get; } abstract TSyntaxKind SimpleCommentTrivia { get; } abstract TSyntaxKind SimpleMemberAccessExpression { get; } abstract TSyntaxKind[] StringLiteralExpressions { get; } abstract TSyntaxKind StructDeclaration { get; } abstract TSyntaxKind SubtractExpression { get; } abstract TSyntaxKind[] TypeDeclaration { get; } abstract TSyntaxKind VariableDeclarator { get; } abstract TSyntaxKind[] LocalDeclarationKinds { get; } abstract TSyntaxKind WhitespaceTrivia { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Facade/ITrackerFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Facade; public interface ITrackerFacade where TSyntaxKind : struct { ArgumentTracker Argument { get; } BaseTypeTracker BaseType { get; } ElementAccessTracker ElementAccess { get; } FieldAccessTracker FieldAccess { get; } InvocationTracker Invocation { get; } MethodDeclarationTracker MethodDeclaration { get; } ObjectCreationTracker ObjectCreation { get; } PropertyAccessTracker PropertyAccess { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Facade/SyntaxFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Facade; public abstract class SyntaxFacade where TSyntaxKind : struct { public abstract bool AreEquivalent(SyntaxNode firstNode, SyntaxNode secondNode); public abstract IEnumerable ArgumentExpressions(SyntaxNode node); public abstract int? ArgumentIndex(SyntaxNode argument); public abstract IReadOnlyList ArgumentList(SyntaxNode node); public abstract SyntaxToken? ArgumentNameColon(SyntaxNode argument); public abstract SyntaxNode AssignmentLeft(SyntaxNode assignment); public abstract SyntaxNode AssignmentRight(SyntaxNode assignment); public abstract ImmutableArray AssignmentTargets(SyntaxNode assignment); public abstract SyntaxNode BinaryExpressionLeft(SyntaxNode binary); public abstract SyntaxNode BinaryExpressionRight(SyntaxNode binary); public abstract SyntaxNode CastType(SyntaxNode cast); public abstract SyntaxNode CastExpression(SyntaxNode cast); public abstract ComparisonKind ComparisonKind(SyntaxNode node); public abstract IEnumerable EnumMembers(SyntaxNode @enum); public abstract ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node); public abstract bool HasExactlyNArguments(SyntaxNode invocation, int count); public abstract SyntaxToken? InvocationIdentifier(SyntaxNode invocation); public abstract bool IsAnyKind(SyntaxNode node, ISet syntaxKinds); [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public abstract bool IsAnyKind(SyntaxNode node, params TSyntaxKind[] syntaxKinds); public abstract bool IsAnyKind(SyntaxTrivia trivia, ISet syntaxKinds); public abstract bool IsInExpressionTree(SemanticModel model, SyntaxNode node); public abstract bool IsKind(SyntaxNode node, TSyntaxKind kind); public abstract bool IsKind(SyntaxToken token, TSyntaxKind kind); public abstract bool IsKind(SyntaxTrivia trivia, TSyntaxKind kind); public abstract bool IsKnownAttributeType(SemanticModel model, SyntaxNode attribute, KnownType knownType); public abstract bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string name, KnownType knownType, SemanticModel model); public abstract bool IsNullLiteral(SyntaxNode node); public abstract bool IsPartOfBinaryNegationOrCondition(SyntaxNode node); public abstract bool IsStatic(SyntaxNode node); public abstract bool IsWrittenTo(SyntaxNode expression, SemanticModel model, CancellationToken cancel); public abstract TSyntaxKind Kind(SyntaxNode node); public abstract string LiteralText(SyntaxNode literal); public abstract ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node); public abstract TSyntaxKind[] ModifierKinds(SyntaxNode node); public abstract SyntaxNode NodeExpression(SyntaxNode node); public abstract SyntaxToken? NodeIdentifier(SyntaxNode node); public abstract SyntaxToken? ObjectCreationTypeIdentifier(SyntaxNode objectCreation); public abstract SyntaxNode RemoveConditionalAccess(SyntaxNode node); public abstract SyntaxNode RemoveParentheses(SyntaxNode node); public abstract string StringValue(SyntaxNode node, SemanticModel model); public abstract string InterpolatedTextValue(SyntaxNode node, SemanticModel model); public abstract Pair Operands(SyntaxNode invocation); public abstract SyntaxNode ParseExpression(string expression); protected static T Cast(SyntaxNode node) where T : SyntaxNode => node as T ?? throw new InvalidCastException($"A {node.GetType().Name} node can not be cast to a {typeof(T).Name} node."); protected static Exception InvalidOperation(SyntaxNode node, string method) => new InvalidOperationException($"{method} can not be performed on a {node.GetType().Name} node."); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/JsonException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Json; [Serializable] public sealed class JsonException : Exception { public JsonException(string message, LinePosition position) : base($"{message} at line {position.Line + 1} position {position.Character + 1}") { } [ExcludeFromCodeCoverage] private JsonException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/JsonNode.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Json; public sealed class JsonNode : IEnumerable { public Kind Kind { get; } private readonly List list; private readonly Dictionary map; private readonly object value; public LinePosition Start { get; } public LinePosition End { get; private set; } public JsonNode this[int index] => Kind == Kind.List ? list[index] : throw InvalidKind(); public JsonNode this[string key] => Kind == Kind.Object ? map[key] : throw InvalidKind(); public IEnumerable Keys => Kind == Kind.Object ? map.Keys : throw InvalidKind(); public object Value => Kind == Kind.Value ? value : throw InvalidKind(); public int Count => Kind switch { Kind.Object => map.Count, Kind.List => list.Count, _ => throw InvalidKind() }; public JsonNode(LinePosition start, LinePosition end, object value) { Kind = Kind.Value; Start = start; End = end; this.value = value; } public JsonNode(LinePosition start, Kind kind) { Kind = kind; Start = start; switch (kind) { case Kind.List: list = new List(); break; case Kind.Object: map = new Dictionary(); break; default: throw InvalidKind(); } } public static JsonNode FromString(string json) { try { return new SyntaxAnalyzer(json).Parse(); } catch (JsonException) { return null; // Malformed Json } } public Location ToLocation(string path) { var length = Value.ToString().Length; var start = new LinePosition(Start.Line, Start.Character + 1); var end = new LinePosition(End.Line, End.Character - 1); return Location.Create(path, new TextSpan(start.Line, length), new LinePositionSpan(start, end)); } public void UpdateEnd(LinePosition end) => End = NotInitializedEnd() ? end : throw new InvalidOperationException("End position is already set"); public void Add(JsonNode value) { if (Kind == Kind.List) { list.Add(value); } else { throw InvalidKind(); } } public void Add(string key, JsonNode value) => map[key] = Kind == Kind.Object ? value : throw InvalidKind(); public bool ContainsKey(string key) => Kind == Kind.Object ? map.ContainsKey(key) : throw InvalidKind(); public bool TryGetPropertyNode(string key, out JsonNode node) => map.TryGetValue(key, out node); public IEnumerator GetEnumerator() => Kind == Kind.List ? list.GetEnumerator() : throw InvalidKind(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private InvalidOperationException InvalidKind() => new($"Operation is not valid. Json kind is {Kind}"); private bool NotInitializedEnd() => End == LinePosition.Zero; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/JsonSerializer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.Text; namespace SonarAnalyzer.Core.Json; public static class JsonSerializer { public static string Serialize(object value) => SerializeLines("{", "}", value.GetType().GetProperties().Where(x => x.GetIndexParameters().Length == 0).Select(x => $""" "{SerializeKey(x.Name)}": {SerializeValue(x.GetValue(value))}""")); private static string SerializeKey(string key) => char.ToLowerInvariant(key[0]) + key.Substring(1); private static string SerializeValue(object original) => original switch { null => "null", bool value => value.ToString().ToLower(), sbyte value => value.ToString(), byte value => value.ToString(), short value => value.ToString(), ushort value => value.ToString(), int value => value.ToString(), uint value => value.ToString(), long value => value.ToString(), ulong value => value.ToString(), float value => value.ToString(CultureInfo.InvariantCulture), double value => value.ToString(CultureInfo.InvariantCulture), decimal value => value.ToString(CultureInfo.InvariantCulture), string value => SerializeValue(value), Enum => SerializeValue(original.ToString()), IEnumerable values => $"[{values.JoinStr(", ", SerializeValue)}]", IEnumerable> values => SerializePairs(values, SerializeValue), IEnumerable> values => SerializePairs(values, SerializeValue), _ => throw new NotSupportedException($"Unexpected type: {original.GetType().Name}") }; private static string SerializeValue(string value) { var sb = new StringBuilder(); sb.Append(@""""); foreach (var ch in value) { sb.Append(ch switch { '\\' => @"\\", '\"' => @"\""", '\n' => @"\n", '\r' => @"\r", '\t' => @"\t", '\b' => @"\b", '\f' => @"\f", _ => ch }); } sb.Append(@""""); return sb.ToString(); } private static string SerializePairs(IEnumerable> pairs, Func serializeValue) => SerializeLines("[", " ]", pairs.Select(x => $$""" { "key": {{SerializeValue(x.Key)}}, "value": {{serializeValue(x.Value)}} }""")); private static string SerializeLines(string prefix, string suffix, IEnumerable lines) { var sb = new StringBuilder(); sb.AppendLine(prefix); sb.AppendLine(lines.JoinStr($",{Environment.NewLine}")); sb.Append(suffix); return sb.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/JsonWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Json; public class JsonWalker { protected JsonWalker() { } public virtual void Visit(JsonNode node) { switch (node.Kind) { case Kind.Object: VisitObject(node); break; case Kind.List: VisitList(node); break; case Kind.Value: VisitValue(node); break; } } protected virtual void VisitObject(JsonNode node) { foreach (var key in node.Keys) { VisitObject(key, node[key]); } } protected virtual void VisitObject(string key, JsonNode value) => Visit(value); protected virtual void VisitList(JsonNode node) { foreach (var item in node) { Visit(item); } } protected virtual void VisitValue(JsonNode node) { // Override me } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/Parsing/Kind.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Json.Parsing; public enum Kind { Unknown, Object, List, Value } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/Parsing/LexicalAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.Text; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Json.Parsing; internal class LexicalAnalyzer { private readonly List lines = new(); private int line; private int column = -1; public object Value { get; private set; } public LinePosition LastStart { get; private set; } private char CurrentChar => lines[line][column]; private bool ReachedEndOfInput => line > lines.Count - 1 || (line == lines.Count - 1 && column >= lines[line].Length); public LexicalAnalyzer(string source) { var sb = new StringBuilder(); foreach (var c in source.Replace("\r\n", "\n")) { if (c is '\n' or '\r' || char.GetUnicodeCategory(c) is UnicodeCategory.LineSeparator or UnicodeCategory.ParagraphSeparator) { lines.Add(sb.ToString()); sb.Clear(); } else { sb.Append(c); } } lines.Add(sb.ToString()); } public LinePosition CurrentPosition(int increment) => new(line, column + increment); public Symbol NextSymbol() { Value = null; NextPosition(false); SkipWhitespaceAndComments(); if (ReachedEndOfInput) { return Symbol.EndOfInput; } LastStart = CurrentPosition(0); switch (CurrentChar) { case '{': return Symbol.OpenCurlyBracket; case '}': return Symbol.CloseCurlyBracket; case '[': return Symbol.OpenSquareBracket; case ']': return Symbol.CloseSquareBracket; case ',': return Symbol.Comma; case ':': return Symbol.Colon; case '"': Value = ReadStringValue(); return Symbol.Value; case (>= '0' and <= '9') or '-': Value = ReadNumberValue(); return Symbol.Value; case 'n': ReadKeyword("null"); Value = null; return Symbol.Value; case 't': ReadKeyword("true"); Value = true; return Symbol.Value; case 'f': ReadKeyword("false"); Value = false; return Symbol.Value; default: throw new JsonException($"Unexpected character '{CurrentChar}'", LastStart); } } private void NextPosition(bool throwIfReachedEndOfInput = true) { if (line < lines.Count && column < lines[line].Length - 1) { column++; } else { SeekToNextLine(throwIfReachedEndOfInput); } } private void SeekToNextLine(bool throwIfReachedEndOfInput) { do { line++; } while (line < lines.Count && lines[line].Length == 0); column = 0; if (throwIfReachedEndOfInput && ReachedEndOfInput) { throw new JsonException("Unexpected EOI", LastStart); } } private void SkipWhitespaceAndComments() { var isInMultiLineComment = false; while (!ReachedEndOfInput) { if (char.IsWhiteSpace(CurrentChar)) { NextPosition(false); } else if (IsSingleLineComment()) { // We just need to read starting from the next line SeekToNextLine(false); } else if (IsEndOfMultiLineComment()) { isInMultiLineComment = false; NextPosition(false); // Skip * NextPosition(false); // Skip / } else if (IsMultiLineComment()) { if (!isInMultiLineComment) { // need to skip /* part of the comment NextPosition(false); // Skip / NextPosition(false); // Skip * } isInMultiLineComment = true; NextPosition(); // we still want to fail on non-closed comments } else { return; } } char? NextCharSameLine() => (column + 1 < lines[line].Length) ? lines[line][column + 1] : (char?)null; bool IsSingleLineComment() => CurrentChar == '/' && NextCharSameLine() == '/'; bool IsMultiLineComment() => isInMultiLineComment || (CurrentChar == '/' && NextCharSameLine() == '*'); bool IsEndOfMultiLineComment() => isInMultiLineComment && CurrentChar == '*' && NextCharSameLine() == '/'; } private void ReadKeyword(string keyword) { for (var i = 0; i < keyword.Length; i++) { if (lines[line][column + i] != keyword[i]) { throw new JsonException($"Unexpected character '{lines[line][column + i]}'. Keyword '{keyword}' was expected", LastStart); } } column += keyword.Length - 1; } private string ReadStringValue() { const int UnicodeEscapeLength = 4; var sb = new StringBuilder(); NextPosition(); // Skip quote while (CurrentChar != '"') { if (CurrentChar == '\\') { NextPosition(); switch (CurrentChar) { case '"': sb.Append('"'); break; case '\\': sb.Append('\\'); break; case '/': sb.Append('/'); break; case 'b': sb.Append('\b'); break; case 'f': sb.Append('\f'); break; case 'n': sb.Append('\n'); break; case 'r': sb.Append('\r'); break; case 't': sb.Append('\t'); break; case 'u': if (column + UnicodeEscapeLength >= lines[line].Length) { throw new JsonException(@"Unexpected EOI, \uXXXX escape expected", LastStart); } sb.Append(char.ConvertFromUtf32(int.Parse(lines[line].Substring(column + 1, UnicodeEscapeLength), NumberStyles.HexNumber))); column += UnicodeEscapeLength; break; default: throw new JsonException($@"Unexpected escape sequence \{CurrentChar}", LastStart); } } else { sb.Append(CurrentChar); } NextPosition(); } return sb.ToString(); } private object ReadNumberValue() { StringBuilder @decimal = null; StringBuilder exponent = null; StringBuilder integral = new(); StringBuilder current = integral; while (!ReachedEndOfInput) { switch (CurrentChar) { case '-': if (current.Length == 0) { current.Append('-'); } else { throw new JsonException("Unexpected Number format: Unexpected '-'", LastStart); } break; case (>= '0' and <= '9'): current.Append(CurrentChar); break; case '.': if (current == integral && current.ToString().TrimStart('-').Any()) { @decimal = new StringBuilder(); current = @decimal; } else { throw new JsonException("Unexpected Number format: Unexpected '.'", LastStart); } break; case '+': if (current != exponent || current.Length != 0) { throw new JsonException("Unexpected Number format", LastStart); } break; case 'e' or 'E': exponent = new StringBuilder(); current = exponent; break; default: // Remain on the last digit, position cannot be zero here, since we at least read one digit character column--; return BuildResult(); } NextPosition(false); } return BuildResult(); object BuildResult() { var baseValue = @decimal == null ? (object)double.Parse(integral.ToString(), CultureInfo.InvariantCulture) : decimal.Parse(integral + CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator + @decimal, CultureInfo.InvariantCulture); if (exponent == null) // Integer or Decimal { return baseValue; } else if (exponent.Length == 0 || exponent.ToString() == "-") { throw new JsonException($"Unexpected Number exponent format: {exponent}", LastStart); } else { return Convert.ToDouble(baseValue) * Math.Pow(10, int.Parse(exponent.ToString())); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/Parsing/Symbol.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Json.Parsing; internal enum Symbol { Unknown, EndOfInput, // Special OpenCurlyBracket, CloseCurlyBracket, OpenSquareBracket, CloseSquareBracket, Comma, Colon, // Terms Value // String, Number (Integer, Decimal, Double), Null, True, False } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Json/Parsing/SyntaxAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Json.Parsing; internal class SyntaxAnalyzer { // Parse -> { ParseObject | [ ParseList // ParseObject -> } | ObjectKeyValue ObjectRest // ObjectRest -> , ObjectKeyValue ObjectRest | } // ObjectKeyValue -> String : ParseValue // ParseList -> ] | ParseValue ArrayRest // ArrayRest -> , ParseValue ] | ] // ParseValue -> { ParseObject | [ ParseList | Symbol.Value (Where .Value is true | false | null | String | Number) private readonly LexicalAnalyzer lexer; private Symbol symbol; public SyntaxAnalyzer(string source) => lexer = new LexicalAnalyzer(source); public JsonNode Parse() => ReadNext() switch { Symbol.OpenCurlyBracket => ParseObject(), Symbol.OpenSquareBracket => ParseList(), _ => throw Unexpected("{ or [") }; private Symbol ReadNext() => symbol = lexer.NextSymbol(); private JsonNode ParseObject() { var ret = new JsonNode(lexer.LastStart, Kind.Object); if (ReadNext() != Symbol.CloseCurlyBracket) // Could be empty object {} { ObjectKeyValue(ret); while (ReadNext() == Symbol.Comma) { ReadNext(); ObjectKeyValue(ret); } if (symbol != Symbol.CloseCurlyBracket) { throw Unexpected("}"); } } ret.UpdateEnd(new LinePosition(lexer.LastStart.Line, lexer.LastStart.Character + 1)); return ret; } private void ObjectKeyValue(JsonNode target) { if (symbol == Symbol.Value && lexer.Value is string key) { if (ReadNext() != Symbol.Colon) { throw Unexpected(":"); } ReadNext(); // Prepare before reading Value target.Add(key, ParseValue()); } else { throw Unexpected("String Value"); } } private JsonNode ParseList() { var ret = new JsonNode(lexer.LastStart, Kind.List); if (ReadNext() != Symbol.CloseSquareBracket) // Could be empty array [] { ret.Add(ParseValue()); while (ReadNext() == Symbol.Comma) { ReadNext(); ret.Add(ParseValue()); } if (symbol != Symbol.CloseSquareBracket) { throw Unexpected("]"); } } ret.UpdateEnd(new LinePosition(lexer.LastStart.Line, lexer.LastStart.Character + 1)); return ret; } private JsonNode ParseValue() => // Symbol is already read symbol switch { Symbol.OpenCurlyBracket => ParseObject(), Symbol.OpenSquareBracket => ParseList(), Symbol.Value => new JsonNode(lexer.LastStart, lexer.CurrentPosition(1), lexer.Value), _ => throw Unexpected("{, [ or Value (true, false, null, String, Number)") }; private JsonException Unexpected(string expected) => new JsonException($"{expected} expected, but {symbol} found", lexer.LastStart); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Metrics/CognitiveComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Metrics; public class CognitiveComplexity { public int Complexity { get; } public ImmutableArray Locations { get; } public CognitiveComplexity(int complexity, ImmutableArray locations) { Complexity = complexity; Locations = locations; } } public class CognitiveComplexityWalkerState where TMethodSyntax : SyntaxNode { public TMethodSyntax CurrentMethod { get; set; } public IList IncrementLocations { get; } = new List(); // used to track logical operations inside parentheses public IList LogicalOperationsToIgnore { get; } = new List(); public bool HasDirectRecursiveCall { get; set; } public int NestingLevel { get; set; } public int Complexity { get; set; } public void VisitWithNesting(TSyntaxNode node, Action visit) { NestingLevel++; visit(node); NestingLevel--; } public void IncreaseComplexityByOne(SyntaxToken token) => IncreaseComplexity(token, 1, "+1"); public void IncreaseComplexityByNestingPlusOne(SyntaxToken token) { var increment = NestingLevel + 1; var message = increment == 1 ? "+1" : $"+{increment} (incl {increment - 1} for nesting)"; IncreaseComplexity(token, increment, message); } public void IncreaseComplexity(SyntaxToken token, int increment, string message) { Complexity += increment; IncrementLocations.Add(new SecondaryLocation(token.GetLocation(), message)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Metrics/FileComments.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Metrics; public class FileComments { public ISet NoSonar { get; } public ISet NonBlank { get; } public FileComments(ISet noSonar, ISet nonBlank) { NoSonar = noSonar; NonBlank = nonBlank; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Metrics/MetricsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Metrics; public abstract class MetricsBase { protected const string InitializationErrorTextPattern = "The input tree is not of the expected language."; private readonly SyntaxTree tree; private readonly string filePath; public abstract ImmutableArray ExecutableLines { get; } public abstract int ComputeCyclomaticComplexity(SyntaxNode node); protected abstract bool IsEndOfFile(SyntaxToken token); protected abstract int ComputeCognitiveComplexity(SyntaxNode node); protected abstract bool IsNoneToken(SyntaxToken token); protected abstract bool IsCommentTrivia(SyntaxTrivia trivia); protected abstract bool IsClass(SyntaxNode node); protected abstract bool IsStatement(SyntaxNode node); protected abstract bool IsFunction(SyntaxNode node); public ISet CodeLines => tree.GetRoot() .DescendantTokens() .Select(GetMappedLineSpan) .Where(x => x is not null) .SelectMany( x => { var start = x.Value.StartLinePosition.LineNumberToReport(); var end = x.Value.EndLinePosition.LineNumberToReport(); return Enumerable.Range(start, end - start + 1); }) .ToHashSet(); protected MetricsBase(SyntaxTree tree) { this.tree = tree; filePath = tree.GetRoot().GetMappedFilePathFromRoot(); } public FileComments GetComments(bool ignoreHeaderComments) { var noSonar = new HashSet(); var nonBlank = new HashSet(); foreach (var trivia in tree.GetRoot().DescendantTrivia()) { if (!IsCommentTrivia(trivia) || ignoreHeaderComments && IsNoneToken(trivia.Token.GetPreviousToken())) { continue; } var mappedSpan = tree.GetMappedLineSpan(trivia.FullSpan); if (!IsInSameFile(mappedSpan)) { continue; } var lineNumber = mappedSpan.StartLinePosition.Line + 1; var triviaLines = trivia.ToFullString().Split(Constants.LineTerminators, StringSplitOptions.None); foreach (var line in triviaLines) { CategorizeLines(line, lineNumber, noSonar, nonBlank); lineNumber++; } } return new FileComments(noSonar.ToHashSet(), nonBlank.ToHashSet()); } public int ClassCount => ClassNodes.Count(); public int StatementCount => tree.GetRoot().DescendantNodes().Count(IsStatement); public int FunctionCount => FunctionNodes.Count(); public int Complexity => ComputeCyclomaticComplexity(tree.GetRoot()); public int CognitiveComplexity => ComputeCognitiveComplexity(tree.GetRoot()); protected bool IsInSameFile(FileLinePositionSpan span) => // Syntax tree can contain elements from external files (e.g. razor imports files) // We need to make sure that we don't count these elements. string.Equals(filePath, span.Path, StringComparison.OrdinalIgnoreCase); private IEnumerable ClassNodes => tree.GetRoot().DescendantNodes().Where(IsClass); private IEnumerable FunctionNodes => tree.GetRoot().DescendantNodes().Where(IsFunction); private FileLinePositionSpan? GetMappedLineSpan(SyntaxToken token) => !IsEndOfFile(token) && token.GetLocation().GetMappedLineSpan() is { IsValid: true } mappedLineSpan && (!GeneratedCodeRecognizer.IsRazor(token.SyntaxTree) || mappedLineSpan.HasMappedPath) && IsInSameFile(mappedLineSpan) ? mappedLineSpan : null; private static void CategorizeLines(string line, int lineNumber, ISet noSonar, ISet nonBlank) { if (line.Contains("NOSONAR")) { nonBlank.Remove(lineNumber); noSonar.Add(lineNumber); } else { if (HasValidCommentContent(line) && !noSonar.Contains(lineNumber)) { nonBlank.Add(lineNumber); } } } private static bool HasValidCommentContent(string content) => content.Any(char.IsLetter) || content.Any(char.IsDigit); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Core.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.CSharp")] [assembly: InternalsVisibleTo("SonarAnalyzer.CSharp.Core.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.TestFramework")] [assembly: InternalsVisibleTo("SonarAnalyzer.VisualBasic")] [assembly: InternalsVisibleTo("SonarAnalyzer.VisualBasic.Core.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Protobuf/AnalyzerReport.proto ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ syntax = "proto3"; package sonaranalyzer; option csharp_namespace = "SonarAnalyzer.Protobuf"; option java_package = "org.sonarsource.dotnet.protobuf"; option java_outer_classname = "SonarAnalyzer"; option optimize_for = SPEED; // Naming convention: // we use singular for repeated field names, because that works better in Java, and we don't care in C# // Lines start at 1 and line offsets start at 0 message TextRange { int32 start_line = 1; // End line (inclusive) int32 end_line = 2; int32 start_offset = 3; int32 end_offset = 4; } enum TokenType { UNKNOWN_TOKENTYPE = 0; TYPE_NAME = 1; NUMERIC_LITERAL = 2; STRING_LITERAL = 3; KEYWORD = 4; COMMENT = 5; } // Used for code coloring message TokenTypeInfo { string file_path = 1; message TokenInfo { TokenType token_type = 1; TextRange text_range = 2; } repeated TokenInfo token_info = 2; } // Used for symbol reference highlighting message SymbolReferenceInfo { string file_path = 1; message SymbolReference { TextRange declaration = 1; repeated TextRange reference = 2; } repeated SymbolReference reference = 2; } // Used for copy-paste detection message CopyPasteTokenInfo { string file_path = 1; message TokenInfo { string token_value = 1; TextRange text_range = 2; } repeated TokenInfo token_info = 2; } // Metrics reporting message MetricsInfo { string file_path = 1; int32 class_count = 2; int32 statement_count = 3; int32 function_count = 4; int32 complexity = 7; repeated int32 no_sonar_comment = 12; repeated int32 non_blank_comment = 13; repeated int32 code_line = 14; int32 cognitive_complexity = 15; repeated int32 executable_lines = 16; } message FileMetadataInfo { string file_path = 1; bool is_generated = 2; string encoding = 3; } message MethodDeclarationsInfo { string file_path = 1; string assembly_name = 2; repeated MethodDeclarationInfo method_declarations = 3; } message MethodDeclarationInfo { string type_name = 1; string method_name = 2; } // Logging enum LogSeverity { UNKNOWN_SEVERITY = 0; DEBUG = 1; INFO = 2; WARNING = 3; } message LogInfo { LogSeverity severity = 1; string text = 2; } message Telemetry { string projectFullPath = 10; // From ProjectInfo.xml FullPath string languageVersion = 30; // From CSharpParseOptions.LanguageVersion } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/RegularExpressions/NamingPatterns.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.RegularExpressions; public static class NamingPatterns { public const string PascalCasingInternalPattern = "([A-Z]{1,3}[a-z0-9]+)*" + MaxTwoLongIdPattern; public const string PascalCasingPattern = "^" + PascalCasingInternalPattern + "$"; public const string CamelCasingPattern = "^" + CamelCasingInternalPattern + "$"; public const string CamelCasingPatternWithOptionalPrefixes = "^(s_|_)?" + CamelCasingInternalPattern + "$"; private const string CamelCasingInternalPattern = "[a-z][a-z0-9]*" + PascalCasingInternalPattern; private const string MaxTwoLongIdPattern = "([A-Z]{2})?"; internal static bool IsRegexMatch(string name, string pattern, bool timeoutFallback = false) => SafeRegex.IsMatch(name, pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled, timeoutFallback); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/RegularExpressions/RegexContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.RegularExpressions; internal sealed class RegexContext { private static readonly RegexOptions ValidationMask = (RegexOptions)int.MaxValue ^ RegexOptions.Compiled; private static readonly string[] MatchMethods = new[] { nameof(Regex.IsMatch), nameof(Regex.Match), nameof(Regex.Matches), nameof(Regex.Replace), nameof(Regex.Split), "EnumerateSplits", // https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratesplits?view=net-9.0 "EnumerateMatches", // https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches?view=net-9.0 }; public SyntaxNode PatternNode { get; } public string Pattern { get; } public SyntaxNode OptionsNode { get; } public RegexOptions? Options { get; } public Regex Regex { get; } public Exception ParseError { get; } public RegexContext(SyntaxNode patternNode, string pattern, SyntaxNode optionsNode, RegexOptions? options) { PatternNode = patternNode; Pattern = pattern; OptionsNode = optionsNode; Options = options; if (!string.IsNullOrEmpty(pattern)) { try { Regex = new(Pattern, options.GetValueOrDefault() & ValidationMask, Constants.DefaultRegexTimeout); } catch (Exception ex) { ParseError = ex; } } } public static RegexContext FromAttribute(ILanguageFacade language, SemanticModel model, SyntaxNode node) where TSyntaxKind : struct { if (model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.IsInType(KnownType.System_ComponentModel_DataAnnotations_RegularExpressionAttribute)) { var parameters = language.MethodParameterLookup(node, method); var pattern = TryGetNonParamsSyntax(method, parameters, "pattern"); return new RegexContext(pattern, language.FindConstantValue(model, pattern) as string, null, null); } else { return null; } } public static RegexContext FromCtor(ILanguageFacade language, SemanticModel model, SyntaxNode node) where TSyntaxKind : struct => model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.IsConstructor() && method.ContainingType.Is(KnownType.System_Text_RegularExpressions_Regex) ? FromMethod(language, model, node, method) : null; public static RegexContext FromMethod(ILanguageFacade language, SemanticModel model, SyntaxNode node) where TSyntaxKind : struct => language.Syntax.NodeIdentifier(node).GetValueOrDefault().Text is { } name && MatchMethods.Any(x => name.Equals(x, language.NameComparison)) && model.GetSymbolInfo(node).Symbol is IMethodSymbol { IsStatic: true } method && method.ContainingType.Is(KnownType.System_Text_RegularExpressions_Regex) ? FromMethod(language, model, node, method) : null; private static RegexContext FromMethod(ILanguageFacade language, SemanticModel model, SyntaxNode node, IMethodSymbol method) where TSyntaxKind : struct { var parameters = language.MethodParameterLookup(node, method); var pattern = TryGetNonParamsSyntax(method, parameters, "pattern"); var options = TryGetNonParamsSyntax(method, parameters, "options"); return new RegexContext( pattern, language.FindConstantValue(model, pattern) as string, options, language.FindConstantValue(model, options) is RegexOptions value ? value : null); } private static SyntaxNode TryGetNonParamsSyntax(IMethodSymbol method, IMethodParameterLookup parameters, string paramName) => method.Parameters.SingleOrDefault(x => x.Name == paramName) is { } param && parameters.TryGetNonParamsSyntax(param, out var node) ? node : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/RegularExpressions/WildcardPatternMatcher.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.RegularExpressions; internal static class WildcardPatternMatcher { private static readonly ConcurrentDictionary Cache = new(); public static bool IsMatch(string pattern, string input, bool timeoutFallbackResult) => !(string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(input)) && Cache.GetOrAdd(pattern, _ => new Regex(ToRegex(pattern), RegexOptions.None, Constants.DefaultRegexTimeout)) is var regex && regex.SafeIsMatch(input.Trim('/'), timeoutFallbackResult); /// /// Copied from https://github.com/SonarSource/sonar-plugin-api/blob/a9bd7ff48f0f77811ed909070030678c443c975a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java. /// private static string ToRegex(string wildcardPattern) { var escapedDirectorySeparator = Regex.Escape(Path.DirectorySeparatorChar.ToString()); var sb = new StringBuilder("^", wildcardPattern.Length); var i = IsSlash(wildcardPattern[0]) ? 1 : 0; while (i < wildcardPattern.Length) { var ch = wildcardPattern[i]; if (ch == '*') { if (i + 1 < wildcardPattern.Length && wildcardPattern[i + 1] == '*') { // Double asterisk - Zero or more directories if (i + 2 < wildcardPattern.Length && IsSlash(wildcardPattern[i + 2])) { sb.Append($"(.*{escapedDirectorySeparator}|)"); i += 2; } else { sb.Append(".*"); i += 1; } } else { // Single asterisk - Zero or more characters excluding directory separator sb.Append($"[^{escapedDirectorySeparator}]*?"); } } else if (ch == '?') { // Any single character excluding directory separator sb.Append($"[^{escapedDirectorySeparator}]"); } else if (IsSlash(ch)) { sb.Append(escapedDirectorySeparator); } else { sb.Append(Regex.Escape(ch.ToString())); } i++; } return sb.Append('$').ToString(); } private static bool IsSlash(char ch) => ch == '/' || ch == '\\'; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AllBranchesShouldNotHaveSameImplementationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class AllBranchesShouldNotHaveSameImplementationBase : SonarDiagnosticAnalyzer { protected const string StatementsMessage = "Remove this conditional structure or edit its code blocks so that they're not all the same."; protected const string TernaryMessage = "This conditional operation returns the same value whether the condition is \"true\" or \"false\"."; internal const string DiagnosticId = "S3923"; internal const string MessageFormat = "{0}"; protected abstract class IfStatementAnalyzerBase where TElseSyntax : SyntaxNode where TIfSyntax : SyntaxNode { protected abstract IEnumerable GetStatements(TElseSyntax elseSyntax); protected abstract IEnumerable> GetIfBlocksStatements(TElseSyntax elseSyntax, out TIfSyntax topLevelIf); protected abstract bool IsLastElseInChain(TElseSyntax elseSyntax); protected abstract Location GetLocation(TIfSyntax topLevelIf); public Action GetAnalysisAction(DiagnosticDescriptor rule) => context => { var elseSyntax = (TElseSyntax)context.Node; if (!IsLastElseInChain(elseSyntax)) { return; } var ifBlocksStatements = GetIfBlocksStatements(elseSyntax, out var topLevelIf); var elseStatements = GetStatements(elseSyntax); if (ifBlocksStatements.All(ifStatements => AreEquivalent(ifStatements, elseStatements))) { context.ReportIssue(rule, GetLocation(topLevelIf), StatementsMessage); } }; private static bool AreEquivalent(IEnumerable nodes1, IEnumerable nodes2) => nodes1.Equals(nodes2, (x, y) => x.IsEquivalentTo(y, topLevel: false)); } protected abstract class TernaryStatementAnalyzerBase where TTernaryStatement : SyntaxNode { protected abstract SyntaxNode GetWhenTrue(TTernaryStatement ternaryStatement); protected abstract SyntaxNode GetWhenFalse(TTernaryStatement ternaryStatement); protected abstract Location GetLocation(TTernaryStatement ternaryStatement); public Action GetAnalysisAction(DiagnosticDescriptor rule) => context => { var ternaryStatement = (TTernaryStatement)context.Node; var whenTrue = GetWhenTrue(ternaryStatement); var whenFalse = GetWhenFalse(ternaryStatement); if (whenTrue.IsEquivalentTo(whenFalse, topLevel: false)) { context.ReportIssue(rule, GetLocation(ternaryStatement), TernaryMessage); } }; } protected abstract class SwitchStatementAnalyzerBase where TSwitchStatement : SyntaxNode where TSwitchSection : SyntaxNode { protected abstract IEnumerable GetSections(TSwitchStatement switchStatement); protected abstract bool HasDefaultLabel(TSwitchStatement switchStatement); protected abstract bool AreEquivalent(TSwitchSection section1, TSwitchSection section2); protected abstract Location GetLocation(TSwitchStatement switchStatement); public Action GetAnalysisAction(DiagnosticDescriptor rule) => context => { var switchStatement = (TSwitchStatement)context.Node; var sections = GetSections(switchStatement).ToList(); if (sections.Count >= 2 && HasDefaultLabel(switchStatement) && sections.Skip(1).All(section => AreEquivalent(section, sections[0]))) { context.ReportIssue(rule, GetLocation(switchStatement), StatementsMessage); } }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AlwaysSetDateTimeKindBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class AlwaysSetDateTimeKindBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6562"; protected abstract TSyntaxKind ObjectCreationExpression { get; } protected abstract string[] ValidNames { get; } protected override string MessageFormat => "Provide the \"DateTimeKind\" when creating this object."; protected AlwaysSetDateTimeKindBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.ObjectCreationTypeIdentifier(c.Node) is { IsMissing: false } identifier && Array.Exists(ValidNames, x => x.Equals(identifier.ValueText, Language.NameComparison)) && IsDateTimeConstructorWithoutKindParameter(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, ObjectCreationExpression); protected static bool IsDateTimeConstructorWithoutKindParameter(SyntaxNode objectCreation, SemanticModel semanticModel) => semanticModel.GetSymbolInfo(objectCreation).Symbol is IMethodSymbol ctor && ctor.IsInType(KnownType.System_DateTime) && !ctor.Parameters.Any(x => x.IsType(KnownType.System_DateTimeKind)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ArrayPassedAsParamsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ArrayPassedAsParamsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TArgumentNode : SyntaxNode { private const string DiagnosticId = "S3878"; protected abstract TSyntaxKind[] ExpressionKinds { get; } protected abstract TArgumentNode LastArgumentIfArrayCreation(SyntaxNode expression); protected abstract ITypeSymbol ArrayElementType(TArgumentNode argument, SemanticModel model); protected override string MessageFormat => "Remove this array creation and simply pass the elements."; protected ArrayPassedAsParamsBase() : base(DiagnosticId) {} protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (LastArgumentIfArrayCreation(c.Node) is { } lastArgument && c.Model.GetSymbolInfo(c.Node).Symbol is IMethodSymbol methodSymbol && Language.MethodParameterLookup(c.Node, methodSymbol) is { } parameterLookup && parameterLookup.TryGetSymbol(lastArgument, out var param) && param is { IsParams: true } && !IsArrayOfCandidateTypes(lastArgument, parameterLookup, param, c.Model) && !IsJaggedArrayParam(param)) { c.ReportIssue(Rule, lastArgument.GetLocation()); } }, ExpressionKinds); private static bool IsJaggedArrayParam(IParameterSymbol param) => param.Type is IArrayTypeSymbol { ElementType: IArrayTypeSymbol }; private bool IsArrayOfCandidateTypes(TArgumentNode lastArgument, IMethodParameterLookup parameterLookup, IParameterSymbol param, SemanticModel model) { return param.Type is IArrayTypeSymbol array && (array.ElementType.Is(KnownType.System_Array) || (array.ElementType.Is(KnownType.System_Object) && !ParamArgumentsAreReferenceTypeArrays(lastArgument, parameterLookup, model))); bool ParamArgumentsAreReferenceTypeArrays(TArgumentNode lastArgument, IMethodParameterLookup parameterLookup, SemanticModel model) => ArrayElementType(lastArgument, model) is { IsReferenceType: true } elementType && !elementType.Is(KnownType.System_Object) && parameterLookup.TryGetSyntax(param, out var arguments) && arguments.Length is 1; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AspNet/BackslashShouldBeAvoidedInAspNetRoutesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class BackslashShouldBeAvoidedInAspNetRoutesBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6930"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract bool IsNamedAttributeArgument(SyntaxNode node); protected override string MessageFormat => @"Replace '\' with '/'."; protected BackslashShouldBeAvoidedInAspNetRoutesBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (compilationStartContext.Compilation.ReferencesNetCoreControllers() || compilationStartContext.Compilation.ReferencesNetFrameworkControllers()) { compilationStartContext.RegisterNodeAction(Language.GeneratedCodeRecognizer, Check, SyntaxKinds); } }); protected void Check(SonarSyntaxNodeReportingContext c) { if (!IsNamedAttributeArgument(c.Node) && Language.Syntax.NodeExpression(c.Node) is { } expression && Language.FindConstantValue(c.Model, expression) is string constantRouteTemplate && ContainsBackslash(constantRouteTemplate) && IsRouteTemplate(c.Model, c.Node)) { c.ReportIssue(Rule, expression); } } private bool IsRouteTemplate(SemanticModel model, SyntaxNode node) => node.Parent.Parent is var invocation // can be a method invocation or a tuple expression && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && Language.MethodParameterLookup(invocation, methodSymbol) is { } parameterLookup && parameterLookup.TryGetSymbol(node, out var parameter) && (HasStringSyntaxAttributeOfTypeRoute(parameter) || IsRouteTemplateBeforeAspNet6(parameter, methodSymbol)); private static bool HasStringSyntaxAttributeOfTypeRoute(IParameterSymbol parameter) => parameter.GetAttributes(KnownType.System_Diagnostics_CodeAnalysis_StringSyntaxAttribute).FirstOrDefault() is { } syntaxAttribute && syntaxAttribute.TryGetAttributeValue("syntax", out var syntaxParameter) && string.Equals(syntaxParameter, "Route", StringComparison.Ordinal); private static bool IsRouteTemplateBeforeAspNet6(IParameterSymbol parameter, IMethodSymbol method) => // Remark: route templates cannot be specified via HttpXAttribute in ASP.NET 4.x (method.ContainingType.IsAny(KnownType.RouteAttributes) || method.ContainingType.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttribute)) && method.IsConstructor() && parameter.Name == "template"; private static bool ContainsBackslash(string value) { var firstBackslashIndex = value.IndexOf('\\'); if (firstBackslashIndex < 0) { return false; } var firstRegexIndex = value.IndexOf("regex", StringComparison.Ordinal); return firstRegexIndex < 0 || firstBackslashIndex < firstRegexIndex; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AspNet/RouteTemplateShouldNotStartWithSlashBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; namespace SonarAnalyzer.Core.Rules; public abstract class RouteTemplateShouldNotStartWithSlashBase() : SonarDiagnosticAnalyzer(DiagnosticId) where TSyntaxKind : struct { private const string DiagnosticId = "S6931"; private const string MessageOnlyActions = "Change the paths of the actions of this controller to be relative and adapt the controller route accordingly."; private const string MessageActionsAndController = "Change the paths of the actions of this controller to be relative and add a controller route with the common prefix."; private const string SecondaryMessageOnlyActions = "Change this path to be relative to the controller route defined on class level."; private const string SecondaryMessageActionsAndController = "Add a controller route with a common prefix and change this path to be relative it."; protected override string MessageFormat => "{0}"; protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStartContext => { if (!compilationStartContext.Compilation.ReferencesNetCoreControllers() && !compilationStartContext.Compilation.ReferencesNetFrameworkControllers()) { return; } compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { var symbol = (INamedTypeSymbol)symbolStartContext.Symbol; if (symbol.IsControllerType()) { var controllerActionInfo = new ConcurrentStack(); symbolStartContext.RegisterSyntaxNodeAction(nodeContext => { if (nodeContext.Model.GetDeclaredSymbol(nodeContext.Node) is IMethodSymbol methodSymbol && methodSymbol.IsControllerActionMethod()) { controllerActionInfo.Push(new ActionParametersInfo(RouteAttributeTemplateArguments(methodSymbol.GetAttributes()))); } }, Language.SyntaxKind.MethodDeclarations); symbolStartContext.RegisterSymbolEndAction(symbolEndContext => ReportIssues(symbolEndContext, symbol, controllerActionInfo)); } }, SymbolKind.NamedType); }); private void ReportIssues(SonarSymbolReportingContext context, INamedTypeSymbol controllerSymbol, ConcurrentStack actions) { // If one of the following conditions is true, the rule won't raise an issue // 1. The controller does not have any actions defined // 2. At least one action is not annotated with a route attribute or is annotated with a parameterless attribute // 3. There is at least one action with a route template that does not start with '/' if (!actions.Any() || actions.Any(x => !x.RouteParameters.Any() || x.RouteParameters.Values.Any(x => !x.StartsWith("/") && !x.StartsWith("~/")))) { return; } var issueMessage = controllerSymbol.GetAttributes().Any(x => x.AttributeClass.IsAny(KnownType.RouteAttributes) || x.AttributeClass.Is(KnownType.System_Web_Mvc_RoutePrefixAttribute)) ? MessageOnlyActions : MessageActionsAndController; var secondaryIssueMessage = issueMessage == MessageOnlyActions ? SecondaryMessageOnlyActions : SecondaryMessageActionsAndController; var secondaryLocations = actions.SelectMany(x => x.RouteParameters.Keys.ToSecondary(secondaryIssueMessage)); foreach (var identifier in controllerSymbol.DeclaringSyntaxReferences.Select(x => Language.Syntax.NodeIdentifier(x.GetSyntax())).WhereNotNull()) { context.ReportIssue(Language.GeneratedCodeRecognizer, Rule, identifier.GetLocation(), secondaryLocations, issueMessage); } } private static Dictionary RouteAttributeTemplateArguments(ImmutableArray attributes) { var templates = new Dictionary(); foreach (var attribute in attributes) { if (attribute.GetAttributeRouteTemplate() is { } templateParameter) { templates.Add(attribute.ApplicationSyntaxReference.GetSyntax().GetLocation(), templateParameter); } } return templates; } private readonly record struct ActionParametersInfo(Dictionary RouteParameters); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AvoidDateTimeNowForBenchmarkingBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class AvoidDateTimeNowForBenchmarkingBase : SonarDiagnosticAnalyzer where TMemberAccess : SyntaxNode where TInvocationExpression : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S6561"; protected override string MessageFormat => "Avoid using \"DateTime.Now\" for benchmarking or timespan calculation operations."; protected abstract bool ContainsDateTimeArgument(TInvocationExpression invocation, SemanticModel model); protected AvoidDateTimeNowForBenchmarkingBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckBinaryExpression, Language.SyntaxKind.SubtractExpression); context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckInvocation, Language.SyntaxKind.InvocationExpression); } private void CheckBinaryExpression(SonarSyntaxNodeReportingContext context) { if (Language.Syntax.BinaryExpressionLeft(context.Node) is TMemberAccess memberAccess && IsDateTimeNow(memberAccess, context.Model) && Language.Syntax.BinaryExpressionRight(context.Node) is var right && context.Model.GetTypeInfo(right).Type.Is(KnownType.System_DateTime)) { context.ReportIssue(Rule, context.Node); } } private void CheckInvocation(SonarSyntaxNodeReportingContext context) { var invocation = (TInvocationExpression)context.Node; if (Language.Syntax.NodeExpression(invocation) is TMemberAccess subtract && Language.Syntax.NodeExpression(subtract) is TMemberAccess now && Language.Syntax.IsMemberAccessOnKnownType(subtract, "Subtract", KnownType.System_DateTime, context.Model) && IsDateTimeNow(now, context.Model) && Language.Syntax.HasExactlyNArguments(invocation, 1) && ContainsDateTimeArgument(invocation, context.Model)) { context.ReportIssue(Rule, subtract); } } private bool IsDateTimeNow(TMemberAccess node, SemanticModel model) => Language.Syntax.IsMemberAccessOnKnownType(node, "Now", KnownType.System_DateTime, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/AvoidUnsealedAttributesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class AvoidUnsealedAttributesBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4060"; protected override string MessageFormat => "Seal this attribute or make it abstract."; protected AvoidUnsealedAttributesBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier && c.Model.GetDeclaredSymbol(c.Node) is INamedTypeSymbol { IsAbstract: false, IsSealed: false } symbol && symbol.DerivesFrom(KnownType.System_Attribute) && symbol.IsPubliclyAccessible()) { c.ReportIssue(Rule, identifier); } }, Language.SyntaxKind.ClassDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/BeginInvokePairedWithEndInvokeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class BeginInvokePairedWithEndInvokeBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpressionSyntax : SyntaxNode { protected const string DiagnosticId = "S4583"; protected const string EndInvoke = "EndInvoke"; private const string BeginInvoke = "BeginInvoke"; protected abstract TSyntaxKind InvocationExpressionKind { get; } protected abstract ISet ParentDeclarationKinds { get; } protected abstract string CallbackParameterName { get; } protected abstract void VisitInvocation(EndInvokeContext context); protected override string MessageFormat => "Pair this \"BeginInvoke\" with an \"EndInvoke\"."; /// /// - true if callback code has been resolved and does not contain "EndInvoke". /// - false if callback code contains "EndInvoke" or callback code has not been resolved. /// protected abstract bool IsInvalidCallback(SyntaxNode callbackArg, SemanticModel semanticModel); protected BeginInvokePairedWithEndInvokeBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocationExpressionSyntax)c.Node; if (Language.Syntax.NodeExpression(invocation) is { } expression && expression.ToStringContains(BeginInvoke) && c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && methodSymbol.Name == BeginInvoke && IsDelegate(methodSymbol) && methodSymbol.Parameters.SingleOrDefault(x => x.Name == CallbackParameterName) is { } parameter && Language.MethodParameterLookup(invocation, methodSymbol).TryGetNonParamsSyntax(parameter, out var callbackArg) && IsInvalidCallback(callbackArg, c.Model) && !ParentMethodContainsEndInvoke(invocation, c.Model)) { c.ReportIssue(Rule, Language.Syntax.InvocationIdentifier(invocation).Value); } }, InvocationExpressionKind); protected static bool IsDelegate(IMethodSymbol methodSymbol) => methodSymbol.ReceiverType.Is(TypeKind.Delegate); protected bool IsParentDeclarationWithEndInvoke(SyntaxNode node, SemanticModel semanticModel) { if (IsParentDeclaration(node)) { var context = new EndInvokeContext(this, semanticModel, node); VisitInvocation(context); return context.ContainsEndInvoke; } else { return false; } } private bool ParentMethodContainsEndInvoke(SyntaxNode node, SemanticModel model) => node.AncestorsAndSelf().FirstOrDefault(IsParentDeclaration) is { } parentContext && IsParentDeclarationWithEndInvoke(parentContext, model); private bool IsParentDeclaration(SyntaxNode node) => node is not null && ParentDeclarationKinds.Contains(Language.Syntax.Kind(node)); protected class EndInvokeContext { private readonly BeginInvokePairedWithEndInvokeBase rule; private readonly SemanticModel semanticModel; public SyntaxNode Root { get; } public bool ContainsEndInvoke { get; private set; } public EndInvokeContext(BeginInvokePairedWithEndInvokeBase rule, SemanticModel semanticModel, SyntaxNode root) { this.rule = rule; this.semanticModel = semanticModel; Root = root; } public bool Visit(SyntaxNode node) => !ContainsEndInvoke; // Stop visiting once we found it public bool VisitInvocationExpression(SyntaxNode node) { if (rule.Language.Syntax.NodeExpression(node).ToStringContains(EndInvoke) && semanticModel.GetSymbolInfo(node).Symbol is IMethodSymbol methodSymbol && methodSymbol.Name == EndInvoke && IsDelegate(methodSymbol)) { ContainsEndInvoke = true; return false; // Stop visiting } return true; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/BinaryOperationWithIdenticalExpressionsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class BinaryOperationWithIdenticalExpressionsBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1764"; protected const string OperatorMessageFormat = "Correct one of the identical expressions on both sides of operator '{0}'."; protected const string EqualsMessage = "Change one instance of '{0}' to a different value; comparing '{0}' to itself always returns true."; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/BooleanCheckInvertedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class BooleanCheckInvertedBase : SonarDiagnosticAnalyzer where TBinaryExpression : SyntaxNode { internal const string DiagnosticId = "S1940"; protected const string MessageFormat = "Use the opposite operator ('{0}') instead."; protected abstract SyntaxNode LogicalNotNode(TBinaryExpression expression); protected abstract string SuggestedReplacement(TBinaryExpression expression); protected abstract bool IsUnsafeInversionOperation(TBinaryExpression expression, SemanticModel model); protected Action AnalysisAction(DiagnosticDescriptor rule) => c => { var expression = (TBinaryExpression)c.Node; if (IsUserDefinedOperator(expression, c.Model) || IsUnsafeInversionOperation(expression, c.Model)) { return; } if (LogicalNotNode(expression) is { } logicalNot) { c.ReportIssue(rule, logicalNot, SuggestedReplacement(expression)); } }; protected static bool IsNullable(SyntaxNode expression, SemanticModel model) => model.GetSymbolInfo(expression).Symbol.GetSymbolType() is INamedTypeSymbol symbolType && symbolType.ConstructedFrom.Is(KnownType.System_Nullable_T); protected static bool IsFloatingPoint(SyntaxNode expression, SemanticModel model) => model.GetTypeInfo(expression).Type.IsAny(KnownType.FloatingPointNumbers); private static bool IsUserDefinedOperator(TBinaryExpression expression, SemanticModel model) => model.GetEnclosingSymbol(expression.SpanStart) is IMethodSymbol enclosingSymbol && enclosingSymbol.MethodKind == MethodKind.UserDefinedOperator; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/BooleanLiteralUnnecessaryBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class BooleanLiteralUnnecessaryBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S1125"; private ImmutableArray systemBooleanInterfaces; protected enum ErrorLocation { // The BooleanLiteral node is highlighted BoolLiteral, // The BooleanLiteral node and the operator are highlighted together BoolLiteralAndOperator, // Inside a binary expression, the expression that is not a boolean literal is highlighted // e.g. for 'foo() && True', 'foo()' will be highlighted NonBoolLiteralExpression } protected delegate bool IsBooleanLiteralKind(SyntaxNode node); protected abstract SyntaxNode GetLeftNode(SyntaxNode node); protected abstract SyntaxNode GetRightNode(SyntaxNode node); protected abstract SyntaxToken? GetOperatorToken(SyntaxNode node); protected abstract bool IsTrue(SyntaxNode syntaxNode); protected abstract bool IsFalse(SyntaxNode syntaxNode); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(x => { systemBooleanInterfaces = x.Compilation.GetTypeByMetadataName(KnownType.System_Boolean).Interfaces; }); // For C# 7 syntax protected virtual bool IsInsideTernaryWithThrowExpression(SyntaxNode syntaxNode) => false; protected override string MessageFormat => "Remove the unnecessary Boolean literal(s)."; protected BooleanLiteralUnnecessaryBase() : base(DiagnosticId) { } // LogicalAnd (C#) / AndAlso (VB) protected void CheckAndExpression(SonarSyntaxNodeReportingContext context) { if (IsInsideTernaryWithThrowExpression(context.Node) || CheckForNullabilityAndBooleanConstantsReport(context, context.Node, reportOnTrue: true)) { return; } // When we have 'EXPR And True', the true literal is the redundant part CheckForBooleanConstantOnLeft(context, context.Node, IsTrue, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnRight(context, context.Node, IsTrue, ErrorLocation.BoolLiteralAndOperator); // 'EXPR And False' is always False, thus EXPR is the redundant part CheckForBooleanConstantOnLeft(context, context.Node, IsFalse, ErrorLocation.NonBoolLiteralExpression); CheckForBooleanConstantOnRight(context, context.Node, IsFalse, ErrorLocation.NonBoolLiteralExpression); } // LogicalOr (C#) / OrElse (VB) protected void CheckOrExpression(SonarSyntaxNodeReportingContext context) { if (IsInsideTernaryWithThrowExpression(context.Node) || CheckForNullabilityAndBooleanConstantsReport(context, context.Node, reportOnTrue: false)) { return; } // When we have 'EXPR Or False', the false literal is the redundant part CheckForBooleanConstantOnLeft(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnRight(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); // 'EXPR Or True' is always True, thus EXPR is the redundant part CheckForBooleanConstantOnLeft(context, context.Node, IsTrue, ErrorLocation.NonBoolLiteralExpression); CheckForBooleanConstantOnRight(context, context.Node, IsTrue, ErrorLocation.NonBoolLiteralExpression); } protected void CheckEquals(SonarSyntaxNodeReportingContext context) { if (IsInsideTernaryWithThrowExpression(context.Node) || CheckForNullabilityAndBooleanConstantsReport(context, context.Node, reportOnTrue: true)) { return; } CheckForBooleanConstantOnLeft(context, context.Node, IsTrue, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnLeft(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnRight(context, context.Node, IsTrue, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnRight(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); } protected void CheckNotEquals(SonarSyntaxNodeReportingContext context) { if (IsInsideTernaryWithThrowExpression(context.Node) || CheckForNullabilityAndBooleanConstantsReport(context, context.Node, reportOnTrue: false)) { return; } CheckForBooleanConstantOnLeft(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnLeft(context, context.Node, IsTrue, ErrorLocation.BoolLiteral); CheckForBooleanConstantOnRight(context, context.Node, IsFalse, ErrorLocation.BoolLiteralAndOperator); CheckForBooleanConstantOnRight(context, context.Node, IsTrue, ErrorLocation.BoolLiteral); } protected void CheckTernaryExpressionBranches(SonarSyntaxNodeReportingContext context, SyntaxNode thenBranch, SyntaxNode elseBranch) { var thenNoParantheses = Language.Syntax.RemoveParentheses(thenBranch); var elseNoParantheses = Language.Syntax.RemoveParentheses(elseBranch); var thenIsBooleanLiteral = IsTrue(thenNoParantheses) || IsFalse(thenNoParantheses); var elseIsBooleanLiteral = IsTrue(elseNoParantheses) || IsFalse(elseNoParantheses); var bothSideBool = thenIsBooleanLiteral && elseIsBooleanLiteral; var bothSideTrue = IsTrue(thenNoParantheses) && IsTrue(elseNoParantheses); var bothSideFalse = IsFalse(thenNoParantheses) && IsFalse(elseNoParantheses); if (bothSideBool && !bothSideFalse && !bothSideTrue) { context.ReportIssue(SupportedDiagnostics[0], thenBranch.CreateLocation(elseBranch)); } if (thenIsBooleanLiteral ^ elseIsBooleanLiteral) { // one side is boolean literal, the other is NOT boolean literal var booleanLiteralSide = thenIsBooleanLiteral ? thenBranch : elseBranch; context.ReportIssue(SupportedDiagnostics[0], booleanLiteralSide); } } protected bool CheckForNullabilityAndBooleanConstantsReport(SonarSyntaxNodeReportingContext context, SyntaxNode node, bool reportOnTrue) { var left = Language.Syntax.RemoveParentheses(GetLeftNode(node)); var right = Language.Syntax.RemoveParentheses(GetRightNode(node)); if (right is null // Avoids DeclarationPattern or RecursivePattern || TypeShouldBeIgnored(left, context.Model) || TypeShouldBeIgnored(right, context.Model)) { return true; } var leftIsTrue = IsTrue(left); var leftIsFalse = IsFalse(left); var rightIsTrue = IsTrue(right); var rightIsFalse = IsFalse(right); if ((leftIsTrue || leftIsFalse) && (rightIsTrue || rightIsFalse)) { var bothAreSame = (leftIsTrue && rightIsTrue) || (leftIsFalse && rightIsFalse); var errorLocation = bothAreSame ? CalculateExtendedLocation(node, false) : CalculateExtendedLocation(node, reportOnTrue == leftIsTrue); context.ReportIssue(SupportedDiagnostics[0], errorLocation); return true; } return false; } protected void CheckForBooleanConstant(SonarSyntaxNodeReportingContext context, SyntaxNode node, IsBooleanLiteralKind isBooleanLiteralKind, ErrorLocation errorLocation, bool isLeftSide) { if (isBooleanLiteralKind(node) && GetLocation() is { } location) { context.ReportIssue(SupportedDiagnostics[0], location); } Location GetLocation() => errorLocation switch { ErrorLocation.BoolLiteral => node.GetLocation(), ErrorLocation.BoolLiteralAndOperator => CalculateExtendedLocation(node.Parent, isLeftSide), ErrorLocation.NonBoolLiteralExpression => CalculateExtendedLocation(node.Parent, !isLeftSide), _ => null, }; } private void CheckForBooleanConstantOnLeft(SonarSyntaxNodeReportingContext context, SyntaxNode node, IsBooleanLiteralKind isBooleanLiteralKind, ErrorLocation errorLocation) => CheckForBooleanConstant(context, GetLeftNode(node), isBooleanLiteralKind, errorLocation, isLeftSide: true); private void CheckForBooleanConstantOnRight(SonarSyntaxNodeReportingContext context, SyntaxNode node, IsBooleanLiteralKind isBooleanLiteralKind, ErrorLocation errorLocation) => CheckForBooleanConstant(context, GetRightNode(node), isBooleanLiteralKind, errorLocation, isLeftSide: false); private Location CalculateExtendedLocation(SyntaxNode parent, bool isLeftSide) { if (GetOperatorToken(parent) is not { } token) { return null; } return isLeftSide ? parent.CreateLocation(token) : token.CreateLocation(parent); } private bool TypeShouldBeIgnored(SyntaxNode node, SemanticModel model) { var type = model.GetTypeInfo(node).Type; return type.IsNullableBoolean() || type is ITypeParameterSymbol || type.Is(KnownType.System_Object) || systemBooleanInterfaces.Any(x => x.Equals(type)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/BypassingAccessibilityBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class BypassingAccessibilityBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S3011"; protected const string MessageFormat = "Make sure that this accessibility bypass is safe here."; protected BypassingAccessibilityBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var t = Language.Tracker.FieldAccess; t.Track(input, t.WhenRead(), t.MatchField(new MemberDescriptor(KnownType.System_Reflection_BindingFlags, "NonPublic"))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CatchRethrowBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class CatchRethrowBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TCatchClause : SyntaxNode { internal const string DiagnosticId = "S2737"; protected abstract TCatchClause[] AllCatches(SyntaxNode node); protected abstract SyntaxNode DeclarationType(TCatchClause catchClause); protected abstract bool HasFilter(TCatchClause catchClause); protected abstract bool ContainsOnlyThrow(TCatchClause currentCatch); protected override string MessageFormat => "Add logic to this catch clause or eliminate it and rethrow the exception automatically."; protected CatchRethrowBase() : base(DiagnosticId) { } protected void RaiseOnInvalidCatch(SonarSyntaxNodeReportingContext context) { var catches = AllCatches(context.Node); var caughtExceptionTypes = new Lazy(() => ComputeExceptionTypes(catches, context.Model)); var redundantCatches = new HashSet(); // We handle differently redundant catch clauses (just throw inside) that are followed by a non-redundant catch clause, because if they are removed, the method behavior will change. var followingCatchesOnlyThrow = true; for (var i = catches.Length - 1; i >= 0; i--) { var currentCatch = catches[i]; if (ContainsOnlyThrow(currentCatch)) { if (!HasFilter(currentCatch) // Make sure we report only catch clauses that will not change the method behavior if removed. && (followingCatchesOnlyThrow || IsRedundantToFollowingCatches(i, catches, caughtExceptionTypes.Value, redundantCatches))) { redundantCatches.Add(currentCatch); } } else { followingCatchesOnlyThrow = false; } } foreach (var redundantCatch in redundantCatches) { context.ReportIssue(Rule, redundantCatch); } } private static bool IsRedundantToFollowingCatches(int catchIndex, TCatchClause[] catches, INamedTypeSymbol[] caughtExceptionTypes, ISet redundantCatches) { var currentType = caughtExceptionTypes[catchIndex]; for (var i = catchIndex + 1; i < caughtExceptionTypes.Length; i++) { var nextCatchType = caughtExceptionTypes[i]; if (nextCatchType is null || currentType.DerivesOrImplements(nextCatchType)) { return redundantCatches.Contains(catches[i]); } } return true; } private INamedTypeSymbol[] ComputeExceptionTypes(IEnumerable catches, SemanticModel model) => catches.Select(x => DeclarationType(x) is { } declarationType ? model.GetTypeInfo(declarationType).Type as INamedTypeSymbol : null).ToArray(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CertificateValidationCheckBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class CertificateValidationCheckBase< TSyntaxKind, TArgumentSyntax, TExpressionSyntax, TIdentifierNameSyntax, TAssignmentExpressionSyntax, TInvocationExpressionSyntax, TParameterSyntax, TVariableSyntax, TLambdaSyntax, TMemberAccessSyntax > : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TArgumentSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode where TIdentifierNameSyntax : SyntaxNode where TAssignmentExpressionSyntax : SyntaxNode where TInvocationExpressionSyntax : SyntaxNode where TParameterSyntax : SyntaxNode where TVariableSyntax : SyntaxNode where TLambdaSyntax : SyntaxNode where TMemberAccessSyntax : SyntaxNode { private const string SecondaryMessage = "This function trusts all certificates."; private const string DiagnosticId = "S4830"; internal /* for testing */ abstract MethodParameterLookupBase CreateParameterLookup(SyntaxNode argumentListNode, IMethodSymbol method); protected abstract HashSet MethodDeclarationKinds { get; } protected abstract HashSet TypeDeclarationKinds { get; } protected abstract Location ExpressionLocation(SyntaxNode expression); protected abstract void SplitAssignment(TAssignmentExpressionSyntax assignment, out TIdentifierNameSyntax leftIdentifier, out TExpressionSyntax right); protected abstract IEqualityComparer CreateNodeEqualityComparer(); protected abstract TExpressionSyntax[] FindReturnAndThrowExpressions(InspectionContext c, SyntaxNode block); protected abstract bool IsTrueLiteral(TExpressionSyntax expression); protected abstract TExpressionSyntax VariableInitializer(TVariableSyntax variable); protected abstract ImmutableArray LambdaLocations(InspectionContext c, TLambdaSyntax lambda); protected abstract SyntaxNode LocalVariableScope(TVariableSyntax variable); protected abstract SyntaxNode ExtractArgumentExpressionNode(SyntaxNode expression); protected abstract SyntaxNode SyntaxFromReference(SyntaxReference reference); protected override string MessageFormat => "Enable server certificate validation on this SSL/TLS connection"; protected CertificateValidationCheckBase() : base(DiagnosticId) { } protected void CheckAssignmentSyntax(SonarSyntaxNodeReportingContext c) { SplitAssignment((TAssignmentExpressionSyntax)c.Node, out var leftIdentifier, out var right); if (leftIdentifier is not null && right is not null && c.Model.GetSymbolInfo(leftIdentifier).Symbol is IPropertySymbol left && IsValidationDelegateType(left.Type)) { TryReportLocations(new InspectionContext(c), leftIdentifier.GetLocation(), right); } } protected void CheckConstructorParameterSyntax(SonarSyntaxNodeReportingContext c) { if (c.Model.GetSymbolInfo(c.Node).Symbol is IMethodSymbol ctor) { MethodParameterLookupBase methodParamLookup = null; // Cache, there might be more of them // Validation for TryGetNonParamsSyntax, ParamArray/params and therefore array arguments are not inspected foreach (var param in ctor.Parameters.Where(x => !x.IsParams && IsValidationDelegateType(x.Type))) { methodParamLookup ??= CreateParameterLookup(c.Node, ctor); if (methodParamLookup.TryGetNonParamsSyntax(param, out var expression)) { TryReportLocations(new InspectionContext(c), ExpressionLocation(expression), expression); } } } } protected ImmutableArray ParamLocations(InspectionContext c, TParameterSyntax param) { var containingMethodDeclaration = param.FirstAncestorOrSelf((SyntaxNode x) => Language.Syntax.IsAnyKind(x, MethodDeclarationKinds)); if (containingMethodDeclaration is null || !c.VisitedMethods.Add(containingMethodDeclaration)) { return ImmutableArray.Empty; } // Validation for TryGetNonParamsSyntax, ParamArray/params and therefore array arguments are not inspected var paramSymbol = containingMethodDeclaration.EnsureCorrectSemanticModelOrDefault(c.Context.Model)?.GetDeclaredSymbol(containingMethodDeclaration) is IMethodSymbol containingMethod && Language.Syntax.NodeIdentifier(param)?.ValueText is { } identText ? containingMethod.Parameters.Single(x => x.Name == identText) : null; if (paramSymbol is null || paramSymbol.IsParams) { return ImmutableArray.Empty; } var methodSymbol = paramSymbol.ContainingSymbol as IMethodSymbol; return FindInvocationList(c.Context, FindRootTypeDeclaration(param), methodSymbol) .SelectMany(x => CreateParameterLookup(x, methodSymbol).TryGetNonParamsSyntax(paramSymbol, out var expr) && expr is not null ? CallStackSublocations(c, expr) : Enumerable.Empty()) .ToImmutableArray(); } protected ImmutableArray BlockLocations(InspectionContext c, SyntaxNode block) { var ret = ImmutableArray.CreateBuilder(); if (block is not null) { var returnExpressions = FindReturnAndThrowExpressions(c, block); // There must be at least one return, that does not return true to be compliant. There can be NULL from standalone Throw statement. if (returnExpressions.All(IsTrueLiteral)) { ret.AddRange(returnExpressions.Select(x => x.GetLocation())); } } return ret.ToImmutable(); } protected virtual SyntaxNode FindRootTypeDeclaration(SyntaxNode node) { SyntaxNode candidate; var current = node.FirstAncestorOrSelf(IsTypeDeclaration); while (current is not null && (candidate = current.Parent.FirstAncestorOrSelf(IsTypeDeclaration)) is not null) // Search for parent of nested type { current = candidate; } return current; } private bool IsTypeDeclaration(SyntaxNode node) => Language.Syntax.IsAnyKind(node, TypeDeclarationKinds); private void TryReportLocations(InspectionContext c, Location primaryLocation, SyntaxNode expression) { var locations = ArgumentLocations(c, expression); if (!locations.IsEmpty) { // Report both, assignment as well as all implementation occurrences c.Context.ReportIssue(Rule, primaryLocation, locations.ToSecondary(SecondaryMessage)); } } private static bool IsValidationDelegateType(ITypeSymbol type) { if (type.Is(KnownType.System_Net_Security_RemoteCertificateValidationCallback)) { return true; } if (type is INamedTypeSymbol namedSymbol && type.OriginalDefinition.Is(KnownType.System_Func_T1_T2_T3_T4_TResult)) { // HttpClientHandler.ServerCertificateCustomValidationCallback uses Func // We're actually looking for Func var parameters = namedSymbol.DelegateInvokeMethod.Parameters; return parameters.Length == 4 // And it should! T1, T2, T3, T4 && parameters[0].Type.IsClassOrStruct() // We don't care about common (Object) nor specific (HttpRequestMessage) type of Sender && parameters[1].Type.Is(KnownType.System_Security_Cryptography_X509Certificates_X509Certificate2) && parameters[2].Type.Is(KnownType.System_Security_Cryptography_X509Certificates_X509Chain) && parameters[3].Type.Is(KnownType.System_Net_Security_SslPolicyErrors) && namedSymbol.DelegateInvokeMethod.ReturnType.Is(KnownType.System_Boolean); } return false; } private ImmutableArray ArgumentLocations(InspectionContext c, SyntaxNode expression) { switch (ExtractArgumentExpressionNode(expression)) { case TIdentifierNameSyntax identifier: if (SymbolFromCorrectModel(identifier, c.Context.Model) is { DeclaringSyntaxReferences.Length: 1 } identSymbol) { return IdentifierLocations(c, SyntaxFromReference(identSymbol.DeclaringSyntaxReferences.Single())); } break; case TLambdaSyntax lambda: return LambdaLocations(c, lambda); case TInvocationExpressionSyntax invocation: return VisitInvocation(invocation, c); case TMemberAccessSyntax memberAccess: if (SymbolFromCorrectModel(memberAccess, c.Context.Model) is { } maSymbol && maSymbol.IsInType(KnownType.System_Net_Http_HttpClientHandler) && maSymbol.Name == "DangerousAcceptAnyServerCertificateValidator") { return new[] { memberAccess.GetLocation() }.ToImmutableArray(); } break; } return ImmutableArray.Empty; } private ImmutableArray VisitInvocation(TInvocationExpressionSyntax invocation, InspectionContext c) { if (SymbolFromCorrectModel(invocation, c.Context.Model) is IMethodSymbol invSymbol && (invSymbol.PartialImplementationPart?.DeclaringSyntaxReferences ?? invSymbol.DeclaringSyntaxReferences).SingleOrDefault() is { } reference) { var syntax = SyntaxFromReference(reference); c.VisitedMethods.Add(syntax); return InvocationLocations(c, syntax); } return ImmutableArray.Empty; } private ImmutableArray IdentifierLocations(InspectionContext c, SyntaxNode syntax) => syntax switch { TParameterSyntax parameter => ParamLocations(c, parameter), // Value arrived as a parameter TVariableSyntax variable => VariableLocations(c, variable), // Value passed as variable _ => Language.Syntax.IsAnyKind(syntax, MethodDeclarationKinds) ? BlockLocations(c, syntax) // Direct delegate name : ImmutableArray.Empty, }; private ImmutableArray VariableLocations(InspectionContext c, TVariableSyntax variable) { var allAssignedExpressions = new List(); var parentScope = LocalVariableScope(variable); if (parentScope is not null) { var identText = Language.Syntax.NodeIdentifier(variable)?.ValueText; allAssignedExpressions.AddRange(parentScope.DescendantNodes().OfType() .Select(x => { SplitAssignment(x, out var leftIdentifier, out var right); return new { leftIdentifier, right }; }) .Where(x => x.leftIdentifier is not null && Language.Syntax.NodeIdentifier(x.leftIdentifier) is { } identifier && identifier.ValueText == identText) .Select(x => x.right)); } var initializer = VariableInitializer(variable); if (initializer is not null) // Declarator initializer is counted as (default) assignment as well { allAssignedExpressions.Add(initializer); } return MultiExpressionSublocations(c, allAssignedExpressions); } private ImmutableArray InvocationLocations(InspectionContext c, SyntaxNode method) { // Ignore all return statements with recursive call. Result depends on returns that could return compliant validator. var returnExpressionSublocationsList = FindReturnAndThrowExpressions(c, method).Where(x => !IsVisited(c, x)); return MultiExpressionSublocations(c, returnExpressionSublocationsList); } private bool IsVisited(InspectionContext c, SyntaxNode expression) => expression is TInvocationExpressionSyntax invocation && SymbolFromCorrectModel(invocation, c.Context.Model) is { } symbol && symbol.DeclaringSyntaxReferences.Select(SyntaxFromReference).Any(c.VisitedMethods.Contains); private ImmutableArray MultiExpressionSublocations(InspectionContext c, IEnumerable expressions) { var exprSublocationsList = expressions.Distinct(CreateNodeEqualityComparer()) .Select(x => CallStackSublocations(c, x)) .ToArray(); // If there's at least one concurrent expression, that returns compliant delegate, then there's some logic and this scope is compliant if (exprSublocationsList.Any(x => x.IsEmpty)) { return ImmutableArray.Empty; } return exprSublocationsList.SelectMany(x => x).ToImmutableArray(); // Else every return statement is noncompliant } private ImmutableArray CallStackSublocations(InspectionContext c, SyntaxNode expression) { var lst = ArgumentLocations(c, expression); if (!lst.IsEmpty) // There's noncompliant issue in this chain { var loc = expression.GetLocation(); if (!lst.Any(x => x.SourceSpan.IntersectsWith(loc.SourceSpan))) { // Add 2nd, 3rd, 4th etc //Secondary marker. If it is not marked already from direct Delegate name or direct Lambda occurrence return lst.Concat([loc]).ToImmutableArray(); } } return lst; } private ImmutableArray FindInvocationList(SonarSyntaxNodeReportingContext c, SyntaxNode root, IMethodSymbol method) { if (root is null || method is null) { return ImmutableArray.Empty; } var ret = ImmutableArray.CreateBuilder(); foreach (var invocation in root.DescendantNodesAndSelf().OfType()) { if (Language.Syntax.InvocationIdentifier(invocation) is { } invocationIdentifier && invocationIdentifier.ValueText.Equals(method.Name, Language.NameComparison) && SymbolFromCorrectModel(invocation, c.Model) is { } symbol && symbol.Equals(method)) { ret.Add(invocation); } } return ret.ToImmutable(); } private static ISymbol SymbolFromCorrectModel(SyntaxNode node, SemanticModel model) => node.EnsureCorrectSemanticModelOrDefault(model) is { } correctModel && correctModel.GetSymbolInfo(node).Symbol is { } symbol ? symbol : null; protected readonly struct InspectionContext { public readonly SonarSyntaxNodeReportingContext Context; public readonly HashSet VisitedMethods; public InspectionContext(SonarSyntaxNodeReportingContext context) { Context = context; VisitedMethods = []; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CheckFileLicenseBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class CheckFileLicenseBase : ParametrizedDiagnosticAnalyzer { internal const string DiagnosticId = "S1451"; internal const string HeaderFormatPropertyKey = nameof(HeaderFormat); internal const string IsRegularExpressionPropertyKey = nameof(IsRegularExpression); protected const string HeaderFormatRuleParameterKey = "headerFormat"; private const string IsRegularExpressionRuleParameterKey = "isRegularExpression"; private const string IsRegularExpressionDefaultValue = "false"; private const string MessageFormat = "Add or update the header of this file."; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } public abstract string HeaderFormat { get; set; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); [RuleParameter(IsRegularExpressionRuleParameterKey, PropertyType.Boolean, "Whether the headerFormat is a regular expression.", IsRegularExpressionDefaultValue)] public bool IsRegularExpression { get; set; } = bool.Parse(IsRegularExpressionDefaultValue); protected CheckFileLicenseBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterTreeAction(Language.GeneratedCodeRecognizer, c => { if (HeaderFormat == null) { return; } if (IsRegularExpression && !IsRegexPatternValid(HeaderFormat)) { throw new InvalidOperationException($"Invalid regular expression: {HeaderFormat}"); } var firstNode = c.Tree.GetRoot().ChildTokens().FirstOrDefault().Parent; if (!HasValidLicenseHeader(firstNode)) { var properties = CreateDiagnosticProperties(); c.ReportIssue(rule, Location.Create(c.Tree, TextSpan.FromBounds(0, 0)), properties); } }); private static bool IsRegexPatternValid(string pattern) { try { Regex.Match(string.Empty, pattern, RegexOptions.None, Constants.DefaultRegexTimeout); return true; } catch (ArgumentException) { return false; } } private bool HasValidLicenseHeader(SyntaxNode node) { if (node == null || !node.HasLeadingTrivia) { return false; } var trivias = node.GetLeadingTrivia(); var header = trivias.ToString(); return header != null && AreHeadersEqual(header); } private bool AreHeadersEqual(string currentHeader) { var unixEndingHeader = currentHeader.Replace("\r\n", "\n"); var unixEndingHeaderFormat = HeaderFormat.Replace("\r\n", "\n").Replace("\\r\\n", "\n"); if (!IsRegularExpression && !unixEndingHeaderFormat.EndsWith("\n")) { // In standard text mode, we want to be sure that the matched header is on its own line, with nothing else on the same line. unixEndingHeaderFormat += "\n"; } return IsRegularExpression ? SafeRegex.IsMatch(unixEndingHeader, unixEndingHeaderFormat, RegexOptions.Singleline) : unixEndingHeader.StartsWith(unixEndingHeaderFormat, StringComparison.Ordinal); } private ImmutableDictionary CreateDiagnosticProperties() => ImmutableDictionary.Empty .Add(HeaderFormatPropertyKey, HeaderFormat) .Add(IsRegularExpressionPropertyKey, IsRegularExpression.ToString()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ClassNamedExceptionBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ClassNamedExceptionBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S2166"; protected override string MessageFormat => "Rename this class to remove \"Exception\" or correct its inheritance."; protected ClassNamedExceptionBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } classIdentifier && classIdentifier.ValueText.EndsWith("Exception", StringComparison.InvariantCultureIgnoreCase) && c.Model.GetDeclaredSymbol(c.Node) is INamedTypeSymbol { } classSymbol && !classSymbol.DerivesFrom(KnownType.System_Exception) && !classSymbol.Implements(KnownType.System_Runtime_InteropServices_Exception)) { c.ReportIssue(Rule, classIdentifier); } }, Language.SyntaxKind.ClassAndModuleDeclarations); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ClassNotInstantiatableBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ClassNotInstantiatableBase : SonarDiagnosticAnalyzer where TBaseTypeSyntax : SyntaxNode where TSyntaxKind : struct { protected const string DiagnosticId = "S3453"; protected abstract IEnumerable CollectRemovableDeclarations(INamedTypeSymbol namedType, Compilation compilation, string messageArg); protected override string MessageFormat => "This {0} can't be instantiated; make {1} 'public'."; protected ClassNotInstantiatableBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction(CheckClassWithOnlyUnusedPrivateConstructors, SymbolKind.NamedType); private bool IsClassTypeDeclaration(SyntaxNode node) => Language.Syntax.IsAnyKind(node, Language.SyntaxKind.ClassAndRecordDeclarations); private bool IsAnyConstructorCalled(INamedTypeSymbol namedType, IEnumerable typeDeclarations) => typeDeclarations .Select(typeDeclaration => new { typeDeclaration.NodeAndModel.Model, DescendantNodes = typeDeclaration.NodeAndModel.Node.DescendantNodes().ToList() }) .Any(descendants => IsAnyConstructorToCurrentType(descendants.DescendantNodes, namedType, descendants.Model) || IsAnyNestedTypeExtendingCurrentType(descendants.DescendantNodes, namedType, descendants.Model)); private void CheckClassWithOnlyUnusedPrivateConstructors(SonarSymbolReportingContext context) { var namedType = (INamedTypeSymbol)context.Symbol; if (!IsNonStaticClassWithNoAttributes(namedType) || DerivesFromSafeHandle(namedType)) { return; } var members = namedType.GetMembers(); var constructors = GetConstructors(members).Where(x => !x.IsImplicitlyDeclared).ToList(); if (!HasOnlyCandidateConstructors(constructors) || HasOnlyStaticMembers(members.Except(constructors).ToList())) { return; } var messageArg = constructors.Count > 1 ? "at least one of its constructors" : "its constructor"; var removableDeclarationsAndErrors = CollectRemovableDeclarations(namedType, context.Compilation, messageArg).ToList(); if (!IsAnyConstructorCalled(namedType, removableDeclarationsAndErrors)) { foreach (var typeDeclaration in removableDeclarationsAndErrors) { context.ReportIssue(Language.GeneratedCodeRecognizer, typeDeclaration.Diagnostic); } } } private bool IsAnyNestedTypeExtendingCurrentType(IEnumerable descendantNodes, INamedTypeSymbol namedType, SemanticModel semanticModel) => descendantNodes .Where(IsClassTypeDeclaration) .Select(x => (semanticModel.GetDeclaredSymbol(x) as ITypeSymbol)?.BaseType) .WhereNotNull() .Any(baseType => baseType.OriginalDefinition.DerivesFrom(namedType)); private bool IsAnyConstructorToCurrentType(IEnumerable descendantNodes, INamedTypeSymbol namedType, SemanticModel semanticModel) => descendantNodes .Where(x => Language.Syntax.IsAnyKind(x, Language.SyntaxKind.ObjectCreationExpressions)) .Select(ctor => semanticModel.GetSymbolInfo(ctor).Symbol as IMethodSymbol) .WhereNotNull() .Any(ctor => Equals(ctor.ContainingType.OriginalDefinition, namedType)); private static bool HasNonPrivateConstructor(IEnumerable constructors) => constructors.Any(method => method.DeclaredAccessibility != Accessibility.Private); private static IEnumerable GetConstructors(IEnumerable members) => members .OfType() .Where(method => method.MethodKind == MethodKind.Constructor); private static bool HasOnlyStaticMembers(ICollection members) => members.Any() && members.All(member => member.IsStatic); private static bool IsNonStaticClassWithNoAttributes(INamedTypeSymbol namedType) => namedType.IsClass() && !namedType.IsStatic && !namedType.GetAttributes().Any(); private static bool HasOnlyCandidateConstructors(ICollection constructors) => constructors.Any() && !HasNonPrivateConstructor(constructors) && constructors.All(c => !c.GetAttributes().Any()); private static bool DerivesFromSafeHandle(ITypeSymbol typeSymbol) => typeSymbol.DerivesFrom(KnownType.System_Runtime_InteropServices_SafeHandle); protected class ConstructorContext { public NodeAndModel NodeAndModel { get; } public Diagnostic Diagnostic { get; } public ConstructorContext(NodeAndModel nodeAndModel, Diagnostic diagnostic) { NodeAndModel = nodeAndModel; Diagnostic = diagnostic; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ClassShouldNotBeEmptyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ClassShouldNotBeEmptyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TDeclarationSyntax : SyntaxNode { private const string DiagnosticId = "S2094"; private static readonly ImmutableArray BaseClassesToIgnore = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_RazorPages_PageModel, KnownType.System_Attribute, KnownType.System_Exception); private static readonly IEnumerable IgnoredNames = ["AssemblyDoc", "NamespaceDoc"]; // https://github.com/Doraku/DefaultDocumentation private static readonly IEnumerable IgnoredSuffixes = ["Command", "Event", "Message", "Query"]; protected abstract bool IsEmptyAndNotPartial(SyntaxNode node); protected abstract TDeclarationSyntax GetIfHasDeclaredBaseClassOrInterface(SyntaxNode node); protected abstract bool HasInterfaceOrGenericBaseClass(TDeclarationSyntax declaration); protected abstract bool HasAnyAttribute(SyntaxNode node); protected abstract string DeclarationTypeKeyword(SyntaxNode node); protected abstract bool HasConditionalCompilationDirectives(SyntaxNode node); protected override string MessageFormat => "Remove this empty {0}, write its code or make it an \"interface\"."; protected ClassShouldNotBeEmptyBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier && IsEmptyAndNotPartial(c.Node) && !HasAnyAttribute(c.Node) && !HasConditionalCompilationDirectives(c.Node) && !ShouldIgnoreBecauseOfName(identifier) && !ShouldIgnoreBecauseOfBaseClassOrInterface(c.Node, c.Model)) { c.ReportIssue(Rule, identifier, DeclarationTypeKeyword(c.Node)); } }, Language.SyntaxKind.ClassAndRecordDeclarations); private static bool ShouldIgnoreBecauseOfName(SyntaxToken identifier) => IgnoredNames.Contains(identifier.ValueText) || IgnoredSuffixes.Any(identifier.ValueText.EndsWith); private bool ShouldIgnoreBecauseOfBaseClassOrInterface(SyntaxNode node, SemanticModel model) => GetIfHasDeclaredBaseClassOrInterface(node) is { } declaration && (HasInterfaceOrGenericBaseClass(declaration) || ShouldIgnoreType(declaration, model) || HasNonPublicDefaultConstructor(declaration, model)); private static bool ShouldIgnoreType(TDeclarationSyntax node, SemanticModel model) => model.GetDeclaredSymbol(node) is INamedTypeSymbol classSymbol && (classSymbol.BaseType is { IsAbstract: true } || classSymbol.DerivesFromAny(BaseClassesToIgnore) || classSymbol.Interfaces.Any(x => !x.Is(KnownType.System_IEquatable_T))); // every record type implicitly implements System.IEquatable private static bool HasNonPublicDefaultConstructor(TDeclarationSyntax declaration, SemanticModel model) => model.GetDeclaredSymbol(declaration) is INamedTypeSymbol classSymbol && classSymbol.BaseType.Constructors.FirstOrDefault(x => x.Parameters.Length == 0) is { } constructor && (constructor.GetEffectiveAccessibility() < classSymbol.DeclaredAccessibility // GetEffectiveAccessibility is not handling Protected || (constructor.DeclaredAccessibility == Accessibility.Protected && classSymbol.DeclaredAccessibility > Accessibility.Protected) || (constructor.GetEffectiveAccessibility() == Accessibility.Internal && classSymbol.DeclaredAccessibility == Accessibility.Protected)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CognitiveComplexityBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; namespace SonarAnalyzer.Core.Rules { public abstract class CognitiveComplexityBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S3776"; private const string MessageFormat = "Refactor this {0} to reduce its Cognitive Complexity from {1} to the {2} allowed."; private const int DefaultThreshold = 15; private const int DefaultPropertyThreshold = 3; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } [RuleParameter("threshold", PropertyType.Integer, "The maximum authorized complexity.", DefaultThreshold)] public int Threshold { get; set; } = DefaultThreshold; [RuleParameter("propertyThreshold", PropertyType.Integer, "The maximum authorized complexity in a property.", DefaultPropertyThreshold)] public int PropertyThreshold { get; set; } = DefaultPropertyThreshold; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected CognitiveComplexityBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected void CheckComplexity(SonarSyntaxNodeReportingContext context, Func nodeSelector, Func getLocationToReport, Func getComplexity, string declarationType, int threshold) where TSyntax : SyntaxNode { var syntax = (TSyntax)context.Node; var nodeToAnalyze = nodeSelector(syntax); if (nodeToAnalyze == null) { return; } var metric = getComplexity(nodeToAnalyze); if (metric.Complexity > Threshold) { context.ReportIssue(rule, getLocationToReport(syntax), metric.Locations, declarationType, metric.Complexity.ToString(), threshold.ToString()); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CollectionEmptinessCheckingBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class CollectionEmptinessCheckingBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S1155"; protected CollectionEmptinessCheckingBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var binaryLeft = Language.Syntax.BinaryExpressionLeft(c.Node); var binaryRight = Language.Syntax.BinaryExpressionRight(c.Node); if (Language.ExpressionNumericConverter.ConstantIntValue(binaryLeft) is { } left) { CheckExpression(c, binaryRight, left, Language.Syntax.ComparisonKind(c.Node).Mirror()); } else if (Language.ExpressionNumericConverter.ConstantIntValue(binaryRight) is { } right) { CheckExpression(c, binaryLeft, right, Language.Syntax.ComparisonKind(c.Node)); } }, Language.SyntaxKind.ComparisonKinds); private void CheckExpression(SonarSyntaxNodeReportingContext context, SyntaxNode expression, int constant, ComparisonKind comparison) { if (comparison.Compare(constant).IsEmptyOrNotEmpty() && TryGetCountCall(expression, context.Model, out var location, out var typeArgument)) { context.ReportIssue(Rule, location, typeArgument); } } private bool TryGetCountCall(SyntaxNode expression, SemanticModel model, out Location countLocation, out string typeArgument) { if (Language.Syntax.NodeIdentifier(expression) is { } identifier && identifier.ValueText == nameof(Enumerable.Count) && model.GetSymbolInfo(identifier.Parent.Parent).Symbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) && methodSymbol.ReceiverType is INamedTypeSymbol receiverType) { countLocation = identifier.Parent.GetLocation(); typeArgument = (methodSymbol.TypeArguments.Any() ? methodSymbol.TypeArguments : receiverType.TypeArguments).Single().ToDisplayString(); return true; } else { countLocation = null; typeArgument = null; return false; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CommentKeywordBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class CommentKeywordBase : SonarDiagnosticAnalyzer { private const string ToDoKeyword = "TODO"; protected const string ToDoDiagnosticId = "S1135"; protected const string ToDoMessageFormat = "Complete the task associated to this '" + ToDoKeyword + "' comment."; private const string FixMeKeyword = "FIXME"; protected const string FixMeDiagnosticId = "S1134"; protected const string FixMeMessageFormat = "Take the required action to fix the issue indicated by this '" + FixMeKeyword + "' comment."; private readonly DiagnosticDescriptor toDoRule; private readonly DiagnosticDescriptor fixMeRule; protected abstract ILanguageFacade Language { get; } protected abstract bool IsComment(SyntaxTrivia trivia); public sealed override ImmutableArray SupportedDiagnostics { get; } protected CommentKeywordBase() { toDoRule = Language.CreateDescriptor(ToDoDiagnosticId, ToDoMessageFormat); fixMeRule = Language.CreateDescriptor(FixMeDiagnosticId, FixMeMessageFormat); SupportedDiagnostics = ImmutableArray.Create(toDoRule, fixMeRule); } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction( Language.GeneratedCodeRecognizer, c => { var comments = c.Tree.GetRoot().DescendantTrivia() .Where(trivia => IsComment(trivia)); foreach (var comment in comments) { foreach (var location in GetKeywordLocations(c.Tree, comment, ToDoKeyword)) { c.ReportIssue(toDoRule, location); } foreach (var location in GetKeywordLocations(c.Tree, comment, FixMeKeyword)) { c.ReportIssue(fixMeRule, location); } } }); private static IEnumerable GetKeywordLocations(SyntaxTree tree, SyntaxTrivia comment, string word) { var text = comment.ToString(); return AllIndexesOf(text, word) .Where(i => IsWordAt(text, i, word.Length)) .Select( i => { var startLocation = comment.SpanStart + i; var location = Location.Create(tree, TextSpan.FromBounds(startLocation, startLocation + word.Length)); return location; }); } private static IEnumerable AllIndexesOf(string text, string value, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) { var index = 0; while ((index = text.IndexOf(value, index, text.Length - index, comparisonType)) != -1) { yield return index; index += value.Length; } } private static bool IsWordAt(string text, int index, int count) { var leftBoundary = true; if (index > 0) { leftBoundary = !char.IsLetterOrDigit(text[index - 1]); } var rightBoundary = true; var rightOffset = index + count; if (rightOffset < text.Length) { rightBoundary = !char.IsLetterOrDigit(text[rightOffset]); } return leftBoundary && rightBoundary; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/CommentsShouldNotBeEmptyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class CommentsShouldNotBeEmptyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4663"; protected abstract string GetCommentText(SyntaxTrivia trivia); protected abstract bool IsValidTriviaType(SyntaxTrivia trivia); protected override string MessageFormat => "Remove this empty comment"; protected CommentsShouldNotBeEmptyBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction(Language.GeneratedCodeRecognizer, c => { foreach (var token in c.Tree.GetRoot().DescendantTokens()) { // Hotpath: Don't allocate the trivia enumerable if not needed if (token.HasLeadingTrivia) { CheckTrivia(c, token.LeadingTrivia); } if (token.HasTrailingTrivia) { CheckTrivia(c, token.TrailingTrivia); } } }); private void CheckTrivia(SonarSyntaxTreeReportingContext context, IEnumerable trivia) { var partitions = PartitionComments(trivia); if (partitions is null) { return; } foreach (var partition in partitions.Where(trivia => trivia.Any() && trivia.All(x => string.IsNullOrWhiteSpace(GetCommentText(x))))) { var location = partition.First().GetLocation(); var secondary = partition.Skip(1).Select(x => x.GetLocation().ToSecondary(MessageFormat)); context.ReportIssue(Rule, location, secondary); } } private List> PartitionComments(IEnumerable trivia) { // Hotpath: avoid unnecessary allocations List> partitions = null; List current = null; var firstEndOfLineFound = false; foreach (var trivium in trivia) { if (IsSimpleComment(trivium)) { AddTriviaToPartition(ref current, trivium, ref firstEndOfLineFound); } // This is for the case, of two different comment types, for example: // // // /// else if (IsValidTriviaType(trivium)) // valid but not "//", because of the upper if { CloseCurrentPartition(ref current, ref partitions, ref firstEndOfLineFound); // all comments except single-line comments are parsed as a block already. AddTriviaToPartition(ref current, trivium, ref firstEndOfLineFound); CloseCurrentPartition(ref current, ref partitions, ref firstEndOfLineFound); } // This handles an empty line, for example: // // some comment \n <- EOL found, firstEndOfLineFound set to true // // \n <- EOL is set to false at CommentTrivia, set to true after it // // some other comment <- EOL is set to false at CommentTrivia, set to true after it // \n <- EOL found, is already true, closes current partition else if (IsEndOfLine(trivium)) { if (firstEndOfLineFound) { CloseCurrentPartition(ref current, ref partitions, ref firstEndOfLineFound); } else { firstEndOfLineFound = true; } } else if (!IsWhitespace(trivium)) { CloseCurrentPartition(ref current, ref partitions, ref firstEndOfLineFound); } } CloseCurrentPartition(ref current, ref partitions, ref firstEndOfLineFound); return partitions; // Hotpath: Don't capture variables static void AddTriviaToPartition(ref List current, SyntaxTrivia trivia, ref bool firstEndOfLineFound) { current ??= new(); current.Add(trivia); firstEndOfLineFound = false; } static void CloseCurrentPartition(ref List current, ref List> partitions, ref bool firstEndOfLineFound) { if (current is { Count: > 0 }) { partitions ??= new(); partitions.Add(current); current = null; } firstEndOfLineFound = false; } } private bool IsSimpleComment(SyntaxTrivia trivia) => Language.Syntax.IsKind(trivia, Language.SyntaxKind.SimpleCommentTrivia); private bool IsEndOfLine(SyntaxTrivia trivia) => Language.Syntax.IsKind(trivia, Language.SyntaxKind.EndOfLineTrivia); private bool IsWhitespace(SyntaxTrivia trivia) => Language.Syntax.IsKind(trivia, Language.SyntaxKind.WhitespaceTrivia); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ConditionalStructureSameConditionBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ConditionalStructureSameConditionBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S1862"; protected const string MessageFormat = "This branch duplicates the one on line {0}."; protected readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected ConditionalStructureSameConditionBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ConditionalStructureSameImplementationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ConditionalStructureSameImplementationBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1871"; internal const string MessageFormat = "Either merge this {1} with the identical one on line {0} or change one of the implementations."; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ConstructorArgumentValueShouldExistBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ConstructorArgumentValueShouldExistBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TAttribute : SyntaxNode { private const string DiagnosticId = "S4260"; protected abstract SyntaxNode GetFirstAttributeArgument(TAttribute attributeSyntax); protected override string MessageFormat => "Change this 'ConstructorArgumentAttribute' value to match one of the existing constructors arguments."; protected ConstructorArgumentValueShouldExistBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var attribute = (TAttribute)c.Node; if (Language.Syntax.IsKnownAttributeType(c.Model, c.Node, KnownType.System_Windows_Markup_ConstructorArgumentAttribute) && GetFirstAttributeArgument(attribute) is { } firstAttribute && c.Model.GetConstantValue(Language.Syntax.NodeExpression(firstAttribute)) is { HasValue: true, Value: string constructorParameterName } && c.ContainingSymbol is IPropertySymbol { ContainingType: { } containingType } && !GetConstructorParameterNames(containingType).Contains(constructorParameterName)) { c.ReportIssue(Rule, firstAttribute.GetLocation()); } }, Language.SyntaxKind.Attribute); private static IEnumerable GetConstructorParameterNames(INamedTypeSymbol containingSymbol) => containingSymbol?.Constructors.SelectMany(x => x.Parameters).Select(x => x.Name) ?? []; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DangerousGetHandleShouldNotBeCalledBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class DangerousGetHandleShouldNotBeCalledBase : DoNotCallMethodsBase where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S3869"; protected override string MessageFormat => "Refactor the code to remove this use of '{0}'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Runtime_InteropServices_SafeHandle, "DangerousGetHandle") }; protected DangerousGetHandleShouldNotBeCalledBase() : base(DiagnosticId) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S3363"; protected static readonly string[] KeyAttributeTypeNames = TypeNamesForAttribute(KnownType.System_ComponentModel_DataAnnotations_KeyAttribute); protected static readonly KnownType[] TemporalTypes = new[] { KnownType.System_DateOnly, KnownType.System_DateTime, KnownType.System_DateTimeOffset, KnownType.System_TimeOnly, KnownType.System_TimeSpan }; protected abstract IEnumerable TypeNodesOfTemporalKeyProperties(SonarSyntaxNodeReportingContext context); protected override string MessageFormat => "'{0}' should not be used as a type for primary keys"; protected DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { if (ShouldRegisterAction(startContext.Compilation)) { startContext.RegisterNodeAction(Language.GeneratedCodeRecognizer, nodeContext => { foreach (var propertyType in TypeNodesOfTemporalKeyProperties(nodeContext)) { nodeContext.ReportIssue(Rule, propertyType, Language.Syntax.NodeIdentifier(propertyType)?.ValueText); } }, Language.SyntaxKind.ClassDeclaration); } }); protected static bool IsKeyPropertyBasedOnName(string propertyName, string className) => propertyName.Equals("Id", StringComparison.InvariantCultureIgnoreCase) || propertyName.Equals($"{className}Id", StringComparison.InvariantCultureIgnoreCase); protected virtual bool IsTemporalType(string propertyTypeName) => Array.Exists(TemporalTypes, x => propertyTypeName.Equals(x.TypeName, Language.NameComparison) || propertyTypeName.Equals(x.FullName, Language.NameComparison)); protected bool MatchesAttributeName(string attributeName, string[] candidates) => Array.Exists(candidates, x => attributeName.Equals(x, Language.NameComparison)); private static bool ShouldRegisterAction(Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_DbContext) is not null || compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFramework_DbContext) is not null; private static string[] TypeNamesForAttribute(KnownType attributeType) => new[] { attributeType.TypeName, attributeType.FullName, RemoveFromEnd(attributeType.TypeName, "Attribute"), RemoveFromEnd(attributeType.FullName, "Attribute"), }; private static string RemoveFromEnd(string text, string subtextFromEnd) => text.Substring(0, text.LastIndexOf(subtextFromEnd)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DateTimeFormatShouldNotBeHardcodedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class DateTimeFormatShouldNotBeHardcodedBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S6585"; protected abstract Location HardCodedArgumentLocation(TInvocation invocation); protected abstract bool HasInvalidFirstArgument(TInvocation invocation, SemanticModel semanticModel); protected override string MessageFormat => "Do not hardcode the format specifier."; protected IEnumerable CheckedTypes { get; } = new List { KnownType.System_DateTime, KnownType.System_DateTimeOffset, KnownType.System_DateOnly, KnownType.System_TimeOnly, KnownType.System_TimeSpan, }; protected DateTimeFormatShouldNotBeHardcodedBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, AnalyzeInvocation, Language.SyntaxKind.InvocationExpression); private void AnalyzeInvocation(SonarSyntaxNodeReportingContext analysisContext) { if ((TInvocation)analysisContext.Node is var invocation && Language.Syntax.InvocationIdentifier(invocation) is { } identifier && identifier.ValueText.Equals("ToString", Language.NameComparison) && HasInvalidFirstArgument(invocation, analysisContext.Model) // Standard date and time format strings are 1 char long and they are allowed && analysisContext.Model.GetSymbolInfo(identifier.Parent).Symbol is { } methodCallSymbol && CheckedTypes.Any(x => methodCallSymbol.ContainingType.ConstructedFrom.Is(x))) { analysisContext.ReportIssue(SupportedDiagnostics[0], HardCodedArgumentLocation(invocation)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DebuggerDisplayUsesExistingMembersBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules; public abstract class DebuggerDisplayUsesExistingMembersBase : SonarDiagnosticAnalyzer where TAttributeArgumentSyntax : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S4545"; private static readonly ArgumentDescriptor ConstructorDescriptor = ArgumentDescriptor.AttributeArgument(KnownType.System_Diagnostics_DebuggerDisplayAttribute, "value", 0); private static readonly ArgumentDescriptor NameDescriptor = ArgumentDescriptor.AttributeProperty(KnownType.System_Diagnostics_DebuggerDisplayAttribute, nameof(DebuggerDisplayAttribute.Name)); private static readonly ArgumentDescriptor TypeDescriptor = ArgumentDescriptor.AttributeProperty(KnownType.System_Diagnostics_DebuggerDisplayAttribute, nameof(DebuggerDisplayAttribute.Type)); // Source of the regex: https://stackoverflow.com/questions/546433/regular-expression-to-match-balanced-parentheses#comment120990638_35271017 private static readonly Regex EvaluatedExpressionRegex = new(@"\{(?>\{(?)|[^{}]+|\}(?<-c>))*(?(c)(?!))\}", RegexOptions.None, Constants.DefaultRegexTimeout); // Remove the "nq" (no quotes) modifier, others like "d" or "raw" are undocumented. We allow any word here. private static readonly Regex RemoveNqModifierRegex = new(@"\s*,\s*[a-zA-Z]*\s*$", RegexOptions.None, Constants.DefaultRegexTimeout); protected abstract SyntaxNode AttributeTarget(TAttributeArgumentSyntax attribute); protected abstract ImmutableArray ResolvableIdentifiers(SyntaxNode expression); protected override string MessageFormat => "{0}"; protected DebuggerDisplayUsesExistingMembersBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var attributeArgument = (TAttributeArgumentSyntax)c.Node; var trackingContext = new ArgumentContext(attributeArgument, c.Model); var argumentMatcher = Language.Tracker.Argument; if (argumentMatcher.Or( argumentMatcher.MatchArgument(ConstructorDescriptor), argumentMatcher.MatchArgument(NameDescriptor), argumentMatcher.MatchArgument(TypeDescriptor))(trackingContext) && Language.Syntax.NodeExpression(attributeArgument) is { } formatString && Language.FindConstantValue(c.Model, formatString) is string formatStringText && FirstInvalidExpression(c, formatStringText, attributeArgument) is { } firstInvalidMember) { c.ReportIssue(Rule, formatString, firstInvalidMember); } }, Language.SyntaxKind.AttributeArgument); private string FirstInvalidExpression(SonarSyntaxNodeReportingContext context, string formatString, TAttributeArgumentSyntax attributeSyntax) { return AttributeTarget(attributeSyntax) is { } targetSyntax && context.Model.GetDeclaredSymbol(targetSyntax) is { } targetSymbol && TypeContainingReferencedMembers(targetSymbol) is { } typeSymbol ? FirstInvalidMemberName(typeSymbol) : null; string FirstInvalidMemberName(ITypeSymbol typeSymbol) { var allMembers = typeSymbol .GetSelfAndBaseTypes() .SelectMany(x => x.GetMembers()) .Concat(typeSymbol.ContainingNamespace.GetAllNamedTypes() .Where(x => x.IsStatic) .SelectMany(x => x.GetMembers().Prepend(x))) .Select(ResolveName) .WhereNotNull() .ToHashSet(Language.NameComparer); foreach (Match match in EvaluatedExpressionRegex.SafeMatches(formatString)) { if (match is { Success: true } && ParseExpression(match.Value) is { } parsedExpression && CheckParsedExpression(allMembers, parsedExpression, match.Value) is { } message) { return message; } } return null; } string CheckParsedExpression(HashSet allMembers, SyntaxNode parsedExpression, string evaluatedExpression) { if (parsedExpression.ContainsDiagnostics && parsedExpression.GetDiagnostics() is { } diagnostics && diagnostics.FirstOrDefault(x => x.Severity == DiagnosticSeverity.Error) is { } firstError) { return $"""'{evaluatedExpression}' is not a valid expression. {firstError.Id}: {firstError.GetMessage()}."""; } if (ResolvableIdentifiers(parsedExpression).Select(Language.GetName).ToImmutableHashSet(Language.NameComparer) is { } resolvableNames && resolvableNames.Except(allMembers) is { Count: > 0 } unresolvableNames) { return $"""'{unresolvableNames.First()}' doesn't exist in this context."""; } return null; } SyntaxNode ParseExpression(string evaluatedExpression) { var removeBraces = evaluatedExpression.TrimStart('{').TrimEnd('}').Trim(); var sanitizedExpression = RemoveNqModifierRegex.SafeReplace(removeBraces, string.Empty); return Language.Syntax.ParseExpression(sanitizedExpression); } static ITypeSymbol TypeContainingReferencedMembers(ISymbol symbol) => symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; string ResolveName(ISymbol symbol) => symbol switch { IMethodSymbol { IsStatic: true, IsExtension: true } extension => extension.Parameters.FirstOrDefault() is { } targetType && typeSymbol.DerivesOrImplements(targetType.Type) ? symbol.Name : null, IMethodSymbol { IsStatic: true, IsImplicitlyDeclared: true } extensionProp => extensionProp.Name.StartsWith("get_") && extensionProp.Parameters.FirstOrDefault() is { } targetType && typeSymbol.DerivesOrImplements(targetType.Type) ? symbol.Name.Substring(4) : null, ITypeSymbol { IsStatic: true, ContainingSymbol: INamespaceSymbol } => symbol.Name, ITypeSymbol { IsStatic: true, ContainingSymbol: ITypeSymbol containingSymbol } => containingSymbol.Equals(typeSymbol) ? symbol.Name : OutermostType(symbol).Name, ISymbol { IsStatic: true } => OutermostType(symbol).Name, IMethodSymbol { ReturnsVoid: true } => null, _ => symbol.Name }; static ISymbol OutermostType(ISymbol symbol) { while (symbol.ContainingSymbol is ITypeSymbol) { symbol = symbol.ContainingSymbol; } return symbol; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DeclareTypesInNamespacesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DeclareTypesInNamespacesBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S3903"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract bool IsInnerTypeOrWithinNamespace(SyntaxNode declaration, SemanticModel semanticModel); protected abstract SyntaxToken GetTypeIdentifier(SyntaxNode declaration); protected abstract bool IsException(SyntaxNode node); protected override string MessageFormat => "Move '{0}' into a named namespace."; protected DeclareTypesInNamespacesBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var declaration = c.Node; if (c.IsRedundantPositionalRecordContext() || IsInnerTypeOrWithinNamespace(declaration, c.Model) || IsException(c.Node)) { return; } var identifier = GetTypeIdentifier(declaration); c.ReportIssue(Rule, identifier.GetLocation(), identifier.ValueText); }, SyntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotCallInsecureSecurityAlgorithmBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotCallInsecureSecurityAlgorithmBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpressionSyntax : SyntaxNode where TArgumentListSyntax : SyntaxNode where TArgumentSyntax : SyntaxNode { protected abstract ISet AlgorithmParameterlessFactoryMethods { get; } protected abstract ISet AlgorithmParameterizedFactoryMethods { get; } protected abstract ISet FactoryParameterNames { get; } protected abstract ILanguageFacade Language { get; } private protected abstract ImmutableArray AlgorithmTypes { get; } protected abstract Location Location(SyntaxNode objectCreation); protected abstract TArgumentListSyntax ArgumentList(TInvocationExpressionSyntax invocationExpression); protected abstract SeparatedSyntaxList Arguments(TArgumentListSyntax argumentList); protected abstract bool IsStringLiteralArgument(TArgumentSyntax argument); protected abstract SyntaxNode Expression(TArgumentSyntax argument); protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckObjectCreation, Language.SyntaxKind.ObjectCreationExpressions); context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckInvocation, Language.SyntaxKind.InvocationExpression); } private void CheckInvocation(SonarSyntaxNodeReportingContext context) { var invocation = (TInvocationExpressionSyntax)context.Node; if (Language.Syntax.NodeExpression(invocation) is { } expression && (context.Model.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol) && (methodSymbol.ReturnType.DerivesFromAny(AlgorithmTypes) || IsInsecureBaseAlgorithmCreationFactoryCall(methodSymbol, invocation))) { ReportAllDiagnostics(context, invocation.GetLocation()); } } private void CheckObjectCreation(SonarSyntaxNodeReportingContext context) { var objectCreation = context.Node; var typeInfo = context.Model.GetTypeInfo(objectCreation); if (typeInfo.Type == null || typeInfo.Type is IErrorTypeSymbol) { return; } if (typeInfo.Type.DerivesFromAny(AlgorithmTypes)) { ReportAllDiagnostics(context, Location(objectCreation)); } } private bool IsInsecureBaseAlgorithmCreationFactoryCall(IMethodSymbol methodSymbol, TInvocationExpressionSyntax invocationExpression) { var argumentList = ArgumentList(invocationExpression); if (argumentList == null || methodSymbol.ContainingType == null) { return false; } var methodFullName = $"{methodSymbol.ContainingType}.{methodSymbol.Name}"; if (Arguments(argumentList).Count == 0) { return AlgorithmParameterlessFactoryMethods.Contains(methodFullName); } if (Arguments(argumentList).Count > 1 || !IsStringLiteralArgument(Arguments(argumentList).First())) { return false; } if (!AlgorithmParameterizedFactoryMethods.Contains(methodFullName)) { return false; } return FactoryParameterNames.Any(x => x.Equals(Language.Syntax.LiteralText(Expression(Arguments(argumentList).First())), StringComparison.Ordinal)); } private void ReportAllDiagnostics(SonarSyntaxNodeReportingContext context, Location location) { foreach (var supportedDiagnostic in SupportedDiagnostics) { context.ReportIssue(supportedDiagnostic, location); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotCallMethodsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class DoNotCallMethodsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpressionSyntax : SyntaxNode { protected abstract IEnumerable CheckedMethods { get; } protected virtual bool ShouldReportOnMethodCall(TInvocationExpressionSyntax invocation, SemanticModel semanticModel, MemberDescriptor memberDescriptor) => true; protected virtual bool IsInValidContext(TInvocationExpressionSyntax invocationSyntax, SemanticModel semanticModel) => true; protected virtual bool ShouldRegisterAction(Compilation compilation) => true; protected DoNotCallMethodsBase(string diagnosticId) : base(diagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (!ShouldRegisterAction(c.Compilation)) { return; } c.RegisterNodeAction(Language.GeneratedCodeRecognizer, AnalyzeInvocation, Language.SyntaxKind.InvocationExpression); }); private void AnalyzeInvocation(SonarSyntaxNodeReportingContext analysisContext) { if ((TInvocationExpressionSyntax)analysisContext.Node is var invocation && Language.Syntax.InvocationIdentifier(invocation) is { } identifier && CheckedMethods.Where(x => x.Name.Equals(identifier.ValueText)) is var nameMatch && nameMatch.Any() && analysisContext.Model.GetSymbolInfo(identifier.Parent).Symbol is { } methodCallSymbol && nameMatch.FirstOrDefault(x => methodCallSymbol.ContainingType.ConstructedFrom.Is(x.ContainingType)) is { } disallowedMethodSignature && IsInValidContext(invocation, analysisContext.Model) && ShouldReportOnMethodCall(invocation, analysisContext.Model, disallowedMethodSignature)) { analysisContext.ReportIssue(SupportedDiagnostics[0], identifier.GetLocation(), disallowedMethodSignature.ToString()); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotCheckZeroSizeCollectionBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotCheckZeroSizeCollectionBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S3981"; private const string CountName = nameof(Enumerable.Count); private const string LengthName = nameof(Array.Length); private const string LongLengthName = nameof(Array.LongLength); protected abstract string IEnumerableTString { get; } protected override string MessageFormat => "The '{0}' of '{1}' always evaluates as '{2}' regardless the size."; protected DoNotCheckZeroSizeCollectionBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var binary = c.Node; var binaryLeft = Language.Syntax.BinaryExpressionLeft(binary); var binaryRight = Language.Syntax.BinaryExpressionRight(binary); if (Language.ExpressionNumericConverter.ConstantIntValue(c.Model, binaryLeft) is { } left) { CheckExpression(c, binary, binaryRight, left, Language.Syntax.ComparisonKind(binary).Mirror()); } else if (Language.ExpressionNumericConverter.ConstantIntValue(c.Model, binaryRight) is { } right) { CheckExpression(c, binary, binaryLeft, right, Language.Syntax.ComparisonKind(binary)); } }, Language.SyntaxKind.ComparisonKinds); protected void CheckExpression(SonarSyntaxNodeReportingContext context, SyntaxNode issue, SyntaxNode expression, int constant, ComparisonKind comparison) { expression = Language.Syntax.RemoveConditionalAccess(expression); var result = comparison.Compare(constant); if (result.IsInvalid() && HasCandidateName(Language.Syntax.NodeIdentifier(expression)?.ValueText) && context.Model.GetSymbolInfo(expression).Symbol is ISymbol symbol && CollecionSizeTypeName(symbol) is { } symbolType) { context.ReportIssue(Rule, issue, symbol.Name, symbolType, (result == CountComparisonResult.AlwaysTrue).ToString()); } } private bool HasCandidateName(string name) => CountName.Equals(name, Language.NameComparison) || LengthName.Equals(name, Language.NameComparison) || LongLengthName.Equals(name, Language.NameComparison); private bool IsEnumerableCountMethod(ISymbol symbol) => CountName.Equals(symbol.Name, Language.NameComparison) && symbol is IMethodSymbol methodSymbol && methodSymbol.IsExtensionMethod && methodSymbol.ReceiverType is not null && methodSymbol.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); private bool IsArrayLengthProperty(ISymbol symbol) => (LengthName.Equals(symbol.Name, Language.NameComparison) || LongLengthName.Equals(symbol.Name, Language.NameComparison)) && symbol is IPropertySymbol propertySymbol && propertySymbol.ContainingType.Is(KnownType.System_Array); private bool IsStringLengthProperty(ISymbol symbol) => LengthName.Equals(symbol.Name, Language.NameComparison) && symbol is IPropertySymbol propertySymbol && propertySymbol.ContainingType.Is(KnownType.System_String); private bool IsCollectionCountProperty(ISymbol symbol) => CountName.Equals(symbol.Name, Language.NameComparison) && symbol is IPropertySymbol propertySymbol && propertySymbol.ContainingType.DerivesOrImplements(KnownType.System_Collections_Generic_ICollection_T); private bool IsReadonlyCollectionCountProperty(ISymbol symbol) => CountName.Equals(symbol.Name, Language.NameComparison) && symbol is IPropertySymbol propertySymbol && propertySymbol.ContainingType.DerivesOrImplements(KnownType.System_Collections_Generic_IReadOnlyCollection_T); private string CollecionSizeTypeName(ISymbol symbol) { if (IsArrayLengthProperty(symbol)) { return nameof(Array); } else if (IsStringLengthProperty(symbol)) { return nameof(String); } else if (IsEnumerableCountMethod(symbol)) { return IEnumerableTString; } else if (IsCollectionCountProperty(symbol)) { return nameof(ICollection); } else if (IsReadonlyCollectionCountProperty(symbol)) { return nameof(IReadOnlyCollection); } else { return null; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotHardcodeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; using System.Xml.Linq; using SonarAnalyzer.Core.Json; using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Rules; public abstract class DoNotHardcodeBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct { protected const char KeywordSeparator = ';'; protected static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(250); protected static readonly Regex ValidKeywordPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout); protected string keyWords; protected DiagnosticDescriptor rule; protected ImmutableList splitKeyWords; protected Regex keyWordPattern; protected abstract void ExtractKeyWords(string value); protected abstract string FindIssue(string variableName, string variableValue); protected abstract string DiagnosticId { get; } protected abstract ILanguageFacade Language { get; } public string FilterWords { get => keyWords; set => ExtractKeyWords(value); } protected static ImmutableList SplitKeyWordsByComma(string keyWords) => keyWords.ToUpperInvariant() .Split([','], StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .Where(x => x.Length != 0) .ToImmutableList(); protected static bool IsValidKeyword(string suffix) { var candidateKeyword = suffix.Split(KeywordSeparator)[0].Trim(); return string.IsNullOrWhiteSpace(candidateKeyword) || ValidKeywordPattern.SafeIsMatch(candidateKeyword); } protected void CheckWebConfig(SonarCompilationReportingContext context) { foreach (var path in context.WebConfigFiles()) { if (File.ReadAllText(path).ParseXDocument() is { } doc) { CheckWebConfig(context, path, doc.Descendants()); } } } protected void CheckAppSettings(SonarCompilationReportingContext context) => CheckJsonSettings(context, context.AppSettingsFiles()); protected void CheckLaunchSettings(SonarCompilationReportingContext context) => CheckJsonSettings(context, context.LaunchSettingsFiles()); private void CheckJsonSettings(SonarCompilationReportingContext context, IEnumerable jsonFiles) { foreach (var path in jsonFiles) { if (JsonNode.FromString(File.ReadAllText(path)) is { } json) { var walker = new CredentialWordsJsonWalker(this, context, path); walker.Visit(json); } } } private void CheckWebConfig(SonarCompilationReportingContext context, string path, IEnumerable elements) { foreach (var element in elements) { if (!element.HasElements && FindIssue(element.Name.LocalName, element.Value) is { } message && element.CreateLocation(path) is { } elementLocation) { context.ReportIssue(Language.GeneratedCodeRecognizer, rule, elementLocation, message); } foreach (var attribute in element.Attributes()) { if (FindIssue(attribute.Name.LocalName, attribute.Value) is { } attributeMessage && attribute.CreateLocation(path) is { } attributeLocation) { context.ReportIssue(Language.GeneratedCodeRecognizer, rule, attributeLocation, attributeMessage); } } } } private sealed class CredentialWordsJsonWalker : JsonWalker { private readonly DoNotHardcodeBase analyzer; private readonly SonarCompilationReportingContext context; private readonly string path; public CredentialWordsJsonWalker(DoNotHardcodeBase analyzer, SonarCompilationReportingContext context, string path) { this.analyzer = analyzer; this.context = context; this.path = path; } protected override void VisitObject(string key, JsonNode value) { if (value.Kind == Kind.Value) { CheckKeyValue(key, value); } else { base.VisitObject(key, value); } } protected override void VisitValue(JsonNode node) => CheckKeyValue(null, node); private void CheckKeyValue(string key, JsonNode value) { if (value.Value is string str && analyzer.FindIssue(key, str) is { } message) { context.ReportIssue(analyzer.Language.GeneratedCodeRecognizer, analyzer.rule, value.ToLocation(path), message); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotHardcodeCredentialsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Security; using System.Text.RegularExpressions; using SonarAnalyzer.CFG.Extensions; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules; public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeBase where TSyntaxKind : struct { private const string MessageFormat = "{0}"; private const string MessageHardcodedPassword = "Please review this hard-coded password."; private const string MessageFormatCredential = @"""{0}"" detected here, make sure this is not a hard-coded credential."; private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; private static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); protected abstract bool IsSecureStringAppendCharFromConstant(SyntaxNode argumentNode, SemanticModel model); [RuleParameter("credentialWords", PropertyType.String, "Comma separated list of words identifying potential credentials", DefaultCredentialWords)] public string CredentialWords { get => FilterWords; set => FilterWords = value; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override string DiagnosticId => "S2068"; protected DoNotHardcodeCredentialsBase() { rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } protected override void ExtractKeyWords(string value) { keyWords = value; splitKeyWords = SplitKeyWordsByComma(keyWords); var credentialWordsPattern = splitKeyWords.Select(Regex.Escape).JoinStr("|"); keyWordPattern = new Regex($@"(?{credentialWordsPattern})\s*=\s*(?.+)$", RegexOptions.IgnoreCase, RegexTimeout); } protected sealed override void Initialize(SonarParametrizedAnalysisContext context) { var input = new TrackerInput(context, AnalyzerConfiguration.AlwaysEnabled, rule); var oc = Language.Tracker.ObjectCreation; oc.Track( input, [MessageHardcodedPassword], oc.MatchConstructor(KnownType.System_Net_NetworkCredential), oc.ArgumentAtIndexIs(1, KnownType.System_String), oc.ArgumentAtIndexIsConst(1)); oc.Track( input, [MessageHardcodedPassword], oc.MatchConstructor(KnownType.System_Security_Cryptography_PasswordDeriveBytes), oc.ArgumentAtIndexIs(0, KnownType.System_String), oc.ArgumentAtIndexIsConst(0)); var pa = Language.Tracker.PropertyAccess; pa.Track( input, [MessageHardcodedPassword], pa.MatchSetter(), pa.AssignedValueIsConstant(), pa.MatchProperty(new MemberDescriptor(KnownType.System_Net_NetworkCredential, "Password"))); var inv = Language.Tracker.Invocation; inv.Track( input, [MessageHardcodedPassword], inv.MatchMethod(new MemberDescriptor(KnownType.System_Security_SecureString, nameof(SecureString.AppendChar))), inv.ArgumentAtIndexIs(0, IsSecureStringAppendCharFromConstant)); InitializeActions(context); context.RegisterCompilationAction(CheckWebConfig); context.RegisterCompilationAction(CheckAppSettings); context.RegisterCompilationAction(CheckLaunchSettings); } protected override string FindIssue(string variableName, string variableValue) { if (string.IsNullOrWhiteSpace(variableValue)) { return null; } else if (FindKeyWords(variableName, variableValue) is { } bannedWords and not "") { return string.Format(MessageFormatCredential, bannedWords); } else if (ContainsUriUserInfo(variableValue)) { return MessageUriUserInfo; } else { return null; } } private string FindKeyWords(string variableName, string variableValue) { var credentialWordsFound = variableName .SplitCamelCaseToWords() .Intersect(splitKeyWords) .ToHashSet(StringComparer.OrdinalIgnoreCase); if (credentialWordsFound.Any(x => variableValue.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) >= 0)) { // See https://github.com/SonarSource/sonar-dotnet/issues/2868 return null; } if (keyWordPattern.SafeMatch(variableValue) is { Success: true } match && !IsValidKeyword(match.Groups["suffix"].Value)) { credentialWordsFound.Add(match.Groups["credential"].Value); } // Rule was initially implemented with everything lower (which is wrong) so we have to force lower before reporting to avoid new issues to appear on SQ/SC. return credentialWordsFound.Select(x => x.ToLowerInvariant()).JoinAnd(); } private static Regex CreateUriUserInfoPattern() { const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping const string Rfc3986_Pct = "%"; const string Rfc3986_SubDelims = "!$&'()*+,;="; const string UriPasswordSpecialCharacters = Rfc3986_Unreserved + Rfc3986_Pct + Rfc3986_SubDelims; // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims var loginGroup = CreateUserInfoGroup("Login"); var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it return new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled, RegexTimeout); static string CreateUserInfoGroup(string name, string additionalCharacters = null) => $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; } private static bool ContainsUriUserInfo(string variableValue) { var match = UriUserInfoPattern.SafeMatch(variableValue); return match.Success && match.Groups["Password"].Value is { } password && !string.Equals(match.Groups["Login"].Value, password, StringComparison.OrdinalIgnoreCase) && password != KeywordSeparator.ToString() && !ValidKeywordPattern.SafeIsMatch(password); } protected abstract class CredentialWordsFinderBase where TSyntaxNode : SyntaxNode { private readonly DoNotHardcodeCredentialsBase analyzer; protected abstract bool ShouldHandle(TSyntaxNode syntaxNode, SemanticModel model); protected abstract string VariableName(TSyntaxNode syntaxNode); protected abstract string AssignedValue(TSyntaxNode syntaxNode, SemanticModel model); protected CredentialWordsFinderBase(DoNotHardcodeCredentialsBase analyzer) => this.analyzer = analyzer; public Action AnalysisAction() => context => { var declarator = (TSyntaxNode)context.Node; if (ShouldHandle(declarator, context.Model) && AssignedValue(declarator, context.Model) is { } variableValue && analyzer.FindIssue(VariableName(declarator), variableValue) is { } message) { context.ReportIssue(analyzer.rule, declarator, message); } }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotHardcodeSecretsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Rules; public abstract class DoNotHardcodeSecretsBase : DoNotHardcodeBase where TSyntaxKind : struct { protected const string MessageFormat = @"""{0}"" detected here, make sure this is not a hard-coded secret."; protected const string DefaultSecretWords = @"api[_\-]?key, auth, credential, secret, token"; protected const double DefaultRandomnessSensibility = 3; protected const double LanguageScoreIncrement = 0.3; protected const string EqualsName = nameof(string.Equals); // https://docs.gitguardian.com/secrets-detection/secrets-detection-engine/detectors/generics/generic_high_entropy_secret#:~:text=Follow%20this%20regular%20expression protected static readonly Regex ValidationPattern = new(@"^[a-zA-Z0-9_.+/~$-]([a-zA-Z0-9_.+\/=~$-]|\\(?![ntr""])){14,1022}[a-zA-Z0-9_.+/=~$-]$", RegexOptions.None, Constants.DefaultRegexTimeout); protected static readonly Regex BanList = new(@"public[_.-]?key|document_?key|client[_.-]?id|localhost|127\.0\.0\.1|test|xsrf|csrf", RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout); protected Regex keyWordInVariablePattern; protected abstract void RegisterNodeActions(SonarCompilationStartAnalysisContext context); protected abstract SyntaxNode IdentifierRoot(SyntaxNode node); protected abstract SyntaxNode RightHandSide(SyntaxNode node); [RuleParameter("secretWords", PropertyType.String, "Comma separated list of words identifying potential secret", DefaultSecretWords)] public string SecretWords { get => FilterWords; set => FilterWords = value; } [RuleParameter("randomnessSensibility", PropertyType.Float, "Allows to tune the Randomness Sensibility (from 0 to 10)", DefaultRandomnessSensibility)] public double RandomnessSensibility { get; set; } = DefaultRandomnessSensibility; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override string DiagnosticId => "S6418"; protected double MaxLanguageScore => (10 - RandomnessSensibility) * LanguageScoreIncrement; protected DoNotHardcodeSecretsBase() { rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); SecretWords = DefaultSecretWords; } protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterCompilationStartAction(RegisterNodeActions); context.RegisterCompilationAction(CheckWebConfig); context.RegisterCompilationAction(CheckAppSettings); context.RegisterCompilationAction(CheckLaunchSettings); } protected override void ExtractKeyWords(string value) { keyWords = value; splitKeyWords = SplitKeyWordsByComma(keyWords); keyWordPattern = new Regex(splitKeyWords.JoinStr("|"), RegexOptions.IgnoreCase, RegexTimeout); keyWordInVariablePattern = new Regex($@"(?\b\w*?({keyWordPattern}))\s*[:=]\s*(?[^"";$]+)", RegexOptions.IgnoreCase, RegexTimeout); } protected override string FindIssue(string variableName, string variableValue) { if (ShouldRaiseBinary(variableName, variableValue)) { return variableName; } else if (FindLiteralIssue(variableValue) is { } message) { return message; } else { return null; } } protected void ReportIssues(SonarSyntaxNodeReportingContext context) { var node = context.Node; if (Language.Syntax.NodeIdentifier(IdentifierRoot(node)) is { } identifier && RightHandSide(node) is { } rhs && Language.FindConstantValue(context.Model, rhs) is string secret) { if (ShouldRaiseBinary(identifier.ValueText, secret)) { context.ReportIssue(rule, rhs, identifier.ValueText); } else if (FindLiteralIssue(secret) is { } message) { context.ReportIssue(rule, rhs, message); } } } protected void ReportIssuesForEquals(SonarSyntaxNodeReportingContext context, SyntaxNode memberAccessExpression, IdentifierValuePair identifierAndValue) { if (identifierAndValue is { Identifier: { } identifier, Value: { } value } && Language.FindConstantValue(context.Model, value) is string secret && ShouldRaiseBinary(identifier.ValueText, secret)) { context.ReportIssue(rule, memberAccessExpression, identifier.ValueText); } } private string FindLiteralIssue(string secret) { var variableMatch = keyWordInVariablePattern.SafeMatches(secret); var keyWordsFound = new List(); foreach (Match match in variableMatch) { if (match.Success && !IsValidKeyword(match.Groups["suffix"].Value) && ShouldRaiseBinary(match.Groups["secret"].Value, match.Groups["suffix"].Value)) { keyWordsFound.Add(match.Groups["secret"].Value); } } return keyWordsFound.Count > 0 ? keyWordsFound.JoinAnd() : null; } private bool ShouldRaiseBinary(string left, string right) => !string.IsNullOrEmpty(left) && keyWordPattern.SafeMatch(left) is { Success: true } keyWord && !BanList.SafeIsMatch(left) && right.IndexOf(keyWord.Value, StringComparison.InvariantCultureIgnoreCase) < 0 && IsToken(right); private bool IsToken(string value) => ShannonEntropy.Calculate(value) > RandomnessSensibility && ValidationPattern.SafeIsMatch(value) && MaxLanguageScore > NaturalLanguageDetector.HumanLanguageScore(value); protected sealed record IdentifierValuePair(SyntaxToken? Identifier, SyntaxNode Value); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotInstantiateSharedClassesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotInstantiateSharedClassesBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4277"; protected const string MessageFormat = "Refactor this code so that it doesn't invoke the constructor of this class."; protected static bool IsShared(AttributeData data) { // This is equivalent to System.ComponentModel.Composition.CreationPolicy.Shared, // but we do not want dependency on System.ComponentModel.Composition just for that. const int CreationPolicy_Shared = 1; return data.ConstructorArguments.Any(arg => arg.Type.Is(KnownType.System_ComponentModel_Composition_CreationPolicy) && Equals(arg.Value, CreationPolicy_Shared)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotLockOnSharedResourceBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotLockOnSharedResourceBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S2551"; protected const string MessageFormat = "Lock on a dedicated object instance instead."; private static readonly ImmutableArray _invalidLockTypes = ImmutableArray.Create( KnownType.System_String, KnownType.System_Type ); protected static bool IsLockOnForbiddenKnownType(SyntaxNode expression, SemanticModel semanticModel) => semanticModel.GetTypeInfo(expression).Type.IsAny(_invalidLockTypes); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotLockWeakIdentityObjectsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotLockWeakIdentityObjectsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S3998"; private readonly ImmutableArray weakIdentityTypes = ImmutableArray.Create( KnownType.System_MarshalByRefObject, KnownType.System_ExecutionEngineException, KnownType.System_OutOfMemoryException, KnownType.System_StackOverflowException, KnownType.System_String, KnownType.System_IO_FileStream, KnownType.System_Reflection_MemberInfo, KnownType.System_Reflection_ParameterInfo, KnownType.System_Threading_Thread ); protected abstract TSyntaxKind SyntaxKind { get; } protected override string MessageFormat => "Replace this lock on '{0}' with a lock against an object that cannot be accessed across application domain boundaries."; protected DoNotLockWeakIdentityObjectsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var lockExpression = Language.Syntax.NodeExpression(c.Node); if (c.Model.GetSymbolInfo(lockExpression).Symbol?.GetSymbolType() is { } lockExpressionType && lockExpressionType.DerivesFromAny(weakIdentityTypes)) { c.ReportIssue(Rule, lockExpression, lockExpressionType.Name); } }, SyntaxKind); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotNestTernaryOperatorsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotNestTernaryOperatorsBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S3358"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotOverwriteCollectionElementsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class DoNotOverwriteCollectionElementsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TStatementSyntax : SyntaxNode { protected const string DiagnosticId = "S4143"; /// /// Returns the index or key from the provided InvocationExpression or SimpleAssignmentExpression. /// Returns null if the provided SyntaxNode is not an InvocationExpression or SimpleAssignmentExpression. /// protected abstract SyntaxNode GetIndexOrKey(TStatementSyntax statement); /// /// Returns the identifier of a collection that is modified in the provided InvocationExpression /// or SimpleAssignmentExpression. Returns null if the provided SyntaxNode is not an /// InvocationExpression or SimpleAssignmentExpression. /// protected abstract SyntaxNode GetCollectionIdentifier(TStatementSyntax statement); /// /// Returns a value specifying whether the provided SyntaxNode is an identifier or /// a literal (string, numeric, bool, etc.). /// protected abstract bool IsIdentifierOrLiteral(SyntaxNode node); protected override string MessageFormat => "Verify this is the index/key that was intended; " + "a value has already been set for it."; private static string SecondaryMessage => "The index/key set here gets set again later."; protected DoNotOverwriteCollectionElementsBase() : base(DiagnosticId) { } protected void AnalysisAction(SonarSyntaxNodeReportingContext context) { var statement = (TStatementSyntax)context.Node; var collectionIdentifier = GetCollectionIdentifier(statement); var indexOrKey = GetIndexOrKey(statement); if (collectionIdentifier is null || indexOrKey is null || !IsIdentifierOrLiteral(indexOrKey) || !IsDictionaryOrCollection(collectionIdentifier, context.Model)) { return; } var previousSet = GetPreviousStatements(statement) .TakeWhile(IsSameCollection(collectionIdentifier)) .FirstOrDefault(IsSameIndexOrKey(indexOrKey)); if (previousSet is not null) { context.ReportIssue(Rule, context.Node, [previousSet.ToSecondaryLocation(SecondaryMessage)]); } } private Func IsSameCollection(SyntaxNode collectionIdentifier) => x => GetCollectionIdentifier(x) is { } identifier && identifier.ToString() == collectionIdentifier.ToString(); private Func IsSameIndexOrKey(SyntaxNode indexOrKey) => x => GetIndexOrKey(x)?.ToString() == indexOrKey.ToString(); private static bool IsDictionaryOrCollection(SyntaxNode identifier, SemanticModel model) { var identifierType = model.GetTypeInfo(identifier).Type; return identifierType.DerivesOrImplements(KnownType.System_Collections_Generic_IDictionary_TKey_TValue) || identifierType.DerivesOrImplements(KnownType.System_Collections_Generic_ICollection_T); } /// /// Returns all statements before the specified statement within the containing method. /// This method recursively traverses all parent blocks of the provided statement. /// private static IEnumerable GetPreviousStatements(TStatementSyntax statement) { var previousStatements = statement.Parent.ChildNodes() .OfType() .TakeWhile(x => x != statement) .Reverse(); return statement.Parent is TStatementSyntax parentStatement ? previousStatements.Union(GetPreviousStatements(parentStatement)) : previousStatements; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotThrowFromDestructorsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class DoNotThrowFromDestructorsBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S1048"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/DoNotUseDateTimeNowBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules; public abstract class DoNotUseDateTimeNowBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6563"; private static readonly MemberDescriptor DateTimeNow = new(KnownType.System_DateTime, nameof(DateTime.Now)); private static readonly MemberDescriptor DateTimeToday = new(KnownType.System_DateTime, nameof(DateTime.Today)); private static readonly MemberDescriptor DateTimeOffsetNow = new(KnownType.System_DateTimeOffset, nameof(DateTimeOffset.Now)); private static readonly MemberDescriptor DateTimeOffsetDate = new(KnownType.System_DateTimeOffset, nameof(DateTimeOffset.Date)); private static readonly MemberDescriptor DateTimeOffsetDateTime = new(KnownType.System_DateTimeOffset, nameof(DateTimeOffset.DateTime)); protected override string MessageFormat => "Use UTC when recording DateTime instants"; protected abstract bool IsInsideNameOf(SyntaxNode node); protected DoNotUseDateTimeNowBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if ((IsDateTimeNowOrToday(c.Node, c.Model) || IsDateTimeOffsetNowDateTime(c.Node, c.Model)) && !IsInsideNameOf(c.Node)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.SimpleMemberAccessExpression); private bool IsDateTimeNowOrToday(SyntaxNode node, SemanticModel semanticModel) => MatchesAnyProperty(node, semanticModel, DateTimeNow, DateTimeToday); private bool IsDateTimeOffsetNowDateTime(SyntaxNode node, SemanticModel semanticModel) => MatchesAnyProperty(node, semanticModel, DateTimeOffsetDateTime, DateTimeOffsetDate) && MatchesAnyProperty(Language.Syntax.NodeExpression(node), semanticModel, DateTimeOffsetNow); private bool MatchesAnyProperty(SyntaxNode node, SemanticModel semanticModel, params MemberDescriptor[] members) => Language.Syntax.NodeIdentifier(node) is { IsMissing: false } identifier && Array.Exists(members, x => MemberDescriptor.MatchesAny(identifier.ValueText, new Lazy(() => semanticModel.GetSymbolInfo(node).Symbol), false, Language.NameComparison, members)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/EmptyMethodBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class EmptyMethodBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S1186"; protected abstract HashSet SyntaxKinds { get; } protected abstract void CheckMethod(SonarSyntaxNodeReportingContext context); protected override string MessageFormat => "Add a nested comment explaining why this method is empty, throw a 'NotSupportedException' or complete the implementation."; protected EmptyMethodBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckMethod, SyntaxKinds.ToArray()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/EmptyNestedBlockBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class EmptyNestedBlockBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S108"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract IEnumerable EmptyBlocks(SyntaxNode node); protected override string MessageFormat => "Either remove or fill this block of code."; protected EmptyNestedBlockBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { foreach (var node in EmptyBlocks(c.Node)) { c.ReportIssue(Rule, node); } }, SyntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/EncryptionAlgorithmsShouldBeSecureBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class EncryptionAlgorithmsShouldBeSecureBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S5542"; private const string MessageFormat = "Use secure mode and padding scheme."; protected abstract TrackerBase.Condition IsInsideObjectInitializer(); protected abstract TrackerBase.Condition HasPkcs1PaddingArgument(); protected EncryptionAlgorithmsShouldBeSecureBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var inv = Language.Tracker.Invocation; inv.Track(input, inv.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_RSA, "Encrypt"), new MemberDescriptor(KnownType.System_Security_Cryptography_RSA, "TryEncrypt")), inv.Or( inv.ArgumentIsBoolConstant("fOAEP", false), HasPkcs1PaddingArgument())); // There exist no GCM mode with AesManaged, so any mode we set will be insecure. We do not raise // when inside an ObjectInitializerExpression, as the issue is already raised on the constructor var pa = Language.Tracker.PropertyAccess; pa.Track(input, pa.MatchProperty(new MemberDescriptor(KnownType.System_Security_Cryptography_AesManaged, "Mode")), pa.MatchSetter(), pa.ExceptWhen(IsInsideObjectInitializer())); var oc = Language.Tracker.ObjectCreation; oc.Track(input, oc.MatchConstructor(KnownType.System_Security_Cryptography_AesManaged)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/EnumNameHasEnumSuffixBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class EnumNameHasEnumSuffixBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S2344"; private readonly IEnumerable nameEndings = ImmutableArray.Create("enum", "flags"); protected override string MessageFormat => "Rename this enumeration to remove the '{0}' suffix."; protected EnumNameHasEnumSuffixBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeIdentifier(c.Node) is { } identifier && nameEndings.FirstOrDefault(ending => identifier.ValueText.EndsWith(ending, System.StringComparison.OrdinalIgnoreCase)) is { } nameEnding) { c.ReportIssue(Rule, identifier, identifier.ValueText.Substring(identifier.ValueText.Length - nameEnding.Length)); } }, Language.SyntaxKind.EnumDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/EnumNameShouldFollowRegexBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.RegularExpressions; namespace SonarAnalyzer.Core.Rules; public abstract class EnumNameShouldFollowRegexBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S2342"; private const string MessageFormat = "Rename the enumeration '{0}' to match the regular expression: '{1}'."; private const string DefaultEnumNamePattern = NamingPatterns.PascalCasingPattern; private const string DefaultFlagsEnumNamePattern = "^" + NamingPatterns.PascalCasingInternalPattern + "s$"; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } [RuleParameter("format", PropertyType.String, "Regular expression used to check the enumeration type names against.", DefaultEnumNamePattern)] public string EnumNamePattern { get; set; } = DefaultEnumNamePattern; [RuleParameter("flagsAttributeFormat", PropertyType.String, "Regular expression used to check the flags enumeration type names against.", DefaultFlagsEnumNamePattern)] public string FlagsEnumNamePattern { get; set; } = DefaultFlagsEnumNamePattern; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected EnumNameShouldFollowRegexBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected sealed override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var pattern = c.Node.HasFlagsAttribute(c.Model) ? FlagsEnumNamePattern : EnumNamePattern; if (Language.Syntax.NodeIdentifier(c.Node) is { } identifier && !NamingPatterns.IsRegexMatch(identifier.ValueText, pattern, true)) { c.ReportIssue(SupportedDiagnostics[0], identifier, identifier.ValueText, pattern); } }, Language.SyntaxKind.EnumDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ExceptionsShouldBePublicBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ExceptionsShouldBePublicBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S3871"; private static readonly KnownType[] BaseTypes = new[] { KnownType.System_Exception, KnownType.System_ApplicationException, KnownType.System_SystemException }; protected ExceptionsShouldBePublicBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (c.Model.GetDeclaredSymbol(c.Node) is INamedTypeSymbol classSymbol && classSymbol.GetEffectiveAccessibility() != Accessibility.Public && classSymbol.BaseType.IsAny(BaseTypes)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(c.Node).Value); } }, Language.SyntaxKind.ClassAndRecordDeclarations); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ExcludeFromCodeCoverageAttributesNeedJustificationBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6513"; protected const string JustificationPropertyName = "Justification"; protected override string MessageFormat => "Add a justification."; protected abstract SyntaxNode GetJustificationExpression(SyntaxNode node); protected ExcludeFromCodeCoverageAttributesNeedJustificationBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (NoJustification(c.Node, c.Model) && c.Model.GetSymbolInfo(c.Node).Symbol is IMethodSymbol attribute && attribute.IsInType(KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute) && HasJustificationProperty(attribute.ContainingType)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.Attribute); private bool NoJustification(SyntaxNode node, SemanticModel model) => GetJustificationExpression(node) is not { } justification || string.IsNullOrWhiteSpace(Language.FindConstantValue(model, justification) as string); /// "Justification" was added in .Net 5, while ExcludeFromCodeCoverage in netstandard2.0. private static bool HasJustificationProperty(INamedTypeSymbol symbol) => symbol.MemberNames.Contains(JustificationPropertyName); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ExpectedExceptionAttributeShouldNotBeUsedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ExpectedExceptionAttributeShouldNotBeUsedBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S3431"; protected abstract SyntaxNode FindExpectedExceptionAttribute(SyntaxNode node); protected abstract bool HasMultiLineBody(SyntaxNode node); protected abstract bool AssertInCatchFinallyBlock(SyntaxNode node); protected override string MessageFormat => "Replace the 'ExpectedException' attribute with a throw assertion or a try/catch block."; protected ExpectedExceptionAttributeShouldNotBeUsedBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (!ContainExpectedExceptionType(c.Compilation)) { return; } c.RegisterNodeAction(Language.GeneratedCodeRecognizer, cc => { if (FindExpectedExceptionAttribute(cc.Node) is {} attribute && HasMultiLineBody(cc.Node) && !AssertInCatchFinallyBlock(cc.Node)) { cc.ReportIssue(Rule, attribute); } }, Language.SyntaxKind.MethodDeclarations); }); private static bool ContainExpectedExceptionType(Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_ExpectedExceptionAttribute) is not null || compilation.GetTypeByMetadataName(KnownType.NUnit_Framework_ExpectedExceptionAttribute) is not null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ExpressionComplexityBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ExpressionComplexityBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct, Enum { protected const string DiagnosticId = "S1067"; private const string MessageFormat = "Reduce the number of conditional operators ({1}) used in the expression (maximum allowed {0})."; private const int DefaultValueMaximum = 3; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } protected abstract HashSet ComplexityIncreasingKinds { get; } protected abstract HashSet TransparentKinds { get; } protected abstract SyntaxNode[] ExpressionChildren(SyntaxNode node); [RuleParameter("max", PropertyType.Integer, "Maximum number of allowed conditional operators in an expression", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected ExpressionComplexityBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected sealed override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsRoot(c.Node) && !IsEqualsMethod(c.ContainingSymbol)) { var complexity = CalculateComplexity(c.Node); if (complexity > Maximum) { c.ReportIssue(rule, c.Node, Maximum.ToString(), complexity.ToString()); } } }, ComplexityIncreasingKinds.Concat(TransparentKinds).ToArray()); private static bool IsEqualsMethod(ISymbol symbol) => symbol is IMethodSymbol method && (method.IsObjectEquals() || method.IsImplementingInterfaceMember(KnownType.System_IEquatable_T, nameof(IEquatable<>.Equals))); private bool IsRoot(SyntaxNode node) => node?.Parent is null || (node.Parent.Kind() is var parentKind && !ComplexityIncreasingKinds.Contains(parentKind) && !TransparentKinds.Contains(parentKind)); private int CalculateComplexity(SyntaxNode node) { var complexity = 0; var stack = new Stack(); stack.Push(node); while (stack.TryPop(out var current)) { if (ComplexityIncreasingKinds.Contains(current.Kind())) { complexity++; } stack.Push(ExpressionChildren(current)); } return complexity; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ExtensionMethodShouldNotExtendObjectBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ExtensionMethodShouldNotExtendObjectBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TMethodDeclaration : SyntaxNode { private const string DiagnosticId = "S4225"; protected override string MessageFormat => "Refactor this extension to extend a more concrete type."; protected abstract bool IsExtensionMethod(TMethodDeclaration declaration, SemanticModel semanticModel); protected ExtensionMethodShouldNotExtendObjectBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsExtensionMethod((TMethodDeclaration)c.Node, c.Model) && c.Model.GetDeclaredSymbol(c.Node) is IMethodSymbol method && method.IsExtensionMethod && method.Parameters.Length > 0 && method.Parameters[0].Type.Is(KnownType.System_Object)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(c.Node).Value); } }, Language.SyntaxKind.MethodDeclarations); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FieldShadowsParentFieldBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; [Obsolete("This rule has been deprecated since 9.32")] public abstract class FieldShadowsParentFieldBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TVariableDeclaratorSyntax : SyntaxNode { private const string S2387DiagnosticId = "S2387"; private const string S2387MessageFormat = "'{0}' is the name of a field in '{1}'."; private const string S4025DiagnosticId = "S4025"; private const string S4025MessageFormat = "Rename this field; it may be confused with '{0}' in '{1}'."; private readonly DiagnosticDescriptor s2387; private readonly DiagnosticDescriptor s4025; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(s2387, s4025); protected FieldShadowsParentFieldBase() { s2387 = Language.CreateDescriptor(S2387DiagnosticId, S2387MessageFormat); s4025 = Language.CreateDescriptor(S4025DiagnosticId, S4025MessageFormat); } protected IEnumerable CheckFields(SemanticModel model, TVariableDeclaratorSyntax variableDeclarator) { if (model.GetDeclaredSymbol(variableDeclarator) is IFieldSymbol fieldSymbol) { var fieldName = fieldSymbol.Name; foreach (var baseType in fieldSymbol.ContainingType.BaseType.GetSelfAndBaseTypes()) { var similarFields = baseType.GetMembers().OfType().Where(IsMatch).ToList(); if (similarFields.Any(x => x.Name == fieldName)) { yield return Diagnostic.Create(s2387, Language.Syntax.NodeIdentifier(variableDeclarator).Value.GetLocation(), fieldName, baseType.Name); } else if (similarFields.Any()) { yield return Diagnostic.Create(s4025, Language.Syntax.NodeIdentifier(variableDeclarator).Value.GetLocation(), similarFields.First().Name, baseType.Name); } } bool IsMatch(IFieldSymbol field) => field.DeclaredAccessibility != Accessibility.Private && !field.IsStatic && field.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FieldShouldNotBePublicBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class FieldShouldNotBePublicBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TFieldDeclarationSyntax : SyntaxNode where TVariableSyntax : SyntaxNode { private const string DiagnosticId = "S2357"; protected abstract IEnumerable Variables(TFieldDeclarationSyntax fieldDeclaration); protected override string MessageFormat => "Make '{0}' private."; protected FieldShouldNotBePublicBase() : base(DiagnosticId) { } protected static bool FieldIsRelevant(IFieldSymbol fieldSymbol) => fieldSymbol is { IsStatic: false, IsConst: false } && fieldSymbol.GetEffectiveAccessibility() == Accessibility.Public && fieldSymbol.ContainingType.IsClass(); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var fieldDeclaration = (TFieldDeclarationSyntax)c.Node; var variables = Variables(fieldDeclaration); foreach (var variable in variables.Select(x => new Pair(x, c.Model.GetDeclaredSymbol(x) as IFieldSymbol)).Where(x => FieldIsRelevant(x.Symbol))) { var identifier = Language.Syntax.NodeIdentifier(variable.Node); c.ReportIssue(Rule, identifier.Value, identifier.Value.ValueText); } }, Language.SyntaxKind.FieldDeclaration); private sealed record Pair(TVariableSyntax Node, IFieldSymbol Symbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FileLinesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class FileLinesBase : ParametrizedDiagnosticAnalyzer { internal const string DiagnosticId = "S104"; internal const string MessageFormat = "This file has {1} lines, which is greater than {0} authorized. Split it into " + "smaller files."; private const int DefaultValueMaximum = 1000; [RuleParameter("maximumFileLocThreshold", PropertyType.Integer, "Maximum authorized lines in a file.", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterTreeAction( GeneratedCodeRecognizer, stac => { var linesCount = stac.Tree .GetRoot() .DescendantTokens() .Where(token => !IsEndOfFileToken(token)) .SelectMany(token => token.LineNumbers(isZeroBasedCount: false)) .Distinct() .LongCount(); if (linesCount > Maximum) { stac.ReportIssue(SupportedDiagnostics[0], Location.Create(stac.Tree, TextSpan.FromBounds(0, 0)), Maximum.ToString(), linesCount.ToString()); } }); } protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract bool IsEndOfFileToken(SyntaxToken token); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FindInsteadOfFirstOrDefaultBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class FindInsteadOfFirstOrDefaultBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpression : SyntaxNode { private const string DiagnosticId = "S6602"; private const int NumberOfArgument = 1; private const string GenericMessage = @"""Find"" method should be used instead of the ""FirstOrDefault"" extension method."; private const string ArrayMessage = @"""Array.Find"" static method should be used instead of the ""FirstOrDefault"" extension method."; protected override string MessageFormat => "{0}"; private static readonly ImmutableArray ListTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_List_T, KnownType.System_Collections_Immutable_ImmutableList_T); protected FindInsteadOfFirstOrDefaultBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocationExpression)c.Node; if (Language.GetName(invocation).Equals(nameof(Enumerable.FirstOrDefault), Language.NameComparison) && Language.Syntax.HasExactlyNArguments(invocation, NumberOfArgument) && Language.Syntax.Operands(invocation) is { Left: { } left, Right: { } right } && IsCorrectCall(right, c.Model) && IsCorrectType(left, c.Model, out var isArray) && !Language.Syntax.IsInExpressionTree(c.Model, invocation)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), isArray ? ArrayMessage : GenericMessage); } }, Language.SyntaxKind.InvocationExpression); private static bool IsCorrectCall(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) && method.Parameters.Length == NumberOfArgument && method.Parameters[0].IsType(KnownType.System_Func_T_TResult); private static bool IsCorrectType(SyntaxNode left, SemanticModel model, out bool isArray) { var type = model.GetTypeInfo(left).Type; isArray = type.DerivesFrom(KnownType.System_Array); return isArray || type.DerivesFromAny(ListTypes); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FlagsEnumWithoutInitializerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class FlagsEnumWithoutInitializerBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TEnumMemberDeclarationSyntax : SyntaxNode { protected const string DiagnosticId = "S2345"; private const int AllowedEmptyMemberCount = 3; protected abstract bool IsInitialized(TEnumMemberDeclarationSyntax member); protected override string MessageFormat => "Initialize all the members of this 'Flags' enumeration."; protected FlagsEnumWithoutInitializerBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (c.Node.HasFlagsAttribute(c.Model) && !AreAllRequiredMembersInitialized(c.Node) && Language.Syntax.NodeIdentifier(c.Node) is { } identifier) { c.ReportIssue(Rule, identifier); } }, Language.SyntaxKind.EnumDeclaration); private bool AreAllRequiredMembersInitialized(SyntaxNode declaration) { var members = Language.Syntax.EnumMembers(declaration).Cast().ToList(); var firstNonInitialized = members.FirstOrDefault(m => !IsInitialized(m)); if (firstNonInitialized == null) { // All members initialized return true; } var firstInitialized = members.FirstOrDefault(m => IsInitialized(m)); if (firstInitialized == null) { // No members initialized return members.Count <= AllowedEmptyMemberCount; } var firstInitializedIndex = members.IndexOf(firstInitialized); if (firstInitializedIndex >= AllowedEmptyMemberCount || members.IndexOf(firstNonInitialized) > firstInitializedIndex) { // Have first uninitialized member after the first initialized member, or // Have too many uninitialized in the beginning return false; } return members.Skip(firstInitializedIndex).All(m => IsInitialized(m)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FlagsEnumZeroMemberBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class FlagsEnumZeroMemberBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S2346"; protected override string MessageFormat => "Rename '{0}' to 'None'."; protected FlagsEnumZeroMemberBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (c.Node.HasFlagsAttribute(c.Model) && ZeroMember(c.Node, c.Model) is { } zeroMember && Language.Syntax.NodeIdentifier(zeroMember) is { } identifier && identifier.ValueText != "None") { c.ReportIssue(Rule, zeroMember, identifier.ValueText); } }, Language.SyntaxKind.EnumDeclaration); private SyntaxNode ZeroMember(SyntaxNode node, SemanticModel semanticModel) { foreach (var item in Language.Syntax.EnumMembers(node)) { if (semanticModel.GetDeclaredSymbol(item) is IFieldSymbol symbol && symbol.ConstantValue is { } constValue) { try { if (Convert.ToInt64(constValue) == 0) { return item; } } catch (OverflowException) { return null; } } } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FunctionComplexityBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class FunctionComplexityBase : ParametrizedDiagnosticAnalyzer { protected const string DiagnosticId = "S1541"; protected const string MessageFormat = "The Cyclomatic Complexity of this {2} is {1} which is greater than {0} authorized."; protected const int DefaultValueMaximum = 10; [RuleParameter("maximumFunctionComplexityThreshold", PropertyType.Integer, "The maximum authorized complexity.", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract override void Initialize(SonarParametrizedAnalysisContext context); protected void CheckComplexity(SonarSyntaxNodeReportingContext context, Func nodeSelector, Func location, string declarationType) where TSyntax : SyntaxNode { var syntax = (TSyntax)context.Node; var nodeToAnalyze = nodeSelector(syntax); if (nodeToAnalyze == null) { return; } var complexity = GetComplexity(nodeToAnalyze, context.Model); if (complexity > Maximum) { context.ReportIssue(SupportedDiagnostics[0], location(syntax), Maximum.ToString(), complexity.ToString(), declarationType); } } protected void CheckComplexity(SonarSyntaxNodeReportingContext context, Func location, string declarationType) where TSyntax : SyntaxNode { CheckComplexity(context, t => t, location, declarationType); } protected abstract int GetComplexity(SyntaxNode node, SemanticModel semanticModel); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/FunctionNestingDepthBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class FunctionNestingDepthBase : ParametrizedDiagnosticAnalyzer { protected const string DiagnosticId = "S134"; private const string MessageFormat = "Refactor this code to not nest more than {0} control flow statements."; private const int DefaultValueMaximum = 3; protected readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); [RuleParameter("maximumNestingLevel", PropertyType.Integer, "Maximum allowed control flow statement nesting depth.", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; protected FunctionNestingDepthBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected class NestingDepthCounter { private readonly int maximumNestingDepth; private readonly Action actionMaximumExceeded; private int currentDepth; public NestingDepthCounter(int maximumNestingDepth, Action actionMaximumExceeded) { this.maximumNestingDepth = maximumNestingDepth; this.actionMaximumExceeded = actionMaximumExceeded; } public void CheckNesting(SyntaxToken keyword, Action visitAction) { currentDepth++; if (currentDepth <= maximumNestingDepth) { visitAction(); } else { actionMaximumExceeded(keyword); } currentDepth--; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/GenericInheritanceShouldNotBeRecursiveBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class GenericInheritanceShouldNotBeRecursiveBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TDeclaration : SyntaxNode { protected const string DiagnosticId = "S3464"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract INamedTypeSymbol GetNamedTypeSymbol(TDeclaration declaration, SemanticModel semanticModel); protected abstract Location GetLocation(TDeclaration declaration); protected abstract SyntaxToken GetKeyword(TDeclaration declaration); protected override string MessageFormat => "Refactor this {0} so that the generic inheritance chain is not recursive."; protected GenericInheritanceShouldNotBeRecursiveBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var declaration = (TDeclaration)c.Node; if (!c.IsRedundantPositionalRecordContext() && IsRecursiveInheritance(GetNamedTypeSymbol(declaration, c.Model))) { c.ReportIssue(Rule, GetLocation(declaration), GetKeyword(declaration).ValueText); } }, SyntaxKinds); private static IEnumerable GetBaseTypes(INamedTypeSymbol typeSymbol) { var interfaces = typeSymbol.Interfaces.Where(IsGenericType); return typeSymbol.IsClass() ? interfaces.Concat(new[] { typeSymbol.BaseType }) : interfaces; } private static bool HasRecursiveGenericSubstitution(INamedTypeSymbol typeSymbol, INamedTypeSymbol declaredType) { bool IsSameAsDeclaredType(INamedTypeSymbol type) => type.OriginalDefinition.Equals(declaredType) && HasSubstitutedTypeArguments(type); bool ContainsRecursiveGenericSubstitution(IEnumerable types) => types.OfType() .Any(type => IsSameAsDeclaredType(type) || ContainsRecursiveGenericSubstitution(type.TypeArguments)); return ContainsRecursiveGenericSubstitution(typeSymbol.TypeArguments); } private static bool IsGenericType(INamedTypeSymbol type) => type != null && type.IsGenericType; private static bool HasSubstitutedTypeArguments(INamedTypeSymbol type) => type.TypeArguments.OfType().Any(); private static bool IsRecursiveInheritance(INamedTypeSymbol typeSymbol) { if (!IsGenericType(typeSymbol)) { return false; } var baseTypes = GetBaseTypes(typeSymbol); return baseTypes.Any(t => IsGenericType(t) && HasRecursiveGenericSubstitution(t, typeSymbol)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/GotoStatementBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class GotoStatementBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S907"; protected override string MessageFormat => $"Remove this use of '{GoToLabel}'."; protected abstract TSyntaxKind[] GotoSyntaxKinds { get; } protected abstract string GoToLabel { get; } protected GotoStatementBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => c.ReportIssue(Rule, c.Node.GetFirstToken()), GotoSyntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/CommandPathBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class CommandPathBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S4036"; private const string MessageFormat = """Make sure the "PATH" variable only contains fixed, unwriteable directories."""; private readonly Regex validPath = new Regex(@"^(\.{0,2}[\\/]|\w+:)", RegexOptions.None, Constants.DefaultRegexTimeout); protected abstract string FirstArgument(InvocationContext context); protected CommandPathBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var inv = Language.Tracker.Invocation; inv.Track(input, inv.MatchMethod(new MemberDescriptor(KnownType.System_Diagnostics_Process, "Start")), c => IsInvalid(FirstArgument(c))); var pa = Language.Tracker.PropertyAccess; pa.Track(input, pa.MatchProperty(new MemberDescriptor(KnownType.System_Diagnostics_ProcessStartInfo, "FileName")), pa.MatchSetter(), c => IsInvalid((string)pa.AssignedValue(c))); var oc = Language.Tracker.ObjectCreation; oc.Track(input, oc.MatchConstructor(KnownType.System_Diagnostics_ProcessStartInfo), c => oc.ConstArgumentForParameter(c, "fileName") is string value && IsInvalid(value)); } private bool IsInvalid(string path) => !string.IsNullOrEmpty(path) && !validPath.SafeIsMatch(path); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/ConfiguringLoggersBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { [Obsolete("This rule has been deprecated since 9.16")] public abstract class ConfiguringLoggersBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S4792"; protected const string MessageFormat = "Make sure that this logger's configuration is safe."; protected ConfiguringLoggersBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var inv = Language.Tracker.Invocation; var pa = Language.Tracker.PropertyAccess; var oc = Language.Tracker.ObjectCreation; // ASP.NET Core inv.Track( input, inv.MatchMethod( new MemberDescriptor(KnownType.Microsoft_AspNetCore_Hosting_WebHostBuilderExtensions, "ConfigureLogging"), new MemberDescriptor(KnownType.Microsoft_Extensions_DependencyInjection_LoggingServiceCollectionExtensions, "AddLogging"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_ConsoleLoggerExtensions, "AddConsole"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_DebugLoggerFactoryExtensions, "AddDebug"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_EventLoggerFactoryExtensions, "AddEventLog"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_EventLoggerFactoryExtensions, "AddEventSourceLogger"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_EventSourceLoggerFactoryExtensions, "AddEventSourceLogger"), new MemberDescriptor(KnownType.Microsoft_Extensions_Logging_AzureAppServicesLoggerFactoryExtensions, "AddAzureWebAppDiagnostics")), inv.MethodIsExtension()); oc.Track(input, oc.WhenImplements(KnownType.Microsoft_Extensions_Logging_ILoggerFactory)); // log4net inv.Track( input, inv.MatchMethod( new MemberDescriptor(KnownType.log4net_Config_XmlConfigurator, "Configure"), new MemberDescriptor(KnownType.log4net_Config_XmlConfigurator, "ConfigureAndWatch"), new MemberDescriptor(KnownType.log4net_Config_DOMConfigurator, "Configure"), new MemberDescriptor(KnownType.log4net_Config_DOMConfigurator, "ConfigureAndWatch"), new MemberDescriptor(KnownType.log4net_Config_BasicConfigurator, "Configure"))); // NLog pa.Track( input, pa.MatchSetter(), pa.MatchProperty(new MemberDescriptor(KnownType.NLog_LogManager, "Configuration"))); // Serilog oc.Track( input, oc.WhenDerivesFrom(KnownType.Serilog_LoggerConfiguration)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/CreatingHashAlgorithmsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules; public abstract class CreatingHashAlgorithmsBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S4790"; protected const string MessageFormat = "Make sure this weak hash algorithm is not used in a sensitive context here."; private const string CreateMethodName = "Create"; private const string HashDataName = "HashData"; private const string HashDataAsyncName = "HashDataAsync"; private const string TryHashDataName = "TryHashData"; private readonly KnownType[] algorithmTypes = [ KnownType.System_Security_Cryptography_DSA, KnownType.System_Security_Cryptography_HMACMD5, KnownType.System_Security_Cryptography_HMACRIPEMD160, KnownType.System_Security_Cryptography_HMACSHA1, KnownType.System_Security_Cryptography_MD5, KnownType.System_Security_Cryptography_RIPEMD160, KnownType.System_Security_Cryptography_SHA1 ]; private readonly string[] unsafeAlgorithms = [ "DSA", "System.Security.Cryptography.DSA", "HMACMD5", "System.Security.Cryptography.HMACMD5", "HMACRIPEMD160", "System.Security.Cryptography.HMACRIPEMD160", "HMACSHA1", "System.Security.Cryptography.HMACSHA1", "MD5", "System.Security.Cryptography.MD5", "RIPEMD160", "System.Security.Cryptography.RIPEMD160", "SHA1", "System.Security.Cryptography.SHA1", ]; protected abstract bool IsUnsafeAlgorithm(SyntaxNode argumentNode, SemanticModel model); protected CreatingHashAlgorithmsBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var oc = Language.Tracker.ObjectCreation; oc.Track(input, oc.WhenDerivesOrImplementsAny(algorithmTypes)); var tracker = Language.Tracker.Invocation; tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_DSA, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_HMAC, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_MD5, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_RIPEMD160, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_SHA1, CreateMethodName)), tracker.MethodHasParameters(0)); tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_AsymmetricAlgorithm, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptoConfig, "CreateFromName"), new MemberDescriptor(KnownType.System_Security_Cryptography_DSA, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_HashAlgorithm, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_HMAC, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_KeyedHashAlgorithm, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_MD5, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_RIPEMD160, CreateMethodName), new MemberDescriptor(KnownType.System_Security_Cryptography_SHA1, CreateMethodName)), tracker.ArgumentAtIndexIsAny(0, unsafeAlgorithms)); tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_MD5, HashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_MD5, TryHashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_MD5, HashDataAsyncName), new MemberDescriptor(KnownType.System_Security_Cryptography_SHA1, HashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_SHA1, TryHashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_SHA1, HashDataAsyncName))); tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, HashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, HashDataAsyncName), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, TryHashDataName), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, "HmacData"), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, "HmacDataAsync"), new MemberDescriptor(KnownType.System_Security_Cryptography_CryptographicOperations, "TryHmacData")), tracker.ArgumentAtIndexIs(0, IsUnsafeAlgorithm)); tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_DSA, HashDataName)), tracker.ArgumentAtIndexIs(1, IsUnsafeAlgorithm)); tracker.Track( input, tracker.MatchMethod( new MemberDescriptor(KnownType.System_Security_Cryptography_DSA, TryHashDataName)), tracker.ArgumentAtIndexIs(2, IsUnsafeAlgorithm)); tracker.Track( input, tracker.MatchMethod(new MemberDescriptor(KnownType.System_Security_Cryptography_DSA, HashDataName)), tracker.ArgumentAtIndexIs(3, IsUnsafeAlgorithm)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/DeliveringDebugFeaturesInProductionBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class DeliveringDebugFeaturesInProductionBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4507"; protected const string StartupDevelopment = "StartupDevelopment"; private const string MessageFormat = "Make sure this debug feature is deactivated before delivering the code in production."; private readonly ImmutableArray isDevelopmentMethods = ImmutableArray.Create( new MemberDescriptor(KnownType.Microsoft_AspNetCore_Hosting_HostingEnvironmentExtensions, "IsDevelopment"), new MemberDescriptor(KnownType.Microsoft_Extensions_Hosting_HostEnvironmentEnvExtensions, "IsDevelopment")); protected abstract bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel); protected abstract bool IsInDevelopmentContext(SyntaxNode node); protected DeliveringDebugFeaturesInProductionBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var t = Language.Tracker.Invocation; t.Track(input, t.MatchMethod(new MemberDescriptor(KnownType.Microsoft_AspNetCore_Builder_DeveloperExceptionPageExtensions, "UseDeveloperExceptionPage"), new MemberDescriptor(KnownType.Microsoft_AspNetCore_Builder_DatabaseErrorPageExtensions, "UseDatabaseErrorPage")), t.ExceptWhen(c => IsDevelopmentCheckInvoked(c.Node, c.Model)), t.ExceptWhen(c => IsInDevelopmentContext(c.Node))); } protected bool IsValidationMethod(SemanticModel semanticModel, SyntaxNode condition, string methodName) => new Lazy(() => semanticModel.GetSymbolInfo(condition).Symbol as IMethodSymbol) is var lazySymbol && isDevelopmentMethods.Any(x => x.IsMatch(methodName, lazySymbol, Language.NameComparison)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/DisablingRequestValidationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.IO; using System.Xml.Linq; using System.Xml.XPath; namespace SonarAnalyzer.Core.Rules { public abstract class DisablingRequestValidationBase : HotspotDiagnosticAnalyzer { private const string DiagnosticId = "S5753"; private const string MessageFormat = "Make sure disabling ASP.NET Request Validation feature is safe here."; // See https://docs.microsoft.com/en-us/dotnet/api/system.web.configuration.httpruntimesection.requestvalidationmode private const int MinimumAcceptedRequestValidationModeValue = 4; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected DisablingRequestValidationBase(IAnalyzerConfiguration configuration) : base(configuration) => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); protected override void Initialize(SonarAnalysisContext context) { context.RegisterSymbolAction(CheckController, SymbolKind.NamedType, SymbolKind.Method); context.RegisterCompilationAction(CheckWebConfig); } private void CheckController(SonarSymbolReportingContext context) { if (!IsEnabled(context.Options)) { return; } var attributes = context.Symbol.GetAttributes(); if (attributes.IsEmpty) { return; } var attributeWithFalseParameter = attributes.FirstOrDefault(a => a.ConstructorArguments.Length == 1 && a.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && a.ConstructorArguments[0].Value is bool enableValidationValue && !enableValidationValue && a.AttributeClass.Is(KnownType.System_Web_Mvc_ValidateInputAttribute)); if (attributeWithFalseParameter is not null) { context.ReportIssue(Language.GeneratedCodeRecognizer, rule, attributeWithFalseParameter.ApplicationSyntaxReference.GetSyntax()); } } private void CheckWebConfig(SonarCompilationReportingContext context) { if (!IsEnabled(context.Options)) { return; } foreach (var fullPath in context.WebConfigFiles()) { var webConfig = File.ReadAllText(fullPath); if (webConfig.Contains("") && webConfig.ParseXDocument() is { } doc) { ReportValidateRequest(context, doc, fullPath); ReportRequestValidationMode(context, doc, fullPath); } } } private void ReportValidateRequest(SonarCompilationReportingContext context, XDocument doc, string webConfigPath) { foreach (var pages in doc.XPathSelectElements("configuration/system.web/pages")) { if (pages.GetAttributeIfBoolValueIs("validateRequest", false) is { } validateRequest && validateRequest.CreateLocation(webConfigPath) is { } location) { context.ReportIssue(Language.GeneratedCodeRecognizer, rule, location); } } } private void ReportRequestValidationMode(SonarCompilationReportingContext context, XDocument doc, string webConfigPath) { foreach (var httpRuntime in doc.XPathSelectElements("configuration/system.web/httpRuntime")) { if (httpRuntime.Attribute("requestValidationMode") is { } requestValidationMode && decimal.TryParse(requestValidationMode.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var value) && value < MinimumAcceptedRequestValidationModeValue && requestValidationMode.CreateLocation(webConfigPath) is { } location) { context.ReportIssue(Language.GeneratedCodeRecognizer, rule, location); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/ExecutingSqlQueriesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class ExecutingSqlQueriesBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct where TExpressionSyntax : SyntaxNode where TIdentifierNameSyntax : SyntaxNode { private const string DiagnosticId = "S2077"; private const string AssignmentWithFormattingMessage = "SQL Query is dynamically formatted and assigned to {0}."; private const string AssignmentMessage = "SQL query is assigned to {0}."; private const string MessageFormat = "Make sure using a dynamically formatted SQL query is safe here."; private const int FirstArgumentIndex = 0; private const int SecondArgumentIndex = 1; private readonly KnownType[] constructorsForFirstArgument = { KnownType.System_Data_Odbc_OdbcCommand, KnownType.System_Data_Odbc_OdbcDataAdapter, KnownType.System_Data_SqlClient_SqlCommand, KnownType.System_Data_SqlClient_SqlDataAdapter, KnownType.System_Data_Sqlite_SqliteCommand, KnownType.System_Data_Sqlite_SQLiteDataAdapter, KnownType.System_Data_SqlServerCe_SqlCeCommand, KnownType.System_Data_SqlServerCe_SqlCeDataAdapter, KnownType.System_Data_OracleClient_OracleCommand, KnownType.System_Data_OracleClient_OracleDataAdapter, KnownType.MySql_Data_MySqlClient_MySqlCommand, KnownType.MySql_Data_MySqlClient_MySqlDataAdapter, KnownType.MySql_Data_MySqlClient_MySqlScript, KnownType.Microsoft_Data_Sqlite_SqliteCommand, KnownType.Mono_Data_Sqlite_SqliteCommand, KnownType.Mono_Data_Sqlite_SqliteDataAdapter, KnownType.Microsoft_EntityFrameworkCore_RawSqlString, KnownType.Dapper_CommandDefinition, KnownType.Microsoft_Azure_Cosmos_QueryDefinition, KnownType.Microsoft_Data_SqlClient_SqlCommand, KnownType.Microsoft_Data_SqlClient_SqlDataAdapter, KnownType.NHibernate_Engine_NamedQueryDefinition, KnownType.NHibernate_Engine_NamedSQLQueryDefinition, KnownType.NHibernate_Impl_QueryImpl, KnownType.Oracle_ManagedDataAccess_Client_OracleCommand, KnownType.Oracle_ManagedDataAccess_Client_OracleDataAdapter }; private readonly KnownType[] constructorsForSecondArgument = { KnownType.MySql_Data_MySqlClient_MySqlScript }; private readonly MemberDescriptor[] invocationsForFirstTwoArguments = { new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommandAsync"), new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlCommand"), new(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions, "FromSql") }; private readonly MemberDescriptor[] invocationsForFirstTwoArgumentsAfterV2 = { new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlRaw"), new(KnownType.Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions, "ExecuteSqlRawAsync"), new(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions, "FromSqlRaw"), new(KnownType.Dapper_SqlMapper, "Execute"), new(KnownType.Dapper_SqlMapper, "ExecuteAsync"), new(KnownType.Dapper_SqlMapper, "ExecuteReader"), new(KnownType.Dapper_SqlMapper, "ExecuteReaderAsync"), new(KnownType.Dapper_SqlMapper, "ExecuteScalar"), new(KnownType.Dapper_SqlMapper, "ExecuteScalarAsync"), new(KnownType.Dapper_SqlMapper, "Query"), new(KnownType.Dapper_SqlMapper, "QueryAsync"), new(KnownType.Dapper_SqlMapper, "QueryFirst"), new(KnownType.Dapper_SqlMapper, "QueryFirstAsync"), new(KnownType.Dapper_SqlMapper, "QueryFirstOrDefault"), new(KnownType.Dapper_SqlMapper, "QueryFirstOrDefaultAsync"), new(KnownType.Dapper_SqlMapper, "QueryMultiple"), new(KnownType.Dapper_SqlMapper, "QueryMultipleAsync"), new(KnownType.Dapper_SqlMapper, "QuerySingle"), new(KnownType.Dapper_SqlMapper, "QuerySingleAsync"), new(KnownType.Dapper_SqlMapper, "QuerySingleOrDefault"), new(KnownType.Dapper_SqlMapper, "QuerySingleOrDefaultAsync"), new(KnownType.System_Data_Entity_Database, "SqlQuery"), new(KnownType.System_Data_Entity_Database, "ExecuteSqlCommand"), new(KnownType.System_Data_Entity_Database, "ExecuteSqlCommandAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Select"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "SelectLazy"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "SelectNonDefaults"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Single"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Scalar"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Column"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "ColumnLazy"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "ColumnDistinct"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Lookup"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Dictionary"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "Exists"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "SqlList"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "SqlColumn"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "SqlScalar"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApi, "ExecuteNonQuery"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SelectAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SelectNonDefaultsAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "ScalarAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SingleAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "ColumnAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "ColumnDistinctAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "LookupAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "DictionaryAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "ExistsAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SqlListAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SqlColumnAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "SqlScalarAsync"), new(KnownType.ServiceStack_OrmLite_OrmLiteReadApiAsync, "ExecuteNonQueryAsync") }; private readonly MemberDescriptor[] invocationsForFirstArgument = { new(KnownType.System_Data_Sqlite_SqliteCommand, "Execute"), new(KnownType.System_Data_Entity_DbSet, "SqlQuery"), new(KnownType.System_Data_Entity_DbSet_TEntity, "SqlQuery"), new(KnownType.NHibernate_ISession, "CreateQuery"), new(KnownType.NHibernate_ISession, "CreateSQLQuery"), new(KnownType.NHibernate_ISession, "Delete"), new(KnownType.NHibernate_ISession, "DeleteAsync"), new(KnownType.NHibernate_ISession, "GetNamedQuery"), new(KnownType.NHibernate_Impl_AbstractSessionImpl, "CreateQuery"), new(KnownType.NHibernate_Impl_AbstractSessionImpl, "CreateSQLQuery"), new(KnownType.NHibernate_Impl_AbstractSessionImpl, "GetNamedQuery"), new(KnownType.NHibernate_Impl_AbstractSessionImpl, "GetNamedSQLQuery"), new(KnownType.Microsoft_Azure_Cosmos_Container, "GetItemQueryIterator"), new(KnownType.Microsoft_Azure_Cosmos_Container, "GetItemQueryStreamIterator") }; private readonly MemberDescriptor[] invocationsForSecondArgument = { new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRow"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataRowAsync"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDataset"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteDatasetAsync"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQuery"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteNonQueryAsync"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReader"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteReaderAsync"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalar"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "ExecuteScalarAsync"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSet"), new(KnownType.MySql_Data_MySqlClient_MySqlHelper, "UpdateDataSetAsync"), new(KnownType.NHibernate_ISession, "CreateFilter"), new(KnownType.NHibernate_ISession, "CreateFilterAsync") }; private readonly MemberDescriptor[] properties = { new(KnownType.System_Data_IDbCommand, "CommandText"), // Also covers SqlCommand and OracleCommand new(KnownType.NHibernate_Cfg_Loquacious_NamedQueryDefinitionBuilder, "Query") }; protected abstract TExpressionSyntax GetArgumentAtIndex(InvocationContext context, int index); protected abstract TExpressionSyntax GetArgumentAtIndex(ObjectCreationContext context, int index); protected abstract TExpressionSyntax GetSetValue(PropertyAccessContext context); protected abstract bool IsTracked(TExpressionSyntax expression, SyntaxBaseContext context); protected abstract bool IsSensitiveExpression(TExpressionSyntax expression, SemanticModel semanticModel); protected abstract Location SecondaryLocationForExpression(TExpressionSyntax node, string identifierNameToFind, out string identifierNameFound); protected ExecutingSqlQueriesBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var inv = Language.Tracker.Invocation; inv.Track(input, inv.MatchMethod(invocationsForFirstTwoArguments), inv.And(MethodHasRawSqlQueryParameter(), inv.Or(ArgumentAtIndexIsTracked(0), ArgumentAtIndexIsTracked(1))), inv.ExceptWhen(inv.ArgumentAtIndexIsStringConstant(0))); inv.Track(input, inv.MatchMethod(invocationsForFirstTwoArgumentsAfterV2), inv.Or(ArgumentAtIndexIsTracked(0), ArgumentAtIndexIsTracked(1)), inv.ExceptWhen(inv.ArgumentAtIndexIsStringConstant(0))); TrackInvocations(input, invocationsForFirstArgument, FirstArgumentIndex); TrackInvocations(input, invocationsForSecondArgument, SecondArgumentIndex); var pa = Language.Tracker.PropertyAccess; pa.Track(input, pa.MatchProperty(true, properties), pa.MatchSetter(), c => IsTracked(GetSetValue(c), c), pa.ExceptWhen(pa.AssignedValueIsConstant())); TrackObjectCreation(input, constructorsForFirstArgument, FirstArgumentIndex); TrackObjectCreation(input, constructorsForSecondArgument, SecondArgumentIndex); } protected bool IsTrackedVariableDeclaration(TExpressionSyntax expression, SyntaxBaseContext context) { var node = expression; while (node is TIdentifierNameSyntax identifierNameSyntax) { var identifierName = Language.Syntax.NodeIdentifier(identifierNameSyntax).Value.ValueText; node = Language.AssignmentFinder.FindLinearPrecedingAssignmentExpression(identifierName, node) as TExpressionSyntax; var location = SecondaryLocationForExpression(node, identifierName, out var foundName); if (IsSensitiveExpression(node, context.Model)) { context.AddSecondaryLocation(location, AssignmentWithFormattingMessage, foundName); return true; } else { context.AddSecondaryLocation(location, AssignmentMessage, foundName); } } return false; } private void TrackObjectCreation(TrackerInput input, KnownType[] objectCreationTypes, int argumentIndex) { var t = Language.Tracker.ObjectCreation; t.Track(input, t.MatchConstructor(objectCreationTypes), t.ArgumentAtIndexIs(argumentIndex, KnownType.System_String), c => IsTracked(GetArgumentAtIndex(c, argumentIndex), c), t.ExceptWhen(t.ArgumentAtIndexIsConst(argumentIndex))); } private void TrackInvocations(TrackerInput input, MemberDescriptor[] invocationsDescriptors, int argumentIndex) { var t = Language.Tracker.Invocation; t.Track(input, t.MatchMethod(invocationsDescriptors), ArgumentAtIndexIsTracked(argumentIndex), t.ExceptWhen(t.ArgumentAtIndexIsStringConstant(argumentIndex))); } private TrackerBase.Condition MethodHasRawSqlQueryParameter() => context => { return Language.Syntax.NodeExpression(context.Node) is { } invocationExpression && context.Model.GetSymbolInfo(invocationExpression).Symbol is IMethodSymbol methodSymbol && (ParameterIsRawString(methodSymbol, 0) || ParameterIsRawString(methodSymbol, 1)); static bool ParameterIsRawString(IMethodSymbol method, int index) => method.Parameters.Length > index && method.Parameters[index].IsType(KnownType.Microsoft_EntityFrameworkCore_RawSqlString); }; private TrackerBase.Condition ArgumentAtIndexIsTracked(int index) => context => IsTracked(GetArgumentAtIndex(context, index), context); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/ExpandingArchivesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class ExpandingArchivesBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S5042"; protected const string MessageFormat = "Make sure that decompressing this archive file is safe."; protected ExpandingArchivesBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var t = Language.Tracker.Invocation; t.Track(input, t.MatchMethod( new MemberDescriptor(KnownType.System_IO_Compression_ZipFileExtensions, "ExtractToFile"), new MemberDescriptor(KnownType.System_IO_Compression_ZipFileExtensions, "ExtractToDirectory"), new MemberDescriptor(KnownType.System_IO_Compression_ZipFile, "ExtractToDirectory"))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/HardcodedIpAddressBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Net; using System.Net.Sockets; namespace SonarAnalyzer.Core.Rules { public abstract class HardcodedIpAddressBase : HotspotDiagnosticAnalyzer where TSyntaxKind : struct where TLiteralExpression : SyntaxNode { private const string DiagnosticId = "S1313"; private const string MessageFormat = "Make sure using this hardcoded IP address '{0}' is safe here."; private const int IPv4AddressParts = 4; private const string IPv4Broadcast = "255.255.255.255"; private const string OIDPrefix = "2.5."; private readonly string[] ignoredVariableNames = { "VERSION", "ASSEMBLY", }; // https://datatracker.ietf.org/doc/html/rfc5737#section-3 private readonly byte[][] interNetworkDocumentationRanges = { new byte[] { 192, 0, 2 }, new byte[] { 198, 51, 100 }, new byte[] { 203, 0, 113 } }; // https://datatracker.ietf.org/doc/html/rfc3849#section-2 private readonly byte[] interNetwork6DocumentationRange = { 0x20, 0x01, 0x0d, 0xb8 }; // 2001:0DB8::/32 protected abstract ILanguageFacade Language { get; } protected abstract string GetAssignedVariableName(SyntaxNode stringLiteral); protected abstract bool HasAttributes(SyntaxNode literalExpression); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected DiagnosticDescriptor Rule { get; init; } protected HardcodedIpAddressBase(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) => Rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckForHardcodedIpAddressesInStringLiteral, Language.SyntaxKind.StringLiteralExpressions); context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckForHardcodedIpAddressesInStringInterpolation, Language.SyntaxKind.InterpolatedStringExpression); } protected bool IsHardcodedIp(string literalValue, SyntaxNode node) => literalValue != IPv4Broadcast && !IsObjectIdentifier(literalValue) && IsRoutableNonLoopbackIPAddress(literalValue, out var address) && (address.AddressFamily != AddressFamily.InterNetwork || literalValue.Count(x => x == '.') == IPv4AddressParts - 1) && !IsInDocumentationBlock(address) && !IsIgnoredVariableName(node) && !HasAttributes(node); private void CheckForHardcodedIpAddressesInStringLiteral(SonarSyntaxNodeReportingContext context) { if (IsEnabled(context.Options) && (TLiteralExpression)context.Node is var stringLiteral && Language.Syntax.LiteralText(stringLiteral) is var literalValue && IsHardcodedIp(literalValue, stringLiteral)) { context.ReportIssue(Rule, stringLiteral, literalValue); } } private void CheckForHardcodedIpAddressesInStringInterpolation(SonarSyntaxNodeReportingContext context) { if (IsEnabled(context.Options) && Language.Syntax.InterpolatedTextValue(context.Node, context.Model) is { } stringContent && IsHardcodedIp(stringContent, context.Node)) { context.ReportIssue(Rule, context.Node, stringContent); } } private static bool IsRoutableNonLoopbackIPAddress(string literalValue, out IPAddress ipAddress) => IPAddress.TryParse(literalValue, out ipAddress) && !IPAddress.IsLoopback(ipAddress) && !(ipAddress.IsIPv4MappedToIPv6 && IPAddress.IsLoopback(ipAddress.MapToIPv4())) && !ipAddress.GetAddressBytes().All(x => x == 0); // Nonroutable 0.0.0.0 or 0::0 private bool IsInDocumentationBlock(IPAddress address) { var ip = address.GetAddressBytes(); return address.AddressFamily switch { AddressFamily.InterNetwork => interNetworkDocumentationRanges.Any(x => SequenceStartsWith(ip, x)), AddressFamily.InterNetworkV6 => SequenceStartsWith(ip, interNetwork6DocumentationRange), _ => false, }; static bool SequenceStartsWith(byte[] sequence, byte[] startsWith) => sequence.Take(startsWith.Length).SequenceEqual(startsWith); } private static bool IsObjectIdentifier(string literalValue) => literalValue.StartsWith(OIDPrefix); // Looks like OID private bool IsIgnoredVariableName(SyntaxNode node) => GetAssignedVariableName(node) is { } variableName && ignoredVariableNames.Any(x => variableName.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) >= 0); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/PubliclyWritableDirectoriesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Rules { public abstract class PubliclyWritableDirectoriesBase : HotspotDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpression : SyntaxNode { protected const string DiagnosticId = "S5443"; private const string MessageFormat = "Make sure publicly writable directories are used safely here."; private const RegexOptions WindowsAndUnixOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; protected static string[] InsecureEnvironmentVariables { get; } private static readonly Regex UserProfile; private static readonly Regex LinuxDirectories; private static readonly Regex MacDirectories; private static readonly Regex WindowsDirectories; private static readonly Regex EnvironmentVariables; static PubliclyWritableDirectoriesBase() { var insecureEnvironmentVariables = new[] { "tmp", "temp", "tmpdir" }; InsecureEnvironmentVariables = insecureEnvironmentVariables; UserProfile = new("""^%USERPROFILE%[\\\/]AppData[\\\/]Local[\\\/]Temp""", WindowsAndUnixOptions, Constants.DefaultRegexTimeout); LinuxDirectories = new($@"^({LinuxDirs().JoinStr("|", Regex.Escape)})(\/|$)", RegexOptions.Compiled, Constants.DefaultRegexTimeout); MacDirectories = new($@"^({MacDirs().JoinStr("|", Regex.Escape)})(\/|$)", WindowsAndUnixOptions, Constants.DefaultRegexTimeout); WindowsDirectories = new("""^([a-z]:[\\\/]?|[\\\/][\\\/][^\\\/]+[\\\/]|[\\\/])(windows[\\\/])?te?mp([\\\/]|$)""", WindowsAndUnixOptions, Constants.DefaultRegexTimeout); EnvironmentVariables = new($@"^%({insecureEnvironmentVariables.JoinStr("|")})%([\\\/]|$)", WindowsAndUnixOptions, Constants.DefaultRegexTimeout); } private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } private protected abstract bool IsGetTempPathAssignment(TInvocationExpression invocationExpression, KnownType type, string methodName, SemanticModel semanticModel); private protected abstract bool IsInsecureEnvironmentVariableRetrieval(TInvocationExpression invocation, KnownType type, string methodName, SemanticModel semanticModel); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected PubliclyWritableDirectoriesBase(IAnalyzerConfiguration configuration) : base(configuration) { rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); } protected override void Initialize(SonarAnalysisContext context) { var kinds = Language.SyntaxKind.StringLiteralExpressions.ToList(); kinds.Add(Language.SyntaxKind.InterpolatedStringExpression); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsEnabled(c.Options) && Language.Syntax.StringValue(c.Node, c.Model) is { } stringValue && IsSensitiveDirectoryUsage(stringValue)) { c.ReportIssue(rule, c.Node); } }, kinds.ToArray()); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsEnabled(c.Options) && c.Node is TInvocationExpression invocation && (IsGetTempPathAssignment(invocation, KnownType.System_IO_Path, "GetTempPath", c.Model) || IsInsecureEnvironmentVariableRetrieval(invocation, KnownType.System_Environment, "GetEnvironmentVariable", c.Model))) { c.ReportIssue(rule, c.Node); } }, Language.SyntaxKind.InvocationExpression); } private static bool IsSensitiveDirectoryUsage(string directory) => WindowsDirectories.SafeIsMatch(directory) || MacDirectories.SafeIsMatch(directory) || LinuxDirectories.SafeIsMatch(directory) || EnvironmentVariables.SafeIsMatch(directory) || UserProfile.SafeIsMatch(directory); private static string[] LinuxDirs() => new[] { "/dev/mqueue", "/run/lock", "/var/run/lock", }; private static string[] MacDirs() => new[] { "/var/tmp", "/usr/tmp", "/dev/shm", "/library/caches", "/users/shared", "/private/tmp", "/private/var/tmp", }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/RequestsWithExcessiveLengthBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Xml.Linq; using System.Xml.XPath; namespace SonarAnalyzer.Core.Rules { public abstract class RequestsWithExcessiveLengthBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct where TAttributeSyntax : SyntaxNode { protected const string MultipartBodyLengthLimit = "MultipartBodyLengthLimit"; private const string DiagnosticId = "S5693"; private const string RequestSizeLimit = "RequestSizeLimit"; private const string RequestSizeLimitAttribute = RequestSizeLimit + Attribute; private const string DisableRequestSizeLimit = "DisableRequestSizeLimit"; private const string DisableRequestSizeLimitAttribute = DisableRequestSizeLimit + Attribute; private const string RequestFormLimits = "RequestFormLimits"; private const string RequestFormLimitsAttribute = RequestFormLimits + Attribute; private const string MessageFormat = "Make sure the content length limit is safe here."; private const string Attribute = "Attribute"; private const int DefaultFileUploadSizeLimit = 8_388_608; // 8 MB (in bytes) private const int OneKilobyte = 1024; // 1 KB = 1024 bytes protected readonly DiagnosticDescriptor Rule; protected readonly IAnalyzerConfiguration AnalyzerConfiguration; protected abstract ILanguageFacade Language { get; } protected abstract TAttributeSyntax IsInvalidRequestFormLimits(TAttributeSyntax attribute, SemanticModel semanticModel); protected abstract TAttributeSyntax IsInvalidRequestSizeLimit(TAttributeSyntax attribute, SemanticModel semanticModel); protected abstract SyntaxNode GetMethodLocalFunctionOrClassDeclaration(TAttributeSyntax attribute); protected abstract string AttributeName(TAttributeSyntax attribute); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); [RuleParameter("fileUploadSizeLimit", PropertyType.Integer, "The maximum size of HTTP requests handling file uploads (in bytes).", DefaultFileUploadSizeLimit)] public int FileUploadSizeLimit { get; set; } = DefaultFileUploadSizeLimit; protected override bool EnableConcurrentExecution => false; protected RequestsWithExcessiveLengthBase(IAnalyzerConfiguration analyzerConfiguration) { AnalyzerConfiguration = analyzerConfiguration; Rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); } protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterCompilationStartAction( c => { var attributesOverTheLimit = new Dictionary(); c.RegisterNodeAction( Language.GeneratedCodeRecognizer, cc => CollectAttributesOverTheLimit(cc, attributesOverTheLimit), Language.SyntaxKind.Attribute); c.RegisterCompilationEndAction(cc => ReportOnCollectedAttributes(cc, attributesOverTheLimit)); }); context.RegisterCompilationAction(CheckWebConfig); } protected bool IsRequestFormLimits(string attributeName) => attributeName.Equals(RequestFormLimits, Language.NameComparison) || attributeName.Equals(RequestFormLimitsAttribute, Language.NameComparison); protected bool IsRequestSizeLimit(string attributeName) => attributeName.Equals(RequestSizeLimit, Language.NameComparison) || attributeName.Equals(RequestSizeLimitAttribute, Language.NameComparison); private void CollectAttributesOverTheLimit(SonarSyntaxNodeReportingContext context, IDictionary attributesOverTheLimit) { if (!IsEnabled(context.Options)) { return; } var attribute = (TAttributeSyntax)context.Node; if (IsDisableRequestSizeLimit(AttributeName(attribute)) && attribute.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_DisableRequestSizeLimitAttribute, context.Model)) { context.ReportIssue(Rule, attribute); return; } var requestSizeLimit = IsInvalidRequestSizeLimit(attribute, context.Model); var requestFormLimits = IsInvalidRequestFormLimits(attribute, context.Model); if ((requestSizeLimit != null || requestFormLimits != null) && GetMethodLocalFunctionOrClassDeclaration(attribute) is { } declaration) { attributesOverTheLimit[declaration] = attributesOverTheLimit.TryGetValue(declaration, out var existingAttribute) ? new Attributes(requestFormLimits, requestSizeLimit, existingAttribute) : new Attributes(requestFormLimits, requestSizeLimit); } } private void ReportOnCollectedAttributes(SonarCompilationReportingContext context, IDictionary attributesOverTheLimit) { foreach (var invalidAttributes in attributesOverTheLimit.Values) { context.ReportIssue( Language.GeneratedCodeRecognizer, Rule, invalidAttributes.MainAttribute.GetLocation(), invalidAttributes.SecondaryAttribute is null ? [] : [invalidAttributes.SecondaryAttribute.ToSecondaryLocation(MessageFormat)]); } } private bool IsDisableRequestSizeLimit(string attributeName) => attributeName.Equals(DisableRequestSizeLimit, Language.NameComparison) || attributeName.Equals(DisableRequestSizeLimitAttribute, Language.NameComparison); private bool IsEnabled(AnalyzerOptions options) { AnalyzerConfiguration.Initialize(options); return SupportedDiagnostics.Any(x => AnalyzerConfiguration.IsEnabled(x.Id)); } private void CheckWebConfig(SonarCompilationReportingContext c) { foreach (var fullPath in c.WebConfigFiles()) { var webConfig = File.ReadAllText(fullPath); if (webConfig.Contains(" int.TryParse(value, out var val) && val > limit; // This struct is used as the same attributes can not be applied multiple times to the same declaration. private readonly struct Attributes { private readonly TAttributeSyntax requestForm; private readonly TAttributeSyntax requestSize; public SyntaxNode MainAttribute => requestForm ?? requestSize; public SyntaxNode SecondaryAttribute => requestForm != null && requestSize != null ? requestSize : null; public Attributes(TAttributeSyntax requestForm, TAttributeSyntax requestSize) { this.requestForm = requestForm; this.requestSize = requestSize; } public Attributes(TAttributeSyntax requestForm, TAttributeSyntax requestSize, Attributes oldAttributes) { this.requestForm = requestForm ?? oldAttributes.requestForm; this.requestSize = requestSize ?? oldAttributes.requestSize; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/SpecifyTimeoutOnRegexBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Rules; public abstract class SpecifyTimeoutOnRegexBase : HotspotDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6444"; // NonBacktracking was added in .NET 7 // See: https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regexoptions?view=net-7.0 private const int NonBacktracking = 1024; private readonly string[] matchMethods = { nameof(Regex.IsMatch), nameof(Regex.Match), nameof(Regex.Matches), nameof(Regex.Replace), nameof(Regex.Split), "EnumerateSplits", // https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratesplits?view=net-9.0 "EnumerateMatches", // https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches?view=net-9.0 }; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected virtual string MessageFormat => "Pass a timeout to limit the execution time."; private DiagnosticDescriptor Rule => Language.CreateDescriptor(DiagnosticId, MessageFormat); protected SpecifyTimeoutOnRegexBase(IAnalyzerConfiguration config) : base(config) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (!IsEnabled(c.Options)) { return; } if (IsCandidateCtor(c.Node) && RegexMethodLacksTimeout(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.ObjectCreationExpressions); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (!IsEnabled(c.Options)) { return; } if (IsRegexMatchMethod(Language.Syntax.NodeIdentifier(c.Node).GetValueOrDefault().Text) && RegexMethodLacksTimeout(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.InvocationExpression); } private bool RegexMethodLacksTimeout(SyntaxNode node, SemanticModel model) => model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.ContainingType.Is(KnownType.System_Text_RegularExpressions_Regex) && (method.IsStatic || method.IsConstructor()) && !ContainsMatchTimeout(method) && !NoBacktracking(method, node, model); private static bool ContainsMatchTimeout(IMethodSymbol method) => method.Parameters.Any(x => x.Name == "matchTimeout"); private bool NoBacktracking(IMethodSymbol method, SyntaxNode node, SemanticModel model) => method.Parameters.SingleOrDefault(x => x.Name == "options") is { } parameter && Language.MethodParameterLookup(node, method).TryGetNonParamsSyntax(parameter, out var expression) && Language.FindConstantValue(model, expression) is int options && (options & NonBacktracking) == NonBacktracking; private bool IsCandidateCtor(SyntaxNode ctorNode) => Language.Syntax.ArgumentExpressions(ctorNode).Count() < 3; private bool IsRegexMatchMethod(string name) => matchMethods.Any(x => x.Equals(name, Language.NameComparison)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Hotspots/UsingNonstandardCryptographyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UsingNonstandardCryptographyBase : HotspotDiagnosticAnalyzer where TTypeDeclarationSyntax : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S2257"; private const string MessageFormat = "Make sure using a non-standard cryptographic algorithm is safe here."; private readonly ImmutableArray nonInheritableClassesAndInterfaces = ImmutableArray.Create( KnownType.System_Security_Cryptography_AsymmetricAlgorithm, KnownType.System_Security_Cryptography_AsymmetricKeyExchangeDeformatter, KnownType.System_Security_Cryptography_AsymmetricKeyExchangeFormatter, KnownType.System_Security_Cryptography_AsymmetricSignatureDeformatter, KnownType.System_Security_Cryptography_AsymmetricSignatureFormatter, KnownType.System_Security_Cryptography_DeriveBytes, KnownType.System_Security_Cryptography_HashAlgorithm, KnownType.System_Security_Cryptography_ICryptoTransform, KnownType.System_Security_Cryptography_SymmetricAlgorithm); private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract Location Location(TTypeDeclarationSyntax typeDeclarationSyntax); protected abstract bool DerivesOrImplementsAny(TTypeDeclarationSyntax typeDeclarationSyntax); protected abstract INamedTypeSymbol DeclaredSymbol(TTypeDeclarationSyntax typeDeclarationSyntax, SemanticModel semanticModel); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected UsingNonstandardCryptographyBase(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var declaration = (TTypeDeclarationSyntax)c.Node; if (!c.IsRedundantPositionalRecordContext() && IsEnabled(c.Options) && DerivesOrImplementsAny(declaration) && DeclaredSymbol(declaration, c.Model).DerivesOrImplementsAny(nonInheritableClassesAndInterfaces)) { c.ReportIssue(rule, Location(declaration)); } }, SyntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/IfChainWithoutElseBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class IfChainWithoutElseBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TIfSyntax : SyntaxNode { protected const string DiagnosticId = "S126"; protected abstract TSyntaxKind SyntaxKind { get; } protected abstract string ElseClause { get; } protected abstract bool IsElseIfWithoutElse(TIfSyntax ifSyntax); protected abstract Location IssueLocation(SonarSyntaxNodeReportingContext context, TIfSyntax ifSyntax); protected override string MessageFormat => "Add the missing '{0}' clause with either the appropriate action or a suitable comment as to why no action is taken."; protected IfChainWithoutElseBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var ifNode = (TIfSyntax)c.Node; if (!IsElseIfWithoutElse(ifNode)) { return; } c.ReportIssue(Rule, IssueLocation(c, ifNode), ElseClause); }, SyntaxKind); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/IfCollapsibleBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class IfCollapsibleBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S1066"; protected const string MessageFormat = "Merge this if statement with the enclosing one."; protected const string SecondaryMessage = "Merge this if statement with its nested one."; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ImplementSerializationMethodsCorrectlyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ImplementSerializationMethodsCorrectlyBase : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3927"; private const string MessageFormat = "Make this method {0}."; private const string AttributeNotConsideredMessageFormat = "Serialization attributes on {0} are not considered."; private const string ProblemParameterText = "have a single parameter of type 'StreamingContext'"; private const string ProblemGenericParameterText = "have no type parameters"; private const string ProblemPublicText = "non-public"; private readonly DiagnosticDescriptor rule; private static readonly ImmutableArray SerializationAttributes = ImmutableArray.Create(KnownType.System_Runtime_Serialization_OnSerializingAttribute, KnownType.System_Runtime_Serialization_OnSerializedAttribute, KnownType.System_Runtime_Serialization_OnDeserializingAttribute, KnownType.System_Runtime_Serialization_OnDeserializedAttribute); protected abstract ILanguageFacade Language { get; } protected abstract string MethodStaticMessage { get; } protected abstract string MethodReturnTypeShouldBeVoidMessage { get; } protected abstract Location GetIdentifierLocation(IMethodSymbol methodSymbol); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule, AttributeNotConsideredRule); protected DiagnosticDescriptor AttributeNotConsideredRule { get; init; } protected ImplementSerializationMethodsCorrectlyBase() { rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); AttributeNotConsideredRule = Language.CreateDescriptor(DiagnosticId, AttributeNotConsideredMessageFormat); } protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction(c => { var methodSymbol = (IMethodSymbol)c.Symbol; if (!methodSymbol.ContainingType.IsInterface() && methodSymbol.GetAttributes(SerializationAttributes).Any() && !HiddenByEditorBrowsableAttribute(methodSymbol) && FindIssues(methodSymbol) is var issues && issues.Any() && GetIdentifierLocation(methodSymbol) is { } location) { c.ReportIssue(Language.GeneratedCodeRecognizer, rule, location, issues.ToSentence()); } }, SymbolKind.Method); private static bool HiddenByEditorBrowsableAttribute(IMethodSymbol methodSymbol) => methodSymbol.GetAttributes(KnownType.System_ComponentModel_EditorBrowsableAttribute) .Any(x => x.ConstructorArguments.Any(a => (int)a.Value == 1)); private IEnumerable FindIssues(IMethodSymbol methodSymbol) { var ret = new List(); Evaluate(ProblemPublicText, methodSymbol.DeclaredAccessibility == Accessibility.Public); Evaluate(MethodStaticMessage, methodSymbol.IsStatic); Evaluate(MethodReturnTypeShouldBeVoidMessage, !methodSymbol.ReturnsVoid); Evaluate(ProblemGenericParameterText, !methodSymbol.TypeParameters.IsEmpty); Evaluate(ProblemParameterText, methodSymbol.Parameters.Length != 1 || !methodSymbol.Parameters.First().IsType(KnownType.System_Runtime_Serialization_StreamingContext)); return ret; void Evaluate(string message, bool condition) { if (condition) { ret.Add(message); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/IndexOfCheckAgainstZeroBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class IndexOfCheckAgainstZeroBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TBinaryExpressionSyntax : SyntaxNode { protected const string DiagnosticId = "S2692"; private static readonly string[] TrackedMethods = [ "IndexOf", "IndexOfAny", "LastIndexOf", "LastIndexOfAny" ]; private static readonly ImmutableArray CheckedTypes = ImmutableArray.Create( KnownType.System_MemoryExtensions, // Span and ReadOnlySpan TrackedMethods are located here KnownType.System_Array, KnownType.System_Collections_Generic_IList_T, KnownType.System_String, KnownType.System_Collections_IList); protected abstract TSyntaxKind LessThanExpression { get; } protected abstract TSyntaxKind GreaterThanExpression { get; } protected abstract SyntaxNode Left(TBinaryExpressionSyntax binaryExpression); protected abstract SyntaxNode Right(TBinaryExpressionSyntax binaryExpression); protected abstract SyntaxToken OperatorToken(TBinaryExpressionSyntax binaryExpression); protected override string MessageFormat => "0 is a valid index, but this check ignores it."; protected IndexOfCheckAgainstZeroBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var lessThan = (TBinaryExpressionSyntax)c.Node; if (IsInvalidComparison(Left(lessThan), Right(lessThan), c.Model)) { c.ReportIssue(Rule, Left(lessThan).CreateLocation(OperatorToken(lessThan))); } }, LessThanExpression); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var greaterThan = (TBinaryExpressionSyntax)c.Node; if (IsInvalidComparison(Right(greaterThan), Left(greaterThan), c.Model)) { c.ReportIssue(Rule, OperatorToken(greaterThan).CreateLocation(Right(greaterThan))); } }, GreaterThanExpression); } private bool IsInvalidComparison(SyntaxNode constantExpression, SyntaxNode methodInvocationExpression, SemanticModel model) => Language.ExpressionNumericConverter.ConstantIntValue(constantExpression) is { } constValue && constValue == 0 && model.GetSymbolInfo(methodInvocationExpression).Symbol is IMethodSymbol indexOfSymbol && TrackedMethods.Any(x => x.Equals(indexOfSymbol.Name, Language.NameComparison)) && indexOfSymbol.ContainingType.DerivesOrImplementsAny(CheckedTypes); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/InsecureEncryptionAlgorithmBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class InsecureEncryptionAlgorithmBase : DoNotCallInsecureSecurityAlgorithmBase where TSyntaxKind : struct where TInvocationExpressionSyntax : SyntaxNode where TArgumentListSyntax : SyntaxNode where TArgumentSyntax : SyntaxNode { protected const string DiagnosticId = "S5547"; private const string MessageFormat = "Use a strong cipher algorithm."; protected DiagnosticDescriptor Rule { get; } protected override ISet AlgorithmParameterlessFactoryMethods { get; } = new HashSet(); protected override ISet AlgorithmParameterizedFactoryMethods { get; } = new HashSet { "System.Security.Cryptography.CryptoConfig.CreateFromName", "System.Security.Cryptography.SymmetricAlgorithm.Create" }; protected override ISet FactoryParameterNames { get; } = new HashSet { "DES", "3DES", "TripleDES", "RC2" }; private protected override ImmutableArray AlgorithmTypes { get; } = ImmutableArray.Create( KnownType.System_Security_Cryptography_DES, KnownType.System_Security_Cryptography_TripleDES, KnownType.System_Security_Cryptography_RC2, KnownType.Org_BouncyCastle_Crypto_Engines_AesFastEngine ); protected InsecureEncryptionAlgorithmBase() => Rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/InsecureTemporaryFilesCreationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class InsecureTemporaryFilesCreationBase : SonarDiagnosticAnalyzer where TMemberAccessSyntax : SyntaxNode where TSyntaxKind : struct { protected const string DiagnosticId = "S5445"; private const string VulnerableApiName = "GetTempFileName"; protected override string MessageFormat => "'Path.GetTempFileName()' is insecure. Use 'Path.GetRandomFileName()' instead."; protected InsecureTemporaryFilesCreationBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, Visit, Language.SyntaxKind.SimpleMemberAccessExpression); private void Visit(SonarSyntaxNodeReportingContext context) { var memberAccess = (TMemberAccessSyntax)context.Node; if (Language.Syntax.IsMemberAccessOnKnownType(memberAccess, VulnerableApiName, KnownType.System_IO_Path, context.Model)) { context.ReportIssue(Rule, memberAccess); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/InsteadOfAnyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class InsteadOfAnyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpression : SyntaxNode { private const string ExistsDiagnosticId = "S6605"; private const string ContainsDiagnosticId = "S6617"; private const string MessageFormat = "Collection-specific \"{0}\" method should be used instead of the \"Any\" extension."; private readonly DiagnosticDescriptor existsRule; private readonly DiagnosticDescriptor containsRule; private static readonly ImmutableArray ExistsTypes = ImmutableArray.Create( KnownType.System_Array, KnownType.System_Collections_Immutable_ImmutableList_T); private static readonly ImmutableArray ContainsTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_HashSet_T, KnownType.System_Collections_Generic_SortedSet_T); protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(existsRule, containsRule); protected abstract bool IsSimpleEqualityCheck(TInvocationExpression node, SemanticModel model); protected abstract SyntaxNode GetArgumentExpression(TInvocationExpression invocation, int index); protected abstract bool AreValidOperands(string lambdaVariable, SyntaxNode first, SyntaxNode second); protected InsteadOfAnyBase() { existsRule = Language.CreateDescriptor(ExistsDiagnosticId, MessageFormat); containsRule = Language.CreateDescriptor(ContainsDiagnosticId, MessageFormat); } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocationExpression)c.Node; if (IsNameEqualTo(invocation, nameof(Enumerable.Any)) && Language.Syntax.HasExactlyNArguments(invocation, 1) && Language.Syntax.Operands(invocation) is { Left: { } left, Right: { } right } && IsCorrectCall(right, c.Model) && c.Model.GetTypeInfo(left).Type is { } type && !Language.Syntax.IsInExpressionTree(c.Model, invocation)) { if (ExistsTypes.Any(x => type.DerivesFrom(x))) { RaiseIssue(c, invocation, existsRule, "Exists"); } else if (ContainsTypes.Any(x => type.DerivesFrom(x) && IsSimpleEqualityCheck(invocation, c.Model))) { RaiseIssue(c, invocation, containsRule, "Contains"); } else if (type.DerivesFrom(KnownType.System_Collections_Generic_List_T)) { if (IsSimpleEqualityCheck(invocation, c.Model)) { RaiseIssue(c, invocation, containsRule, "Contains"); } else { RaiseIssue(c, invocation, existsRule, "Exists"); } } } }, Language.SyntaxKind.InvocationExpression); protected bool IsNameEqualTo(SyntaxNode node, string name) => Language.GetName(node).Equals(name, Language.NameComparison); protected static bool IsValueTypeOrString(SyntaxNode expression, SemanticModel model) => model.GetTypeInfo(expression).Type is { } type && (type.IsValueType || type.Is(KnownType.System_String)); protected bool HasValidInvocationOperands(TInvocationExpression invocation, string lambdaVariableName, SemanticModel model) { if (IsNameEqualTo(invocation, nameof(Equals))) { if (Language.Syntax.HasExactlyNArguments(invocation, 1)) // x.Equals(y) { return Language.Syntax.Operands(invocation).Left is { } left && HasInvocationValidOperands(left, GetArgumentExpression(invocation, 0)) && IsSystemEquals(); } if (Language.Syntax.HasExactlyNArguments(invocation, 2)) // Equals(x,y) { return HasInvocationValidOperands(GetArgumentExpression(invocation, 0), GetArgumentExpression(invocation, 1)) && IsSystemEquals(); } } return false; bool HasInvocationValidOperands(SyntaxNode first, SyntaxNode second) => AreValidOperands(lambdaVariableName, first, second) || AreValidOperands(lambdaVariableName, second, first); bool IsSystemEquals() => model.GetSymbolInfo(invocation).Symbol.ContainingNamespace.Name == nameof(System); } private static bool IsCorrectCall(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); private void RaiseIssue(SonarSyntaxNodeReportingContext c, SyntaxNode invocation, DiagnosticDescriptor rule, string methodName) => c.ReportIssue(rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), methodName); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/InvalidCastToInterfaceBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using TypeMap = System.Collections.Generic.Dictionary>; namespace SonarAnalyzer.Core.Rules; public abstract class InvalidCastToInterfaceBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S1944"; protected const string MessageFormat = "{0}"; private const string MessageInterface = "Review this cast; in this project there's no type that implements both '{0}' and '{1}'."; private const string MessageClass = "Review this cast; in this project there's no type that extends '{0}' and implements '{1}'."; // Once we remove the old SE engine, this should inherit SonarDiagnosticAnalyzer to delegate and simplify this scaffolding. public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected abstract ILanguageFacade Language { get; } protected abstract DiagnosticDescriptor Rule { get; } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction( compilationStartContext => { var interfaceImplementers = BuildTypeMap(compilationStartContext.Compilation.GlobalNamespace.GetAllNamedTypes()); compilationStartContext.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var type = Language.Syntax.CastType(c.Node); var interfaceType = c.Model.GetTypeInfo(type).Type as INamedTypeSymbol; var expressionType = c.Model.GetTypeInfo(Language.Syntax.CastExpression(c.Node)).Type as INamedTypeSymbol; if (IsImpossibleCast(interfaceImplementers, interfaceType, expressionType)) { var location = type.GetLocation(); var interfaceTypeName = interfaceType.ToMinimalDisplayString(c.Model, location.SourceSpan.Start); var expressionTypeName = expressionType.ToMinimalDisplayString(c.Model, location.SourceSpan.Start); var message = expressionType.IsInterface() ? MessageInterface : MessageClass; c.ReportIssue(Rule, location, string.Format(message, expressionTypeName, interfaceTypeName)); } }, Language.SyntaxKind.CastExpressions); }); private static TypeMap BuildTypeMap(IEnumerable allTypes) { var ret = new TypeMap(); foreach (var type in allTypes) { if (type.IsInterface()) { Add(type, type); } foreach (var @interface in type.AllInterfaces) { Add(@interface, type); } } return ret; void Add(INamedTypeSymbol key, INamedTypeSymbol value) { if (!ret.TryGetValue(key, out var values)) { values = new(); ret.Add(key, values); } values.Add(value); } } private static bool IsImpossibleCast(TypeMap interfaceImplementers, INamedTypeSymbol interfaceType, INamedTypeSymbol expressionType) { return interfaceType.IsInterface() && ConcreteImplementationExists(interfaceType) && ExpressionTypeIsRelevant() && !expressionType.DerivesOrImplements(interfaceType) && interfaceImplementers.TryGetValue(interfaceType, out var implementers) && !implementers.Any(x => x.DerivesOrImplements(expressionType)); bool ExpressionTypeIsRelevant() => expressionType is not null && !expressionType.IsSealed && !expressionType.Is(KnownType.System_Object) && (!expressionType.IsInterface() || ConcreteImplementationExists(expressionType)); bool ConcreteImplementationExists(INamedTypeSymbol type) => interfaceImplementers.TryGetValue(type, out var implementers) && implementers.Any(x => x.IsClassOrStruct()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/JwtSignedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class JwtSignedBase : TrackerHotspotDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationSyntax : SyntaxNode { protected const string DiagnosticId = "S5659"; protected const bool JwtBuilderConstructorIsSafe = false; private const string MessageFormat = "Use only strong cipher algorithms when verifying the signature of this JWT."; private const int ExtensionStaticCallParameters = 2; protected abstract BuilderPatternCondition CreateBuilderPatternCondition(); protected JwtSignedBase(IAnalyzerConfiguration configuration) : base(configuration, DiagnosticId, MessageFormat) { } protected override void Initialize(TrackerInput input) { var t = Language.Tracker.Invocation; t.Track(input, t.MatchMethod( new MemberDescriptor(KnownType.JWT_IJwtDecoder, "Decode"), new MemberDescriptor(KnownType.JWT_IJwtDecoder, "DecodeToObject")), t.Or( t.ArgumentIsBoolConstant("verify", false), t.MethodHasParameters(1))); t.Track(input, t.MatchMethod( new MemberDescriptor(KnownType.JWT_JwtDecoderExtensions, "Decode"), new MemberDescriptor(KnownType.JWT_JwtDecoderExtensions, "DecodeToObject")), t.Or( t.ArgumentIsBoolConstant("verify", false), t.MethodHasParameters(1), t.MethodHasParameters(ExtensionStaticCallParameters))); t.Track(input, t.MatchMethod(new MemberDescriptor(KnownType.JWT_Builder_JwtBuilder, "Decode")), t.IsInvalidBuilderInitialization(CreateBuilderPatternCondition())); } protected BuilderPatternDescriptor[] JwtBuilderDescriptors(Func singleArgumentIsNotFalseLiteral) => [ new BuilderPatternDescriptor(true, Language.Tracker.Invocation.MethodNameIs("MustVerifySignature")), new BuilderPatternDescriptor(false, Language.Tracker.Invocation.MethodNameIs("DoNotVerifySignature")), new BuilderPatternDescriptor(singleArgumentIsNotFalseLiteral, Language.Tracker.Invocation.MethodNameIs("WithVerifySignature")) ]; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/LineLengthBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class LineLengthBase : ParametrizedDiagnosticAnalyzer { internal const string DiagnosticId = "S103"; internal const string MessageFormat = "Split this {1} characters long line (which is greater than {0} authorized)."; private const int DefaultValueMaximum = 200; [RuleParameter("maximumLineLength", PropertyType.Integer, "The maximum authorized line length.", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected sealed override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterTreeAction( GeneratedCodeRecognizer, c => { foreach (var line in c.Tree.GetText().Lines .Where(line => line.Span.Length > Maximum)) { c.ReportIssue(SupportedDiagnostics[0], c.Tree.GetLocation(line.Span), Maximum.ToString(), line.Span.Length.ToString()); } }); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/LinkedListPropertiesInsteadOfMethodsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class LinkedListPropertiesInsteadOfMethodsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpression : SyntaxNode { internal const string DiagnosticId = "S6613"; protected override string MessageFormat => "'{0}' property of 'LinkedList' should be used instead of the '{0}()' extension method."; protected abstract bool IsRelevantCallAndType(TInvocationExpression invocation, SemanticModel model); protected LinkedListPropertiesInsteadOfMethodsBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocationExpression)c.Node; var methodName = Language.GetName(invocation); if (IsFirstOrLast(methodName) && IsRelevantCallAndType(invocation, c.Model)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), methodName); } }, Language.SyntaxKind.InvocationExpression); protected static bool IsRelevantType(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); private bool IsFirstOrLast(string methodName) => methodName.Equals("First", Language.NameComparison) || methodName.Equals("Last", Language.NameComparison); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/LooseFilePermissionsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class LooseFilePermissionsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TMemberAccess : SyntaxNode { protected const string DiagnosticId = "S2612"; protected const string Everyone = "Everyone"; protected const string MessageFormat = "Make sure this permission is safe."; protected readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } protected abstract void VisitInvocations(SonarSyntaxNodeReportingContext context); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected LooseFilePermissionsBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { c.RegisterNodeAction(Language.GeneratedCodeRecognizer, VisitInvocations, Language.SyntaxKind.InvocationExpression); c.RegisterNodeAction(Language.GeneratedCodeRecognizer, VisitAssignments, Language.SyntaxKind.IdentifierName); }); protected void VisitAssignments(SonarSyntaxNodeReportingContext context) { var node = context.Node; if (IsFileAccessPermissions(node, context.Model) && !Language.Syntax.IsPartOfBinaryNegationOrCondition(node)) { context.ReportIssue(rule, node); } } protected bool IsSetAccessRule(SyntaxNode invocation, SemanticModel model) => Language.Syntax.NodeExpression(invocation) is TMemberAccess memberAccess && Language.Syntax.IsMemberAccessOnKnownType(memberAccess, "SetAccessRule", KnownType.System_Security_AccessControl_FileSystemSecurity, model); protected bool IsAddAccessRule(SyntaxNode invocation, SemanticModel model) => Language.Syntax.NodeExpression(invocation) is TMemberAccess memberAccess && Language.Syntax.IsMemberAccessOnKnownType(memberAccess, "AddAccessRule", KnownType.System_Security_AccessControl_FileSystemSecurity, model); protected bool IsFileAccessPermissions(SyntaxNode node, SemanticModel model) => Language.Syntax.NodeIdentifier(node) is { } identifier && LooseFilePermissionsConfig.WeakFileAccessPermissions.Contains(identifier.Text) && node.IsKnownType(KnownType.Mono_Unix_FileAccessPermissions, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/LooseFilePermissionsConfig.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public static class LooseFilePermissionsConfig { public static readonly ImmutableHashSet WeakFileAccessPermissions = ImmutableHashSet.Create("AllPermissions", "DefaultPermissions", "OtherExecute", "OtherWrite", "OtherRead", "OtherReadWriteExecute"); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MarkAssemblyWithAssemblyVersionAttributeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MarkAssemblyWithAssemblyVersionAttributeBase() : MarkAssemblyWithAttributeBase(DiagnosticId, MessageFormat) { private const string DiagnosticId = "S3904"; private const string MessageFormat = "Provide an 'AssemblyVersion' attribute for assembly '{0}'."; private protected override KnownType AttributeToFind => KnownType.System_Reflection_AssemblyVersionAttribute; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MarkAssemblyWithAttributeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MarkAssemblyWithAttributeBase : SonarDiagnosticAnalyzer { private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } private protected abstract KnownType AttributeToFind { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override bool EnableConcurrentExecution => false; protected MarkAssemblyWithAttributeBase(string diagnosticId, string messageFormat) => rule = Language.CreateDescriptor(diagnosticId, messageFormat); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => c.RegisterCompilationEndAction(cc => { if (!cc.Compilation.Assembly.HasAttribute(AttributeToFind) && !cc.Compilation.Assembly.HasAttribute(KnownType.Microsoft_AspNetCore_Razor_Hosting_RazorCompiledItemAttribute)) { cc.ReportIssue(Language.GeneratedCodeRecognizer, rule, (Location)null, cc.Compilation.AssemblyName); } }) ); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MarkAssemblyWithClsCompliantAttributeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MarkAssemblyWithClsCompliantAttributeBase() : MarkAssemblyWithAttributeBase(DiagnosticId, MessageFormat) { private const string DiagnosticId = "S3990"; private const string MessageFormat = "Provide a 'CLSCompliant' attribute for assembly '{0}'."; private protected override KnownType AttributeToFind => KnownType.System_CLSCompliantAttribute; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MarkAssemblyWithComVisibleAttributeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MarkAssemblyWithComVisibleAttributeBase() : MarkAssemblyWithAttributeBase(DiagnosticId, MessageFormat) { private const string DiagnosticId = "S3992"; private const string MessageFormat = "Provide a 'ComVisible' attribute for assembly '{0}'."; private protected override KnownType AttributeToFind => KnownType.System_Runtime_InteropServices_ComVisibleAttribute; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MarkWindowsFormsMainWithStaThreadBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MarkWindowsFormsMainWithStaThreadBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TMethodSyntax : SyntaxNode { private const string DiagnosticId = "S4210"; private const string AddStaThreadMessage = "Add the 'STAThread' attribute to this entry point."; private const string ChangeMtaThreadToStaThreadMessage = "Change the 'MTAThread' attribute of this entry point to 'STAThread'."; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract Location GetLocation(TMethodSyntax method); protected override string MessageFormat => "{0}"; protected MarkWindowsFormsMainWithStaThreadBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, Action, SyntaxKinds); private void Action(SonarSyntaxNodeReportingContext c) { var methodDeclaration = (TMethodSyntax)c.Node; if (c.Model.GetDeclaredSymbol(methodDeclaration) is IMethodSymbol methodSymbol && methodSymbol.IsMainMethod() && !methodSymbol.IsAsync && !methodSymbol.HasAttribute(KnownType.System_STAThreadAttribute) && IsAssemblyReferencingWindowsForms(c.Model.Compilation) && c.Compilation.Options.OutputKind == OutputKind.WindowsApplication) { var message = methodSymbol.HasAttribute(KnownType.System_MTAThreadAttribute) ? ChangeMtaThreadToStaThreadMessage : AddStaThreadMessage; c.ReportIssue(Rule, GetLocation(methodDeclaration), message); } } private static bool IsAssemblyReferencingWindowsForms(Compilation compilation) => compilation.ReferencedAssemblyNames.Any(r => r.IsStrongName && r.Name == "System.Windows.Forms"); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MethodOverloadsShouldBeGroupedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class MethodOverloadsShouldBeGroupedBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TMemberDeclarationSyntax : SyntaxNode { protected const string DiagnosticId = "S4136"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract IEnumerable MemberDeclarations(SyntaxNode node); protected abstract MemberInfo CreateMemberInfo(SonarSyntaxNodeReportingContext c, TMemberDeclarationSyntax member); protected override string MessageFormat => "All '{0}' method overloads should be adjacent."; protected MethodOverloadsShouldBeGroupedBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (c.IsRedundantPositionalRecordContext()) { return; } foreach (var misplacedOverload in MisplacedOverloads(c, MemberDeclarations(c.Node))) { var firstName = misplacedOverload[0].NameSyntax; var secondaryLocations = misplacedOverload.Skip(1).Select(x => new SecondaryLocation(x.NameSyntax.GetLocation(), "Non-adjacent overload")).ToList(); c.ReportIssue(Rule, firstName, secondaryLocations, firstName.ValueText); } }, SyntaxKinds); protected List[] MisplacedOverloads(SonarSyntaxNodeReportingContext c, IEnumerable members) { var misplacedOverloads = new Dictionary>(); var membersGroupedByInterface = MembersGroupedByInterface(c, members); MemberInfo previous = null; foreach (var member in members) { if (CreateMemberInfo(c, member) is not MemberInfo current) { previous = null; continue; } if (misplacedOverloads.TryGetValue(current, out var values)) { if (!current.NameEquals(previous) && IsMisplacedCandidate(member, values)) { values.Add(current); } } else { misplacedOverloads.Add(current, [current]); } previous = current; } return misplacedOverloads.Values.Where(x => x.Count > 1).ToArray(); bool IsMisplacedCandidate(TMemberDeclarationSyntax member, List others) => !membersGroupedByInterface.TryGetValue(member, out var interfaces) || (interfaces.Length == 1 && others.Any(x => FindInterfaces(c.Model, x.Member).Contains(interfaces.Single()))); } /// /// Function returns members that are considered to be grouped with another member of the same interface (adjacent members). /// These members are allowed to be grouped by interface and not forced to be grouped by member name. /// Another overload (not related by interface) can be placed somewhere else. /// /// Returned ImmutableArray of interfaces for each member is used to determine whether overloads of the same interface should be grouped by name. /// If all methods of the class implement single interface, we want the overloads to be placed together within interface group. /// private static Dictionary> MembersGroupedByInterface(SonarSyntaxNodeReportingContext c, IEnumerable members) { var ret = new Dictionary>(); var previousInterfaces = ImmutableArray.Empty; TMemberDeclarationSyntax previous = null; foreach (var member in members) { var currentInterfaces = FindInterfaces(c.Model, member); if (currentInterfaces.Intersect(previousInterfaces).Any()) { ret.Add(member, currentInterfaces); if (previous is not null && !ret.ContainsKey(previous)) { ret.Add(previous, previousInterfaces); } } previousInterfaces = currentInterfaces; previous = member; } return ret; } private static ImmutableArray FindInterfaces(SemanticModel model, TMemberDeclarationSyntax member) { var ret = new HashSet(); if (model.GetDeclaredSymbol(member) is { } symbol) { ret.AddRange(ExplicitInterfaceImplementations(symbol).Select(x => x.ContainingType)); ret.AddRange(symbol.ContainingType.AllInterfaces.Where(IsImplicityImplemented)); } return ret.ToImmutableArray(); bool IsImplicityImplemented(INamedTypeSymbol @interface) => @interface.GetMembers().Any(x => symbol.ContainingType.FindImplementationForInterfaceMember(x)?.Equals(symbol) == true); } private static IEnumerable ExplicitInterfaceImplementations(ISymbol symbol) => symbol switch { IEventSymbol @event => @event.ExplicitInterfaceImplementations, IMethodSymbol method => method.ExplicitInterfaceImplementations, IPropertySymbol property => property.ExplicitInterfaceImplementations, _ => [] }; protected class MemberInfo { private readonly string accessibility; private readonly bool isStatic; private readonly bool isAbstract; private readonly bool isCaseSensitive; public TMemberDeclarationSyntax Member { get; } public SyntaxToken NameSyntax { get; } public MemberInfo(SonarSyntaxNodeReportingContext context, TMemberDeclarationSyntax member, SyntaxToken nameSyntax, bool isStatic, bool isAbstract, bool isCaseSensitive) { Member = member; accessibility = context.Model.GetDeclaredSymbol(member)?.DeclaredAccessibility.ToString(); NameSyntax = nameSyntax; this.isStatic = isStatic; this.isAbstract = isAbstract; this.isCaseSensitive = isCaseSensitive; } public bool NameEquals(MemberInfo other) => NameSyntax.ValueText.Equals(other?.NameSyntax.ValueText, isCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase); public override bool Equals(object obj) => // Groups that should be together are defined by accessibility, abstract, static and member name #4136 obj is MemberInfo other && NameEquals(other) && accessibility == other.accessibility && isStatic == other.isStatic && isAbstract == other.isAbstract; public override int GetHashCode() => NameSyntax.ValueText.ToUpperInvariant().GetHashCode(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MethodParameterUnusedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MethodParameterUnusedBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1172"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MethodsShouldNotHaveIdenticalImplementationsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class MethodsShouldNotHaveIdenticalImplementationsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4144"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract IEnumerable GetMethodDeclarations(SyntaxNode node); protected abstract SyntaxToken GetMethodIdentifier(TMethodDeclarationSyntax method); protected abstract bool AreDuplicates(SemanticModel model, TMethodDeclarationSyntax firstMethod, TMethodDeclarationSyntax secondMethod); protected override string MessageFormat => "Update this method so that its implementation is not identical to '{0}'."; protected MethodsShouldNotHaveIdenticalImplementationsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsExcludedFromBeingExamined(c)) { return; } var methodsToHandle = new LinkedList(GetMethodDeclarations(c.Node)); while (methodsToHandle.Any()) { var method = methodsToHandle.First.Value; methodsToHandle.RemoveFirst(); var duplicates = methodsToHandle.Where(x => AreDuplicates(c.Model, method, x)).ToList(); foreach (var duplicate in duplicates) { methodsToHandle.Remove(duplicate); var identifier = GetMethodIdentifier(method); var otherIdentifier = GetMethodIdentifier(duplicate); c.ReportIssue(SupportedDiagnostics[0], otherIdentifier, [identifier.ToSecondaryLocation(MessageFormat, otherIdentifier.ValueText)], identifier.ValueText); } } }, SyntaxKinds); protected virtual bool IsExcludedFromBeingExamined(SonarSyntaxNodeReportingContext context) => context.ContainingSymbol.Kind != SymbolKind.NamedType; protected static bool HaveSameParameters(SeparatedSyntaxList? leftParameters, SeparatedSyntaxList? rightParameters) where TSyntax : SyntaxNode => (leftParameters is null && rightParameters is null) // In VB.Net the parameter list can be omitted || (leftParameters?.Count == rightParameters?.Count && HaveSameParameterLists(leftParameters.Value, rightParameters.Value)); protected static bool HaveSameTypeParameters(SemanticModel model, SeparatedSyntaxList? firstTypeParameterList, SeparatedSyntaxList? secondTypeParameterList) where TSyntax : SyntaxNode { var firstSymbols = firstTypeParameterList?.Select(x => model.GetDeclaredSymbol(x)).OfType() ?? []; var secondSymbols = secondTypeParameterList?.Select(x => model.GetDeclaredSymbol(x)).OfType().ToArray() ?? []; return firstSymbols.All(x => Array.Exists(secondSymbols, secondSymbol => TypeParametersHaveSameNameAndConstraints(x, secondSymbol))); } protected static bool AreTheSameType(SemanticModel model, SyntaxNode first, SyntaxNode second) => (first is null && second is null) || (first is not null && second is not null && AreTheSameTypeSymbols(model.GetTypeInfo(first).Type, model.GetTypeInfo(second).Type)); private static bool AreTheSameTypeSymbols(ITypeSymbol first, ITypeSymbol second) => first is not null && second is not null && (first.Equals(second) // string == System.String, Task == Task || AreSameNamedTypeParameters(first, second) // T == T (type parameter with same name) || TypesAreSameGenericType(first, second) // List == List, (T,V) == (T,V) || (first is IArrayTypeSymbol firstArray && second is IArrayTypeSymbol secondArray // T[] == T[] && AreTheSameTypeSymbols(firstArray.ElementType, secondArray.ElementType))); private static bool HaveSameParameterLists(SeparatedSyntaxList firstParameters, SeparatedSyntaxList secondParameters) where TSyntax : SyntaxNode => firstParameters.Equals(secondParameters, (first, second) => first.IsEquivalentTo(second)); // Perf: Syntactic equivalence for all parameters private static bool TypeParametersHaveSameNameAndConstraints(ITypeParameterSymbol first, ITypeParameterSymbol second) => first.Name == second.Name && first.HasConstructorConstraint == second.HasConstructorConstraint && first.HasReferenceTypeConstraint == second.HasReferenceTypeConstraint && first.HasValueTypeConstraint == second.HasValueTypeConstraint && first.HasUnmanagedTypeConstraint() == second.HasUnmanagedTypeConstraint() && first.ConstraintTypes.Length == second.ConstraintTypes.Length && first.ConstraintTypes.All(x => second.ConstraintTypes.Any(y => TypeConstraintsAreSame(x, y))); private static bool TypeConstraintsAreSame(ITypeSymbol first, ITypeSymbol second) => first.Equals(second) // M1(T x) where T: IComparable <-> M2(T x) where T: IComparable || AreSameNamedTypeParameters(first, second) // M1() where T1: T2 <-> M2() where T1: T2 // T2 of M1 is a different symbol than T2 of M2, but if they have the same name they can be interchanged. // T2 equivalency is checked as well by the TypeConstraintsAreSame call in TypeParametersHaveSameNameAndConstraints. || TypesAreSameGenericType(first, second) // M1(T x) where T: IEquatable <-> M2(T x) where T: IEquatable || (first is IArrayTypeSymbol firstArray && second is IArrayTypeSymbol secondArray // T[] == T[] && TypeConstraintsAreSame(firstArray.ElementType, secondArray.ElementType)); private static bool TypesAreSameGenericType(ITypeSymbol firstParameterType, ITypeSymbol secondParameterType) => firstParameterType is INamedTypeSymbol { IsGenericType: true } namedTypeFirst && secondParameterType is INamedTypeSymbol { IsGenericType: true } namedTypeSecond && namedTypeFirst.OriginalDefinition.Equals(namedTypeSecond.OriginalDefinition) && namedTypeFirst.TypeArguments.Length == namedTypeSecond.TypeArguments.Length && namedTypeFirst.TypeArguments.Equals(namedTypeSecond.TypeArguments, TypeConstraintsAreSame); private static bool AreSameNamedTypeParameters(ITypeSymbol first, ITypeSymbol second) => first is ITypeParameterSymbol x && second is ITypeParameterSymbol y && x.Name == y.Name; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MethodsShouldNotHaveTooManyLinesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class MethodsShouldNotHaveTooManyLinesBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct where TBaseMethodSyntax : SyntaxNode { internal const string DiagnosticId = "S138"; protected const string MessageFormat = "This {0} has {1} lines, which is greater than the {2} lines authorized. Split it into smaller {3}."; private const int DefaultMaxMethodLines = 80; [RuleParameter("max", PropertyType.Integer, "Maximum authorized lines of code in a method", DefaultMaxMethodLines)] public int Max { get; set; } = DefaultMaxMethodLines; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract string MethodKeyword { get; } protected abstract IEnumerable GetMethodTokens(TBaseMethodSyntax baseMethodDeclaration); protected abstract SyntaxToken? GetMethodIdentifierToken(TBaseMethodSyntax baseMethodDeclaration); protected abstract string GetMethodKindAndName(SyntaxToken identifierToken); protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction( GeneratedCodeRecognizer, c => { if (Max < 2) { throw new InvalidOperationException($"Invalid rule parameter: maximum number of lines = {Max}. Must be at least 2."); } var baseMethod = (TBaseMethodSyntax)c.Node; var linesCount = GetMethodTokens(baseMethod).SelectMany(token => token.LineNumbers()) .Distinct() .LongCount(); if (linesCount > Max) { var identifierToken = GetMethodIdentifierToken(baseMethod); if (!string.IsNullOrEmpty(identifierToken?.ValueText)) { c.ReportIssue( SupportedDiagnostics[0], identifierToken.Value, GetMethodKindAndName(identifierToken.Value), linesCount.ToString(), Max.ToString(), MethodKeyword); } } }, SyntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MultipleVariableDeclarationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public static class MultipleVariableDeclarationConstants { internal const string DiagnosticId = "S1659"; } public abstract class MultipleVariableDeclarationBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected override string MessageFormat => "Declare '{0}' in a separate statement."; protected MultipleVariableDeclarationBase() : base(MultipleVariableDeclarationConstants.DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { CheckAndReportVariables(c, Rule, Language.Syntax.LocalDeclarationIdentifiers(c.Node)); }, Language.SyntaxKind.LocalDeclaration); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { CheckAndReportVariables(c, Rule, Language.Syntax.FieldDeclarationIdentifiers(c.Node)); }, Language.SyntaxKind.FieldDeclaration); } private static void CheckAndReportVariables(SonarSyntaxNodeReportingContext context, DiagnosticDescriptor rule, ICollection variables) { if (variables.Count <= 1) { return; } foreach (var variable in variables.Skip(1)) { context.ReportIssue(rule, variable, variable.ValueText); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/MultipleVariableDeclarationCodeFixBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class MultipleVariableDeclarationCodeFixBase : SonarCodeFix { internal const string Title = "Separate declarations"; public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MultipleVariableDeclarationConstants.DiagnosticId); protected sealed override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); context.RegisterCodeFix( Title, c => { var newRoot = CalculateNewRoot(root, node); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } protected abstract SyntaxNode CalculateNewRoot(SyntaxNode root, SyntaxNode node); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/NameOfShouldBeUsedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class NameOfShouldBeUsedBase : SonarDiagnosticAnalyzer where TMethodSyntax : SyntaxNode where TSyntaxKind : struct where TThrowSyntax : SyntaxNode { private const string DiagnosticId = "S2302"; // when the parameter name is inside a bigger string, we want to avoid common English words like "a", "then", "he", "of", "have" etc, to avoid false positives private const int MinStringLength = 5; private readonly char[] separators = { ' ', '.', ',', ';', '!', '?' }; protected abstract string NameOf { get; } protected abstract IEnumerable GetParameterNames(TMethodSyntax method); // Handle parameters with the same name (in the IDE it can happen) protected abstract bool IsStringLiteral(SyntaxToken t); protected abstract bool LeastLanguageVersionMatches(SonarSyntaxNodeReportingContext context); protected abstract bool IsArgumentExceptionCallingNameOf(SyntaxNode node, IEnumerable arguments); protected abstract TMethodSyntax MethodSyntax(SyntaxNode node); protected override string MessageFormat => "Replace the string '{0}' with '{1}({0})'."; protected NameOfShouldBeUsedBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { var kinds = Language.SyntaxKind.MethodDeclarations.ToList(); kinds.Add(Language.SyntaxKind.ConstructorDeclaration); context.RegisterNodeAction(Language.GeneratedCodeRecognizer, ReportIssues, kinds.ToArray()); } protected int ArgumentExceptionNameOfPosition(string name) { if (name.Equals("ArgumentNullException", Language.NameComparison) || name.Equals("ArgumentOutOfRangeException", Language.NameComparison)) { return 0; } else if (name.Equals("ArgumentException", Language.NameComparison)) { return 1; } else { return int.MaxValue - 1; } } private void ReportIssues(SonarSyntaxNodeReportingContext context) { if (!LeastLanguageVersionMatches(context)) { return; } var methodSyntax = MethodSyntax(context.Node); var parameterNames = GetParameterNames(methodSyntax); // either no parameters, or duplicated parameters if (!parameterNames.Any()) { return; } var stringTokensInsideThrowExpressions = methodSyntax .DescendantNodes() .OfType() .Where(x => !IsArgumentExceptionCallingNameOf(x, parameterNames)) .SelectMany(th => th.DescendantTokens()) .Where(IsStringLiteral); foreach (var stringTokenAndParam in GetStringTokenAndParamNamePairs(stringTokensInsideThrowExpressions, parameterNames)) { context.ReportIssue(Rule, stringTokenAndParam.Key, stringTokenAndParam.Value, NameOf); } } /// /// Iterates over the string tokens (either from simple strings or from interpolated strings) /// and returns pairs where /// - the key is the string SyntaxToken which contains the verbatim parameter name /// - the value is the name of the parameter which is present in the string token. /// private Dictionary GetStringTokenAndParamNamePairs(IEnumerable tokens, IEnumerable parameterNames) { var result = new Dictionary(); foreach (var stringToken in tokens) { var stringTokenText = stringToken.ValueText; foreach (var parameterName in parameterNames) { if (parameterName.Equals(stringTokenText, Language.NameComparison)) { // given it's exact equality, there can be only one stringToken key in the dictionary result.Add(stringToken, parameterName); } else if (parameterName.Length > MinStringLength // we are looking at the words inside the string, so there can be multiple parameters matching inside the token stop after the first one is found && !result.ContainsKey(stringToken) && stringTokenText.Split(separators, StringSplitOptions.RemoveEmptyEntries).Any(word => word.Equals(parameterName, Language.NameComparison))) { result.Add(stringToken, parameterName); } } } return result; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/NoExceptionsInFinallyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class NoExceptionsInFinallyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S1163"; protected override string MessageFormat => "Refactor this code to not throw exceptions in finally blocks."; protected NoExceptionsInFinallyBase() : base(DiagnosticId) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/NonAsyncTaskShouldNotReturnNullBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class NonAsyncTaskShouldNotReturnNullBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S4586"; private static readonly ImmutableArray TaskTypes = ImmutableArray.Create( KnownType.System_Threading_Tasks_Task, KnownType.System_Threading_Tasks_Task_T ); protected static bool IsInvalidEnclosingSymbolContext(SyntaxNode enclosingMember, SemanticModel model) { var enclosingMemberSymbol = model.GetDeclaredSymbol(enclosingMember) ?? model.GetSymbolInfo(enclosingMember).Symbol; var enclosingMemberMethodSymbol = enclosingMemberSymbol as IMethodSymbol; return enclosingMemberSymbol != null && IsTaskReturnType(enclosingMemberSymbol, enclosingMemberMethodSymbol) && !IsSafeTaskReturnType(enclosingMemberMethodSymbol); } private static bool IsTaskReturnType(ISymbol symbol, IMethodSymbol methodSymbol) { return GetReturnType() is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.ConstructedFrom.DerivesFromAny(TaskTypes); ITypeSymbol GetReturnType() => methodSymbol != null ? methodSymbol.ReturnType : symbol.GetSymbolType(); } private static bool IsSafeTaskReturnType(IMethodSymbol methodSymbol) => // IMethodSymbol also handles lambdas methodSymbol != null && methodSymbol.IsAsync; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ObsoleteAttributesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ObsoleteAttributesBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string ExplanationNeededDiagnosticId = "S1123"; private const string ExplanationNeededMessageFormat = "Add an explanation."; private const string RemoveDiagnosticId = "S1133"; private const string RemoveMessageFormat = "Do not forget to remove this deprecated code someday."; protected abstract ILanguageFacade Language { get; } protected abstract SyntaxNode GetExplanationExpression(SyntaxNode node); public override ImmutableArray SupportedDiagnostics { get; } internal DiagnosticDescriptor ExplanationNeededRule { get; } internal DiagnosticDescriptor RemoveRule { get; } protected ObsoleteAttributesBase() { ExplanationNeededRule = Language.CreateDescriptor(ExplanationNeededDiagnosticId, ExplanationNeededMessageFormat); RemoveRule = Language.CreateDescriptor(RemoveDiagnosticId, RemoveMessageFormat); SupportedDiagnostics = ImmutableArray.Create(ExplanationNeededRule, RemoveRule); } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (c.Model.GetSymbolInfo(c.Node).Symbol is { } attribute && attribute.IsInType(KnownType.System_ObsoleteAttribute)) { var location = c.Node.GetLocation(); c.ReportIssue(RemoveRule, location); if (NoExplanation(c.Node, c.Model)) { c.ReportIssue(ExplanationNeededRule, location); } } }, Language.SyntaxKind.Attribute); private bool NoExplanation(SyntaxNode node, SemanticModel model) => GetExplanationExpression(node) is not { } justification || string.IsNullOrWhiteSpace(Language.FindConstantValue(model, justification) as string); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/OptionalParameterBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class OptionalParameterBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S2360"; protected const string MessageFormat = "Use the overloading mechanism instead of the optional parameters."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } } public abstract class OptionalParameterBase : OptionalParameterBase where TLanguageKindEnum : struct where TMethodSyntax : SyntaxNode where TParameterSyntax: SyntaxNode { protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var method = (TMethodSyntax)c.Node; var symbol = c.Model.GetDeclaredSymbol(method); if (symbol == null || !symbol.IsPubliclyAccessible() || symbol.InterfaceMembers().Any() || symbol.GetOverriddenMember() != null) { return; } var parameters = GetParameters(method); foreach (var parameter in parameters.Where(p => IsOptional(p) && !HasAllowedAttribute(p, c.Model))) { var location = GetReportLocation(parameter); c.ReportIssue(SupportedDiagnostics[0], location); } }, SyntaxKindsOfInterest.ToArray()); } private static bool HasAllowedAttribute(TParameterSyntax parameterSyntax, SemanticModel semanticModel) { var parameterSymbol = semanticModel.GetDeclaredSymbol(parameterSyntax) as IParameterSymbol; return parameterSymbol.GetAttributes(KnownType.CallerInfoAttributes).Any(); } protected abstract IEnumerable GetParameters(TMethodSyntax method); protected abstract bool IsOptional(TParameterSyntax parameter); protected abstract Location GetReportLocation(TParameterSyntax parameter); public abstract ImmutableArray SyntaxKindsOfInterest { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/OptionalParameterNotPassedToBaseCallBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class OptionalParameterNotPassedToBaseCallBase : SonarDiagnosticAnalyzer where TInvocationExpressionSyntax : SyntaxNode { protected const string DiagnosticId = "S3466"; protected const string MessageFormat = "Pass the missing user-supplied parameter value{0} to this 'base' call."; protected void ReportOptionalParameterNotPassedToBase(SonarSyntaxNodeReportingContext c, TInvocationExpressionSyntax invocation) { if (!(c.Model.GetSymbolInfo(invocation).Symbol is IMethodSymbol calledMethod)) { return; } int difference = calledMethod.Parameters.Length - GetArgumentCount(invocation); if (!calledMethod.IsVirtual || difference == 0 || !IsCallInsideOverride(invocation, calledMethod, c.Model)) { return; } var pluralize = difference > 1 ? "s" : string.Empty; c.ReportIssue(Rule, invocation, pluralize); } protected abstract int GetArgumentCount(TInvocationExpressionSyntax invocation); protected abstract DiagnosticDescriptor Rule { get; } protected static bool IsCallInsideOverride(SyntaxNode invocation, IMethodSymbol calledMethod, SemanticModel semanticModel) { return semanticModel.GetEnclosingSymbol(invocation.SpanStart) is IMethodSymbol enclosingSymbol && enclosingSymbol.IsOverride && object.Equals(enclosingSymbol.OverriddenMethod, calledMethod); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ParameterAssignedToBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ParameterAssignedToBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TIdentifierNameSyntax : SyntaxNode { private const string DiagnosticId = "S1226"; protected abstract bool IsAssignmentToCatchVariable(ISymbol symbol, SyntaxNode node); protected override string MessageFormat => "Introduce a new variable instead of reusing the parameter '{0}'."; protected ParameterAssignedToBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { foreach (var target in Language.Syntax.AssignmentTargets(c.Node)) { if (c.Model.GetSymbolInfo(target).Symbol is { } symbol && (symbol is IParameterSymbol { RefKind: RefKind.None } || IsAssignmentToCatchVariable(symbol, target)) && !IsReadBefore(c.Model, symbol, c.Node)) { c.ReportIssue(Rule, target, target.ToString()); } } }, Language.SyntaxKind.SimpleAssignment); private bool IsReadBefore(SemanticModel semanticModel, ISymbol parameterSymbol, SyntaxNode assignment) { // Same problem as in VB.NET / IsAssignmentToCatchVariable: // parameterSymbol.DeclaringSyntaxReferences is empty for Catch syntax in VB.NET as well as for indexer syntax for C# // https://github.com/dotnet/roslyn/issues/6209 var stopLocation = parameterSymbol.Locations.FirstOrDefault(); if (stopLocation == null) { return true; // If we can't find the location, it's going to be FN } return GetPreviousNodes(parameterSymbol.Locations.First(), assignment) .Union(Language.Syntax.AssignmentRight(assignment).DescendantNodes()) .OfType() .Any(x => parameterSymbol.Equals(semanticModel.GetSymbolInfo(x).Symbol)); } /// /// Returns all nodes before the specified statement to the declaration of variable/parameter given by stopLocation. /// This method recursively traverses all parent blocks of the provided statement. /// private static IEnumerable GetPreviousNodes(Location stopLocation, SyntaxNode statement) { // Method declaration or Catch variable declaration, stop here and do not include this statement if (statement == null || statement.GetLocation().SourceSpan.IntersectsWith(stopLocation.SourceSpan)) { return Array.Empty(); } var previousNodes = statement.Parent.ChildNodes() .TakeWhile(x => x != statement) // Take all from beginning, including "catch ex" on the way, down to current statement .Reverse() // Reverse in order to keep the tail .TakeWhile(x => !x.GetLocation().SourceSpan.IntersectsWith(stopLocation.SourceSpan)) // Keep the tail until "catch ex" or "int i" is found .SelectMany(x => x.DescendantNodes()); return previousNodes.Union(GetPreviousNodes(stopLocation, statement.Parent)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ParameterNameMatchesOriginalBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ParameterNameMatchesOriginalBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TMethodDeclarationSyntax : SyntaxNode { protected const string DiagnosticId = "S927"; protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract IEnumerable ParameterIdentifiers(TMethodDeclarationSyntax method); protected override string MessageFormat => "Rename parameter '{0}' to '{1}' to match the {2} declaration."; protected ParameterNameMatchesOriginalBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var methodSyntax = (TMethodDeclarationSyntax)c.Node; if (c.Model.GetDeclaredSymbol(methodSyntax) is IMethodSymbol methodSymbol && methodSymbol.Parameters.Any()) { if (methodSymbol.PartialImplementationPart != null) { if (methodSymbol.PartialImplementationPart.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is TMethodDeclarationSyntax methodImplementationSyntax) { VerifyParameters(c, methodImplementationSyntax, methodSymbol.Parameters, "partial class"); } } else if (methodSymbol.OverriddenMethod != null) { VerifyGenericParameters(c, methodSyntax, methodSymbol.Parameters, methodSymbol.OverriddenMethod.OriginalDefinition.Parameters, "base class"); } else if (methodSymbol.InterfaceMembers().FirstOrDefault() is { } interfaceMember) { VerifyGenericParameters(c, methodSyntax, methodSymbol.Parameters, interfaceMember.OriginalDefinition.Parameters, "interface"); } } }, SyntaxKinds); private void VerifyParameters(SonarSyntaxNodeReportingContext context, TMethodDeclarationSyntax methodSyntax, IList expectedParameters, string expectedLocation) { foreach (var item in ParameterIdentifiers(methodSyntax) .Zip(expectedParameters, (actual, expected) => new { actual, expected }) .Where(x => !x.actual.ValueText.Equals(x.expected.Name, Language.NameComparison))) { context.ReportIssue(Rule, item.actual, item.actual.ValueText, item.expected.Name, expectedLocation); } } private void VerifyGenericParameters(SonarSyntaxNodeReportingContext context, TMethodDeclarationSyntax methodSyntax, IList actualParameters, IList expectedParameters, string expectedLocation) { var parameters = ParameterIdentifiers(methodSyntax).ToList(); for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; var expectedParameter = expectedParameters[i]; if (!parameter.ValueText.Equals(expectedParameter.Name, Language.NameComparison) && !AreGenericTypeParametersWithDifferentTypes(actualParameters[i].Type, expectedParameter.Type) && (expectedParameter.Type.Kind != SymbolKind.TypeParameter || actualParameters[i].Type.Kind == SymbolKind.TypeParameter)) { context.ReportIssue(Rule, parameter, parameter.ValueText, expectedParameter.Name, expectedLocation); } } } private static bool AreGenericTypeParametersWithDifferentTypes(ITypeSymbol actualType, ITypeSymbol expectedType) => actualType is INamedTypeSymbol actualNamedType && expectedType is INamedTypeSymbol expectedNamedType && AreTypeArgumentsDifferent(actualNamedType, expectedNamedType); private static bool AreTypeArgumentsDifferent(INamedTypeSymbol namedType, INamedTypeSymbol otherNamedType) { for (var i = 0; i < namedType.TypeArguments.Count(); i++) { if (namedType.TypeArguments[i].TypeKind != otherNamedType.TypeArguments[i].TypeKind) { return true; } } return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ParametersCorrectOrderBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ParametersCorrectOrderBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S2234"; protected abstract TSyntaxKind[] InvocationKinds { get; } protected override string MessageFormat => "Parameters to '{0}' have the same names but not the same order as the method arguments."; protected ParametersCorrectOrderBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { const int MinNumberOfNameableArguments = 2; if (!c.IsRedundantPrimaryConstructorBaseTypeContext() && Language.Syntax.ArgumentList(c.Node) is { Count: >= MinNumberOfNameableArguments } argumentList // there must be at least two arguments to be able to swap, and further && argumentList.Select(ArgumentName).WhereNotNull().Take(MinNumberOfNameableArguments).Count() == MinNumberOfNameableArguments // at least two arguments with a "name" && Language.MethodParameterLookup(c.Node, c.Model) is var methodParameterLookup) { foreach (var argument in argumentList) { // Example void M(int x, int y) <- p_x and p_y are the parameter // M(y, x); <- a_y and a_x are the arguments if (methodParameterLookup.TryGetSymbol(argument, out var parameterSymbol) // argument = a_x and parameterSymbol = p_y && parameterSymbol is { IsParams: false } && ArgumentName(argument) is { } argumentName // "x" && !MatchingNames(parameterSymbol, argumentName) // "x" != "y" && Language.Syntax.NodeExpression(argument) is { } argumentExpression && c.Context.SemanticModel.GetTypeInfo(argumentExpression).ConvertedType is { } argumentType // is there another parameter that seems to be a better fit (name and type match): p_x && methodParameterLookup.MethodSymbol.Parameters.FirstOrDefault(p => MatchingNames(p, argumentName)) is { IsParams: false } otherParameter && argumentType.DerivesOrImplements(otherParameter.Type) // is there an argument that matches the parameter p_y by name: a_y && Language.Syntax.ArgumentList(c.Node).FirstOrDefault(x => MatchingNames(parameterSymbol, ArgumentName(x))) is { }) { var secondaryLocations = methodParameterLookup.MethodSymbol.DeclaringSyntaxReferences .Select(x => Language.Syntax.NodeIdentifier(x.GetSyntax())?.ToSecondaryLocation()) .WhereNotNull(); c.ReportIssue(Rule, PrimaryLocation(c.Node), secondaryLocations, methodParameterLookup.MethodSymbol.Name); return; } } } }, InvocationKinds); protected virtual Location PrimaryLocation(SyntaxNode node) => Language.Syntax.NodeIdentifier(node)?.GetLocation() ?? node.GetLocation(); private bool MatchingNames(IParameterSymbol parameter, string argumentName) => Language.NameComparer.Equals(parameter.Name, argumentName); private string ArgumentName(SyntaxNode argument) => Language.Syntax.NodeIdentifier(Language.Syntax.NodeExpression(argument))?.ValueText; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PartCreationPolicyShouldBeUsedWithExportAttributeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class PartCreationPolicyShouldBeUsedWithExportAttributeBase : SonarDiagnosticAnalyzer where TAttributeSyntax : SyntaxNode where TDeclarationSyntax : SyntaxNode { internal const string DiagnosticId = "S4428"; protected const string MessageFormat = "Add the 'ExportAttribute' or remove 'PartCreationPolicyAttribute' to/from this type definition."; protected abstract TDeclarationSyntax GetTypeDeclaration(TAttributeSyntax attribute); protected void AnalyzeNode(SonarSyntaxNodeReportingContext c) { var attribute = (TAttributeSyntax)c.Node; if (!IsPartCreationPolicyAttribute(attribute)) { return; } var declaration = GetTypeDeclaration(attribute); if (declaration == null) { return; } if (!(c.Model.GetDeclaredSymbol(declaration) is INamedTypeSymbol symbol) || symbol.IsMefExportedType()) { return; } c.ReportIssue(SupportedDiagnostics[0], attribute); bool IsPartCreationPolicyAttribute(TAttributeSyntax attributeSyntax) => c.Model.GetSymbolInfo(attributeSyntax).Symbol is IMethodSymbol attributeSymbol && attributeSymbol.ContainingType.Is(KnownType.System_ComponentModel_Composition_PartCreationPolicyAttribute); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PreferGuidEmptyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class PreferGuidEmptyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S4581"; protected override string MessageFormat => "Use 'Guid.NewGuid()' or 'Guid.Empty' or add arguments to this GUID instantiation."; protected PreferGuidEmptyBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (IsInvalidCtor(c.Node, c.Model) && c.Model.GetSymbolInfo(c.Node).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.Is(KnownType.System_Guid)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.ObjectCreationExpressions); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (!IsInParameter(c.Node) && c.Model.GetTypeInfo(c.Node).ConvertedType.Is(KnownType.System_Guid)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.DefaultExpressions); } private bool IsInvalidCtor(SyntaxNode ctorNode, SemanticModel semanticModel) { var arguments = Language.Syntax.ArgumentExpressions(ctorNode).ToArray(); return arguments.Length == 0 || CreatesEmptyGuid(arguments, semanticModel); } private bool IsInParameter(SyntaxNode defaultExpression) => defaultExpression.Ancestors().Any(x => Language.Syntax.IsKind(x, Language.SyntaxKind.Parameter)); private static bool CreatesEmptyGuid(SyntaxNode[] arguments, SemanticModel semanticModel) => arguments.Length == 1 && Guid.TryParse(semanticModel.GetConstantValue(arguments[0]).Value as string, out var guid) && guid == Guid.Empty; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PropertiesAccessCorrectFieldBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class PropertiesAccessCorrectFieldBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4275"; /** * Assignments can be done either * - directly via an assignment * - indirectly, when passed as 'out' or 'ref' parameter */ protected enum AccessorKind { Getter, Setter } protected abstract IEnumerable FindFieldAssignments(IPropertySymbol property, Compilation compilation); protected abstract IEnumerable FindFieldReads(IPropertySymbol property, Compilation compilation); protected abstract bool ImplementsExplicitGetterOrSetter(IPropertySymbol property); protected abstract bool ShouldIgnoreAccessor(IMethodSymbol accessorMethod, Compilation compilation); protected override string MessageFormat => "Refactor this {0} so that it actually refers to the field '{1}'."; protected PropertiesAccessCorrectFieldBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => // We want to check the fields read and assigned in all properties in this class // so this is a symbol-level rule (also means the callback is called only once // for partial classes) context.RegisterSymbolAction(CheckType, SymbolKind.NamedType); protected static bool AccessesSelfBaseProperty(IMethodSymbol accessorMethod, SyntaxNode accessorBody, Compilation compilation) => accessorMethod.AssociatedSymbol is IPropertySymbol { OverriddenProperty: { } overriddenProperty } && compilation.GetSemanticModel(accessorBody.SyntaxTree) is { } model && accessorBody.DescendantNodes().Any(x => model.GetSymbolInfo(x).Symbol is IPropertySymbol propertyReference && propertyReference.Equals(overriddenProperty)); protected static SyntaxNode FindInvokedMethod(Compilation compilation, INamedTypeSymbol containingType, SyntaxNode expression) => compilation.GetSemanticModel(expression.SyntaxTree) is { } semanticModel && semanticModel.GetSymbolInfo(expression).Symbol is { } invocationSymbol && invocationSymbol.ContainingType.Equals(containingType) && invocationSymbol.DeclaringSyntaxReferences.Length == 1 && invocationSymbol.DeclaringSyntaxReferences.Single().GetSyntax() is { } invokedMethod ? invokedMethod : null; private void CheckType(SonarSymbolReportingContext context) { var symbol = (INamedTypeSymbol)context.Symbol; if (!symbol.TypeKind.Equals(TypeKind.Class) && !symbol.TypeKind.Equals(TypeKind.Structure)) { return; } var fields = SelfAndBaseTypesFieldSymbols(symbol); if (!fields.Any()) { return; } var properties = ExplicitlyDeclaredProperties(symbol); if (!properties.Any()) { return; } var propertyToFieldMatcher = new PropertyToFieldMatcher(fields); var allPropertyData = CollectPropertyData(properties, context.Compilation); // Check that if there is a single matching field name it is used by the property foreach (var data in allPropertyData) { var expectedField = propertyToFieldMatcher.SingleMatchingFieldOrNull(data.PropertySymbol); if (expectedField is not null) { if (!data.IgnoreGetter) { CheckExpectedFieldIsUsed(context, data.PropertySymbol.GetMethod, expectedField, data.ReadFields); } if (!data.IgnoreSetter) { CheckExpectedFieldIsUsed(context, data.PropertySymbol.SetMethod, expectedField, data.UpdatedFields); } } } } private static IEnumerable SelfAndBaseTypesFieldSymbols(INamedTypeSymbol typeSymbol) => typeSymbol.GetSelfAndBaseTypes() .SelectMany(x => x.GetMembers().OfType() .Where(f => x.Equals(typeSymbol) || f.DeclaredAccessibility != Accessibility.Private)); private IEnumerable ExplicitlyDeclaredProperties(INamedTypeSymbol symbol) => symbol.GetMembers() .Where(x => x.Kind.Equals(SymbolKind.Property)) .SelectMany(x => x.AllPartialParts()) .OfType() .Where(ImplementsExplicitGetterOrSetter); private void CheckExpectedFieldIsUsed(SonarSymbolReportingContext context, IMethodSymbol methodSymbol, IFieldSymbol expectedField, ImmutableArray actualFields) { var expectedFieldIsUsed = actualFields.Any(x => x.Field.Equals(expectedField)); if (!expectedFieldIsUsed || !actualFields.Any()) { var locationAndAccessorType = GetLocationAndAccessor(actualFields, methodSymbol); if (locationAndAccessorType.Item1 is not null) { context.ReportIssue(Language.GeneratedCodeRecognizer, Rule, locationAndAccessorType.Item1, locationAndAccessorType.Item2, expectedField.Name); } } static Tuple GetLocationAndAccessor(ImmutableArray fields, IMethodSymbol method) { Location location; string accessorType; if (fields.Count(x => x.UseFieldLocation) == 1) { var fieldWithValue = fields.First(); location = fieldWithValue.LocationNode.GetLocation(); accessorType = fieldWithValue.AccessorKind.Equals(AccessorKind.Getter) ? "getter" : "setter"; } else { Debug.Assert(method is not null, "Method symbol should not be null at this point"); location = method?.Locations.First(); accessorType = method?.MethodKind == MethodKind.PropertyGet ? "getter" : "setter"; } return Tuple.Create(location, accessorType); } } private IList CollectPropertyData(IEnumerable properties, Compilation compilation) { IList allPropertyData = []; // Collect the list of fields read/written by each property foreach (var property in properties) { var readFields = FindFieldReads(property, compilation); var updatedFields = FindFieldAssignments(property, compilation); var ignoreGetter = ShouldIgnoreAccessor(property.GetMethod, compilation); var ignoreSetter = ShouldIgnoreAccessor(property.SetMethod, compilation); var data = new PropertyData(property, readFields, updatedFields, ignoreGetter, ignoreSetter); allPropertyData.Add(data); } return allPropertyData; } private readonly struct PropertyData { public IPropertySymbol PropertySymbol { get; } public ImmutableArray ReadFields { get; } public ImmutableArray UpdatedFields { get; } public bool IgnoreGetter { get; } public bool IgnoreSetter { get; } public PropertyData(IPropertySymbol propertySymbol, IEnumerable read, IEnumerable updated, bool ignoreGetter, bool ignoreSetter) { PropertySymbol = propertySymbol; ReadFields = read.ToImmutableArray(); UpdatedFields = updated.ToImmutableArray(); IgnoreGetter = ignoreGetter; IgnoreSetter = ignoreSetter; } } protected readonly struct FieldData { public AccessorKind AccessorKind { get; } public IFieldSymbol Field { get; } public SyntaxNode LocationNode { get; } public bool UseFieldLocation { get; } public FieldData(AccessorKind accessor, IFieldSymbol field, SyntaxNode locationNode, bool useFieldLocation) { AccessorKind = accessor; Field = field; LocationNode = locationNode; UseFieldLocation = useFieldLocation; } } /// /// The rule decides if a property is returning/settings the expected field. /// We decide what the expected field name should be based on a fuzzy match /// between the field name and the property name. /// This class hides the details of matching logic. /// private sealed class PropertyToFieldMatcher { private readonly IDictionary fieldToStandardNameMap; public PropertyToFieldMatcher(IEnumerable fields) => // Calculate and cache the standardised versions of the field names to avoid // calculating them every time fieldToStandardNameMap = fields.ToDictionary(x => x, x => CanonicalName(x.Name)); public IFieldSymbol SingleMatchingFieldOrNull(IPropertySymbol propertySymbol) { var matchingFields = fieldToStandardNameMap.Keys .Where(x => FieldMatchesTheProperty(x, propertySymbol)) .Take(2) .ToList(); return matchingFields.Count == 1 ? matchingFields[0] : null; } private static string CanonicalName(string name) => name.Replace("_", string.Empty); private static bool AreCanonicalNamesEqual(string name1, string name2) => name1.Equals(name2, StringComparison.OrdinalIgnoreCase); private bool FieldMatchesTheProperty(IFieldSymbol field, IPropertySymbol property) => // We're not caching the property name as only expect to be called once per property !field.IsConst && ((property.IsStatic && field.IsStatic) || (!property.IsStatic && !field.IsStatic)) && field.Type.Equals(property.Type) && AreCanonicalNamesEqual(fieldToStandardNameMap[field], CanonicalName(property.Name)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PropertyGetterWithThrowBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class PropertyGetterWithThrowBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S2372"; protected const string MessageFormat = "Remove the exception throwing from this property getter, or refactor the " + "property into a method."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } internal static readonly ImmutableArray AllowedExceptionBaseTypes = ImmutableArray.Create( KnownType.System_NotImplementedException, KnownType.System_NotSupportedException, KnownType.System_InvalidOperationException ); } public abstract class PropertyGetterWithThrowBase : PropertyGetterWithThrowBase where TLanguageKindEnum : struct where TAccessorSyntax : SyntaxNode { protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterCodeBlockStartAction( GeneratedCodeRecognizer, cbc => { if (!(cbc.CodeBlock is TAccessorSyntax propertyGetter) || !IsGetter(propertyGetter) || IsIndexer(propertyGetter)) { return; } cbc.RegisterNodeAction( c => { var throwExpression = GetThrowExpression(c.Node); // This is the case in rethrow - see ticket #730. if (throwExpression == null) { return; } var type = c.Model.GetTypeInfo(throwExpression).Type; if (type == null || type.DerivesFromAny(AllowedExceptionBaseTypes)) { return; } c.ReportIssue(SupportedDiagnostics[0], c.Node); }, ThrowSyntaxKind); }); } protected abstract bool IsIndexer(TAccessorSyntax propertyGetter); protected abstract bool IsGetter(TAccessorSyntax propertyGetter); protected abstract TLanguageKindEnum ThrowSyntaxKind { get; } protected abstract SyntaxNode GetThrowExpression(SyntaxNode syntaxNode); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PropertyWriteOnlyBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class PropertyWriteOnlyBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TPropertyDeclaration : SyntaxNode { protected const string DiagnosticId = "S2376"; protected abstract TSyntaxKind SyntaxKind { get; } protected abstract bool IsWriteOnlyProperty(TPropertyDeclaration prop); protected override string MessageFormat => "Provide a getter for '{0}' or replace the property with a 'Set{0}' method."; protected PropertyWriteOnlyBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var prop = (TPropertyDeclaration)c.Node; if (IsWriteOnlyProperty(prop) && Language.Syntax.NodeIdentifier(prop) is { } identifier) { c.ReportIssue(SupportedDiagnostics[0], identifier, identifier.ValueText); } }, SyntaxKind); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ProvideDeserializationMethodsForOptionalFieldsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ProvideDeserializationMethodsForOptionalFieldsBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3926"; private const string MessageFormat = "{0}"; private const string BothDeserializationMethodsMissing = "Add deserialization event handlers."; private const string OnDeserializedMethodMissing = "Add the missing 'OnDeserializedAttribute' event handler."; private const string OnDeserializingMethodMissing = "Add the missing 'OnDeserializingAttribute' event handler."; private static readonly ImmutableArray AttributesToFind = ImmutableArray.Create(KnownType.System_Runtime_Serialization_OptionalFieldAttribute, KnownType.System_Runtime_Serialization_OnDeserializingAttribute, KnownType.System_Runtime_Serialization_OnDeserializedAttribute); private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected ProvideDeserializationMethodsForOptionalFieldsBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { var symbol = (INamedTypeSymbol)c.Symbol; if (!symbol.IsClassOrStruct()) { return; } var watchedAttributes = symbol.GetMembers() .SelectMany(m => m.GetAttributes(AttributesToFind)) .ToList(); var errorMessage = GetErrorMessage(watchedAttributes); var declaringSyntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); if (errorMessage == null || declaringSyntax == null) { return; } c.ReportIssue(Language.GeneratedCodeRecognizer, SupportedDiagnostics[0], GetNamedTypeIdentifierLocation(declaringSyntax), errorMessage); }, SymbolKind.NamedType); protected abstract Location GetNamedTypeIdentifierLocation(SyntaxNode node); private static string GetErrorMessage(IReadOnlyCollection attributes) { var hasOptionalFieldAttribute = attributes.Any(attribute => attribute.AttributeClass.Is(KnownType.System_Runtime_Serialization_OptionalFieldAttribute)); var hasOnDeserializingAttribute = attributes.Any(attribute => attribute.AttributeClass.Is(KnownType.System_Runtime_Serialization_OnDeserializingAttribute)); var hasOnDeserializedAttribute = attributes.Any(attribute => attribute.AttributeClass.Is(KnownType.System_Runtime_Serialization_OnDeserializedAttribute)); if (!hasOptionalFieldAttribute || (hasOnDeserializingAttribute && hasOnDeserializedAttribute)) { return null; } if (hasOnDeserializingAttribute) { return OnDeserializedMethodMissing; } if (hasOnDeserializedAttribute) { return OnDeserializingMethodMissing; } return BothDeserializationMethodsMissing; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PublicConstantFieldBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class PublicConstantFieldBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S2339"; protected const string MessageFormat = "Change this constant to a {0} property."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } } public abstract class PublicConstantFieldBase : PublicConstantFieldBase where TLanguageKindEnum : struct where TFieldDeclarationSyntax : SyntaxNode where TFieldName : SyntaxNode { protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var field = (TFieldDeclarationSyntax)c.Node; var variables = GetVariables(field).ToList(); if (!variables.Any()) { return; } var anyVariable = variables.First(); if (!(c.Model.GetDeclaredSymbol(anyVariable) is IFieldSymbol symbol) || !symbol.IsConst || symbol.GetEffectiveAccessibility() != Accessibility.Public) { return; } foreach (var variable in variables) { c.ReportIssue(SupportedDiagnostics[0], GetReportLocation(variable), MessageArgument); } }, FieldDeclarationKind); } protected abstract IEnumerable GetVariables(TFieldDeclarationSyntax node); public abstract TLanguageKindEnum FieldDeclarationKind { get; } public abstract string MessageArgument { get; } protected abstract Location GetReportLocation(TFieldName node); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PublicMethodWithMultidimensionalArrayBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class PublicMethodWithMultidimensionalArrayBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S2368"; protected override string MessageFormat => "Make this {0} private or simplify its parameters to not use multidimensional/jagged arrays."; protected abstract ImmutableArray SyntaxKindsOfInterest { get; } protected abstract Location GetIssueLocation(SyntaxNode node); protected abstract string GetType(SyntaxNode node); protected PublicMethodWithMultidimensionalArrayBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (MethodSymbolOfNode(c.Model, c.Node) is { } methodSymbol && methodSymbol.InterfaceMembers().IsEmpty() && !methodSymbol.IsOverride && methodSymbol.IsPubliclyAccessible() && MethodHasMultidimensionalArrayParameters(methodSymbol)) { c.ReportIssue(SupportedDiagnostics[0], GetIssueLocation(c.Node), GetType(c.Node)); } }, SyntaxKindsOfInterest.ToArray()); protected virtual IMethodSymbol MethodSymbolOfNode(SemanticModel semanticModel, SyntaxNode node) => semanticModel.GetDeclaredSymbol(node) as IMethodSymbol; private static bool MethodHasMultidimensionalArrayParameters(IMethodSymbol methodSymbol) => methodSymbol.IsExtensionMethod ? methodSymbol.Parameters.Skip(1).Any(IsMultidimensionalArrayParameter) : methodSymbol.Parameters.Any(IsMultidimensionalArrayParameter); // Perf: Make sure the Any method of ImmutableArray is called when possible. Don't do `Skip(m.IsExtensionMethod ? 0 : 1)` private static bool IsMultidimensionalArrayParameter(IParameterSymbol param) => param.Type is IArrayTypeSymbol arrayType && (arrayType.Rank > 1 || IsJaggedArrayParam(param, arrayType)); private static bool IsJaggedArrayParam(IParameterSymbol param, IArrayTypeSymbol arrayType) => param.IsParams ? arrayType.ElementType is IArrayTypeSymbol { ElementType: IArrayTypeSymbol } : arrayType.ElementType is IArrayTypeSymbol; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/PureAttributeOnVoidMethodBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class PureAttributeOnVoidMethodBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S3603"; protected override string MessageFormat => "Remove the 'Pure' attribute or change the method to return a value."; protected PureAttributeOnVoidMethodBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterSymbolAction( c => { if (InvalidPureDataAttributeUsage((IMethodSymbol)c.Symbol) is { } pureAttribute) { c.ReportIssue(Language.GeneratedCodeRecognizer, Rule, pureAttribute.ApplicationSyntaxReference.GetSyntax()); } }, SymbolKind.Method); protected static AttributeData InvalidPureDataAttributeUsage(IMethodSymbol method) => method is not null && NoOutParameters(method) && (method.ReturnsVoid || ReturnsTask(method)) && PureAttribute(method) is { } pureAttribute ? pureAttribute : null; private static bool NoOutParameters(IMethodSymbol method) => method.Parameters.All(x => x.RefKind is RefKind.None || x.RefKind is RefKindEx.In); private static bool ReturnsTask(IMethodSymbol method) => method.ReturnType.Is(KnownType.System_Threading_Tasks_Task); private static AttributeData PureAttribute(IMethodSymbol method) => method.GetAttributes().FirstOrDefault(x => x.AttributeClass.Is(KnownType.System_Diagnostics_Contracts_PureAttribute)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/RedundantNullCheckBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class RedundantNullCheckBase : SonarDiagnosticAnalyzer where TBinaryExpression : SyntaxNode { internal const string DiagnosticId = "S4201"; protected abstract SyntaxNode GetLeftNode(TBinaryExpression binaryExpression); protected abstract SyntaxNode GetRightNode(TBinaryExpression binaryExpression); protected abstract SyntaxNode GetNullCheckVariable(SyntaxNode node); protected abstract SyntaxNode GetNonNullCheckVariable(SyntaxNode node); protected abstract SyntaxNode GetIsOperatorCheckVariable(SyntaxNode node); protected abstract SyntaxNode GetInvertedIsOperatorCheckVariable(SyntaxNode node); protected abstract bool AreEquivalent(SyntaxNode node1, SyntaxNode node2); // LogicalAnd (C#) / AndAlso (VB) protected void CheckAndExpression(SonarSyntaxNodeReportingContext context) { var binaryExpression = (TBinaryExpression)context.Node; var binaryExpressionLeft = GetLeftNode(binaryExpression); var binaryExpressionRight = GetRightNode(binaryExpression); if (GetNonNullCheckVariable(binaryExpressionLeft) is SyntaxNode nonNullCheckVariable1 && GetIsOperatorCheckVariable(binaryExpressionRight) is SyntaxNode isCheckVariable1 && AreEquivalent(nonNullCheckVariable1, isCheckVariable1)) { context.ReportIssue(SupportedDiagnostics[0], binaryExpressionLeft); } if (GetNonNullCheckVariable(binaryExpressionRight) is SyntaxNode nonNullCheckVariable2 && GetIsOperatorCheckVariable(binaryExpressionLeft) is SyntaxNode isCheckVariable2 && AreEquivalent(nonNullCheckVariable2, isCheckVariable2)) { context.ReportIssue(SupportedDiagnostics[0], binaryExpressionRight); } } // LogicalOr (C#) / OrElse (VB) protected void CheckOrExpression(SonarSyntaxNodeReportingContext context) { var binaryExpression = (TBinaryExpression)context.Node; var binaryExpressionLeft = GetLeftNode(binaryExpression); var binaryExpressionRight = GetRightNode(binaryExpression); if (GetNullCheckVariable(binaryExpressionLeft) is SyntaxNode nullCheckVariable1 && GetInvertedIsOperatorCheckVariable(binaryExpressionRight) is SyntaxNode invertedIsCheckVariable1 && AreEquivalent(nullCheckVariable1, invertedIsCheckVariable1)) { context.ReportIssue(SupportedDiagnostics[0], binaryExpressionLeft); } if (GetNullCheckVariable(binaryExpressionRight) is SyntaxNode nullCheckVariable2 && GetInvertedIsOperatorCheckVariable(binaryExpressionLeft) is SyntaxNode invertedIsCheckVariable2 && AreEquivalent(nullCheckVariable2, invertedIsCheckVariable2)) { context.ReportIssue(SupportedDiagnostics[0], binaryExpressionRight); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/RedundantParenthesesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class RedundantParenthesesBase : SonarDiagnosticAnalyzer where TParenthesizedExpression : SyntaxNode where TSyntaxKind : struct { internal const string DiagnosticId = "S1110"; protected const string MessageFormat = "Remove these redundant parentheses."; protected const string SecondaryMessage = "Remove the redundant closing parentheses."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract TSyntaxKind ParenthesizedExpressionSyntaxKind { get; } protected abstract SyntaxNode GetExpression(TParenthesizedExpression parenthesizedExpression); protected abstract SyntaxToken GetOpenParenToken(TParenthesizedExpression parenthesizedExpression); protected abstract SyntaxToken GetCloseParenToken(TParenthesizedExpression parenthesizedExpression); protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var expression = (TParenthesizedExpression)c.Node; if (!(expression.Parent is TParenthesizedExpression) && (GetExpression(expression) is TParenthesizedExpression)) { var innermostExpression = GetSelfAndDescendantParenthesizedExpressions(expression) .Reverse() .Skip(1) .First(); // There are always at least two parenthesized expressions var location = GetOpenParenToken(expression).CreateLocation(GetOpenParenToken(innermostExpression)); var secondaryLocation = GetCloseParenToken(innermostExpression).CreateLocation(GetCloseParenToken(expression)).ToSecondary(SecondaryMessage); c.ReportIssue(SupportedDiagnostics[0], location, [secondaryLocation]); } }, ParenthesizedExpressionSyntaxKind); } protected IEnumerable GetSelfAndDescendantParenthesizedExpressions(TParenthesizedExpression expression) { var descendant = expression; while (descendant is not null) { yield return descendant; descendant = GetExpression(descendant) as TParenthesizedExpression; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/RegularExpressions/RegexMustHaveValidSyntaxBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.RegularExpressions; namespace SonarAnalyzer.Core.Rules; public abstract class RegexMustHaveValidSyntaxBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S5856"; protected sealed override string MessageFormat => "Fix the syntax error inside this regex: {0}"; protected RegexMustHaveValidSyntaxBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => Analyze(c, RegexContext.FromCtor(Language, c.Model, c.Node)), Language.SyntaxKind.ObjectCreationExpressions); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => Analyze(c, RegexContext.FromMethod(Language, c.Model, c.Node)), Language.SyntaxKind.InvocationExpression); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => Analyze(c, RegexContext.FromAttribute(Language, c.Model, c.Node)), Language.SyntaxKind.Attribute); } private void Analyze(SonarSyntaxNodeReportingContext c, RegexContext context) { if (context?.ParseError is { } error) { c.ReportIssue(Rule, context.PatternNode, error.Message); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ReversedOperatorsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ReversedOperatorsBase : SonarDiagnosticAnalyzer where TUnaryExpressionSyntax : SyntaxNode { protected const string DiagnosticId = "S2757"; protected const string MessageFormat = "Was '{0}' meant instead?"; protected abstract SyntaxToken GetOperatorToken(TUnaryExpressionSyntax e); protected abstract bool IsEqualsToken(SyntaxToken token); protected abstract bool IsMinusToken(SyntaxToken token); private static bool TiedTogether(FileLinePositionSpan left, FileLinePositionSpan right) => left.EndLinePosition == right.StartLinePosition; protected Action GetAnalysisAction(DiagnosticDescriptor rule) => c => { var unaryExpression = (TUnaryExpressionSyntax)c.Node; var operatorToken = GetOperatorToken(unaryExpression); var equalsToken = operatorToken.GetPreviousToken(); if (!IsEqualsToken(equalsToken)) { return; } var operandToken = operatorToken.GetNextToken(); var operatorLocation = operatorToken.GetLocation(); var operatorSpan = operatorLocation.GetLineSpan(); var equalsSignSpan = equalsToken.GetLocation().GetLineSpan(); var operandSpan = operandToken.GetLocation().GetLineSpan(); if (TiedTogether(equalsSignSpan, operatorSpan) && !(IsMinusToken(operatorToken) && TiedTogether(operatorSpan, operandSpan))) { c.ReportIssue(rule, operatorLocation, $"{operatorToken.Text}="); } }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SecurityPInvokeMethodShouldNotBeCalledBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { [Obsolete("This rule has been deprecated since 9.18")] public abstract class SecurityPInvokeMethodShouldNotBeCalledBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpressionSyntax : SyntaxNode { protected const string DiagnosticId = "S3884"; protected const string InteropName = "ole32"; protected const string InteropDllName = InteropName + ".dll"; protected abstract IMethodSymbol MethodSymbolForInvalidInvocation(SyntaxNode syntaxNode, SemanticModel semanticModel); protected override string MessageFormat => "Refactor the code to remove this use of '{0}'."; protected ISet InvalidMethods { get; } = new HashSet { "CoSetProxyBlanket", "CoInitializeSecurity" }; protected SecurityPInvokeMethodShouldNotBeCalledBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckForIssue, Language.SyntaxKind.InvocationExpression); protected virtual bool IsImportFromInteropDll(IMethodSymbol symbol, SemanticModel semanticModel) => (symbol.IsExtern ? symbol.GetAttributes(KnownType.System_Runtime_InteropServices_DllImportAttribute) : symbol.GetAttributes(KnownType.System_Runtime_InteropServices_LibraryImportAttribute)) .SelectMany(x => x.ConstructorArguments) // Both attributes have a single constructor which takes a single string "library" argument .Any(x => x.Value is string stringValue && IsInterop(stringValue)); protected virtual string GetMethodName(ISymbol symbol, SemanticModel semanticModel) => symbol.Name; protected static bool IsInterop(string dllName) => dllName.Equals(InteropName, StringComparison.OrdinalIgnoreCase) || dllName.Equals(InteropDllName, StringComparison.OrdinalIgnoreCase); private void CheckForIssue(SonarSyntaxNodeReportingContext analysisContext) { if (analysisContext.Node is TInvocationExpressionSyntax invocation && Language.Syntax.NodeExpression(invocation) is { } directMethodCall && MethodSymbolForInvalidInvocation(directMethodCall, analysisContext.Model) is IMethodSymbol methodSymbol && methodSymbol.IsStatic && IsImportFromInteropDll(methodSymbol, analysisContext.Model)) { analysisContext.ReportIssue(Rule, Language.Syntax.NodeIdentifier(directMethodCall).Value, GetMethodName(methodSymbol, analysisContext.Model)); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SelfAssignmentBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class SelfAssignmentBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S1656"; protected override string MessageFormat => "Remove or correct this useless self-assignment."; protected SelfAssignmentBase() : base(DiagnosticId) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SetPropertiesInsteadOfMethodsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class SetPropertiesInsteadOfMethodsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S6609"; protected override string MessageFormat => "\"{0}\" property of Set type should be used instead of the \"{0}()\" extension method."; private static readonly ImmutableArray TargetTypes = ImmutableArray.Create( KnownType.System_Collections_Generic_SortedSet_T, KnownType.System_Collections_Immutable_ImmutableSortedSet_T); protected abstract bool HasCorrectArgumentCount(TInvocation invocation); protected abstract bool TryGetOperands(TInvocation invocation, out SyntaxNode typeNode, out SyntaxNode methodNode); protected SetPropertiesInsteadOfMethodsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocation)c.Node; var methodName = Language.GetName(invocation); if (HasCorrectName(methodName) && HasCorrectArgumentCount(invocation) && TryGetOperands(invocation, out var typeNode, out var methodNode) && IsCorrectType(typeNode, c.Model) && IsCorrectCall(methodNode, c.Model)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), methodName); } }, Language.SyntaxKind.InvocationExpression); private bool HasCorrectName(string methodName) => methodName.Equals(nameof(Enumerable.Min), Language.NameComparison) || methodName.Equals(nameof(Enumerable.Max), Language.NameComparison); private static bool IsCorrectType(SyntaxNode node, SemanticModel model) => model.GetTypeInfo(node).Type.DerivesFromAny(TargetTypes); private static bool IsCorrectCall(SyntaxNode node, SemanticModel model) => model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ShiftDynamicNotIntegerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ShiftDynamicNotIntegerBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S3449"; protected abstract bool CanBeConvertedTo(SyntaxNode expression, ITypeSymbol type, SemanticModel model); protected abstract bool ShouldRaise(SemanticModel model, SyntaxNode left, SyntaxNode right); protected override string MessageFormat => "Remove this erroneous shift, it will fail because '{0}' can't be implicitly converted to 'int'."; protected ShiftDynamicNotIntegerBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => CheckExpressionWithTwoParts(c, b => Language.Syntax.BinaryExpressionLeft(b), b => Language.Syntax.BinaryExpressionRight(b)), Language.SyntaxKind.LeftShiftExpression, Language.SyntaxKind.RightShiftExpression); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => CheckExpressionWithTwoParts(c, b => Language.Syntax.AssignmentLeft(b), b => Language.Syntax.AssignmentRight(b)), Language.SyntaxKind.LeftShiftAssignmentStatement, Language.SyntaxKind.RightShiftAssignmentStatement); } protected bool IsConvertibleToInt(SyntaxNode expression, SemanticModel model) => model.Compilation.GetTypeByMetadataName(KnownType.System_Int32) is { } intType && CanBeConvertedTo(expression, intType, model); private void CheckExpressionWithTwoParts(SonarSyntaxNodeReportingContext context, Func getLeft, Func getRight) { var expression = context.Node; var left = getLeft(expression); var right = getRight(expression); if (!IsErrorType(right, context.Model, out var typeOfRight) && ShouldRaise(context.Model, left, right)) { var typeInMessage = TypeNameForMessage(right, typeOfRight, context.Model); context.ReportIssue(Rule, right, typeInMessage); } } private static string TypeNameForMessage(SyntaxNode expression, ITypeSymbol typeOfRight, SemanticModel model) => model.GetConstantValue(expression) is { HasValue: true, Value: null } ? "null" : typeOfRight.ToMinimalDisplayString(model, expression.SpanStart); private static bool IsErrorType(SyntaxNode expression, SemanticModel model, out ITypeSymbol type) { type = model.GetTypeInfo(expression).Type; return type.Is(TypeKind.Error); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ShouldImplementExportedInterfacesBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class ShouldImplementExportedInterfacesBase : SonarDiagnosticAnalyzer where TArgumentSyntax : SyntaxNode where TAttributeSyntax : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S4159"; private const string ActionForInterface = "Implement"; private const string ActionForClass = "Derive from"; private readonly ImmutableArray exportAttributes = ImmutableArray.Create( KnownType.System_ComponentModel_Composition_ExportAttribute, KnownType.System_ComponentModel_Composition_InheritedExportAttribute, KnownType.System_Composition_ExportAttribute); protected abstract SeparatedSyntaxList? GetAttributeArguments(TAttributeSyntax attributeSyntax); protected abstract SyntaxNode GetAttributeName(TAttributeSyntax attributeSyntax); protected abstract bool IsClassOrRecordSyntax(SyntaxNode syntaxNode); // Retrieve the expression inside of the typeof()/GetType() (e.g. typeof(Foo) => Foo) protected abstract SyntaxNode GetTypeOfOrGetTypeExpression(SyntaxNode expressionSyntax); protected override string MessageFormat => "{0} '{1}' on '{2}' or remove this export attribute."; protected ShouldImplementExportedInterfacesBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var attributeSyntax = (TAttributeSyntax)c.Node; if (c.Model.GetSymbolInfo(GetAttributeName(attributeSyntax)).Symbol is not IMethodSymbol attributeCtorSymbol || !attributeCtorSymbol.ContainingType.IsAny(exportAttributes)) { return; } var exportedType = GetExportedTypeSymbol(GetAttributeArguments(attributeSyntax), c.Model); var attributeTargetType = GetAttributeTargetSymbol(attributeSyntax, c.Model); if (exportedType is null || attributeTargetType is null || IsOfExportType(attributeTargetType, exportedType)) { return; } var action = exportedType.IsInterface() ? ActionForInterface : ActionForClass; c.ReportIssue( Rule, attributeSyntax, action, exportedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), attributeTargetType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); }, Language.SyntaxKind.Attribute); private static bool IsOfExportType(ITypeSymbol type, INamedTypeSymbol exportedType) => type is INamedTypeSymbol namedType && namedType.SelfBaseTypesAndInterfaces() .Any(currentType => exportedType.IsUnboundGenericType ? currentType.OriginalDefinition.Equals(exportedType.ConstructedFrom) : currentType.Equals(exportedType)); private INamedTypeSymbol GetExportedTypeSymbol(SeparatedSyntaxList? attributeArguments, SemanticModel semanticModel) { if (!attributeArguments.HasValue) { return null; } var arguments = attributeArguments.Value; if (arguments.Count != 1 && arguments.Count != 2) { return null; } var argumentSyntax = GetArgumentFromNamedArgument(arguments) ?? GetArgumentFromSingleArgumentAttribute(arguments) ?? GetArgumentFromDoubleArgumentAttribute(arguments, semanticModel); var typeOfOrGetTypeExpression = Language.Syntax.NodeExpression(argumentSyntax); var exportedTypeSyntax = GetTypeOfOrGetTypeExpression(typeOfOrGetTypeExpression); return exportedTypeSyntax == null ? null : semanticModel.GetSymbolInfo(exportedTypeSyntax).Symbol as INamedTypeSymbol; } private ITypeSymbol GetAttributeTargetSymbol(SyntaxNode syntaxNode, SemanticModel semanticModel) => // Parent is AttributeListSyntax, we handle only class attributes !IsClassOrRecordSyntax(syntaxNode.Parent?.Parent) ? null : semanticModel.GetDeclaredSymbol(syntaxNode.Parent.Parent) as ITypeSymbol; private TArgumentSyntax GetArgumentFromNamedArgument(IEnumerable arguments) => arguments.FirstOrDefault(x => "contractType".Equals(Language.Syntax.NodeIdentifier(x)?.ValueText, Language.NameComparison)); private TArgumentSyntax GetArgumentFromDoubleArgumentAttribute(SeparatedSyntaxList arguments, SemanticModel semanticModel) { if (arguments.Count != 2) { return null; } if (Language.Syntax.NodeExpression(arguments[0]) is { } firstArgument && semanticModel.GetConstantValue(firstArgument).Value is string) { // Two arguments, second should be typeof expression return arguments[1]; } return null; } private static TArgumentSyntax GetArgumentFromSingleArgumentAttribute(SeparatedSyntaxList arguments) => arguments.Count == 1 ? arguments[0] : null; // Only one argument, should be typeof expression } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SingleStatementPerLineBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class SingleStatementPerLineBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TStatementSyntax : SyntaxNode { protected const string DiagnosticId = "S122"; protected override string MessageFormat => "Reformat the code to have only one statement per line."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract bool StatementShouldBeExcluded(TStatementSyntax statement); protected SingleStatementPerLineBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterTreeAction( GeneratedCodeRecognizer, c => { var statements = c.Tree.GetRoot() .DescendantNodesAndSelf() .OfType() .Where(st => !StatementShouldBeExcluded(st)); var statementsByLines = MultiValueDictionary.Create>(); foreach (var statement in statements) { AddStatementToLineCache(statement, statementsByLines); } var lines = c.Tree.GetText().Lines; foreach (var statementsByLine in statementsByLines.Where(pair => pair.Value.Count > 1)) { var location = CalculateLocationForLine(lines[statementsByLine.Key], c.Tree, statementsByLine.Value); c.ReportIssue(SupportedDiagnostics[0], location); } }); private static Location CalculateLocationForLine(TextLine line, SyntaxTree tree, ICollection statements) { var lineSpan = line.Span; var min = statements.Min(st => lineSpan.Intersection(st.Span).Value.Start); var max = statements.Max(st => lineSpan.Intersection(st.Span).Value.End); return Location.Create(tree, TextSpan.FromBounds(min, max)); } private void AddStatementToLineCache(TStatementSyntax statement, MultiValueDictionary statementsByLines) { var startLine = statement.GetLocation().StartLine(); statementsByLines.AddWithKey(startLine, statement); var lastToken = statement.GetLastToken(); var tokenBelonsTo = GetContainingStatement(lastToken); if (tokenBelonsTo == statement) { var endLine = statement.GetLocation().EndLine(); statementsByLines.AddWithKey(endLine, statement); } } private TStatementSyntax GetContainingStatement(SyntaxToken token) { var node = token.Parent; var statement = node as TStatementSyntax; while (node != null && (statement == null || !StatementShouldBeExcluded(statement))) { node = node.Parent; statement = node as TStatementSyntax; } return statement; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/StringConcatenationInLoopBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class StringConcatenationInLoopBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TAssignmentExpression : SyntaxNode where TBinaryExpression : SyntaxNode { protected const string DiagnosticId = "S1643"; protected abstract TSyntaxKind[] CompoundAssignmentKinds { get; } protected abstract ISet ExpressionConcatenationKinds { get; } protected abstract ISet LoopKinds { get; } protected abstract SyntaxNode LeftMostExpression(SyntaxNode expression); protected override string MessageFormat => "Use a StringBuilder instead."; protected StringConcatenationInLoopBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckSimpleAssignment, Language.SyntaxKind.SimpleAssignment); context.RegisterNodeAction(Language.GeneratedCodeRecognizer, CheckCompoundAssignment, CompoundAssignmentKinds); } private void CheckSimpleAssignment(SonarSyntaxNodeReportingContext context) { var assignment = (TAssignmentExpression)context.Node; if (Language.Syntax.AssignmentRight(assignment) is TBinaryExpression { } rightExpression && Language.Syntax.IsAnyKind(rightExpression, ExpressionConcatenationKinds) && Language.Syntax.AssignmentLeft(assignment) is var assigned && IsIdentifierOnTheRight(assigned, rightExpression) && IsSystemString(assigned, context.Model) && LeftMostExpression(assigned) is { } expressionToCheck && AreNotDefinedInTheSameLoop(expressionToCheck, assignment, context.Model)) { context.ReportIssue(SupportedDiagnostics[0], assignment); } } private void CheckCompoundAssignment(SonarSyntaxNodeReportingContext context) { var addAssignment = (TAssignmentExpression)context.Node; if (Language.Syntax.AssignmentLeft(addAssignment) is { } expression && IsSystemString(expression, context.Model) && LeftMostExpression(expression) is { } expressionToCheck && AreNotDefinedInTheSameLoop(expressionToCheck, addAssignment, context.Model)) { context.ReportIssue(SupportedDiagnostics[0], addAssignment); } } private bool IsIdentifierOnTheRight(SyntaxNode identifier, SyntaxNode expression) { while (expression is TBinaryExpression && Language.Syntax.IsAnyKind(expression, ExpressionConcatenationKinds)) { var left = Language.Syntax.BinaryExpressionLeft(expression); var right = Language.Syntax.BinaryExpressionRight(expression); if (Language.Syntax.AreEquivalent(left, identifier) || Language.Syntax.AreEquivalent(right, identifier)) { return true; } // No need to recurse into the right branch as the only useful concatenation is flat `"a" + "b" + s` // `"a" + (s + "b")` seems not worth to support. expression = left; } return false; } private static bool IsSystemString(SyntaxNode node, SemanticModel model) => node.IsKnownType(KnownType.System_String, model); private SyntaxNode NearestLoop(SyntaxNode node) => node.AncestorsAndSelf().FirstOrDefault(x => Language.Syntax.IsAnyKind(x, LoopKinds)); private bool AreNotDefinedInTheSameLoop(SyntaxNode expression, SyntaxNode assignment, SemanticModel model) => NearestLoop(assignment) is { } nearestLoopForConcatenation && !(model.GetSymbolInfo(expression).Symbol is { } symbol && symbol.GetFirstSyntaxRef() is { } declaration && NearestLoop(declaration) is { } nearestLoop && Language.Syntax.AreEquivalent(nearestLoop, nearestLoopForConcatenation)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/StringLiteralShouldNotBeDuplicatedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class StringLiteralShouldNotBeDuplicatedBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct where TLiteralExpressionSyntax : SyntaxNode { private const string DiagnosticId = "S1192"; private const string MessageFormat = "Define a constant instead of using this literal '{0}' {1} times."; private const int MinimumStringLength = 5; private const int ThresholdDefaultValue = 3; private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract bool IsMatchingMethodParameterName(TLiteralExpressionSyntax literalExpression); protected abstract bool IsInnerInstance(SonarSyntaxNodeReportingContext context); protected abstract IEnumerable FindLiteralExpressions(SyntaxNode node); protected abstract SyntaxToken LiteralToken(TLiteralExpressionSyntax literal); [RuleParameter("threshold", PropertyType.Integer, "Number of times a literal must be duplicated to trigger an issue.", ThresholdDefaultValue)] public int Threshold { get; set; } = ThresholdDefaultValue; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected StringLiteralShouldNotBeDuplicatedBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected override void Initialize(SonarParametrizedAnalysisContext context) => // Ideally we would like to report at assembly/project level for the primary and all string instances for secondary // locations. The problem is that this scenario is not yet supported on SonarQube side. // Hence the decision to do like other languages, at class-level context.RegisterCompilationStartAction(c => { var usingDapper = c.Compilation.GetTypeByMetadataName(KnownType.Dapper_DynamicParameters) is not null; c.RegisterNodeAction(Language.GeneratedCodeRecognizer, nodeContext => ReportOnViolation(nodeContext, usingDapper), SyntaxKinds); }); protected virtual bool IsNamedTypeOrTopLevelMain(SonarSyntaxNodeReportingContext context) => IsNamedType(context); protected static bool IsNamedType(SonarSyntaxNodeReportingContext context) => context.ContainingSymbol.Kind == SymbolKind.NamedType; private void ReportOnViolation(SonarSyntaxNodeReportingContext context, bool usingDapper) { if (!IsNamedTypeOrTopLevelMain(context) || IsInnerInstance(context)) { return; } var stringLiterals = FindLiteralExpressions(context.Node); var duplicateValuesAndPositions = stringLiterals.Select(x => new { literal = x, literalToken = LiteralToken(x) }) .Where(x => x.literalToken.ValueText is { Length: >= MinimumStringLength } && !IsMatchingMethodParameterName(x.literal) && !IsInEFMigration(x.literal, context.Model) && !(usingDapper && x.literalToken.ValueText.StartsWith("@"))) .GroupBy(x => x.literalToken.ValueText, x => x.literalToken) .Where(x => x.Count() > Threshold); // Report duplications foreach (var item in duplicateValuesAndPositions) { var duplicates = item.ToList(); var firstToken = duplicates[0]; context.ReportIssue(rule, firstToken, duplicates.Skip(1).ToSecondaryLocations(), ExtractStringContent(firstToken), duplicates.Count.ToString()); } } private static bool IsInEFMigration(SyntaxNode node, SemanticModel model) => model.GetEnclosingSymbol(node.SpanStart)?.ContainingType is { } typeSymbol && IsEFMigrationType(typeSymbol); private static bool IsEFMigrationType(INamedTypeSymbol typeSymbol) => typeSymbol.DerivesFrom(KnownType.Microsoft_EntityFrameworkCore_Migrations_Migration) || typeSymbol.DerivesFrom(KnownType.System_Data_Entity_Migrations_DbMigration); private static string ExtractStringContent(SyntaxToken literalToken) => // Use literalToken.Text to get the text as written by the developer. The unescaped text (literalToken.ValueText) // might contain control characters that may cause trouble when used as error message (e.g. a null-terminator). // The literalToken.Text contains leading and trailing double quotes that we strip of. literalToken.Text.StartsWith("@\"") ? literalToken.Text.Substring(2, literalToken.Text.Length - 3) : literalToken.Text.Substring(1, literalToken.Text.Length - 2); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SwitchCasesMinimumThreeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class SwitchCasesMinimumThreeBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S1301"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SwitchSectionShouldNotHaveTooManyStatementsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class SwitchSectionShouldNotHaveTooManyStatementsBase : ParametrizedDiagnosticAnalyzer { protected const string DiagnosticId = "S1151"; protected const string MessageFormat = "Reduce this {0} number of statements from {1} to at most {2}, for example by extracting code into a {3}."; private const int ThresholdDefaultValue = 8; [RuleParameter("max", PropertyType.Integer, "Maximum number of statements.", ThresholdDefaultValue)] public int Threshold { get; set; } = ThresholdDefaultValue; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SwitchShouldNotBeNestedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class SwitchShouldNotBeNestedBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S1821"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/SwitchWithoutDefaultBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class SwitchWithoutDefaultBase : SonarDiagnosticAnalyzer { protected const string DiagnosticId = "S131"; protected const string MessageFormat = "Add a '{0}' clause to this '{1}' statement."; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } } public abstract class SwitchWithoutDefaultBase : SwitchWithoutDefaultBase where TLanguageKindEnum : struct { protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { if (TryGetDiagnostic(c.Node, out var diagnostic)) { c.ReportIssue(diagnostic); } }, SyntaxKindsOfInterest.ToArray()); } protected abstract bool TryGetDiagnostic(SyntaxNode node, out Diagnostic diagnostic); public abstract ImmutableArray SyntaxKindsOfInterest { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/TabCharacterBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class TabCharacterBase : SonarDiagnosticAnalyzer { protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterTreeAction( GeneratedCodeRecognizer, c => { var offset = c.Tree.GetText().ToString().IndexOf('\t'); if (offset < 0) { return; } var location = c.Tree.GetLocation(TextSpan.FromBounds(offset, offset)); c.ReportIssue(SupportedDiagnostics[0], location); }); } protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/TestsShouldNotUseThreadSleepBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class TestsShouldNotUseThreadSleepBase : SonarDiagnosticAnalyzer where TMethodSyntax : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S2925"; protected override string MessageFormat => "Do not use 'Thread.Sleep()' in a test."; protected abstract SyntaxNode MethodDeclaration(TMethodSyntax method); protected TestsShouldNotUseThreadSleepBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (c.Node.ToStringContains(nameof(Thread.Sleep), Language.NameComparison) && c.Model.GetSymbolInfo(c.Node).Symbol is IMethodSymbol method && method.Is(KnownType.System_Threading_Thread, nameof(Thread.Sleep)) && IsInTestMethod(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.InvocationExpression); private bool IsInTestMethod(SyntaxNode node, SemanticModel model) => node.Ancestors().OfType().FirstOrDefault() is { } method && model.GetDeclaredSymbol(MethodDeclaration(method)) is IMethodSymbol symbol && symbol.IsTestMethod(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ThreadResumeOrSuspendShouldNotBeCalledBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules { public abstract class ThreadResumeOrSuspendShouldNotBeCalledBase : DoNotCallMethodsBase where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S3889"; protected override string MessageFormat => "Refactor the code to remove this use of '{0}'."; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.System_Threading_Thread, "Suspend"), new(KnownType.System_Threading_Thread, "Resume") }; protected ThreadResumeOrSuspendShouldNotBeCalledBase() : base(DiagnosticId) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ThrowReservedExceptionsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ThrowReservedExceptionsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected const string DiagnosticId = "S112"; private readonly ImmutableArray reservedExceptions = ImmutableArray.Create( KnownType.System_Exception, KnownType.System_ApplicationException, KnownType.System_SystemException, KnownType.System_ExecutionEngineException, KnownType.System_IndexOutOfRangeException, KnownType.System_NullReferenceException, KnownType.System_OutOfMemoryException); protected override string MessageFormat => "'{0}' should not be thrown by user code."; protected ThrowReservedExceptionsBase() : base(DiagnosticId) { } protected void Process(SonarSyntaxNodeReportingContext context, SyntaxNode thrownExpression) { if (thrownExpression is not null && Language.Syntax.IsAnyKind(thrownExpression, Language.SyntaxKind.ObjectCreationExpressions)) { var expressionType = context.Model.GetTypeInfo(thrownExpression).Type; if (expressionType.IsAny(reservedExceptions)) { context.ReportIssue(Rule, thrownExpression, expressionType.ToDisplayString()); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ToStringShouldNotReturnNullBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class ToStringShouldNotReturnNullBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S2225"; protected abstract TSyntaxKind MethodKind { get; } protected abstract bool IsLocalOrLambda(SyntaxNode node); protected abstract IEnumerable Conditionals(SyntaxNode expression); protected override string MessageFormat => "Return an empty string instead."; protected ToStringShouldNotReturnNullBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => ToStringReturnsNull(c, c.Node), Language.SyntaxKind.ReturnStatement); protected void ToStringReturnsNull(SonarSyntaxNodeReportingContext context, SyntaxNode node) { if (node is not null && ReturnsNull(Language.Syntax.NodeExpression(node)) && WithinToString(node)) { context.ReportIssue(Rule, node); } } private bool ReturnsNull(SyntaxNode node) => Language.Syntax.IsNullLiteral(node) || Conditionals(node).Select(Language.Syntax.RemoveParentheses).Any(ReturnsNull); private bool WithinToString(SyntaxNode node) => node.Ancestors() .TakeWhile(x => !IsLocalOrLambda(x)) .Any(x => Language.Syntax.IsKind(x, MethodKind) && nameof(ToString).Equals(Language.Syntax.NodeIdentifier(x)?.ValueText, Language.NameComparison) && x.Parent?.RawKind is not (int)SyntaxKindEx.ExtensionBlockDeclaration && !Language.Syntax.IsStatic(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/TooManyLabelsInSwitchBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class TooManyLabelsInSwitchBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct where TSwitchStatementSyntax : SyntaxNode { protected const string MessageFormat = "Consider reworking this '{0}' to reduce the number of '{1}' clauses to at most {{0}} or have only one statement per '{1}'."; protected const string DiagnosticId = "S1479"; private const int DefaultValueMaximum = 30; protected abstract DiagnosticDescriptor Rule { get; } protected abstract TSyntaxKind[] SyntaxKinds { get; } protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract SyntaxNode GetExpression(TSwitchStatementSyntax statement); protected abstract int GetSectionsCount(TSwitchStatementSyntax statement); protected abstract bool AllSectionsAreOneLiners(TSwitchStatementSyntax statement); protected abstract Location GetKeywordLocation(TSwitchStatementSyntax statement); [RuleParameter("maximum", PropertyType.Integer, "Maximum number of case", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var switchNode = (TSwitchStatementSyntax)c.Node; if (c.Model.GetTypeInfo(GetExpression(switchNode)).Type is { TypeKind: not TypeKind.Enum } && GetSectionsCount(switchNode) > Maximum && !AllSectionsAreOneLiners(switchNode)) { c.ReportIssue(Rule, GetKeywordLocation(switchNode), Maximum.ToString()); } }, SyntaxKinds); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/TooManyParametersBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class TooManyParametersBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct where TParameterListSyntax : SyntaxNode { protected const string DiagnosticId = "S107"; protected const string MessageFormat = "{0} has {1} parameters, which is greater than the {2} authorized."; private const int DefaultValueMaximum = 7; private readonly DiagnosticDescriptor rule; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); [RuleParameter("max", PropertyType.Integer, "Maximum authorized number of parameters", DefaultValueMaximum)] public int Maximum { get; set; } = DefaultValueMaximum; protected abstract ILanguageFacade Language { get; } protected abstract string UserFriendlyNameForNode(SyntaxNode node); protected abstract int CountParameters(TParameterListSyntax parameterList); protected abstract int BaseParameterCount(SyntaxNode node); protected abstract bool CanBeChanged(SyntaxNode node, SemanticModel semanticModel); protected virtual bool IsExtern(SyntaxNode node) => false; protected TooManyParametersBase() => rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, isEnabledByDefault: false); protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var parametersCount = CountParameters((TParameterListSyntax)c.Node); var baseCount = BaseParameterCount(c.Node.Parent); if (parametersCount - baseCount > Maximum && c.Node.Parent is { } parent && !IsExtern(parent) && CanBeChanged(parent, c.Model)) { var valueText = baseCount == 0 ? parametersCount.ToString() : $"{parametersCount - baseCount} new"; c.ReportIssue(SupportedDiagnostics[0], c.Node, UserFriendlyNameForNode(c.Node.Parent), valueText, Maximum.ToString()); } }, Language.SyntaxKind.ParameterList); protected static bool VerifyCanBeChangedBySymbol(SyntaxNode node, SemanticModel semanticModel) { var declaredSymbol = semanticModel.GetDeclaredSymbol(node); var symbol = semanticModel.GetSymbolInfo(node).Symbol; if (declaredSymbol == null && symbol == null) { return false; } if (symbol != null) { return true; // Not a declaration, such as Action } if (declaredSymbol.IsStatic) { if ((declaredSymbol.IsExtern && declaredSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_DllImportAttribute)) || declaredSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_LibraryImportAttribute)) { return false; // P/Invoke method is defined externally. } } return declaredSymbol.GetOverriddenMember() is null && declaredSymbol.InterfaceMembers().IsEmpty(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UnaryPrefixOperatorRepeatedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UnaryPrefixOperatorRepeatedBase : SonarDiagnosticAnalyzer where TSyntaxNode : SyntaxNode where TSyntaxKindEnum : struct { internal const string DiagnosticId = "S2761"; protected const string MessageFormat = "Use the '{0}' operator just once or not at all."; protected abstract DiagnosticDescriptor Rule { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected abstract ISet SyntaxKinds { get; } protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var topLevelUnary = (TSyntaxNode)c.Node; if (!TopLevelUnaryInChain(topLevelUnary)) { return; } var repeatedCount = 0U; var currentUnary = topLevelUnary; var lastUnary = currentUnary; while (currentUnary != null && SameOperators(currentUnary, topLevelUnary)) { lastUnary = currentUnary; repeatedCount++; currentUnary = GetOperand(currentUnary) as TSyntaxNode; } if (repeatedCount < 2) { return; } c.ReportIssue(Rule, topLevelUnary.CreateLocation(GetOperatorToken(lastUnary)), GetOperatorToken(topLevelUnary).ValueText); }, SyntaxKinds.ToArray()); } private bool TopLevelUnaryInChain(TSyntaxNode unary) => !(unary.Parent is TSyntaxNode parent) || !SameOperators(parent, unary); protected abstract SyntaxNode GetOperand(TSyntaxNode unarySyntax); protected abstract SyntaxToken GetOperatorToken(TSyntaxNode unarySyntax); protected abstract bool SameOperators(TSyntaxNode expression1, TSyntaxNode expression2); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UnconditionalJumpStatementBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UnconditionalJumpStatementBase : SonarDiagnosticAnalyzer where TStatementSyntax : SyntaxNode where TSyntaxKind : struct { protected const string DiagnosticId = "S1751"; protected abstract ISet LoopStatements { get; } protected abstract LoopWalkerBase GetWalker(SonarSyntaxNodeReportingContext context); protected override string MessageFormat => "Refactor the containing loop to do more than one iteration."; protected UnconditionalJumpStatementBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var walker = GetWalker(c); walker.Visit(); foreach (var node in walker.GetRuleViolations()) { c.ReportIssue(Rule, node); } }, LoopStatements.ToArray()); } public abstract class LoopWalkerBase where TStatementSyntax : SyntaxNode where TLanguageKindEnum : struct { protected readonly SyntaxNode rootExpression; protected readonly SemanticModel semanticModel; private readonly ISet ignoredSyntaxesKind; protected abstract ILanguageFacade Language { get; } protected abstract ISet ConditionalStatements { get; } protected abstract ISet StatementsThatCanThrow { get; } protected abstract ISet LambdaSyntaxes { get; } protected abstract ISet LocalFunctionSyntaxes { get; } public abstract void Visit(); protected abstract bool TryGetTryAncestorStatements(TStatementSyntax node, List ancestors, out IEnumerable tryAncestorStatements); protected abstract bool IsPropertyAccess(TStatementSyntax node); protected List ConditionalContinues { get; } = new List(); protected List ConditionalTerminates { get; } = new List(); protected List UnconditionalContinues { get; } = new List(); protected List UnconditionalTerminates { get; } = new List(); protected LoopWalkerBase(SonarSyntaxNodeReportingContext context, ISet loopStatements) { rootExpression = context.Node; semanticModel = context.Model; ignoredSyntaxesKind = LambdaSyntaxes.Union(LocalFunctionSyntaxes).Union(loopStatements).ToHashSet(); } public List GetRuleViolations() { var ruleViolations = new List(UnconditionalContinues); if (!ConditionalContinues.Any()) { ruleViolations.AddRange(UnconditionalTerminates); } return ruleViolations; } protected void StoreVisitData(TStatementSyntax node, List conditionalCollection, List unconditionalCollection) { var ancestors = node .Ancestors() .TakeWhile(n => !rootExpression.Equals(n)) .ToList(); if (ancestors.Any(n => Language.Syntax.IsAnyKind(n, ignoredSyntaxesKind))) { return; } if (ancestors.Any(n => Language.Syntax.IsAnyKind(n, ConditionalStatements)) || IsInTryCatchWithStatementThatCanThrow(node, ancestors)) { conditionalCollection.Add(node); } else { unconditionalCollection.Add(node); } } private bool IsInTryCatchWithStatementThatCanThrow(TStatementSyntax node, List ancestors) { if (!TryGetTryAncestorStatements(node, ancestors, out var tryAncestorStatements)) { return false; } if (Language.Syntax.IsKind(node, Language.SyntaxKind.ReturnStatement) && (node.DescendantNodes().Any(n => Language.Syntax.IsAnyKind(n, StatementsThatCanThrow)) || IsPropertyAccess(node))) { return true; } return tryAncestorStatements .TakeWhile(statement => !statement.Equals(node)) .SelectMany(statement => statement.DescendantNodes()) .Any(statement => Language.Syntax.IsAnyKind(statement, StatementsThatCanThrow)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UnnecessaryBitwiseOperationBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UnnecessaryBitwiseOperationBase : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2437"; internal const string IsReportingOnLeftKey = "IsReportingOnLeft"; private const string MessageFormat = "Remove this unnecessary bit operation."; protected abstract ILanguageFacade Language { get; } public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected DiagnosticDescriptor Rule { get; } protected UnnecessaryBitwiseOperationBase() => Rule = Language.CreateDescriptor(DiagnosticId, MessageFormat, fadeOutCode: true); protected void CheckBinary(SonarSyntaxNodeReportingContext context, SyntaxNode left, SyntaxToken @operator, SyntaxNode right, int constValueToLookFor) { Location location; bool isReportingOnLeftKey; if (FindIntConstant(context.Model, left) is { } valueLeft && valueLeft == constValueToLookFor) { location = left.CreateLocation(@operator); isReportingOnLeftKey = true; } else if (FindIntConstant(context.Model, right) is { } valueRight && valueRight == constValueToLookFor) { location = @operator.CreateLocation(right); isReportingOnLeftKey = false; } else { return; } context.ReportIssue(Rule, location, ImmutableDictionary.Empty.Add(IsReportingOnLeftKey, isReportingOnLeftKey.ToString())); } protected int? FindIntConstant(SemanticModel semanticModel, SyntaxNode node) => semanticModel.GetSymbolInfo(node).Symbol is var symbol && !IsFieldOrPropertyOutsideSystemNamespace(symbol) && !symbol.GetSymbolType().IsEnum() && Language.FindConstantValue(semanticModel, node) is { } value ? Conversions.ToInt(value) : null; private static bool IsFieldOrPropertyOutsideSystemNamespace(ISymbol symbol) => symbol is { Kind: SymbolKind.Field or SymbolKind.Property } && symbol.ContainingNamespace.Name != nameof(System); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UnnecessaryBitwiseOperationCodeFixBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Rules { public abstract class UnnecessaryBitwiseOperationCodeFixBase : SonarCodeFix { internal const string Title = "Remove bitwise operation"; protected abstract Func CreateNewRoot(SyntaxNode root, TextSpan diagnosticSpan, bool isReportingOnLeft); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UnnecessaryBitwiseOperationBase.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var isReportingOnLeft = diagnostic.Properties.ContainsKey(UnnecessaryBitwiseOperationBase.IsReportingOnLeftKey) && bool.Parse(diagnostic.Properties[UnnecessaryBitwiseOperationBase.IsReportingOnLeftKey]); if (CreateNewRoot(root, diagnostic.Location.SourceSpan, isReportingOnLeft) is { } createNewRoot) { context.RegisterCodeFix(Title, c => Task.FromResult(context.Document.WithSyntaxRoot(createNewRoot())), context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UnusedStringBuilderBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UnusedStringBuilderBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TVariableDeclarator : SyntaxNode where TIdentifierName : SyntaxNode { private const string DiagnosticId = "S3063"; private readonly string[] stringBuilderAccessInvocations = ["ToString", "CopyTo", "GetChunks"]; private readonly string[] stringBuilderAccessExpressions = ["Length"]; protected abstract SyntaxNode Scope(TVariableDeclarator declarator); protected abstract ILocalSymbol RetrieveStringBuilderObject(SemanticModel model, TVariableDeclarator declaration); protected abstract SyntaxNode StringBuilderReadExpression(SemanticModel model, SyntaxNode node); protected abstract bool DescendIntoChildren(SyntaxNode node); protected override string MessageFormat => """Remove this "StringBuilder"; ".ToString()" is never called."""; protected UnusedStringBuilderBase() : base(DiagnosticId) { } internal bool IsAccessInvocation(string invocation) => stringBuilderAccessInvocations.Any(x => x.Equals(invocation, Language.NameComparison)); internal bool IsAccessExpression(string expression) => stringBuilderAccessExpressions.Any(x => x.Equals(expression, Language.NameComparison)); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var variableDeclarator = (TVariableDeclarator)c.Node; if (RetrieveStringBuilderObject(c.Model, variableDeclarator) is { } symbol && Scope(variableDeclarator) is { } scope && !IsStringBuilderAccessed(c.Model, symbol, scope)) { c.ReportIssue(Rule, variableDeclarator); } }, Language.SyntaxKind.VariableDeclarator); private bool IsSameVariable(SemanticModel model, ILocalSymbol symbol, SyntaxNode identifier) => Language.GetName(identifier).Equals(symbol.Name, Language.NameComparison) && symbol.Equals(model.GetSymbolInfo(identifier).Symbol); private static IEnumerable LocalReferences(SyntaxNode node) => node.DescendantNodesAndSelf().OfType(); private bool IsStringBuilderAccessed(SemanticModel model, ILocalSymbol symbol, SyntaxNode scope) => scope.DescendantNodes(DescendIntoChildren).Any(x => StringBuilderReadExpression(model, x) is { } expression && LocalReferences(expression).Any(x => IsSameVariable(model, symbol, x))); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UriShouldNotBeHardcodedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; using SonarAnalyzer.CFG.Extensions; namespace SonarAnalyzer.Core.Rules; public abstract class UriShouldNotBeHardcodedBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TLiteralExpressionSyntax : SyntaxNode where TArgumentSyntax : SyntaxNode { protected const string DiagnosticId = "S1075"; protected const string AbsoluteUriMessage = "Refactor your code not to use hardcoded absolute paths or URIs."; protected const string PathDelimiterMessage = "Remove this hardcoded path-delimiter."; // Simplified implementation of specification listed on // https://en.wikipedia.org/wiki/Uniform_Resource_Identifier private const string UriScheme = "^[a-zA-Z][a-zA-Z\\+\\.\\-]+://.+"; private const string AbsoluteDiskUri = @"^[A-Za-z]:(/|\\)"; private const string AbsoluteMappedDiskUri = @"^\\\\\w[ \w\.]*"; protected static readonly Regex UriRegex = new($"{UriScheme}|{AbsoluteDiskUri}|{AbsoluteMappedDiskUri}", RegexOptions.Compiled, Constants.DefaultRegexTimeout); protected static readonly Regex PathDelimiterRegex = new(@"^(\\|/)$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); protected static readonly ISet CheckedVariableNames = new HashSet { "FILE", "FILES", "PATH", "PATHS", "URI", "URIS", "URL", "URLS", "URN", "URNS", "STREAM", "STREAMS" }; protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract TSyntaxKind[] StringConcatenateExpressions { get; } protected abstract TSyntaxKind[] InvocationOrObjectCreationKind { get; } protected abstract SyntaxNode GetRelevantAncestor(SyntaxNode node); protected override string MessageFormat => "{0}"; protected UriShouldNotBeHardcodedBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { if (UriRegex.SafeIsMatch(Language.Syntax.LiteralText(c.Node)) && IsInCheckedContext(c.Node, c.Model)) { c.ReportIssue(SupportedDiagnostics[0], c.Node, AbsoluteUriMessage); } }, Language.SyntaxKind.StringLiteralExpressions); context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var isInCheckedContext = new Lazy(() => IsInCheckedContext(c.Node, c.Model)); var leftNode = Language.Syntax.BinaryExpressionLeft(c.Node); if (IsPathDelimiter(leftNode) && isInCheckedContext.Value) { c.ReportIssue(SupportedDiagnostics[0], leftNode, PathDelimiterMessage); } var rightNode = Language.Syntax.BinaryExpressionRight(c.Node); if (IsPathDelimiter(rightNode) && isInCheckedContext.Value) { c.ReportIssue(SupportedDiagnostics[0], rightNode, PathDelimiterMessage); } }, StringConcatenateExpressions); } private bool IsInCheckedContext(SyntaxNode expression, SemanticModel model) { var argument = expression.FirstAncestorOrSelf(); if (argument is not null) { var argumentIndex = Language.Syntax.ArgumentIndex(argument); if (argumentIndex is null or < 0) { return false; } var constructorOrMethod = argument.Ancestors().FirstOrDefault(x => Language.Syntax.IsAnyKind(x, InvocationOrObjectCreationKind)); var methodSymbol = constructorOrMethod is not null ? model.GetSymbolInfo(constructorOrMethod).Symbol as IMethodSymbol : null; return methodSymbol is not null && argumentIndex.Value < methodSymbol.Parameters.Length && methodSymbol.Parameters[argumentIndex.Value].Name.SplitCamelCaseToWords().Any(CheckedVariableNames.Contains); } return GetRelevantAncestor(expression) is { } relevantAncestor && Language.GetName(relevantAncestor).SplitCamelCaseToWords().Any(CheckedVariableNames.Contains); } private bool IsPathDelimiter(SyntaxNode expression) => expression is TLiteralExpressionSyntax && PathDelimiterRegex.SafeIsMatch(Language.Syntax.LiteralText(expression)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseCharOverloadOfStringMethodsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseCharOverloadOfStringMethodsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocation : SyntaxNode { internal const string DiagnosticId = "S6610"; protected override string MessageFormat => "\"{0}\" overloads that take a \"char\" should be used"; protected abstract bool HasCorrectArguments(TInvocation invocation); protected UseCharOverloadOfStringMethodsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(start => { if (!CompilationTargetsValidNetVersion(start.Compilation)) { return; } start.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocation)c.Node; var methodName = Language.GetName(invocation); if (HasCorrectName(methodName) && HasCorrectArguments(invocation) && Language.Syntax.Operands(invocation).Left is { } left && IsCorrectType(left, c.Model)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), methodName); } }, Language.SyntaxKind.InvocationExpression); }); // "char" overload introduced at .NET Core 2.0/.NET Standard 2.1 private static bool CompilationTargetsValidNetVersion(Compilation compilation) { var stringType = compilation.GetTypeByMetadataName(KnownType.System_String); var methods = stringType.GetMembers(nameof(string.StartsWith)).Where(x => x is IMethodSymbol); return methods.Any(x => ((IMethodSymbol)x).Parameters[0].IsType(KnownType.System_Char)); } private bool HasCorrectName(string methodName) => methodName.Equals(nameof(string.StartsWith), Language.NameComparison) || methodName.Equals(nameof(string.EndsWith), Language.NameComparison); private static bool IsCorrectType(SyntaxNode left, SemanticModel model) => model.GetTypeInfo(left).Type.Is(KnownType.System_String); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseDateTimeOffsetInsteadOfDateTimeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseDateTimeOffsetInsteadOfDateTimeBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6566"; protected override string MessageFormat => "Prefer using \"DateTimeOffset\" instead of \"DateTime\""; private static readonly ImmutableArray TargetMemberAccess = ImmutableArray.Create( nameof(DateTime.MaxValue), nameof(DateTime.MinValue), nameof(DateTime.Now), nameof(DateTime.Today), nameof(DateTime.UtcNow), "UnixEpoch"); protected abstract string[] ValidNames { get; } protected UseDateTimeOffsetInsteadOfDateTimeBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if ((c.Node.RawKind == (int)SyntaxKindEx.ImplicitObjectCreationExpression || IsNamedDateTime(GetTypeName(c.Node))) && IsDateTimeType(c.Node, c.Model)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.ObjectCreationExpressions); context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeExpression(c.Node) is var expression && IsNamedDateTime(Language.GetName(expression)) && TargetMemberAccess.Contains(Language.GetName(c.Node)) && IsDateTimeType(expression, c.Model)) { c.ReportIssue(Rule, Language.Syntax.NodeExpression(c.Node)); } }, Language.SyntaxKind.SimpleMemberAccessExpression); } private static bool IsDateTimeType(SyntaxNode node, SemanticModel model) => model.GetTypeInfo(node).Type.Is(KnownType.System_DateTime); private bool IsNamedDateTime(string name) => Array.Exists(ValidNames, x => x.Equals(name, Language.NameComparison)); private string GetTypeName(SyntaxNode node) => Language.Syntax.ObjectCreationTypeIdentifier(node) is { IsMissing: false } identifier ? identifier.ValueText : string.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseFindSystemTimeZoneByIdBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.Core.Rules; public abstract class UseFindSystemTimeZoneByIdBase : DoNotCallMethodsBase where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S6575"; protected override string MessageFormat => "Use \"TimeZoneInfo.FindSystemTimeZoneById\" directly instead of \"{0}\""; protected override IEnumerable CheckedMethods { get; } = new List { new(KnownType.TimeZoneConverter_TZConvert, "IanaToWindows"), new(KnownType.TimeZoneConverter_TZConvert, "WindowsToIana"), new(KnownType.TimeZoneConverter_TZConvert, "TryIanaToWindows"), new(KnownType.TimeZoneConverter_TZConvert, "TryWindowsToIana"), }; protected UseFindSystemTimeZoneByIdBase() : base(DiagnosticId) { } protected override bool ShouldRegisterAction(Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.System_TimeOnly) is not null && compilation.GetTypeByMetadataName(KnownType.TimeZoneConverter_TZConvert) is not null; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseIFormatProviderForParsingDateAndTimeBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseIFormatProviderForParsingDateAndTimeBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6580"; private static readonly string[] ParseMethodNames = new[] { nameof(DateTime.Parse), nameof(DateTime.ParseExact), nameof(DateTime.TryParse), nameof(DateTime.TryParseExact) }; private static readonly KnownType[] TemporalTypes = new[] { KnownType.System_DateOnly, KnownType.System_DateTime, KnownType.System_DateTimeOffset, KnownType.System_TimeOnly, KnownType.System_TimeSpan }; protected override string MessageFormat => "Use a format provider when parsing date and time."; protected UseIFormatProviderForParsingDateAndTimeBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (Language.Syntax.NodeIdentifier(c.Node) is { IsMissing: false } identifier && Array.Exists(ParseMethodNames, x => identifier.ValueText.Equals(x, Language.NameComparison)) && c.Model.GetSymbolInfo(c.Node) is { Symbol: IMethodSymbol methodSymbol } && TemporalTypes.Any(x => x.Matches(methodSymbol.ReceiverType)) && NotUsingFormatProvider(methodSymbol, c.Node)) { c.ReportIssue(Rule, c.Node); } }, Language.SyntaxKind.InvocationExpression); private bool NotUsingFormatProvider(IMethodSymbol methodSymbol, SyntaxNode node) { var parameterLookup = Language.MethodParameterLookup(node, methodSymbol); return (!parameterLookup.TryGetSyntax("provider", out var parameter) && !parameterLookup.TryGetSyntax("formatProvider", out parameter)) || Language.Syntax.IsNullLiteral(parameter[0]); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseIndexingInsteadOfLinqMethodsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseIndexingInsteadOfLinqMethodsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S6608"; protected override string MessageFormat => "{0} should be used instead of the \"Enumerable\" extension method \"{1}\""; private static readonly ImmutableArray TargetInterfaces = ImmutableArray.Create( KnownType.System_Collections_IList, KnownType.System_Collections_Generic_IList_T, KnownType.System_Collections_Generic_IReadOnlyList_T); protected abstract int GetArgumentCount(TInvocation invocation); protected UseIndexingInsteadOfLinqMethodsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocation)c.Node; if (HasValidSignature(invocation, out var methodName, out var indexDescriptor) && Language.Syntax.Operands(invocation) is { Left: { } left, Right: { } right } && IsCorrectType(left, c.Model) && IsCorrectCall(right, c.Model)) { c.ReportIssue( Rule, Language.Syntax.NodeIdentifier(invocation)?.GetLocation(), indexDescriptor is null ? "Indexing" : $"Indexing at {indexDescriptor}", methodName); } }, Language.SyntaxKind.InvocationExpression); private bool HasValidSignature(TInvocation invocation, out string methodName, out string indexDescriptor) { methodName = Language.GetName(invocation); indexDescriptor = null; if (methodName.Equals(nameof(Enumerable.First), Language.NameComparison)) { indexDescriptor = "0"; return GetArgumentCount(invocation) == 0; } if (methodName.Equals(nameof(Enumerable.Last), Language.NameComparison)) { indexDescriptor = "Count-1"; return GetArgumentCount(invocation) == 0; } if (methodName.Equals(nameof(Enumerable.ElementAt), Language.NameComparison)) { return GetArgumentCount(invocation) == 1; } return false; } protected static bool IsCorrectType(SyntaxNode left, SemanticModel model) => model.GetTypeInfo(left).Type is { } type && (type.ImplementsAny(TargetInterfaces) || type.IsAny(TargetInterfaces)); protected static bool IsCorrectCall(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseLambdaParameterInConcurrentDictionaryBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseLambdaParameterInConcurrentDictionaryBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocationExpression : SyntaxNode where TArgumentSyntax : SyntaxNode { private const string DiagnosticId = "S6612"; protected override string MessageFormat => "Use the lambda parameter instead of capturing the argument '{0}'"; protected abstract SeparatedSyntaxList GetArguments(TInvocationExpression invocation); protected abstract bool TryGetKeyName(TArgumentSyntax argument, out string keyName); protected abstract bool IsLambdaAndContainsIdentifier(TArgumentSyntax argument, string keyName); protected UseLambdaParameterInConcurrentDictionaryBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocationExpression)c.Node; if (HasCorrectName(Language.GetName(invocation)) && Language.Syntax.Operands(invocation) is { Left: { } left, Right: { } right } && IsCorrectType(left, c.Model) && IsCorrectCall(right, c.Model) && GetArguments(invocation) is { Count: > 1 } arguments && TryGetKeyName(arguments[0], out var keyName)) { for (var i = 1; i < arguments.Count; i++) { if (IsLambdaAndContainsIdentifier(arguments[i], keyName)) { c.ReportIssue(Rule, arguments[i], keyName); } } } }, Language.SyntaxKind.InvocationExpression); private bool HasCorrectName(string methodName) => methodName.Equals("GetOrAdd", Language.NameComparison) || methodName.Equals("AddOrUpdate", Language.NameComparison); private static bool IsCorrectType(SyntaxNode left, SemanticModel model) => model.GetTypeInfo(left).Type.DerivesFrom(KnownType.System_Collections_Concurrent_ConcurrentDictionary_TKey_TValue); private static bool IsCorrectCall(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.ContainingType.Is(KnownType.System_Collections_Concurrent_ConcurrentDictionary_TKey_TValue); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseShortCircuitingOperatorBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UseShortCircuitingOperatorBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TBinaryExpression : SyntaxNode { internal const string DiagnosticId = "S2178"; protected abstract string GetSuggestedOpName(TBinaryExpression node); protected abstract string GetCurrentOpName(TBinaryExpression node); protected abstract SyntaxToken GetOperator(TBinaryExpression expression); protected abstract ImmutableArray SyntaxKindsOfInterest { get; } protected override string MessageFormat => "Correct this '{0}' to '{1}'{2}."; protected UseShortCircuitingOperatorBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (c.Node is TBinaryExpression node && Language.Syntax.BinaryExpressionLeft(node) is { } left && Language.Syntax.BinaryExpressionRight(node) is { } right && IsBool(left, c.Model) && IsBool(right, c.Model)) { var extractText = c.Model.GetConstantValue(right) is { HasValue: true } || c.Model.GetSymbolInfo(right).Symbol is ILocalSymbol or IFieldSymbol or IPropertySymbol or IParameterSymbol ? string.Empty : " and extract the right operand to a variable if it should always be evaluated"; c.ReportIssue(SupportedDiagnostics[0], GetOperator(node), GetCurrentOpName(node), GetSuggestedOpName(node), extractText); } }, SyntaxKindsOfInterest.ToArray()); private static bool IsBool(SyntaxNode node, SemanticModel semanticModel) => semanticModel.GetTypeInfo(node).Type.Is(KnownType.System_Boolean); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseShortCircuitingOperatorCodeFixBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UseShortCircuitingOperatorCodeFixBase : SonarCodeFix where TSyntaxKind : struct where TBinaryExpression : SyntaxNode { internal const string Title = "Use short-circuiting operators"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseShortCircuitingOperatorBase.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is not TBinaryExpression expression || !IsCandidateExpression(expression)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => ReplaceExpressionAsync(expression, root, context.Document), context.Diagnostics); return Task.CompletedTask; } internal abstract bool IsCandidateExpression(TBinaryExpression expression); private Task ReplaceExpressionAsync(TBinaryExpression expression, SyntaxNode root, Document document) { var replacement = GetShortCircuitingExpressionNode(expression) .WithTriviaFrom(expression); var newRoot = root.ReplaceNode(expression, replacement); return Task.FromResult(document.WithSyntaxRoot(newRoot)); } protected abstract TBinaryExpression GetShortCircuitingExpressionNode(TBinaryExpression expression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseTestableTimeProviderBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseTestableTimeProviderBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6354"; protected override string MessageFormat => "Use a testable (date) time provider instead."; private readonly ImmutableArray trackedTypes = ImmutableArray.Create(KnownType.System_DateTime, KnownType.System_DateTimeOffset); protected abstract bool Ignore(SyntaxNode ancestor, SemanticModel semanticModel); protected UseTestableTimeProviderBase() : base(DiagnosticId) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (IsDateTimeProviderProperty(Language.Syntax.NodeIdentifier(c.Node).Value.Text) && c.Model.GetSymbolInfo(c.Node).Symbol is IPropertySymbol property && property.IsInType(trackedTypes) && !c.Node.Ancestors().Any(x => Ignore(x, c.Model))) { c.ReportIssue(Rule, c.Node.Parent); } }, Language.SyntaxKind.IdentifierName); private bool IsDateTimeProviderProperty(string name) => nameof(DateTime.Now).Equals(name, Language.NameComparison) || nameof(DateTime.UtcNow).Equals(name, Language.NameComparison) || nameof(DateTime.Today).Equals(name, Language.NameComparison); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseTrueForAllBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseTrueForAllBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S6603"; protected override string MessageFormat => "The collection-specific \"TrueForAll\" method should be used instead of the \"All\" extension"; private static readonly ImmutableArray TargetTypes = ImmutableArray.Create( KnownType.System_Array, KnownType.System_Collections_Generic_List_T, KnownType.System_Collections_Immutable_ImmutableList_T); protected UseTrueForAllBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { if (Language.GetName(c.Node).Equals(nameof(Enumerable.All), Language.NameComparison) && Language.Syntax.Operands(c.Node) is { Left: { } left, Right: { } right } && IsCorrectType(left, c.Model) && IsCorrectCall(right, c.Model)) { c.ReportIssue(Rule, Language.Syntax.NodeIdentifier(c.Node)?.GetLocation()); } }, Language.SyntaxKind.InvocationExpression); protected static bool IsCorrectType(SyntaxNode left, SemanticModel model) => model.GetTypeInfo(left).Type.DerivesFromAny(TargetTypes); protected static bool IsCorrectCall(SyntaxNode right, SemanticModel model) => model.GetSymbolInfo(right).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseUnixEpochBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseUnixEpochBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { internal const string DiagnosticId = "S6588"; protected UseUnixEpochBase() : base(DiagnosticId) { } } public abstract class UseUnixEpochBase : UseUnixEpochBase where TSyntaxKind : struct where TLiteralExpression : SyntaxNode where TMemberAccessExpression : SyntaxNode { private const long EpochTicks = 621_355_968_000_000_000; private const int EpochYear = 1970; private const int EpochMonth = 1; private const int EpochDay = 1; private static readonly ImmutableArray TypesWithUnixEpochField = ImmutableArray.Create(KnownType.System_DateTime, KnownType.System_DateTimeOffset); protected override string MessageFormat => "Use \"{0}.UnixEpoch\" instead of creating {0} instances that point to the unix epoch time"; protected abstract bool IsDateTimeKindUtc(TMemberAccessExpression memberAccess); protected abstract bool IsGregorianCalendar(SyntaxNode node); protected abstract bool IsZeroTimeOffset(SyntaxNode node); protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(c => { if (!IsUnixEpochSupported(c.Compilation)) { return; } c.RegisterNodeAction( Language.GeneratedCodeRecognizer, cc => { var arguments = Language.Syntax.ArgumentExpressions(cc.Node); var literalsArguments = arguments.OfType(); if (literalsArguments.Any(x => IsValueEqualTo(x, EpochYear) && literalsArguments.Count(x => IsValueEqualTo(x, EpochMonth)) == 2) && CheckAndGetTypeName(cc.Node, cc.Model) is { } name && IsEpochCtor(cc.Node, cc.Model)) { cc.ReportIssue(Rule, cc.Node, name); } else if (arguments.Count() == 1 && ((literalsArguments.Count() == 1 && IsValueEqualTo(literalsArguments.First(), EpochTicks)) || (Language.FindConstantValue(cc.Model, arguments.First()) is long ticks && ticks == EpochTicks)) && CheckAndGetTypeName(cc.Node, cc.Model) is { } typeName) { cc.ReportIssue(Rule, cc.Node, typeName); } }, Language.SyntaxKind.ObjectCreationExpressions); }); protected static bool IsValueEqualTo(TLiteralExpression literal, long value) => long.TryParse(literal.ChildTokens().First().ValueText, out var parsedValue) && parsedValue == value; private bool IsEpochCtor(SyntaxNode node, SemanticModel model) { var methodSymbol = (IMethodSymbol)model.GetSymbolInfo(node).Symbol; var lookup = Language.MethodParameterLookup(node, methodSymbol); return IsParameterExistingAndLiteralEqualTo("year", EpochYear, lookup) && IsParameterExistingAndLiteralEqualTo("month", EpochMonth, lookup) && IsParameterExistingAndLiteralEqualTo("day", EpochDay, lookup) && IsParameterNonExistingOrLiteralEqualTo("hour", 0, lookup) && IsParameterNonExistingOrLiteralEqualTo("minute", 0, lookup) && IsParameterNonExistingOrLiteralEqualTo("second", 0, lookup) && IsParameterNonExistingOrLiteralEqualTo("millisecond", 0, lookup) && IsParameterNonExistingOrLiteralEqualTo("microsecond", 0, lookup) && IsDateTimeKindNonExistingOrUtc(lookup) && IsCalendarNonExistingOrGregorian(lookup) && IsOffsetNonExistingOrZero(lookup); } private static bool IsParameterExistingAndLiteralEqualTo(string parameterName, int value, IMethodParameterLookup lookup) => lookup.TryGetSyntax(parameterName, out var expressions) && IsLiteralAndEqualTo(expressions[0], value); private static bool IsParameterNonExistingOrLiteralEqualTo(string parameterName, int value, IMethodParameterLookup lookup) => !lookup.TryGetSyntax(parameterName, out var expressions) || IsLiteralAndEqualTo(expressions[0], value); private static bool IsLiteralAndEqualTo(SyntaxNode node, int value) => node is TLiteralExpression literal && IsValueEqualTo(literal, value); private static string CheckAndGetTypeName(SyntaxNode node, SemanticModel model) => model.GetTypeInfo(node).Type is var type && type.IsAny(TypesWithUnixEpochField) ? type.Name : null; private bool IsDateTimeKindNonExistingOrUtc(IMethodParameterLookup lookup) => !lookup.TryGetSyntax("kind", out var expressions) || (expressions[0] is TMemberAccessExpression memberAccess && IsDateTimeKindUtc(memberAccess)); private bool IsCalendarNonExistingOrGregorian(IMethodParameterLookup lookup) => !lookup.TryGetSyntax("calendar", out var expressions) || IsGregorianCalendar(expressions[0]); private bool IsOffsetNonExistingOrZero(IMethodParameterLookup lookup) => !lookup.TryGetSyntax("offset", out var expressions) || IsZeroTimeOffset(expressions[0]); // DateTime.UnixEpoch introduced at .NET Core 2.1/.NET Standard 2.1 private static bool IsUnixEpochSupported(Compilation compilation) => compilation.GetTypeByMetadataName(KnownType.System_DateTime) is var dateType && dateType.GetMembers("UnixEpoch").Any(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseUnixEpochCodeFixBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class UseUnixEpochCodeFixBase : SonarCodeFix where TSyntaxKind : struct { internal const string Title = "Use UnixEpoch field"; protected abstract SyntaxNode ReplaceConstructorWithField(SyntaxNode root, SyntaxNode node, SonarCodeFixContext context); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseUnixEpochBase.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true); context.RegisterCodeFix( Title, _ => { var newRoot = ReplaceConstructorWithField(root, node, context); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/UseWhereBeforeOrderByBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class UseWhereBeforeOrderByBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct where TInvocation : SyntaxNode { private const string DiagnosticId = "S6607"; private const string SecondaryMessage = """This "{0}" precedes a "Where" - you should change the order."""; protected override string MessageFormat => "\"Where\" should be used before \"{0}\""; protected UseWhereBeforeOrderByBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(Language.GeneratedCodeRecognizer, c => { var invocation = (TInvocation)c.Node; if (Language.GetName(invocation).Equals("Where", Language.NameComparison) && Language.Syntax.Operands(invocation) is { Left: { } left, Right: { } right } && LeftHasCorrectName(left, out var orderByMethodDescription) && MethodIsLinqExtension(left, c.Model) && MethodIsLinqExtension(right, c.Model) && Language.Syntax.NodeIdentifier(right) is { } rightIdentifier && Language.Syntax.NodeIdentifier(left) is { } leftIdentifier) { c.ReportIssue(Rule, rightIdentifier.GetLocation(), [leftIdentifier.ToSecondaryLocation(SecondaryMessage, orderByMethodDescription)], orderByMethodDescription); } }, Language.SyntaxKind.InvocationExpression); private bool LeftHasCorrectName(SyntaxNode left, out string methodName) { var leftName = Language.GetName(left); if (leftName.Equals("OrderBy", Language.NameComparison) || leftName.Equals("OrderByDescending", Language.NameComparison)) { methodName = leftName; return true; } methodName = null; return false; } private static bool MethodIsLinqExtension(SyntaxNode node, SemanticModel model) => model.GetSymbolInfo(node).Symbol is IMethodSymbol method && method.IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/AnalysisWarningAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.CFG.Common; namespace SonarAnalyzer.Core.Rules; public abstract class AnalysisWarningAnalyzerBase : UtilityAnalyzerBase { private const string DiagnosticId = "S9999-warning"; private const string Title = "Analysis Warning generator"; protected virtual int VS2017MajorVersion => RoslynVersion.VS2017MajorVersion; // For testing protected virtual int MinimalSupportedRoslynVersion => RoslynVersion.MinimalSupportedMajorVersion; // For testing protected AnalysisWarningAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationAction(c => { var parameter = ReadParameters(c); if (!parameter.IsAnalyzerEnabled || parameter.OutPath is null) { return; } var path = Path.GetFullPath(Path.Combine(parameter.OutPath, "../../AnalysisWarnings.MsBuild.json")); if (!File.Exists(path)) { // This can be removed after we bump Microsoft.CodeAnalysis references to 2.0 or higher. MsBuild 14 is bound with Roslyn 1.x. if (RoslynVersion.IsVersionLessThan(VS2017MajorVersion)) { WriteAllText(path, "The analysis using MsBuild 14 is no longer supported and the analysis with MsBuild 15 is deprecated. Please update your pipeline to MsBuild 16 or higher."); } // This can be removed after we bump Microsoft.CodeAnalysis references to 3.0 or higher. MsBuild 15 is bound with Roslyn 2.x. else if (RoslynVersion.IsVersionLessThan(MinimalSupportedRoslynVersion)) { WriteAllText(path, "The analysis using MsBuild 15 is deprecated. Please update your pipeline to MsBuild 16 or higher."); } } }); private static void WriteAllText(string path, string text) { try { File.WriteAllText(path, $$"""[{"text": "{{text}}"}]"""); } catch { // Nothing to do here. Two compilations running on two different processes are unlikely to lock each other out on a small file write. } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/CopyPasteTokenAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules { public abstract class CopyPasteTokenAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-cpd"; private const string Title = "Copy-paste token calculator"; protected abstract string GetCpdValue(SyntaxToken token); protected abstract bool IsUsingDirective(SyntaxNode node); protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { AnalyzeTestProjects = false }; protected sealed override bool AnalyzeUnchangedFiles => true; protected sealed override string FileName => "token-cpd.pb"; protected CopyPasteTokenAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => !GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) && base.ShouldGenerateMetrics(parameters, tree); protected sealed override CopyPasteTokenInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) { var cpdTokenInfo = new CopyPasteTokenInfo { FilePath = tree.FilePath }; foreach (var token in tree.GetRoot().DescendantTokens(n => !IsUsingDirective(n))) { if (GetCpdValue(token) is var value && !string.IsNullOrWhiteSpace(value)) { cpdTokenInfo.TokenInfo.Add(new CopyPasteTokenInfo.Types.TokenInfo { TokenValue = value, TextRange = ToTextRange(Location.Create(tree, token.Span).GetLineSpan()) }); } } return cpdTokenInfo; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/FileMetadataAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules { public abstract class FileMetadataAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { protected const string DiagnosticId = "S9999-metadata"; private const string Title = "File metadata generator"; protected sealed override string FileName => "file-metadata.pb"; protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { AnalyzeGeneratedCode = true }; protected FileMetadataAnalyzerBase() : base(DiagnosticId, Title) { } protected override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => !GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) && base.ShouldGenerateMetrics(parameters, tree); protected sealed override FileMetadataInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) => new() { FilePath = tree.FilePath, IsGenerated = tree.IsGenerated(Language.GeneratedCodeRecognizer), Encoding = tree.Encoding?.WebName.ToLowerInvariant() ?? string.Empty }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/LogAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules { public abstract class LogAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-log"; private const string Title = "Log generator"; protected abstract string LanguageVersion(Compilation compilation); protected sealed override string FileName => "log.pb"; protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { AnalyzeGeneratedCode = true }; protected LogAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override IEnumerable CreateAnalysisMessages(SonarCompilationReportingContext c) => new[] { new LogInfo { Severity = LogSeverity.Info, Text = "Roslyn version: " + typeof(SyntaxNode).Assembly.GetName().Version }, new LogInfo { Severity = LogSeverity.Info, Text = "Language version: " + LanguageVersion(c.Compilation) }, new LogInfo { Severity = LogSeverity.Info, Text = "Concurrent execution: " + (IsConcurrentExecutionEnabled() ? "enabled" : "disabled") } }; protected sealed override LogInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) => tree.IsGenerated(Language.GeneratedCodeRecognizer) ? CreateMessage(tree) : null; private static LogInfo CreateMessage(SyntaxTree tree) => GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) ? new LogInfo { Severity = LogSeverity.Debug, Text = $"File '{tree.FilePath}' was recognized as razor generated" } : new LogInfo { Severity = LogSeverity.Debug, Text = $"File '{tree.FilePath}' was recognized as generated" }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/MethodDeclarationInfoComparer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules; public sealed record MethodDeclarationInfoComparer : IEqualityComparer { public bool Equals(MethodDeclarationInfo first, MethodDeclarationInfo second) => (first is null && second is null) || (first is not null && second is not null && first.TypeName == second.TypeName && first.MethodName == second.MethodName); public int GetHashCode(MethodDeclarationInfo info) => HashCode.Combine(info.TypeName, info.MethodName); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/MetricsAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules { public abstract class MetricsAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-metrics"; private const string Title = "Metrics calculator"; protected abstract MetricsBase GetMetrics(SyntaxTree syntaxTree, SemanticModel semanticModel); protected sealed override string FileName => "metrics.pb"; protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { AnalyzeTestProjects = false }; protected MetricsAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override MetricsInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) { var metrics = GetMetrics(tree, model); var complexity = metrics.Complexity; var metricsInfo = new MetricsInfo { FilePath = tree.GetRoot().GetMappedFilePathFromRoot(), ClassCount = metrics.ClassCount, StatementCount = metrics.StatementCount, FunctionCount = metrics.FunctionCount, Complexity = complexity, CognitiveComplexity = metrics.CognitiveComplexity, }; var comments = metrics.GetComments(parameters.IgnoreHeaderComments); metricsInfo.NoSonarComment.AddRange(comments.NoSonar); metricsInfo.NonBlankComment.AddRange(comments.NonBlank); metricsInfo.CodeLine.AddRange(metrics.CodeLines); metricsInfo.ExecutableLines.AddRange(metrics.ExecutableLines); return metricsInfo; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/SymbolReferenceAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules; public abstract class SymbolReferenceAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-symbolRef"; private const string Title = "Symbol reference calculator"; private const int TokenCountThreshold = 40_000; protected abstract SyntaxNode GetBindableParent(SyntaxToken token); protected abstract ReferenceInfo[] CreateDeclarationReferenceInfo(SyntaxNode node, SemanticModel model); protected abstract IList GetDeclarations(SyntaxNode node); protected sealed override string FileName => "symrefs.pb"; protected SymbolReferenceAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override SymbolReferenceInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) { var filePath = MapFilePath(tree); var symbolReferenceInfo = new SymbolReferenceInfo { FilePath = filePath }; var references = GetReferences(tree.GetRoot(), model); foreach (var symbol in references.Keys) { if (GetSymbolReference(references[symbol], filePath) is { } reference) { symbolReferenceInfo.Reference.Add(reference); } } return symbolReferenceInfo; } protected sealed override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => base.ShouldGenerateMetrics(parameters, tree) && !HasTooManyTokens(tree); private Dictionary> GetReferences(SyntaxNode root, SemanticModel model) { var references = new Dictionary>(); var knownIdentifiers = new HashSet(Language.NameComparer); var knownNodes = new List(); var declarations = GetDeclarations(root); for (var i = 0; i < declarations.Count; i++) { var declarationReferences = CreateDeclarationReferenceInfo(declarations[i], model); if (declarationReferences is null) { continue; } for (var j = 0; j < declarationReferences.Length; j++) { var currentDeclaration = declarationReferences[j]; if (currentDeclaration.Symbol is not null) { references.GetOrAdd(currentDeclaration.Symbol, _ => []).Add(currentDeclaration); knownNodes.Add(currentDeclaration.Node); knownIdentifiers.Add(currentDeclaration.Identifier.ValueText); } } } foreach (var token in root.DescendantTokens()) { if (Language.Syntax.IsKind(token, Language.SyntaxKind.IdentifierToken) && knownIdentifiers.Contains(token.ValueText) && GetBindableParent(token) is { } parent && !knownNodes.Contains(parent) && GetReferenceSymbol(parent, model) is { } symbol) { foreach (var part in symbol.AllPartialParts().Where(references.ContainsKey)) { references[part].Add(new(parent, token, part, false)); } } } return references; } private static ISymbol GetReferenceSymbol(SyntaxNode node, SemanticModel model) => model.GetSymbolInfo(node).Symbol switch { IMethodSymbol { MethodKind: MethodKind.Constructor, IsImplicitlyDeclared: true } constructor => constructor.ContainingType, var symbol => symbol }; private static SymbolReferenceInfo.Types.SymbolReference GetSymbolReference(IReadOnlyList references, string filePath) { var declarationSpan = GetDeclarationSpan(references, filePath); if (!declarationSpan.HasValue) { return null; } var symbolReference = new SymbolReferenceInfo.Types.SymbolReference { Declaration = ToTextRange(declarationSpan.Value) }; for (var i = 0; i < references.Count; i++) { var reference = references[i]; if (!reference.IsDeclaration && reference.Identifier.GetLocation().GetMappedLineSpanIfAvailable() is var mappedLineSpan // Syntax tree can contain elements from external files (e.g. razor imports files) // We need to make sure that we don't count these elements. && string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase)) { symbolReference.Reference.Add(ToTextRange(mappedLineSpan)); } } return symbolReference; } private static FileLinePositionSpan? GetDeclarationSpan(IReadOnlyList references, string filePath) { for (var i = 0; i < references.Count; i++) { if (references[i].IsDeclaration && references[i].Identifier.GetLocation().GetMappedLineSpanIfAvailable() is var mappedLineSpan && string.Equals(mappedLineSpan.Path, filePath, StringComparison.OrdinalIgnoreCase)) { return mappedLineSpan; } } return null; } private static bool HasTooManyTokens(SyntaxTree tree) => tree.GetRoot().DescendantTokens().Count() > TokenCountThreshold; protected sealed record ReferenceInfo(SyntaxNode Node, SyntaxToken Identifier, ISymbol Symbol, bool IsDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/TelemetryAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Google.Protobuf; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules; public abstract class TelemetryAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-telemetry"; private const string Title = "Telemetry generator"; private const string FileName = "telemetry.pb"; protected abstract ILanguageFacade Language { get; } protected abstract string LanguageVersion(Compilation compilation); protected TelemetryAnalyzerBase() : base(DiagnosticId, Title) { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { var parameters = ReadParameters(startContext); if (!parameters.IsAnalyzerEnabled) { return; } startContext.RegisterCompilationEndAction(endContext => { Directory.CreateDirectory(parameters.OutPath); using var stream = File.Create(Path.Combine(parameters.OutPath, FileName)); CreateTelemetry(endContext).WriteDelimitedTo(stream); }); }); private Telemetry CreateTelemetry(SonarCompilationReportingContext c) { var projectConfiguration = c.ProjectConfiguration(); return new Telemetry { ProjectFullPath = projectConfiguration.ProjectPath is { } path ? path : string.Empty, LanguageVersion = LanguageVersion(c.Compilation) is { } language ? language : string.Empty, }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/TestMethodDeclarationsAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules; /// /// This class is responsible for exporting the method declarations to a protobuf file which will later be /// used in the plugin to map the content of the test reports to the actual files. /// /// Discriminator for C#/VB.NET. public abstract class TestMethodDeclarationsAnalyzerBase() : UtilityAnalyzerBase(DiagnosticId, Title) where TSyntaxKind : struct { private const string DiagnosticId = "S9999-testMethodDeclaration"; private const string Title = "Test method declarations generator"; protected abstract IEnumerable GetTypeDeclarations(SyntaxNode node); protected abstract IEnumerable GetMethodDeclarations(SyntaxNode node); protected sealed override string FileName => "test-method-declarations.pb"; protected override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => // In this analyzer, we want to always generate the metrics for the test projects (contrary to the base class implementation). parameters.IsTestProject && !Language.GeneratedCodeRecognizer.IsGenerated(tree); protected sealed override MethodDeclarationsInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) { // Test method declarations found in the file by starting with the syntax tree. var fileDeclarations = GetMethodDeclarations(tree.GetRoot()) .Select(x => GetTestMethodSymbol(x, model)) .WhereNotNull() .Select(GetDeclarationInfo); // Test method declarations pulled from the base types. var baseTypeDeclarations = GetTypeDeclarations(tree.GetRoot()) .Select(x => (ITypeSymbol)model.GetDeclaredSymbol(x)) .WhereNotNull() .SelectMany(x => GetAllTestMethods(x, x.BaseType)); var declarations = new HashSet(fileDeclarations, new MethodDeclarationInfoComparer()); declarations.UnionWith(baseTypeDeclarations); return declarations.Count == 0 ? null : new MethodDeclarationsInfo { FilePath = tree.FilePath, AssemblyName = model.Compilation.AssemblyName, MethodDeclarations = { declarations } }; } private static MethodDeclarationInfo GetDeclarationInfo(IMethodSymbol methodSymbol) => new() { TypeName = Name(methodSymbol.ContainingType), MethodName = methodSymbol.Name }; private static MethodDeclarationInfo GetDeclarationInfo(ITypeSymbol derivedType, IMethodSymbol methodSymbol) => new() { TypeName = Name(derivedType), MethodName = methodSymbol.Name }; private static string Name(ITypeSymbol typeSymbol) { const string separator = "."; var nameParts = new Stack(); var currentType = typeSymbol; while (currentType is not null) { nameParts.Push(currentType.Name); currentType = currentType.ContainingType; } var currentNamespace = typeSymbol.ContainingNamespace; while (currentNamespace is not null && !currentNamespace.IsGlobalNamespace) { nameParts.Push(currentNamespace.Name); currentNamespace = currentNamespace.ContainingNamespace; } return string.Join(separator, nameParts); } private static IEnumerable GetAllTestMethods(ITypeSymbol derivedType, ITypeSymbol typeSymbol) { if (typeSymbol is null) { return []; } var members = new HashSet(GetTestMethodSymbols(typeSymbol).Select(x => GetDeclarationInfo(derivedType, x))); var baseType = typeSymbol.BaseType; while (baseType is not null) { members.UnionWith(GetAllTestMethods(derivedType, baseType)); baseType = baseType.BaseType; } return members; } private static IEnumerable GetTestMethodSymbols(ITypeSymbol typeSymbol) => typeSymbol.GetMembers().OfType().Where(IsTestMethod); private static IMethodSymbol GetTestMethodSymbol(SyntaxNode methodDeclarationSyntax, SemanticModel model) => model.GetDeclaredSymbol(methodDeclarationSyntax) is IMethodSymbol methodSymbol && IsTestMethod(methodSymbol) ? methodSymbol : null; private static bool IsTestMethod(IMethodSymbol methodSymbol) => methodSymbol is not null && !methodSymbol.IsImplicitlyDeclared && methodSymbol.IsTestMethod(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/TokenTypeAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Protobuf; using static SonarAnalyzer.Protobuf.TokenTypeInfo.Types; namespace SonarAnalyzer.Core.Rules { public abstract class TokenTypeAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct { private const string DiagnosticId = "S9999-token-type"; private const string Title = "Token type calculator"; private const int IdentifierTokenCountThreshold = 4_000; protected sealed override string FileName => "token-type.pb"; protected TokenTypeAnalyzerBase() : base(DiagnosticId, Title) { } protected abstract TokenClassifierBase GetTokenClassifier(SemanticModel semanticModel, bool skipIdentifierTokens); protected abstract TriviaClassifierBase GetTriviaClassifier(); protected sealed override bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => !GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) && base.ShouldGenerateMetrics(parameters, tree); protected sealed override TokenTypeInfo CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) { var tokens = tree.GetRoot().DescendantTokens(); var identifierTokenKind = Language.SyntaxKind.IdentifierToken; // Performance optimization var skipIdentifierTokens = tokens .Where(token => Language.Syntax.IsKind(token, identifierTokenKind)) .Skip(IdentifierTokenCountThreshold) .Any(); var tokenClassifier = GetTokenClassifier(model, skipIdentifierTokens); var triviaClassifier = GetTriviaClassifier(); var spans = new List(); // The second iteration of the tokens is intended since there is no processing done and we want to avoid copying all the tokens to a second collection. foreach (var token in tokens) { if (token.HasLeadingTrivia) { IterateTrivia(triviaClassifier, spans, token.LeadingTrivia); } if (tokenClassifier.ClassifyToken(token) is { } tokenClassification) { spans.Add(tokenClassification); } if (token.HasTrailingTrivia) { IterateTrivia(triviaClassifier, spans, token.TrailingTrivia); } } var tokenTypeInfo = new TokenTypeInfo { FilePath = tree.FilePath }; tokenTypeInfo.TokenInfo.AddRange(spans); return tokenTypeInfo; static void IterateTrivia(TriviaClassifierBase triviaClassifier, List spans, SyntaxTriviaList triviaList) { foreach (var trivia in triviaList) { if (triviaClassifier.ClassifyTrivia(trivia) is { } triviaClassification) { spans.Add(triviaClassification); } } } } protected internal abstract class TokenClassifierBase { private readonly bool skipIdentifiers; private readonly SemanticModel semanticModel; private static readonly ISet ConstructorKinds = new HashSet { MethodKind.Constructor, MethodKind.StaticConstructor, MethodKind.SharedConstructor }; private static readonly ISet VarSymbolKinds = new HashSet { SymbolKind.NamedType, SymbolKind.TypeParameter, SymbolKind.ArrayType, SymbolKind.PointerType }; protected abstract SyntaxNode GetBindableParent(SyntaxToken token); protected abstract bool IsKeyword(SyntaxToken token); protected abstract bool IsIdentifier(SyntaxToken token); protected abstract bool IsNumericLiteral(SyntaxToken token); protected abstract bool IsStringLiteral(SyntaxToken token); protected SemanticModel SemanticModel => semanticModel ?? throw new InvalidOperationException("The code snippet is not supposed to call the semantic model for classification."); protected TokenClassifierBase(SemanticModel semanticModel, bool skipIdentifiers) { this.semanticModel = semanticModel; this.skipIdentifiers = skipIdentifiers; } public TokenInfo ClassifyToken(SyntaxToken token) => token switch { _ when IsKeyword(token) => TokenInfo(token, TokenType.Keyword), _ when IsStringLiteral(token) => TokenInfo(token, TokenType.StringLiteral), _ when IsNumericLiteral(token) => TokenInfo(token, TokenType.NumericLiteral), _ when IsIdentifier(token) && !skipIdentifiers => ClassifyIdentifier(token), _ => null, }; protected static TokenInfo TokenInfo(SyntaxToken token, TokenType tokenType) => tokenType == TokenType.UnknownTokentype || (string.IsNullOrWhiteSpace(token.Text) && tokenType != TokenType.StringLiteral) ? null : new() { TokenType = tokenType, TextRange = ToTextRange(token.GetLocation().GetLineSpan()), }; protected virtual TokenInfo ClassifyIdentifier(SyntaxToken token) { if (SemanticModel.GetDeclaredSymbol(token.Parent) is { } declaration) { return ClassifyIdentifier(token, declaration); } else if (GetBindableParent(token) is { } parent && SemanticModel.GetSymbolInfo(parent).Symbol is { } symbol) { return ClassifyIdentifier(token, symbol); } else { return null; } } private TokenInfo ClassifyIdentifier(SyntaxToken token, ISymbol symbol) => symbol switch { IAliasSymbol alias => ClassifyIdentifier(token, alias.Target), IMethodSymbol ctorSymbol when ConstructorKinds.Contains(ctorSymbol.MethodKind) => TokenInfo(token, TokenType.TypeName), _ when token.ValueText == "var" && VarSymbolKinds.Contains(symbol.Kind) => TokenInfo(token, TokenType.Keyword), { Kind: SymbolKind.Parameter, IsImplicitlyDeclared: true } when token.ValueText == "value" => TokenInfo(token, TokenType.Keyword), { Kind: SymbolKind.NamedType or SymbolKind.TypeParameter } => TokenInfo(token, TokenType.TypeName), { Kind: SymbolKind.DynamicType } => TokenInfo(token, TokenType.Keyword), _ => null, }; } protected internal abstract class TriviaClassifierBase { protected abstract bool IsDocComment(SyntaxTrivia trivia); protected abstract bool IsRegularComment(SyntaxTrivia trivia); public TokenInfo ClassifyTrivia(SyntaxTrivia trivia) => trivia switch { _ when IsRegularComment(trivia) => TokenInfo(trivia.SyntaxTree, TokenType.Comment, trivia.Span), _ when IsDocComment(trivia) => ClassifyDocComment(trivia), // Handle preprocessor directives here _ => null, }; private TokenInfo TokenInfo(SyntaxTree tree, TokenType tokenType, TextSpan span) => new() { TokenType = tokenType, TextRange = ToTextRange(Location.Create(tree, span).GetLineSpan()) }; private TokenInfo ClassifyDocComment(SyntaxTrivia trivia) => TokenInfo(trivia.SyntaxTree, TokenType.Comment, trivia.FullSpan); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/Utilities/UtilityAnalyzerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using System.IO; using Google.Protobuf; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Core.Rules; public readonly record struct UtilityAnalyzerParameters(bool IsAnalyzerEnabled, bool IgnoreHeaderComments, bool AnalyzeGeneratedCode, bool AnalyzeTestProjects, string OutPath, bool IsTestProject, bool IsCloud) { public static readonly UtilityAnalyzerParameters Default = new(false, false, false, true, null, false, false); } public abstract class UtilityAnalyzerBase : SonarDiagnosticAnalyzer { protected static readonly ISet FileExtensionWhitelist = new HashSet { ".cs", ".csx", ".vb" }; private readonly DiagnosticDescriptor rule; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override bool EnableConcurrentExecution => false; protected UtilityAnalyzerBase(string diagnosticId, string title) => rule = DiagnosticDescriptorFactory.CreateUtility(diagnosticId, title); internal static TextRange ToTextRange(FileLinePositionSpan lineSpan) => new() { StartLine = lineSpan.StartLinePosition.LineNumberToReport(), EndLine = lineSpan.EndLinePosition.LineNumberToReport(), StartOffset = lineSpan.StartLinePosition.Character, EndOffset = lineSpan.EndLinePosition.Character }; protected virtual UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) { var outPath = context.ProjectConfiguration().OutPath; // For backward compatibility with S4MSB <= 5.0 if (outPath is null && context.Options.ProjectOutFolderPath() is { } projectOutFolderAdditionalFile) { outPath = projectOutFolderAdditionalFile.GetText().ToString().TrimEnd(); } if (context.Options.SonarLintXml() is not null && !string.IsNullOrEmpty(outPath)) { var language = context.Compilation.Language; var sonarLintXml = context.SonarLintXml(); return new UtilityAnalyzerParameters( IsAnalyzerEnabled: true, IgnoreHeaderComments: sonarLintXml.IgnoreHeaderComments(language), AnalyzeGeneratedCode: sonarLintXml.AnalyzeGeneratedCode(language), AnalyzeTestProjects: true, OutPath: Path.Combine(outPath, language == LanguageNames.CSharp ? "output-cs" : "output-vbnet"), IsTestProject: context.IsTestProject(), IsCloud: context.ProjectConfiguration().AnalysisConfig.IsCloud); } return UtilityAnalyzerParameters.Default; } } public abstract class UtilityAnalyzerBase : UtilityAnalyzerBase where TSyntaxKind : struct where TMessage : class, IMessage, new() { protected abstract ILanguageFacade Language { get; } protected abstract string FileName { get; } protected abstract TMessage CreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model); protected virtual bool AnalyzeUnchangedFiles => false; protected UtilityAnalyzerBase(string diagnosticId, string title) : base(diagnosticId, title) { } protected virtual IEnumerable CreateAnalysisMessages(SonarCompilationReportingContext c) => []; protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCompilationStartAction(startContext => { var parameters = ReadParameters(startContext); if (!parameters.IsAnalyzerEnabled) { return; } var treeMessages = new ConcurrentStack(); startContext.RegisterSemanticModelActionInAllFiles(modelContext => { if (ShouldGenerateMetrics(parameters, modelContext)) { var message = CreateMessage(parameters, modelContext.Tree, modelContext.Model); treeMessages.Push(message); } }); startContext.RegisterCompilationEndAction(endContext => { var allMessages = CreateAnalysisMessages(endContext) .Concat(treeMessages) .WhereNotNull() .ToArray(); Directory.CreateDirectory(parameters.OutPath); using var stream = File.Create(Path.Combine(parameters.OutPath, FileName)); foreach (var message in allMessages) { message.WriteDelimitedTo(stream); } }); }); protected virtual bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SyntaxTree tree) => // The results of Metrics and CopyPasteToken analyzers are not needed for Test projects yet the plugin side expects the protobuf files, so we create empty ones. (parameters.AnalyzeTestProjects || !parameters.IsTestProject) && FileExtensionWhitelist.Contains(Path.GetExtension(tree.FilePath)) && ShouldGenerateMetricsByType(parameters, tree); protected static string MapFilePath(SyntaxTree tree) => // If the syntax tree is constructed for a razor generated file, we need to provide the original file path. GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) && tree.GetRoot() is var root && root.ContainsDirectives ? root.GetMappedFilePathFromRoot() : tree.FilePath; private bool ShouldGenerateMetrics(UtilityAnalyzerParameters parameters, SonarSemanticModelReportingContext context) => (AnalyzeUnchangedFiles || !context.IsUnchanged(context.Tree)) && ShouldGenerateMetrics(parameters, context.Tree); private bool ShouldGenerateMetricsByType(UtilityAnalyzerParameters parameters, SyntaxTree tree) => parameters.AnalyzeGeneratedCode ? !GeneratedCodeRecognizer.IsCshtml(tree) // We cannot upload metrics for .cshtml files. The file is owned by the html plugin. : !tree.IsGenerated(Language.GeneratedCodeRecognizer) || GeneratedCodeRecognizer.IsRazor(tree); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/ValueTypeShouldImplementIEquatableBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.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.Model.GetDeclaredSymbol(c.Node) is INamedTypeSymbol structSymbol && !structSymbol.Implements(KnownType.System_IEquatable_T)) { var identifier = Language.Syntax.NodeIdentifier(c.Node).Value; c.ReportIssue(Rule, identifier, identifier.ValueText); } }, Language.SyntaxKind.StructDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/VariableUnusedBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class VariableUnusedBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { protected abstract bool IsExcludedDeclaration(SyntaxNode node); protected override string MessageFormat => "Remove the unused local variable '{0}'."; protected VariableUnusedBase() : base("S1481") { } protected sealed override void Initialize(SonarAnalysisContext context) => context.RegisterCodeBlockStartAction(Language.GeneratedCodeRecognizer, cbc => { // Two-pass approach: in pass 1, declaration nodes populate declaredLocalNames and declaredLocals. // All IdentifierName nodes are buffered into pendingIdentifiers. In pass 2 (the end-of-block // action), GetSymbolInfo is called only on buffered identifiers whose text appears in // declaredLocalNames — avoiding the expensive call for every unrelated identifier in the block. // // Three collections are needed rather than two: declaredLocalNames serves as a cheap text-based // pre-filter while declaredLocals holds the resolved symbols for the unused check. Combining them // into a Dictionary would break shadowing — the same name can map to multiple // symbols (e.g. 'x' in two sequential blocks), so the relationship is one-to-many. var declaredLocalNames = new HashSet(Language.NameComparer); var declaredLocals = new HashSet(); var pendingIdentifiers = new List(); cbc.RegisterNodeAction(c => CollectDeclaration(c, declaredLocalNames, declaredLocals), Language.SyntaxKind.LocalDeclarationKinds); cbc.RegisterNodeAction(c => pendingIdentifiers.Add(c.Node), Language.SyntaxKind.IdentifierName); cbc.RegisterCodeBlockEndAction(c => ReportUnused(c, declaredLocalNames, declaredLocals, pendingIdentifiers)); }); private void CollectDeclaration(SonarSyntaxNodeReportingContext c, HashSet declaredLocalNames, HashSet declaredLocals) { if (!IsExcludedDeclaration(c.Node) && c.Model.GetDeclaredSymbol(c.Node) is (ILocalSymbol or IRangeVariableSymbol) and { Name: not "_" } symbol) { declaredLocalNames.Add(symbol.Name); declaredLocals.Add(symbol); } } private void ReportUnused(SonarCodeBlockReportingContext c, HashSet declaredLocalNames, HashSet declaredLocals, List pendingIdentifiers) { if (declaredLocalNames.Count == 0) { return; } var usedLocals = new HashSet(); foreach (var id in pendingIdentifiers) { if (Language.Syntax.NodeIdentifier(id) is { ValueText: var idText } && declaredLocalNames.Contains(idText)) { usedLocals.UnionWith(c.Model.GetSymbolInfo(id).AllSymbols()); } } foreach (var unused in declaredLocals.Except(usedLocals)) { c.ReportIssue(Rule, unused.Locations.First(), unused.Name); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/WcfNonVoidOneWayBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules { public abstract class WcfNonVoidOneWayBase : SonarDiagnosticAnalyzer where TMethodSyntax : SyntaxNode where TLanguageKind : struct { internal const string DiagnosticId = "S3598"; protected const string MessageFormat = "This method can't return any values because it is marked as one-way operation."; protected sealed override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( GeneratedCodeRecognizer, c => { var methodDeclaration = (TMethodSyntax)c.Node; var methodSymbol = c.Model.GetDeclaredSymbol(methodDeclaration) as IMethodSymbol; if (methodSymbol == null || methodSymbol.ReturnsVoid) { return; } var operationContractAttribute = methodSymbol .GetAttributes(KnownType.System_ServiceModel_OperationContractAttribute) .FirstOrDefault(); if (operationContractAttribute == null) { return; } var asyncPattern = operationContractAttribute.NamedArguments .FirstOrDefault(na => "AsyncPattern".Equals(na.Key, StringComparison.OrdinalIgnoreCase)) // insensitive for VB.NET .Value.Value as bool?; if (asyncPattern.HasValue && asyncPattern.Value) { return; } var isOneWay = operationContractAttribute.NamedArguments .FirstOrDefault(na => "IsOneWay".Equals(na.Key, StringComparison.OrdinalIgnoreCase)) // insensitive for VB.NET .Value.Value as bool?; if (isOneWay.HasValue && isOneWay.Value) { c.ReportIssue(SupportedDiagnostics[0], GetReturnTypeLocation(methodDeclaration)); } }, MethodDeclarationKind); } protected abstract GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } protected abstract TLanguageKind MethodDeclarationKind { get; } protected abstract Location GetReturnTypeLocation(TMethodSyntax method); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Rules/WeakSslTlsProtocolsBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Rules; public abstract class WeakSslTlsProtocolsBase : SonarDiagnosticAnalyzer where TSyntaxKind : struct { private const string DiagnosticId = "S4423"; private readonly HashSet weakProtocols = [ "Ssl2", "Ssl3", "Tls", "Tls11", "Default", ]; protected override string MessageFormat => "Change this code to use a stronger protocol."; protected WeakSslTlsProtocolsBase() : base(DiagnosticId) { } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( Language.GeneratedCodeRecognizer, c => { var node = c.Node; if (!Language.Syntax.IsPartOfBinaryNegationOrCondition(node) && IsWeakProtocol(node, c.Model)) { c.ReportIssue(Rule, node); } }, Language.SyntaxKind.IdentifierName); private bool IsWeakProtocol(SyntaxNode identifierName, SemanticModel model) => weakProtocols.Contains(Language.Syntax.NodeIdentifier(identifierName).Value.ValueText) && model.GetTypeInfo(identifierName).Type.IsAny(KnownType.System_Net_SecurityProtocolType, KnownType.System_Security_Authentication_SslProtocols); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/IMethodSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Comparison = SonarAnalyzer.Core.Syntax.Utilities.ComparisonKind; namespace SonarAnalyzer.Core.Semantics.Extensions; public static class IMethodSymbolExtensions { private static readonly ImmutableArray NonActionTypes = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_NonActionAttribute, KnownType.System_Web_Mvc_NonActionAttribute); private static readonly ImmutableArray KnownTestMethodAttributes = ImmutableArray.Create( [ ..KnownType.TestMethodAttributesOfMSTest, ..KnownType.TestMethodAttributesOfNUnit, ..KnownType.TestMethodAttributesOfxUnit, ]); private static readonly ImmutableArray NoExpectedResultTestMethodReturnTypes = ImmutableArray.Create( KnownType.Void, KnownType.System_Threading_Tasks_Task); private static readonly ImmutableArray KnownTestIgnoreAttributes = ImmutableArray.Create( // Note: XUnit doesn't have a separate "Ignore" attribute. It has a "Skip" parameter // on the test attribute KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_IgnoreAttribute, KnownType.NUnit_Framework_IgnoreAttribute); public static bool IsExtensionOn(this IMethodSymbol methodSymbol, KnownType type) { if (methodSymbol is { IsExtensionMethod: true }) { var receiverType = methodSymbol.MethodKind == MethodKind.Ordinary ? methodSymbol.Parameters.First().Type as INamedTypeSymbol : methodSymbol.ReceiverType as INamedTypeSymbol; return receiverType?.ConstructedFrom.Is(type) ?? false; } else { return false; } } public static bool IsDestructor(this IMethodSymbol method) => method.MethodKind == MethodKind.Destructor; public static bool IsAnyAttributeInOverridingChain(this IMethodSymbol method) => method.IsAnyAttributeInOverridingChain(x => x.OverriddenMethod); public static bool Is(this IMethodSymbol methodSymbol, KnownType knownType, string name) => methodSymbol.ContainingType.Is(knownType) && methodSymbol.Name == name; public static bool IsAny(this IMethodSymbol methodSymbol, KnownType knownType, params string[] names) => methodSymbol.ContainingType.Is(knownType) && names.Contains(methodSymbol.Name); public static bool IsImplementingInterfaceMember(this IMethodSymbol methodSymbol, KnownType knownInterfaceType, string name) => (methodSymbol.Name == name && (methodSymbol.Is(knownInterfaceType, name) || methodSymbol.InterfaceMembers().Any(x => x.Is(knownInterfaceType, name)))) || methodSymbol.ExplicitInterfaceImplementations.Any(x => x.ContainingType.ConstructedFrom.Is(knownInterfaceType) && x.Name == name); /// /// Returns a value indicating whether the provided method symbol is a ASP.NET MVC /// controller method. /// public static bool IsControllerActionMethod(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.Ordinary, IsStatic: false } && (methodSymbol.OverriddenMethod is null || !methodSymbol.OverriddenMethod.ContainingType.IsAny(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase, KnownType.Microsoft_AspNetCore_Mvc_Controller)) && methodSymbol.GetEffectiveAccessibility() == Accessibility.Public && !methodSymbol.GetAttributes().Any(x => x.AttributeClass.IsAny(NonActionTypes)) && methodSymbol.TypeParameters.Length == 0 && methodSymbol.Parameters.All(x => x.RefKind == RefKind.None) && methodSymbol.ContainingType.IsControllerType(); public static Comparison ComparisonKind(this IMethodSymbol method) => method?.MethodKind == MethodKind.UserDefinedOperator ? ComparisonKind(method.Name) : Comparison.None; public static bool IsTestMethod(this IMethodSymbol method) => method.MethodKind.HasFlag(MethodKindEx.LocalFunction) ? method.IsXunitTestMethod() : method.AnyAttributeDerivesFromOrImplementsAny(KnownTestMethodAttributes); public static bool IsIgnoredTestMethod(this IMethodSymbol method) => method.HasTestIgnoreAttribute() || (method.FindXUnitTestAttribute() is { } testAttribute && (testAttribute.NamedArguments.Any(x => x.Key is "Skip" or "SkipExceptions" or "SkipType" or "SkipUnless" or "SkipWhen") || (testAttribute.TryGetAttributeValue("Explicit", out bool explicitTest) && explicitTest))); public static bool HasExpectedExceptionAttribute(this IMethodSymbol method) => method.GetAttributes().Any(x => x.AttributeClass.IsAny(KnownType.ExpectedExceptionAttributes) || x.AttributeClass.DerivesFrom(KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_ExpectedExceptionBaseAttribute)); public static bool HasAssertionInAttribute(this IMethodSymbol method) => !NoExpectedResultTestMethodReturnTypes.Any(method.ReturnType.Is) && method.GetAttributes().Any(IsAnyTestCaseAttributeWithExpectedResult); public static bool IsMsTestOrNUnitTestIgnored(this IMethodSymbol method) => method.GetAttributes().Any(x => x.AttributeClass.IsAny(KnownType.IgnoreAttributes)); /// /// Returns the that indicates the type of the test method or /// null if the method is not decorated with a known type. /// /// We assume that a test is only marked with a single test attribute e.g. /// not both [Fact] and [Theory]. If there are multiple attributes only one will be /// returned. public static KnownType FindFirstTestMethodType(this IMethodSymbol method) => KnownTestMethodAttributes.FirstOrDefault(x => method.GetAttributes().Any(att => att.AttributeClass.DerivesFrom(x))); extension(IMethodSymbol method) { public bool IsExtension => method is { IsExtensionMethod: true } or { AssociatedExtensionImplementation: not null }; } /// /// Returns whether the method is a constructor in a MEF-exported type. /// MEF (Managed Extensibility Framework) instantiates types via reflection, so these constructors are not unused. /// public static bool IsMefConstructor(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.Constructor, ContainingType: INamedTypeSymbol containingType } && containingType.IsMefExportedType(); private static AttributeData FindXUnitTestAttribute(this IMethodSymbol method) => method.GetAttributes().FirstOrDefault(x => x.AttributeClass.IsAny(KnownType.TestMethodAttributesOfxUnit)); private static bool IsAnyTestCaseAttributeWithExpectedResult(AttributeData a) => IsTestAttributeWithExpectedResult(a) || a.AttributeClass.Is(KnownType.NUnit_Framework_TestCaseSourceAttribute); private static bool HasTestIgnoreAttribute(this IMethodSymbol method) => method.GetAttributes().Any(x => x.AttributeClass.IsAny(KnownTestIgnoreAttributes)); private static bool IsTestAttributeWithExpectedResult(AttributeData attribute) => attribute.AttributeClass.IsAny(KnownType.NUnit_Framework_TestCaseAttribute, KnownType.NUnit_Framework_TestAttribute) && attribute.NamedArguments.Any(x => x.Key == "ExpectedResult"); private static bool IsXunitTestMethod(this IMethodSymbol methodSymbol) => methodSymbol.AnyAttributeDerivesFromAny(KnownType.TestMethodAttributesOfxUnit); private static Comparison ComparisonKind(string method) => method switch { "op_Equality" => Comparison.Equals, "op_Inequality" => Comparison.NotEquals, "op_LessThan" => Comparison.LessThan, "op_LessThanOrEqual" => Comparison.LessThanOrEqual, "op_GreaterThan" => Comparison.GreaterThan, "op_GreaterThanOrEqual" => Comparison.GreaterThanOrEqual, _ => Comparison.None, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/INamedTypeSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; public static class INamedTypeSymbolExtensions { private static readonly ImmutableArray ControllerTypes = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase, KnownType.System_Web_Mvc_Controller); private static readonly ImmutableArray ControllerAttributeTypes = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_ControllerAttribute); private static readonly ImmutableArray KnownTestClassAttributes = ImmutableArray.Create( // xUnit does not have have attributes to identity test classes KnownType.Microsoft_VisualStudio_TestTools_UnitTesting_TestClassAttribute, KnownType.NUnit_Framework_TestFixtureAttribute); private static readonly ImmutableArray NonControllerAttributeTypes = ImmutableArray.Create(KnownType.Microsoft_AspNetCore_Mvc_NonControllerAttribute); public static bool IsTopLevelProgram(this INamedTypeSymbol symbol) => TopLevelStatements.ProgramClassImplicitName.Contains(symbol.Name) && symbol.ContainingNamespace.IsGlobalNamespace && symbol.GetMembers(TopLevelStatements.MainMethodImplicitName).Any(); public static IEnumerable GetAllNamedTypes(this INamedTypeSymbol type) { if (type is null) { yield break; } yield return type; foreach (var nestedType in type.GetTypeMembers().SelectMany(GetAllNamedTypes)) { yield return nestedType; } } /// /// Whether the provided type symbol is a ASP.NET MVC controller. /// public static bool IsControllerType(this INamedTypeSymbol namedType) => namedType is not null && namedType.ContainingSymbol is not INamedTypeSymbol && (namedType.DerivesFromAny(ControllerTypes) || namedType.GetAttributes(ControllerAttributeTypes).Any()) && !namedType.GetAttributes(NonControllerAttributeTypes).Any(); /// /// Whether the provided type symbol is an ASP.NET Core API controller. /// Considers as API controllers also controllers deriving from ControllerBase but not Controller. /// public static bool IsCoreApiController(this INamedTypeSymbol namedType) => namedType.IsControllerType() && (namedType.GetAttributesWithInherited().Any(x => x.AttributeClass.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ApiControllerAttribute)) || (namedType.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_ControllerBase) && !namedType.DerivesFrom(KnownType.Microsoft_AspNetCore_Mvc_Controller))); /// /// Returns whether the class has an attribute that marks the class /// as an MSTest or NUnit test class (xUnit doesn't have any such attributes). /// public static bool IsTestClass(this INamedTypeSymbol classSymbol) => classSymbol.AnyAttributeDerivesFromAny(KnownTestClassAttributes); /// /// Returns whether the type is exported via MEF (Managed Extensibility Framework). /// Checks for [Export] attributes on the type itself or [InheritedExport] on base types/interfaces. /// Supports both MEF1 (System.ComponentModel.Composition) and MEF2 (System.Composition). /// public static bool IsMefExportedType(this INamedTypeSymbol typeSymbol) => typeSymbol is not null && (typeSymbol.AnyAttributeDerivesFrom(KnownType.System_ComponentModel_Composition_ExportAttribute) || typeSymbol.AnyAttributeDerivesFrom(KnownType.System_Composition_ExportAttribute) || typeSymbol.SelfBaseTypesAndInterfaces().Any(x => x.AnyAttributeDerivesFrom(KnownType.System_ComponentModel_Composition_InheritedExportAttribute))); /// /// Returns the type itself, all base types, and all implemented interfaces. /// This is useful for checking inherited attributes across the full type hierarchy. /// public static IEnumerable SelfBaseTypesAndInterfaces(this INamedTypeSymbol typeSymbol) => typeSymbol?.GetSelfAndBaseTypes().Union(typeSymbol.AllInterfaces) ?? []; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/INamespaceSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; internal static class INamespaceSymbolExtensions { /// /// Checks if the fits the . The format of is the same as in a directive. /// /// The namespace symbol to test. /// The name in the form System.Collections.Generic. /// Returns if the namespace symbol refers to the string given. public static bool Is(this INamespaceSymbol symbol, string name) { _ = name ?? throw new ArgumentNullException(nameof(name)); var ns = name.Split(['.'], StringSplitOptions.RemoveEmptyEntries); for (var i = ns.Length - 1; i >= 0; i--) { if (symbol is null || symbol.Name != ns[i]) { return false; } else { symbol = symbol.ContainingNamespace; } } return symbol?.IsGlobalNamespace is true; } public static IEnumerable GetAllNamedTypes(this INamespaceSymbol @namespace) { if (@namespace is null) { yield break; } foreach (var typeMember in @namespace.GetTypeMembers().SelectMany(x => x.GetAllNamedTypes())) { yield return typeMember; } foreach (var typeMember in @namespace.GetNamespaceMembers().SelectMany(GetAllNamedTypes)) { yield return typeMember; } } public static bool IsSameNamespace(this INamespaceSymbol namespace1, INamespaceSymbol namespace2) => (namespace1.IsGlobalNamespace && namespace2.IsGlobalNamespace) || (namespace1.Name.Equals(namespace2.Name) && namespace1.ContainingNamespace is not null && namespace2.ContainingNamespace is not null && namespace1.ContainingNamespace.IsSameNamespace(namespace2.ContainingNamespace)); public static bool IsSameOrAncestorOf(this INamespaceSymbol thisNamespace, INamespaceSymbol namespaceToCheck) => thisNamespace.IsSameNamespace(namespaceToCheck) || (namespaceToCheck.ContainingNamespace is not null && thisNamespace.IsSameOrAncestorOf(namespaceToCheck.ContainingNamespace)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/IParameterSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; public static class IParameterSymbolExtensions { public static bool IsType(this IParameterSymbol parameter, KnownType type) => parameter is not null && parameter.Type.Is(type); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/IPropertySymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; public static class IPropertySymbolExtensions { extension(IPropertySymbol property) { public bool IsExtension => property is { GetMethod.IsExtension: true } or { SetMethod.IsExtension: true }; public bool IsAnyAttributeInOverridingChain() => property.IsAnyAttributeInOverridingChain(x => x.OverriddenProperty); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/ISymbolExtensions.Roslyn.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.Shared.Extensions; // Copied from https://github.com/dotnet/roslyn/blob/ca66296efa86bd8078508fe7b38b91b415364f78/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs [ExcludeFromCodeCoverage] public static class ISymbolExtensions { /// /// If the is a method symbol, returns if the method's return type is "awaitable", but not if it's . /// If the is a type symbol, returns if that type is "awaitable". /// An "awaitable" is any type that exposes a GetAwaiter method which returns a valid "awaiter". This GetAwaiter method may be an instance method or an extension method. /// /// /// Copied from . /// SONAR: The code was simplified to improve performance at the cost of precision. /// public static bool IsAwaitableNonDynamic(this ISymbol? symbol) { var methodSymbol = symbol as IMethodSymbol; ITypeSymbol? typeSymbol = null; if (methodSymbol == null) { typeSymbol = symbol as ITypeSymbol; if (typeSymbol == null) { return false; } } else { if (methodSymbol.ReturnType == null) { return false; } } // otherwise: needs valid GetAwaiter // SONAR: Performance: LookupSymbols is slow. We use the less precise GetMembers instead: // Misses extension methods and method from base classes var container = typeSymbol ?? methodSymbol!.ReturnType.OriginalDefinition; var potentialGetAwaiters = container.GetMembers(WellKnownMemberNames.GetAwaiter); var getAwaiters = potentialGetAwaiters.OfType().Where(x => !x.Parameters.Any()); return getAwaiters.Any(VerifyGetAwaiter); } // Copied from https://github.com/dotnet/roslyn/blob/ca66296efa86bd8078508fe7b38b91b415364f78/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs#L611 private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter) { var returnType = getAwaiter.ReturnType; if (returnType == null) { return false; } // bool IsCompleted { get } if (!returnType.GetMembers().OfType().Any(p => p.Name == WellKnownMemberNames.IsCompleted && p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null)) { return false; } var methods = returnType.GetMembers().OfType(); // NOTE: (vladres) The current version of C# Spec, §7.7.7.3 'Runtime evaluation of await expressions', requires that // NOTE: the interface method INotifyCompletion.OnCompleted or ICriticalNotifyCompletion.UnsafeOnCompleted is invoked // NOTE: (rather than any OnCompleted method conforming to a certain pattern). // NOTE: Should this code be updated to match the spec? // void OnCompleted(Action) // Actions are delegates, so we'll just check for delegates. if (!methods.Any(x => x.Name == WellKnownMemberNames.OnCompleted && x.ReturnsVoid && x.Parameters is { Length: 1 } parameter && parameter[0] is { Type.TypeKind: TypeKind.Delegate })) return false; // void GetResult() || T GetResult() return methods.Any(m => m.Name == WellKnownMemberNames.GetResult && !m.Parameters.Any()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/ISymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; namespace SonarAnalyzer.Core.Semantics.Extensions; public static class ISymbolExtensions { public static bool HasAnyAttribute(this ISymbol symbol, ImmutableArray types) => symbol.GetAttributes(types).Any(); public static bool HasAttribute(this ISymbol symbol, KnownType type) => symbol.GetAttributes(type).Any(); public static SyntaxNode GetFirstSyntaxRef(this ISymbol symbol) => symbol?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); public static bool IsAutoProperty(this ISymbol symbol) => symbol.Kind == SymbolKind.Property && symbol.ContainingType.GetMembers().OfType().Any(x => symbol.Equals(x.AssociatedSymbol)); public static bool IsTopLevelMain(this ISymbol symbol) => symbol is IMethodSymbol { Name: TopLevelStatements.MainMethodImplicitName }; public static bool IsGlobalNamespace(this ISymbol symbol) => symbol is INamespaceSymbol { Name: "" }; public static bool IsInSameAssembly(this ISymbol symbol, ISymbol anotherSymbol) => symbol.ContainingAssembly.Equals(anotherSymbol.ContainingAssembly); public static bool HasNotNullAttribute(this ISymbol parameter) => parameter.GetAttributes() is { Length: > 0 } attributes && attributes.Any(IsNotNullAttribute); // https://github.com/dotnet/roslyn/blob/2a594fa2157a734a988f7b5dbac99484781599bd/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs#L93 [ExcludeFromCodeCoverage] public static ImmutableArray ExplicitOrImplicitInterfaceImplementations(this ISymbol symbol) { if (symbol.Kind is not SymbolKind.Method and not SymbolKind.Property and not SymbolKind.Event) { return ImmutableArray.Empty; } var containingType = symbol.ContainingType; var query = from iface in containingType.AllInterfaces from interfaceMember in iface.GetMembers() let impl = containingType.FindImplementationForInterfaceMember(interfaceMember) where symbol.Equals(impl) select interfaceMember; return query.ToImmutableArray(); } public static bool HasContainingType(this ISymbol method, KnownType containingType, bool checkDerivedTypes) => checkDerivedTypes ? method.ContainingType.DerivesOrImplements(containingType) : method.ContainingType.Is(containingType); public static bool IsInType(this ISymbol symbol, KnownType type) => symbol is not null && symbol.ContainingType.Is(type); public static bool IsInType(this ISymbol symbol, ITypeSymbol type) => symbol?.ContainingType is not null && symbol.ContainingType.Equals(type); public static bool IsInType(this ISymbol symbol, ImmutableArray types) => symbol is not null && symbol.ContainingType.IsAny(types); /// /// Returns all interface members this member implements. /// A single member can implement members from multiple interface. /// public static IEnumerable InterfaceMembers(this T symbol) where T : class, ISymbol => symbol switch { null => [], { } when !CanBeInterfaceMember(symbol) => [], _ => symbol.ContainingType .AllInterfaces .SelectMany(x => x.GetMembers()) .OfType() .Where(x => symbol.GetOverriddenMembersAndSelf().Any(m => m.Equals(m.ContainingType.FindImplementationForInterfaceMember(x)))), }; public static T GetOverriddenMember(this T symbol) where T : class, ISymbol => symbol is { IsOverride: true } ? symbol.Kind switch { SymbolKind.Method => (T)((IMethodSymbol)symbol).OverriddenMethod, SymbolKind.Property => (T)((IPropertySymbol)symbol).OverriddenProperty, SymbolKind.Event => (T)((IEventSymbol)symbol).OverriddenEvent, _ => throw new ArgumentException($"Only methods, properties and events can be overridden. {typeof(T).Name} was provided", nameof(symbol)) } : null; public static IEnumerable GetOverriddenMembersAndSelf(this T symbol) where T : class, ISymbol { yield return symbol; var overriden = symbol.GetOverriddenMember(); while (overriden is not null) { yield return overriden; overriden = overriden.GetOverriddenMember(); } } public static bool IsChangeable(this ISymbol symbol) => !symbol.IsAbstract && !symbol.IsVirtual && symbol.InterfaceMembers().IsEmpty() && symbol.GetOverriddenMember() is null; public static IEnumerable GetParameters(this ISymbol symbol) => symbol.Kind switch { SymbolKind.Method => ((IMethodSymbol)symbol).Parameters, SymbolKind.Property => ((IPropertySymbol)symbol).Parameters, _ => Enumerable.Empty() }; public static Accessibility GetEffectiveAccessibility(this ISymbol symbol) { if (symbol is null) { return Accessibility.NotApplicable; } var result = symbol.DeclaredAccessibility; if (result == Accessibility.Private) { return Accessibility.Private; } for (var container = symbol.ContainingType; container is not null; container = container.ContainingType) { if (container.DeclaredAccessibility == Accessibility.Private) { return Accessibility.Private; } if (container.DeclaredAccessibility == Accessibility.Internal) { result = Accessibility.Internal; } } return result; } public static bool IsPubliclyAccessible(this ISymbol symbol) => symbol.GetEffectiveAccessibility() is Accessibility.Public or Accessibility.Protected or Accessibility.ProtectedOrInternal; public static bool IsConstructor(this ISymbol symbol) => symbol.Kind == SymbolKind.Method && symbol.Name == ".ctor"; public static IEnumerable GetAttributes(this ISymbol symbol, KnownType attributeType) => symbol?.GetAttributes().Where(x => x.AttributeClass.Is(attributeType)) ?? []; public static IEnumerable GetAttributes(this ISymbol symbol, ImmutableArray attributeTypes) => symbol?.GetAttributes().Where(x => x.AttributeClass.IsAny(attributeTypes)) ?? []; /// /// Returns attributes for the symbol by also respecting . /// The returned is consistent with the results from . /// public static IEnumerable GetAttributesWithInherited(this ISymbol symbol) { foreach (var attribute in symbol.GetAttributes()) { yield return attribute; } var baseSymbol = BaseSymbol(symbol); while (baseSymbol is not null) { foreach (var attribute in baseSymbol.GetAttributes().Where(x => x.HasAttributeUsageInherited())) { yield return attribute; } baseSymbol = BaseSymbol(baseSymbol); } static ISymbol BaseSymbol(ISymbol symbol) => symbol switch { INamedTypeSymbol namedType => namedType.BaseType, IMethodSymbol { OriginalDefinition: { } originalDefinition } method when !method.Equals(originalDefinition) => BaseSymbol(originalDefinition), IMethodSymbol { OverriddenMethod: { } overridenMethod } => overridenMethod, // Support for other kinds of symbols needs to be implemented/tested as needed. A full list can be found here: // https://learn.microsoft.com/dotnet/api/system.attributetargets _ => null, }; } public static bool AnyAttributeDerivesFrom(this ISymbol symbol, KnownType attributeType) => symbol?.GetAttributes().Any(x => x.AttributeClass.DerivesFrom(attributeType)) ?? false; public static bool AnyAttributeDerivesFromAny(this ISymbol symbol, ImmutableArray attributeTypes) => symbol?.GetAttributes().Any(x => x.AttributeClass.DerivesFromAny(attributeTypes)) ?? false; public static bool AnyAttributeDerivesFromOrImplementsAny(this ISymbol symbol, ImmutableArray attributeTypesOrInterfaces) => symbol?.GetAttributes().Any(x => x.AttributeClass.DerivesOrImplementsAny(attributeTypesOrInterfaces)) ?? false; public static string GetClassification(this ISymbol symbol) => symbol switch { { Kind: SymbolKind.Alias } => "alias", { Kind: SymbolKind.ArrayType } => "array", { Kind: SymbolKind.Assembly } => "assembly", { Kind: SymbolKindEx.Discard } => "discard", { Kind: SymbolKind.DynamicType } => "dynamic", { Kind: SymbolKind.ErrorType } => "error", { Kind: SymbolKind.Event } => "event", { Kind: SymbolKindEx.FunctionPointerType } => "function pointer", { Kind: SymbolKind.Field } => "field", { Kind: SymbolKind.Label } => "label", { Kind: SymbolKind.Local } => "local", { Kind: SymbolKind.Namespace } => "namespace", { Kind: SymbolKind.NetModule } => "netmodule", { Kind: SymbolKind.PointerType } => "pointer", { Kind: SymbolKind.Preprocessing } => "preprocessing", { Kind: SymbolKind.Parameter } => "parameter", { Kind: SymbolKind.RangeVariable } => "range variable", { Kind: SymbolKind.Property } => "property", { Kind: SymbolKind.TypeParameter } => "type parameter", IMethodSymbol methodSymbol => methodSymbol switch { { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator or MethodKind.Conversion } => "operator", { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor or MethodKind.SharedConstructor } => "constructor", { MethodKind: MethodKind.Destructor } => "destructor", { MethodKind: MethodKind.PropertyGet } => "getter", { MethodKind: MethodKind.PropertySet } => "setter", { MethodKind: MethodKindEx.LocalFunction } => "local function", _ => "method", }, INamedTypeSymbol namedTypeSymbol => namedTypeSymbol switch { { TypeKind: TypeKind.Array } => "array", { TypeKind: TypeKind.Class } namedType => namedType.IsRecord() ? "record" : "class", { TypeKind: TypeKind.Dynamic } => "dynamic", { TypeKind: TypeKind.Delegate } => "delegate", { TypeKind: TypeKind.Enum } => "enum", { TypeKind: TypeKind.Error } => "error", { TypeKind: TypeKindEx.FunctionPointer } => "function pointer", { TypeKind: TypeKind.Interface } => "interface", { TypeKind: TypeKind.Module } => "module", { TypeKind: TypeKind.Pointer } => "pointer", { TypeKind: TypeKind.Struct or TypeKind.Structure } namedType => namedType.IsRecord() ? "record struct" : "struct", { TypeKind: TypeKind.Submission } => "submission", { TypeKind: TypeKind.TypeParameter } => "type parameter", { TypeKind: TypeKind.Unknown } => "unknown", #if DEBUG _ => throw new NotSupportedException($"symbol is of a not yet supported kind."), #else _ => "type", #endif }, #if DEBUG _ => throw new NotSupportedException($"symbol is of a not yet supported kind."), #else _ => "symbol", #endif }; public static bool IsSerializableMember(this ISymbol symbol) => symbol is IFieldSymbol or IPropertySymbol { SetMethod: not null } && symbol.ContainingType.GetAttributes().Any(x => x.AttributeClass.Is(KnownType.System_SerializableAttribute)) && !symbol.GetAttributes().Any(x => x.AttributeClass.Is(KnownType.System_NonSerializedAttribute)); public static bool IsAnyAttributeInOverridingChain(this TSymbol symbol, Func overriddenMember) where TSymbol : class, ISymbol { var currentSymbol = symbol; while (currentSymbol is not null) { if (currentSymbol.GetAttributes().Any()) { return true; } if (!currentSymbol.IsOverride) { return false; } currentSymbol = overriddenMember(currentSymbol); } return false; } /// /// Retrieves all parts of a symbol. For partial methods or properties, both the definition and implementation parts are included. For other symbols, the symbol itself is returned. /// public static IEnumerable AllPartialParts(this ISymbol symbol) { switch (symbol) { case IMethodSymbol method: yield return method; if (method.PartialImplementationPart is { } implementation) { yield return implementation; } else if (method.PartialDefinitionPart is { } definition) { yield return definition; } break; case IPropertySymbol property: yield return property; if (property.PartialImplementationPart() is { } propertyImplementation) { yield return propertyImplementation; } else if (property.PartialDefinitionPart() is { } propertyDefinition) { yield return propertyDefinition; } break; default: yield return symbol; break; } } private static bool CanBeInterfaceMember(ISymbol symbol) => symbol.Kind == SymbolKind.Method || symbol.Kind == SymbolKind.Property || symbol.Kind == SymbolKind.Event; // https://docs.microsoft.com/dotnet/api/microsoft.validatednotnullattribute // https://docs.microsoft.com/dotnet/csharp/language-reference/attributes/nullable-analysis#postconditions-maybenull-and-notnull // https://www.jetbrains.com/help/resharper/Reference__Code_Annotation_Attributes.html#NotNullAttribute private static bool IsNotNullAttribute(AttributeData attribute) => attribute.HasAnyName("ValidatedNotNullAttribute", "NotNullAttribute"); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/ITypeParameterSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; internal static class ITypeParameterSymbolExtensions { public static bool HasAnyConstraint(this ITypeParameterSymbol typeParameter) => typeParameter.HasConstructorConstraint || typeParameter.HasReferenceTypeConstraint || typeParameter.HasValueTypeConstraint || !typeParameter.ConstraintTypes.IsEmpty || typeParameter.HasUnmanagedTypeConstraint(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/ITypeSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; namespace SonarAnalyzer.Core.Semantics.Extensions; public static class ITypeSymbolExtensions { private static readonly PropertyInfo ITypeSymbolIsRecord = typeof(ITypeSymbol).GetProperty("IsRecord"); public static bool IsInterface(this ITypeSymbol self) => self is { TypeKind: TypeKind.Interface }; public static bool IsClass(this ITypeSymbol self) => self is { TypeKind: TypeKind.Class }; public static bool IsStruct(this ITypeSymbol self) => self switch { { TypeKind: TypeKind.Struct } => true, ITypeParameterSymbol { IsValueType: true } => true, _ => false, }; public static bool IsClassOrStruct(this ITypeSymbol self) => self.IsStruct() || self.IsClass(); public static bool IsNullableValueType(this ITypeSymbol self) => self.IsStruct() && self is { SpecialType: SpecialType.System_Nullable_T } or { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }; public static bool IsNonNullableValueType(this ITypeSymbol self) => self.IsStruct() && !self.IsNullableValueType(); public static bool IsEnum(this ITypeSymbol self) => self switch { { TypeKind: TypeKind.Enum } => true, ITypeParameterSymbol { HasReferenceTypeConstraint: false, ConstraintTypes: { IsEmpty: false } constraintTypes } => constraintTypes.Any(x => x.SpecialType == SpecialType.System_Enum), _ => false, }; public static bool CanBeNull(this ITypeSymbol self) => self is { IsReferenceType: true } || self.IsNullableValueType(); public static bool Is(this ITypeSymbol self, TypeKind typeKind) => self?.TypeKind == typeKind; public static bool Is(this ITypeSymbol typeSymbol, KnownType type) => typeSymbol is not null && type.Matches(typeSymbol); public static bool IsAny(this ITypeSymbol typeSymbol, params KnownType[] types) { if (typeSymbol is null) { return false; } // For is twice as fast as foreach on ImmutableArray so don't use Linq here for (var i = 0; i < types.Length; i++) { if (types[i].Matches(typeSymbol)) { return true; } } return false; } public static bool IsAny(this ITypeSymbol typeSymbol, ImmutableArray types) { if (typeSymbol is null) { return false; } // For is twice as fast as foreach on ImmutableArray so don't use Linq here for (var i = 0; i < types.Length; i++) { if (types[i].Matches(typeSymbol)) { return true; } } return false; } public static bool IsNullableOfAny(this ITypeSymbol type, ImmutableArray argumentTypes) => NullableTypeArgument(type).IsAny(argumentTypes); public static bool IsNullableOf(this ITypeSymbol type, KnownType typeArgument) => NullableTypeArgument(type).Is(typeArgument); public static bool IsNullableBoolean(this ITypeSymbol type) => type.IsNullableOf(KnownType.System_Boolean); public static bool Implements(this ITypeSymbol typeSymbol, KnownType type) => typeSymbol is not null && typeSymbol.AllInterfaces.Any(x => x.ConstructedFrom.Is(type)); public static bool ImplementsAny(this ITypeSymbol typeSymbol, ImmutableArray types) => typeSymbol is not null && typeSymbol.AllInterfaces.Any(x => x.ConstructedFrom.IsAny(types)); public static bool DerivesFrom(this ITypeSymbol typeSymbol, KnownType type) { var currentType = typeSymbol; while (currentType is not null) { if (currentType.Is(type)) { return true; } currentType = currentType.BaseType?.ConstructedFrom; } return false; } public static bool DerivesFrom(this ITypeSymbol typeSymbol, ITypeSymbol type) { var currentType = typeSymbol; while (currentType is not null) { if (currentType.Equals(type) || (currentType is INamedTypeSymbol { ConstructedFrom: { } constructedFrom } && constructedFrom.Equals(type))) { return true; } currentType = currentType.BaseType?.ConstructedFrom; } return false; } public static bool DerivesFromAny(this ITypeSymbol typeSymbol, ImmutableArray baseTypes) { var currentType = typeSymbol; while (currentType is not null) { if (currentType.IsAny(baseTypes)) { return true; } currentType = currentType.BaseType?.ConstructedFrom; } return false; } public static bool DerivesOrImplements(this ITypeSymbol type, KnownType baseType) => type.Implements(baseType) || type.DerivesFrom(baseType); public static bool DerivesOrImplements(this ITypeSymbol type, ITypeSymbol baseType) => type.Implements(baseType) || type.DerivesFrom(baseType); public static bool DerivesOrImplementsAny(this ITypeSymbol type, ImmutableArray baseTypes) => type.ImplementsAny(baseTypes) || type.DerivesFromAny(baseTypes); public static ITypeSymbol GetSymbolType(this ISymbol symbol) => symbol switch { ILocalSymbol x => x.Type, IFieldSymbol x => x.Type, IPropertySymbol x => x.Type, IParameterSymbol x => x.Type, IAliasSymbol x => x.Target as ITypeSymbol, IMethodSymbol { MethodKind: MethodKind.Constructor } x => x.ContainingType, IMethodSymbol x => x.ReturnType, ITypeSymbol x => x, _ => null, }; public static IEnumerable GetSelfAndBaseTypes(this ITypeSymbol type) { if (type is null) { yield break; } var currentType = type; while (currentType?.Kind == SymbolKind.NamedType) { yield return (INamedTypeSymbol)currentType; currentType = currentType.BaseType; } } public static bool IsRecord(this ITypeSymbol typeSymbol) => ITypeSymbolIsRecord?.GetValue(typeSymbol) is true; private static ITypeSymbol NullableTypeArgument(ITypeSymbol type) => type is INamedTypeSymbol namedType && namedType.OriginalDefinition.Is(KnownType.System_Nullable_T) ? namedType.TypeArguments[0] : null; private static bool Implements(this ITypeSymbol typeSymbol, ISymbol type) => typeSymbol is not null && typeSymbol.AllInterfaces.Any(x => type.IsDefinition ? x.OriginalDefinition.Equals(type) : x.Equals(type)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/KnownAssemblyExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; internal static class KnownAssemblyExtensions { internal static Func And(this Func @this, Func predicate) => x => @this(x) && predicate(x); internal static Func Or(this Func @this, Func predicate) => x => @this(x) || predicate(x); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/SemanticModelExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; public static class SemanticModelExtensions { public static bool IsExtensionMethod(this SemanticModel model, SyntaxNode expression) => model.GetSymbolInfo(expression).Symbol is IMethodSymbol memberSymbol && memberSymbol.IsExtensionMethod; public static SemanticModel SemanticModelOrDefault(this SyntaxTree tree, SemanticModel model) { // See https://github.com/dotnet/roslyn/issues/18730 if (tree == model.SyntaxTree) { return model; } return model.Compilation.ContainsSyntaxTree(tree) ? model.Compilation.GetSemanticModel(tree) : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/Extensions/SymbolInfoExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics.Extensions; public static class SymbolInfoExtensions { /// /// Returns the or if no symbol could be found the . /// public static IEnumerable AllSymbols(this SymbolInfo symbolInfo) => symbolInfo.Symbol is null ? symbolInfo.CandidateSymbols : new[] { symbolInfo.Symbol }; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/KnownAssembly.Predicates.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics; public sealed partial class KnownAssembly { private const StringComparison AssemblyNameComparison = StringComparison.OrdinalIgnoreCase; internal static class Predicates { internal static Func NameIs(string name) => x => x.Name.Equals(name, AssemblyNameComparison); internal static Func StartsWith(string name) => x => x.Name.StartsWith(name, AssemblyNameComparison); internal static Func EndsWith(string name) => x => x.Name.EndsWith(name, AssemblyNameComparison); internal static Func Contains(string name) => x => x.Name.IndexOf(name, 0, AssemblyNameComparison) >= 0; internal static Func VersionLowerThen(string version) => VersionLowerThen(Version.Parse(version)); internal static Func VersionLowerThen(Version version) => x => x.Version < version; internal static Func VersionGreaterOrEqual(string version) => VersionGreaterOrEqual(Version.Parse(version)); internal static Func VersionGreaterOrEqual(Version version) => x => x.Version >= version; internal static Func VersionBetween(string from, string to) => VersionBetween(Version.Parse(from), Version.Parse(to)); internal static Func VersionBetween(Version from, Version to) => x => x.Version >= from && x.Version <= to; internal static Func OptionalPublicKeyTokenIs(string key) => x => !x.HasPublicKey || PublicKeyEqualHex(x, key); internal static Func PublicKeyTokenIs(string key) => x => x.HasPublicKey && PublicKeyEqualHex(x, key); internal static Func PublicKeyTokenIsAny(params string[] keys) => x => x.HasPublicKey && Array.Exists(keys, key => PublicKeyEqualHex(x, key)); internal static Func NameAndPublicKeyIs(string name, string key) => NameIs(name).And(PublicKeyTokenIs(key)); private static bool PublicKeyEqualHex(AssemblyIdentity identity, string hexString) { var normalizedHexString = hexString.Replace("-", string.Empty); return ArraysEqual(identity.PublicKeyToken.ToArray(), normalizedHexString) || ArraysEqual(identity.PublicKey.ToArray(), normalizedHexString); static bool ArraysEqual(byte[] key, string hexString) => BitConverter.ToString(key).Replace("-", string.Empty).Equals(hexString, StringComparison.OrdinalIgnoreCase); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/KnownAssembly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static SonarAnalyzer.Core.Semantics.KnownAssembly.Predicates; namespace SonarAnalyzer.Core.Semantics; public sealed partial class KnownAssembly { private readonly Func, bool> predicate; public static KnownAssembly XUnit_Assert { get; } = new(And( NameIs("xunit.assert") .Or(NameIs("xunit").And(VersionLowerThen("2.0"))) .Or(NameIs("xunit.v3.assert")), PublicKeyTokenIs("8d05b1bb7a6fdb6c"))); /// /// Any MSTest framework either referenced via /// nuget.org/MicrosoftVisualStudioQualityToolsUnitTestFramework (MSTest V1) /// or nuget.org/MSTest.TestFramework (MSTest V2). /// public static KnownAssembly MSTest { get; } = new(And(NameIs("Microsoft.VisualStudio.QualityTools.UnitTestFramework").Or(NameIs("Microsoft.VisualStudio.TestPlatform.TestFramework")), PublicKeyTokenIs("b03f5f7f11d50a3a"))); public static KnownAssembly NFluent { get; } = new(NameIs("NFluent").And(OptionalPublicKeyTokenIs("18828b37b84b1437"))); public static KnownAssembly FluentAssertions { get; } = new(NameAndPublicKeyIs("FluentAssertions", "33f2691a05b67b6a")); public static KnownAssembly NSubstitute { get; } = new(NameAndPublicKeyIs("NSubstitute", "92dd2e9066daa5ca")); // Logging assemblies public static KnownAssembly MicrosoftExtensionsLoggingAbstractions { get; } = new(NameAndPublicKeyIs("Microsoft.Extensions.Logging.Abstractions", "adb9793829ddae60")); public static KnownAssembly Serilog { get; } = new(NameAndPublicKeyIs("Serilog", "24c2f752a8e58a10")); public static KnownAssembly MicrosoftAspNetCoreMvcCore { get; } = new(NameAndPublicKeyIs("Microsoft.AspNetCore.Mvc.Core", "adb9793829ddae60")); public static KnownAssembly SwashbuckleAspNetCoreSwagger { get; } = new(NameAndPublicKeyIs("Swashbuckle.AspNetCore.Swagger", "62657d7474907593")); public static KnownAssembly NLog { get; } = new(NameAndPublicKeyIs("NLog", "5120e14c03d0593c")); public static KnownAssembly Log4Net { get; } = new(NameIs("log4net").And(PublicKeyTokenIsAny("669e0ddf0bb1aa2a", "1b44e1d426115821"))); public static KnownAssembly CommonLoggingCore { get; } = new(NameAndPublicKeyIs("Common.Logging.Core", "af08829b84f0328e")); public static KnownAssembly CastleCore { get; } = new(NameAndPublicKeyIs("Castle.Core", "407dd0808d44fbdc")); internal KnownAssembly(Func predicate, params Func[] or) : this(predicate is null || Array.Exists(or, x => x is null) ? throw new ArgumentNullException(nameof(predicate), "All predicates must be non-null.") : x => x.Any(y => predicate(y) || Array.Exists(or, orPredicate => orPredicate(y)))) { } internal KnownAssembly(Func, bool> predicate) => this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); public bool IsReferencedBy(Compilation compilation) => predicate(compilation.ReferencedAssemblyNames); internal static Func And(Func left, Func right) => KnownAssemblyExtensions.And(left, right); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/KnownMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics; public static class KnownMethods { private const int NumberOfParamsForBinaryOperator = 2; /// /// List of partial names that are assumed to indicate an assertion method. /// public static readonly ImmutableArray AssertionMethodParts = ImmutableArray.Create( "ASSERT", "CHECK", "EXPECT", "MUST", "SHOULD", "VERIFY", "VALIDATE"); public static bool IsMainMethod(this IMethodSymbol methodSymbol) { // Based on Microsoft definition: https://msdn.microsoft.com/en-us/library/1y814bzs.aspx // Adding support for new async main: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main return methodSymbol is not null && methodSymbol.IsStatic && methodSymbol.Name.Equals("Main", StringComparison.OrdinalIgnoreCase) // VB.NET is case insensitive && HasMainParameters() && HasMainReturnType(); bool HasMainParameters() => methodSymbol.Parameters.Length == 0 || (methodSymbol.Parameters.Length == 1 && methodSymbol.Parameters[0].Type.Is(KnownType.System_String_Array)); bool HasMainReturnType() => methodSymbol.ReturnsVoid || methodSymbol.ReturnType.IsAny(KnownType.System_Int32, KnownType.System_Threading_Tasks_Task) || (methodSymbol.ReturnType.OriginalDefinition.Is(KnownType.System_Threading_Tasks_Task_T) && ((methodSymbol.ReturnType as INamedTypeSymbol)?.TypeArguments.FirstOrDefault().Is(KnownType.System_Int32) ?? false)); } public static bool IsObjectEquals(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == nameof(Equals) && (methodSymbol.IsOverride || methodSymbol.IsInType(KnownType.System_Object)) && methodSymbol.Parameters.Length == 1 && methodSymbol.Parameters[0].Type.Is(KnownType.System_Object) && methodSymbol.ReturnType.Is(KnownType.System_Boolean); public static bool IsStaticObjectEquals(this IMethodSymbol methodSymbol) { return methodSymbol is not null && !methodSymbol.IsOverride && methodSymbol.IsStatic && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == nameof(Equals) && methodSymbol.IsInType(KnownType.System_Object) && HasCorrectParameters() && methodSymbol.ReturnType.Is(KnownType.System_Boolean); bool HasCorrectParameters() => methodSymbol.Parameters.Length == 2 && methodSymbol.Parameters[0].Type.Is(KnownType.System_Object) && methodSymbol.Parameters[1].Type.Is(KnownType.System_Object); } public static bool IsObjectGetHashCode(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == nameof(GetHashCode) && (methodSymbol.IsOverride || methodSymbol.IsInType(KnownType.System_Object)) && methodSymbol.Parameters.Length == 0 && methodSymbol.ReturnType.Is(KnownType.System_Int32); public static bool IsObjectToString(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == nameof(ToString) && (methodSymbol.IsOverride || methodSymbol.IsInType(KnownType.System_Object)) && methodSymbol.Parameters.Length == 0 && methodSymbol.ReturnType.Is(KnownType.System_String); // The Dispose method is either coming from System.IDisposable for classes and records or declared manually for ref struct types: // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using#pattern-based-using public static bool IsIDisposableDispose(this IMethodSymbol methodSymbol) => methodSymbol is { IsStatic: false, Name: "Dispose" or "System.IDisposable.Dispose", Arity: 0, ReturnsVoid: true, Parameters.Length: 0 } && ((ContainingInterface(methodSymbol) is { } containingInterface && containingInterface.Is(KnownType.System_IDisposable)) // class/record implementing System.IDisposable || (methodSymbol.ContainingType is { IsValueType: true } && methodSymbol.ContainingType.IsRefLikeType())); // or a ref struct type public static bool IsIAsyncDisposableDisposeAsync(this IMethodSymbol methodSymbol) => methodSymbol is { IsStatic: false, Name: "DisposeAsync" or "System.IAsyncDisposable.DisposeAsync", Arity: 0, Parameters.Length: 0 } && methodSymbol.ReturnType.Is(KnownType.System_Threading_Tasks_ValueTask) && ContainingInterface(methodSymbol) is { } containingInterface && containingInterface.Is(KnownType.System_IAsyncDisposable); public static bool IsGetObjectData(this IMethodSymbol methodSymbol) { const string explicitName = "System.Runtime.Serialization.ISerializable.GetObjectData"; return methodSymbol is not null && (methodSymbol.Name == "GetObjectData" || methodSymbol.Name == explicitName) && methodSymbol.Parameters.Length == 2 && methodSymbol.Parameters[0].Type.Is(KnownType.System_Runtime_Serialization_SerializationInfo) && methodSymbol.Parameters[1].Type.Is(KnownType.System_Runtime_Serialization_StreamingContext) && methodSymbol.ReturnsVoid; } public static bool IsSerializationConstructor(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.MethodKind == MethodKind.Constructor && methodSymbol.Parameters.Length == 2 && methodSymbol.Parameters[0].Type.Is(KnownType.System_Runtime_Serialization_SerializationInfo) && methodSymbol.Parameters[1].Type.Is(KnownType.System_Runtime_Serialization_StreamingContext); public static bool IsArrayClone(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Name == nameof(Array.Clone) && methodSymbol.Parameters.Length == 0 && methodSymbol.ContainingType.Is(KnownType.System_Array); public static bool IsRecordPrintMembers(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.Ordinary, Name: "PrintMembers", ReturnType.SpecialType: SpecialType.System_Boolean, Parameters.Length: 1, } && methodSymbol.Parameters[0].Type.Is(KnownType.System_Text_StringBuilder) && methodSymbol.ContainingType.IsRecord(); public static bool IsGcSuppressFinalize(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.Name == nameof(GC.SuppressFinalize) && methodSymbol.Parameters.Length == 1 && methodSymbol.ContainingType.Is(KnownType.System_GC); public static bool IsDebugAssert(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.Name == nameof(Debug.Assert) && methodSymbol.ContainingType.Is(KnownType.System_Diagnostics_Debug); public static bool IsDiagnosticDebugMethod(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.ContainingType.Is(KnownType.System_Diagnostics_Debug); public static bool IsOperatorBinaryPlus(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Addition", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorBinaryMinus(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Subtraction", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorBinaryMultiply(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Multiply", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorBinaryDivide(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Division", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorBinaryModulus(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Modulus", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorEquals(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Equality", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsOperatorNotEquals(this IMethodSymbol methodSymbol) => methodSymbol is { MethodKind: MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator, Name: "op_Inequality", Parameters.Length: NumberOfParamsForBinaryOperator }; public static bool IsConsoleWriteLine(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.Name == nameof(Console.WriteLine) && methodSymbol.IsInType(KnownType.System_Console); public static bool IsConsoleWrite(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.Name == nameof(Console.Write) && methodSymbol.IsInType(KnownType.System_Console); public static bool IsEnumerableConcat(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.Concat), 2); public static bool IsEnumerableCount(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.Count), 1, 2); public static bool IsEnumerableExcept(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.Except), 2, 3); public static bool IsEnumerableIntersect(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.Intersect), 2, 3); public static bool IsEnumerableSequenceEqual(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.SequenceEqual), 2, 3); public static bool IsEnumerableToList(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.ToList), 1); public static bool IsEnumerableToArray(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.ToArray), 1); public static bool IsEnumerableUnion(this IMethodSymbol methodSymbol) => methodSymbol.IsEnumerableMethod(nameof(Enumerable.Union), 2, 3); public static bool IsListAddRange(this IMethodSymbol methodSymbol) => methodSymbol is not null && methodSymbol.Name == "AddRange" && methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Parameters.Length == 1 && methodSymbol.ContainingType.ConstructedFrom.Is(KnownType.System_Collections_Generic_List_T); public static bool IsEventHandler(this IMethodSymbol methodSymbol) => methodSymbol is { Parameters.Length: 2 } && (// Inheritance from EventArgs is not enough for UWP or Xamarin as it uses other kind of event args (e.g. ILeavingBackgroundEventArgs) methodSymbol.Parameters[1].Type.Name.EndsWith("EventArgs", StringComparison.Ordinal) || methodSymbol.Parameters[1].Type.DerivesFrom(KnownType.System_EventArgs)) && (methodSymbol.ReturnsVoid // The ResolveEventHandler violates the https://learn.microsoft.com/en-us/dotnet/csharp/event-pattern#event-delegate-signatures // The ResolveEventHandler dates back to .Net1.1, is present in most runtimes and needs to be supported as an exception to the rule // https://github.com/SonarSource/sonar-dotnet/issues/8371 // https://learn.microsoft.com/dotnet/api/system.resolveeventhandler || methodSymbol.ReturnType.Is(KnownType.System_Reflection_Assembly)); private static bool IsEnumerableMethod(this IMethodSymbol methodSymbol, string methodName, params int[] parametersCount) => methodSymbol is not null && methodSymbol.Name == methodName && Array.Exists(parametersCount, methodSymbol.HasExactlyNParameters) && methodSymbol.ContainingType.Is(KnownType.System_Linq_Enumerable); private static bool HasExactlyNParameters(this IMethodSymbol methodSymbol, int parametersCount) => (methodSymbol.MethodKind == MethodKind.Ordinary && methodSymbol.Parameters.Length == parametersCount) || (methodSymbol.MethodKind == MethodKind.ReducedExtension && methodSymbol.Parameters.Length == parametersCount - 1); private static INamedTypeSymbol ContainingInterface(IMethodSymbol symbol) { if (symbol.InterfaceMembers().FirstOrDefault() is { } interfaceMember) { return interfaceMember.ContainingType; } else if (symbol.ContainingType.IsInterface()) { return symbol.ContainingType; } else { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/KnownType.Implementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.Core.Semantics; [DebuggerDisplay("{DebuggerDisplay}")] public sealed partial class KnownType { private readonly string[] parts; private readonly string[] genericParameters; private readonly int parentClassCount; public string TypeName { get; } public string FullName { get; } public bool IsArray { get; init; } public IReadOnlyList GenericParameters => genericParameters; public string MetadataName => $"{FullName}{(GenericParameters.Any() ? $"`{GenericParameters.Count}" : string.Empty)}"; internal string DebuggerDisplay { get { var sb = new StringBuilder(FullName); if (genericParameters.Length > 0) { sb.Append('<').Append(genericParameters.JoinStr(", ")).Append('>'); } if (IsArray) { sb.Append("[]"); } return sb.ToString(); } } public KnownType(string fullName, params string[] genericParameters) { parts = fullName.Split('.', '+'); parentClassCount = fullName.Count(x => x == '+'); if (parentClassCount > 0 && fullName.LastIndexOf('.') > fullName.LastIndexOf('+')) { throw new ArgumentException($"Invalid type name '{fullName}'. It should not contain '.' after the nested type '+'."); } this.genericParameters = genericParameters; FullName = fullName; TypeName = parts[parts.Length - 1]; } public bool Matches(ITypeSymbol symbol) => IsMatch(symbol) || IsMatch(symbol.OriginalDefinition); private bool IsMatch(ITypeSymbol symbol) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); if (IsArray) { if (symbol is IArrayTypeSymbol array) { symbol = array.ElementType; } else { return false; } } return symbol.Name == TypeName && OuterClassMatches(symbol) && NamespaceMatches(symbol) && GenericParametersMatch(symbol); } private bool GenericParametersMatch(ISymbol symbol) => symbol is INamedTypeSymbol namedType ? namedType.TypeParameters.Select(x => x.Name).SequenceEqual(genericParameters) : genericParameters.Length == 0; private bool OuterClassMatches(ISymbol symbol) { var index = parts.Length - 1; while (symbol.ContainingType is not null && index > 0) { index--; // First visit skips the TypeName itself symbol = symbol.ContainingType; if (symbol.Name != parts[index]) { return false; } } return index == parts.Length - 1 - parentClassCount; } private bool NamespaceMatches(ISymbol symbol) { var currentNamespace = symbol.ContainingNamespace; var index = parts.Length - parentClassCount - 2; while (currentNamespace is not null && !string.IsNullOrEmpty(currentNamespace.Name) && index >= 0) { if (currentNamespace.Name != parts[index]) { return false; } currentNamespace = currentNamespace.ContainingNamespace; index--; } return index == -1 && string.IsNullOrEmpty(currentNamespace?.Name); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/KnownType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics; public sealed partial class KnownType { #pragma warning disable S103 // Lines should not be too long #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore #pragma warning disable SA1311 // Static readonly fields should begin with upper-case letter #pragma warning disable SA1307 // Field 'log4net_Config_XmlConfigurator' should begin with upper-case letter #pragma warning disable SA1304 // Non-private readonly fields should begin with upper-case letter #pragma warning disable T0016 // Empty lines between multiline declarations public static readonly KnownType Azure_Messaging_ServiceBus_Administration_ServiceBusAdministrationClient = new("Azure.Messaging.ServiceBus.Administration.ServiceBusAdministrationClient"); public static readonly KnownType Azure_Messaging_ServiceBus_ServiceBusClient = new("Azure.Messaging.ServiceBus.ServiceBusClient"); public static readonly KnownType Azure_Storage_Blobs_BlobServiceClient = new("Azure.Storage.Blobs.BlobServiceClient"); public static readonly KnownType Azure_Storage_Queues_QueueServiceClient = new("Azure.Storage.Queues.QueueServiceClient"); public static readonly KnownType Azure_Storage_Files_Shares_ShareServiceClient = new("Azure.Storage.Files.Shares.ShareServiceClient"); public static readonly KnownType Azure_Storage_Files_DataLake_DataLakeServiceClient = new("Azure.Storage.Files.DataLake.DataLakeServiceClient"); public static readonly KnownType Azure_ResourceManager_ArmClient = new("Azure.ResourceManager.ArmClient"); public static readonly KnownType Castle_Core_Logging_ILogger = new("Castle.Core.Logging.ILogger"); public static readonly KnownType Common_Logging_ILog = new("Common.Logging.ILog"); public static readonly KnownType Dapper_CommandDefinition = new("Dapper.CommandDefinition"); public static readonly KnownType Dapper_DynamicParameters = new("Dapper.DynamicParameters"); public static readonly KnownType Dapper_SqlMapper = new("Dapper.SqlMapper"); public static readonly KnownType FluentAssertions_AssertionExtensions = new("FluentAssertions.AssertionExtensions"); public static readonly KnownType FluentAssertions_Execution_AssertionScope = new("FluentAssertions.Execution.AssertionScope"); public static readonly KnownType FluentAssertions_Primitives_ReferenceTypeAssertions = new("FluentAssertions.Primitives.ReferenceTypeAssertions", "TSubject", "TAssertions"); public static readonly KnownType FluentValidation_IValidator = new("FluentValidation.IValidator"); public static readonly KnownType FluentValidation_IValidator_T = new("FluentValidation.IValidator", "T"); public static readonly KnownType FsCheck_NUnit_PropertyAttribute = new("FsCheck.NUnit.PropertyAttribute"); public static readonly KnownType FsCheck_Xunit_PropertyAttribute = new("FsCheck.Xunit.PropertyAttribute"); public static readonly KnownType JWT_Builder_JwtBuilder = new("JWT.Builder.JwtBuilder"); public static readonly KnownType JWT_IJwtDecoder = new("JWT.IJwtDecoder"); public static readonly KnownType JWT_JwtDecoderExtensions = new("JWT.JwtDecoderExtensions"); public static readonly KnownType log4net_Config_BasicConfigurator = new("log4net.Config.BasicConfigurator"); public static readonly KnownType log4net_Config_DOMConfigurator = new("log4net.Config.DOMConfigurator"); public static readonly KnownType log4net_Config_XmlConfigurator = new("log4net.Config.XmlConfigurator"); public static readonly KnownType log4net_Core_ILogger = new("log4net.Core.ILogger"); public static readonly KnownType log4net_ILog = new("log4net.ILog"); public static readonly KnownType log4net_LogManager = new("log4net.LogManager"); public static readonly KnownType log4net_Util_ILogExtensions = new("log4net.Util.ILogExtensions"); public static readonly KnownType Microsoft_AspNet_Identity_PasswordHasherOptions = new("Microsoft.AspNet.Identity.PasswordHasherOptions"); public static readonly KnownType Microsoft_AspNet_SignalR_Hub = new("Microsoft.AspNet.SignalR.Hub"); public static readonly KnownType Microsoft_AspNetCore_Builder_DeveloperExceptionPageExtensions = new("Microsoft.AspNetCore.Builder.DeveloperExceptionPageExtensions"); public static readonly KnownType Microsoft_AspNetCore_Builder_DatabaseErrorPageExtensions = new("Microsoft.AspNetCore.Builder.DatabaseErrorPageExtensions"); public static readonly KnownType Microsoft_AspNetCore_Components_Forms_IBrowserFile = new("Microsoft.AspNetCore.Components.Forms.IBrowserFile"); public static readonly KnownType Microsoft_AspNetCore_Components_Forms_InputFileChangeEventArgs = new("Microsoft.AspNetCore.Components.Forms.InputFileChangeEventArgs"); public static readonly KnownType Microsoft_AspNetCore_Components_ParameterAttribute = new("Microsoft.AspNetCore.Components.ParameterAttribute"); public static readonly KnownType Microsoft_AspNetCore_Components_Rendering_RenderTreeBuilder = new("Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder"); public static readonly KnownType Microsoft_AspNetCore_Components_RouteAttribute = new("Microsoft.AspNetCore.Components.RouteAttribute"); public static readonly KnownType Microsoft_AspNetCore_Components_SupplyParameterFromQueryAttribute = new("Microsoft.AspNetCore.Components.SupplyParameterFromQueryAttribute"); public static readonly KnownType Microsoft_AspNetCore_Cors_Infrastructure_CorsPolicyBuilder = new("Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder"); public static readonly KnownType Microsoft_AspNetCore_Cryptography_KeyDerivation_KeyDerivation = new("Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation"); public static readonly KnownType Microsoft_AspNetCore_Hosting_HostingEnvironmentExtensions = new("Microsoft.AspNetCore.Hosting.HostingEnvironmentExtensions"); public static readonly KnownType Microsoft_AspNetCore_Hosting_WebHostBuilderExtensions = new("Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions"); public static readonly KnownType Microsoft_AspNetCore_Http_CookieOptions = new("Microsoft.AspNetCore.Http.CookieOptions"); public static readonly KnownType Microsoft_AspNetCore_Http_HeaderDictionaryExtensions = new("Microsoft.AspNetCore.Http.HeaderDictionaryExtensions"); public static readonly KnownType Microsoft_AspNetCore_Http_HttpResponse = new("Microsoft.AspNetCore.Http.HttpResponse"); public static readonly KnownType Microsoft_AspNetCore_Http_IFormCollection = new("Microsoft.AspNetCore.Http.IFormCollection"); public static readonly KnownType Microsoft_AspNetCore_Http_IFormFile = new("Microsoft.AspNetCore.Http.IFormFile"); public static readonly KnownType Microsoft_AspNetCore_Http_IFormFileCollection = new("Microsoft.AspNetCore.Http.IFormFileCollection"); public static readonly KnownType Microsoft_AspNetCore_Http_IHeaderDictionary = new("Microsoft.AspNetCore.Http.IHeaderDictionary"); public static readonly KnownType Microsoft_AspNetCore_Http_IQueryCollection = new("Microsoft.AspNetCore.Http.IQueryCollection"); public static readonly KnownType Microsoft_AspNetCore_Http_IRequestCookieCollection = new("Microsoft.AspNetCore.Http.IRequestCookieCollection"); public static readonly KnownType Microsoft_AspNetCore_Http_IResponseCookies = new("Microsoft.AspNetCore.Http.IResponseCookies"); public static readonly KnownType Microsoft_AspNetCore_Http_IResult = new("Microsoft.AspNetCore.Http.IResult"); public static readonly KnownType Microsoft_AspNetCore_Http_Results = new("Microsoft.AspNetCore.Http.Results"); public static readonly KnownType Microsoft_AspNetCore_Identity_PasswordHasherOptions = new("Microsoft.AspNetCore.Identity.PasswordHasherOptions"); public static readonly KnownType Microsoft_AspNetCore_Mvc_AcceptVerbsAttribute = new("Microsoft.AspNetCore.Mvc.AcceptVerbsAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ApiControllerAttribute = new("Microsoft.AspNetCore.Mvc.ApiControllerAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ApiConventionMethodAttribute = new("Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ApiConventionTypeAttribute = new("Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ApiExplorerSettingsAttribute = new("Microsoft.AspNetCore.Mvc.ApiExplorerSettingsAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Controller = new("Microsoft.AspNetCore.Mvc.Controller"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ControllerBase = new("Microsoft.AspNetCore.Mvc.ControllerBase"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ControllerAttribute = new("Microsoft.AspNetCore.Mvc.ControllerAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_DisableRequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.DisableRequestSizeLimitAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Filters_ActionFilterAttribute = new("Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Filters_IActionFilter = new("Microsoft.AspNetCore.Mvc.Filters.IActionFilter"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Filters_IAsyncActionFilter = new("Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter"); public static readonly KnownType Microsoft_AspNetCore_Mvc_FromServicesAttribute = new("Microsoft.AspNetCore.Mvc.FromServicesAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpDeleteAttribute = new("Microsoft.AspNetCore.Mvc.HttpDeleteAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpGetAttribute = new("Microsoft.AspNetCore.Mvc.HttpGetAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpHeadAttribute = new("Microsoft.AspNetCore.Mvc.HttpHeadAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpOptionsAttribute = new("Microsoft.AspNetCore.Mvc.HttpOptionsAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpPatchAttribute = new("Microsoft.AspNetCore.Mvc.HttpPatchAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpPostAttribute = new("Microsoft.AspNetCore.Mvc.HttpPostAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_HttpPutAttribute = new("Microsoft.AspNetCore.Mvc.HttpPutAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_IActionResult = new("Microsoft.AspNetCore.Mvc.IActionResult"); public static readonly KnownType Microsoft_AspNetCore_Mvc_IgnoreAntiforgeryTokenAttribute = new("Microsoft.AspNetCore.Mvc.IgnoreAntiforgeryTokenAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Infrastructure_ActionResultObjectValueAttribute = new("Microsoft.AspNetCore.Mvc.Infrastructure.ActionResultObjectValueAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ModelBinding_BindNeverAttribute = new("Microsoft.AspNetCore.Mvc.ModelBinding.BindNeverAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ModelBinding_BindRequiredAttribute = new("Microsoft.AspNetCore.Mvc.ModelBinding.BindRequiredAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ModelBinding_ModelStateDictionary = new("Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ModelBinding_Validation_ValidateNeverAttribute = new("Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidateNeverAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_NonActionAttribute = new("Microsoft.AspNetCore.Mvc.NonActionAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_NonControllerAttribute = new("Microsoft.AspNetCore.Mvc.NonControllerAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ObjectResult = new("Microsoft.AspNetCore.Mvc.ObjectResult"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ProducesAttribute = new("Microsoft.AspNetCore.Mvc.ProducesAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ProducesAttribute_T = new("Microsoft.AspNetCore.Mvc.ProducesAttribute", "T"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ProducesResponseTypeAttribute = new("Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_ProducesResponseTypeAttribute_T = new("Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute", "T"); public static readonly KnownType Microsoft_AspNetCore_Mvc_RazorPages_PageModel = new("Microsoft.AspNetCore.Mvc.RazorPages.PageModel"); public static readonly KnownType Microsoft_AspNetCore_Mvc_RequestFormLimitsAttribute = new("Microsoft.AspNetCore.Mvc.RequestFormLimitsAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_RequestSizeLimitAttribute = new("Microsoft.AspNetCore.Mvc.RequestSizeLimitAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_RouteAttribute = new("Microsoft.AspNetCore.Mvc.RouteAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Routing_HttpMethodAttribute = new("Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute"); public static readonly KnownType Microsoft_AspNetCore_Mvc_Routing_IRouteTemplateProvider = new("Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"); public static readonly KnownType Microsoft_AspNetCore_Razor_Hosting_RazorCompiledItemAttribute = new("Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItemAttribute"); public static readonly KnownType Microsoft_AspNetCore_Routing_RouteValueDictionary = new("Microsoft.AspNetCore.Routing.RouteValueDictionary"); public static readonly KnownType Microsoft_Azure_Cosmos_CosmosClient = new("Microsoft.Azure.Cosmos.CosmosClient"); public static readonly KnownType Microsoft_Azure_Cosmos_Container = new("Microsoft.Azure.Cosmos.Container"); public static readonly KnownType Microsoft_Azure_Cosmos_QueryDefinition = new("Microsoft.Azure.Cosmos.QueryDefinition"); public static readonly KnownType Microsoft_Azure_Documents_Client_DocumentClient = new("Microsoft.Azure.Documents.Client.DocumentClient"); public static readonly KnownType Microsoft_Azure_ServiceBus_Management_ManagementClient = new("Microsoft.Azure.ServiceBus.Management.ManagementClient"); public static readonly KnownType Microsoft_Azure_ServiceBus_QueueClient = new("Microsoft.Azure.ServiceBus.QueueClient"); public static readonly KnownType Microsoft_Azure_ServiceBus_SessionClient = new("Microsoft.Azure.ServiceBus.SessionClient"); public static readonly KnownType Microsoft_Azure_ServiceBus_SubscriptionClient = new("Microsoft.Azure.ServiceBus.SubscriptionClient"); public static readonly KnownType Microsoft_Azure_ServiceBus_TopicClient = new("Microsoft.Azure.ServiceBus.TopicClient"); public static readonly KnownType Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableEntityClient = new("Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableEntityClient"); public static readonly KnownType Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableEntityContext = new("Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableEntityContext"); public static readonly KnownType Microsoft_Azure_WebJobs_Extensions_DurableTask_IDurableOrchestrationContext = new("Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableOrchestrationContext"); public static readonly KnownType Microsoft_Azure_WebJobs_FunctionNameAttribute = new("Microsoft.Azure.WebJobs.FunctionNameAttribute"); public static readonly KnownType Microsoft_CodeAnalysis_IMethodSymbol = new("Microsoft.CodeAnalysis.IMethodSymbol"); public static readonly KnownType Microsoft_Data_Sqlite_SqliteCommand = new("Microsoft.Data.Sqlite.SqliteCommand"); public static readonly KnownType Microsoft_EntityFramework_DbContext = new("System.Data.Entity.DbContext"); public static readonly KnownType Microsoft_EntityFrameworkCore_DbContext = new("Microsoft.EntityFrameworkCore.DbContext"); public static readonly KnownType Microsoft_EntityFrameworkCore_DbContextOptionsBuilder = new("Microsoft.EntityFrameworkCore.DbContextOptionsBuilder"); public static readonly KnownType Microsoft_EntityFrameworkCore_DbSet_TEntity = new("Microsoft.EntityFrameworkCore.DbSet", "TEntity"); public static readonly KnownType Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions = new("Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_IDbContextFactory_TContext = new("Microsoft.EntityFrameworkCore.IDbContextFactory", "TContext"); public static readonly KnownType Microsoft_EntityFrameworkCore_Migrations_Migration = new("Microsoft.EntityFrameworkCore.Migrations.Migration"); public static readonly KnownType Microsoft_EntityFrameworkCore_MySQLDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_NpgsqlDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_NpgsqlDbContextOptionsBuilderExtensions = new("Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsBuilderExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_OracleDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.OracleDbContextOptionsExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_RawSqlString = new("Microsoft.EntityFrameworkCore.RawSqlString"); public static readonly KnownType Microsoft_EntityFrameworkCore_RelationalDatabaseFacadeExtensions = new("Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_RelationalQueryableExtensions = new("Microsoft.EntityFrameworkCore.RelationalQueryableExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_SqliteDbContextOptionsBuilderExtensions = new("Microsoft.EntityFrameworkCore.SqliteDbContextOptionsBuilderExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_SqlServerDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.SqlServerDbContextOptionsExtensions"); public static readonly KnownType Microsoft_Extensions_DependencyInjection_LoggingServiceCollectionExtensions = new("Microsoft.Extensions.DependencyInjection.LoggingServiceCollectionExtensions"); public static readonly KnownType Microsoft_Extensions_Hosting_HostEnvironmentEnvExtensions = new("Microsoft.Extensions.Hosting.HostEnvironmentEnvExtensions"); public static readonly KnownType Microsoft_Extensions_Configuration_IConfiguration = new("Microsoft.Extensions.Configuration.IConfiguration"); public static readonly KnownType Microsoft_Extensions_Logging_AzureAppServicesLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.AzureAppServicesLoggerFactoryExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_ConsoleLoggerExtensions = new("Microsoft.Extensions.Logging.ConsoleLoggerExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_DebugLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.DebugLoggerFactoryExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_EventLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.EventLoggerFactoryExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_EventSourceLoggerFactoryExtensions = new("Microsoft.Extensions.Logging.EventSourceLoggerFactoryExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_EventId = new("Microsoft.Extensions.Logging.EventId"); public static readonly KnownType Microsoft_Extensions_Logging_ILogger = new("Microsoft.Extensions.Logging.ILogger"); public static readonly KnownType Microsoft_Extensions_Logging_ILogger_TCategoryName = new("Microsoft.Extensions.Logging.ILogger", "TCategoryName"); public static readonly KnownType Microsoft_Extensions_Logging_ILoggerFactory = new("Microsoft.Extensions.Logging.ILoggerFactory"); public static readonly KnownType Microsoft_Extensions_Logging_LoggerExtensions = new("Microsoft.Extensions.Logging.LoggerExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_LoggerFactoryExtensions = new("Microsoft.Extensions.Logging.LoggerFactoryExtensions"); public static readonly KnownType Microsoft_Extensions_Logging_LogLevel = new("Microsoft.Extensions.Logging.LogLevel"); public static readonly KnownType Microsoft_Extensions_Primitives_StringValues = new("Microsoft.Extensions.Primitives.StringValues"); public static readonly KnownType Microsoft_IdentityModel_Tokens_SymmetricSecurityKey = new("Microsoft.IdentityModel.Tokens.SymmetricSecurityKey"); public static readonly KnownType Microsoft_Net_Http_Headers_HeaderNames = new("Microsoft.Net.Http.Headers.HeaderNames"); public static readonly KnownType Microsoft_JSInterop_JSInvokable = new("Microsoft.JSInterop.JSInvokableAttribute"); public static readonly KnownType Microsoft_VisualBasic_Information = new("Microsoft.VisualBasic.Information"); public static readonly KnownType Microsoft_VisualBasic_Interaction = new("Microsoft.VisualBasic.Interaction"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_Assert = new("Microsoft.VisualStudio.TestTools.UnitTesting.Assert"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_AssertFailedException = new("Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_ExpectedExceptionBaseAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.ExpectedExceptionBaseAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_ExpectedExceptionAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.ExpectedExceptionAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_IgnoreAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.IgnoreAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_TestClassAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_TestMethodAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_DataTestMethodAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.DataTestMethodAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_WorkItemAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.WorkItemAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyCleanupAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyCleanupAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_AssemblyInitializeAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyInitializeAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_ClassCleanupAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_ClassInitializeAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_TestCleanupAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute"); public static readonly KnownType Microsoft_VisualStudio_TestTools_UnitTesting_TestInitializeAttribute = new("Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute"); public static readonly KnownType Microsoft_Web_XmlTransform_XmlFileInfoDocument = new("Microsoft.Web.XmlTransform.XmlFileInfoDocument"); public static readonly KnownType Microsoft_Web_XmlTransform_XmlTransformableDocument = new("Microsoft.Web.XmlTransform.XmlTransformableDocument"); public static readonly KnownType MongoDB_Driver_IMongoCollectionExtensions = new("MongoDB.Driver.IMongoCollectionExtensions"); public static readonly KnownType Mono_Data_Sqlite_SqliteCommand = new("Mono.Data.Sqlite.SqliteCommand"); public static readonly KnownType Mono_Data_Sqlite_SqliteDataAdapter = new("Mono.Data.Sqlite.SqliteDataAdapter"); public static readonly KnownType Mono_Unix_FileAccessPermissions = new("Mono.Unix.FileAccessPermissions"); public static readonly KnownType MySql_Data_MySqlClient_MySqlDataAdapter = new("MySql.Data.MySqlClient.MySqlDataAdapter"); public static readonly KnownType MySql_Data_MySqlClient_MySqlCommand = new("MySql.Data.MySqlClient.MySqlCommand"); public static readonly KnownType MySql_Data_MySqlClient_MySqlHelper = new("MySql.Data.MySqlClient.MySqlHelper"); public static readonly KnownType MySql_Data_MySqlClient_MySqlScript = new("MySql.Data.MySqlClient.MySqlScript"); public static readonly KnownType Nancy_Cookies_NancyCookie = new("Nancy.Cookies.NancyCookie"); public static readonly KnownType Nancy_NancyModule = new("Nancy.NancyModule"); public static readonly KnownType NFluent_Check = new("NFluent.Check"); public static readonly KnownType NFluent_FluentCheckException = new("NFluent.FluentCheckException"); public static readonly KnownType NFluent_Kernel_FluentCheckException = new("NFluent.Kernel.FluentCheckException"); public static readonly KnownType NHibernate_Cfg_Loquacious_NamedQueryDefinitionBuilder = new("NHibernate.Cfg.Loquacious.NamedQueryDefinitionBuilder"); public static readonly KnownType NHibernate_Engine_NamedQueryDefinition = new("NHibernate.Engine.NamedQueryDefinition"); public static readonly KnownType NHibernate_Engine_NamedSQLQueryDefinition = new("NHibernate.Engine.NamedSQLQueryDefinition"); public static readonly KnownType NHibernate_ISession = new("NHibernate.ISession"); public static readonly KnownType NHibernate_Impl_AbstractSessionImpl = new("NHibernate.Impl.AbstractSessionImpl"); public static readonly KnownType NHibernate_Impl_QueryImpl = new("NHibernate.Impl.QueryImpl"); public static readonly KnownType NLog_ILogger = new("NLog.ILogger"); public static readonly KnownType NLog_ILoggerBase = new("NLog.ILoggerBase"); public static readonly KnownType NLog_ILoggerExtensions = new("NLog.ILoggerExtensions"); public static readonly KnownType NLog_LogLevel = new("NLog.LogLevel"); public static readonly KnownType NLog_LogFactory = new("NLog.LogFactory"); public static readonly KnownType NLog_LogManager = new("NLog.LogManager"); public static readonly KnownType NLog_Logger = new("NLog.Logger"); public static readonly KnownType Newtonsoft_Json_JsonPropertyAttribute = new("Newtonsoft.Json.JsonPropertyAttribute"); public static readonly KnownType Newtonsoft_Json_JsonRequiredAttribute = new("Newtonsoft.Json.JsonRequiredAttribute"); public static readonly KnownType Newtonsoft_Json_JsonIgnoreAttribute = new("Newtonsoft.Json.JsonIgnoreAttribute"); public static readonly KnownType NUnit_Framework_Assert = new("NUnit.Framework.Assert"); public static readonly KnownType NUnit_Framework_AssertionException = new("NUnit.Framework.AssertionException"); public static readonly KnownType NUnit_Framework_Legacy_ClassicAssert = new("NUnit.Framework.Legacy.ClassicAssert"); public static readonly KnownType NUnit_Framework_ExpectedExceptionAttribute = new("NUnit.Framework.ExpectedExceptionAttribute"); public static readonly KnownType NUnit_Framework_IgnoreAttribute = new("NUnit.Framework.IgnoreAttribute"); public static readonly KnownType NUnit_Framework_ITestBuilderInterface = new("NUnit.Framework.Interfaces.ITestBuilder"); public static readonly KnownType NUnit_Framework_TestAttribute = new("NUnit.Framework.TestAttribute"); public static readonly KnownType NUnit_Framework_TestCaseAttribute = new("NUnit.Framework.TestCaseAttribute"); public static readonly KnownType NUnit_Framework_TestCaseSourceAttribute = new("NUnit.Framework.TestCaseSourceAttribute"); public static readonly KnownType NUnit_Framework_TestFixtureAttribute = new("NUnit.Framework.TestFixtureAttribute"); public static readonly KnownType NUnit_Framework_TheoryAttribute = new("NUnit.Framework.TheoryAttribute"); public static readonly KnownType Oracle_ManagedDataAccess_Client_OracleCommand = new("Oracle.ManagedDataAccess.Client.OracleCommand"); public static readonly KnownType Oracle_ManagedDataAccess_Client_OracleDataAdapter = new("Oracle.ManagedDataAccess.Client.OracleDataAdapter"); public static readonly KnownType Org_BouncyCastle_Asn1_Nist_NistNamedCurves = new("Org.BouncyCastle.Asn1.Nist.NistNamedCurves"); public static readonly KnownType Org_BouncyCastle_Asn1_Sec_SecNamedCurves = new("Org.BouncyCastle.Asn1.Sec.SecNamedCurves"); public static readonly KnownType Org_BouncyCastle_Asn1_TeleTrust_TeleTrusTNamedCurves = new("Org.BouncyCastle.Asn1.TeleTrust.TeleTrusTNamedCurves"); public static readonly KnownType Org_BouncyCastle_Asn1_X9_ECNamedCurveTable = new("Org.BouncyCastle.Asn1.X9.ECNamedCurveTable"); public static readonly KnownType Org_BouncyCastle_Asn1_X9_X962NamedCurves = new("Org.BouncyCastle.Asn1.X9.X962NamedCurves"); public static readonly KnownType Org_BouncyCastle_Crypto_Engines_AesFastEngine = new("Org.BouncyCastle.Crypto.Engines.AesFastEngine"); public static readonly KnownType Org_BouncyCastle_Crypto_Generators_BCrypt = new("Org.BouncyCastle.Crypto.Generators.BCrypt"); public static readonly KnownType Org_BouncyCastle_Crypto_Generators_SCrypt = new("Org.BouncyCastle.Crypto.Generators.SCrypt"); public static readonly KnownType Org_BouncyCastle_Crypto_Generators_DHParametersGenerator = new("Org.BouncyCastle.Crypto.Generators.DHParametersGenerator"); public static readonly KnownType Org_BouncyCastle_Crypto_Generators_OpenBsdBCrypt = new("Org.BouncyCastle.Crypto.Generators.OpenBsdBCrypt"); public static readonly KnownType Org_BouncyCastle_Crypto_Generators_DsaParametersGenerator = new("Org.BouncyCastle.Crypto.Generators.DsaParametersGenerator"); public static readonly KnownType Org_BouncyCastle_Crypto_Parameters_RsaKeyGenerationParameters = new("Org.BouncyCastle.Crypto.Parameters.RsaKeyGenerationParameters"); public static readonly KnownType Org_BouncyCastle_Crypto_PbeParametersGenerator = new("Org.BouncyCastle.Crypto.PbeParametersGenerator"); public static readonly KnownType Org_BouncyCastle_Crypto_Prng_DigestRandomGenerator = new("Org.BouncyCastle.Crypto.Prng.DigestRandomGenerator"); public static readonly KnownType Org_BouncyCastle_Crypto_Prng_IRandomGenerator = new("Org.BouncyCastle.Crypto.Prng.IRandomGenerator"); public static readonly KnownType Org_BouncyCastle_Crypto_Prng_VmpcRandomGenerator = new("Org.BouncyCastle.Crypto.Prng.VmpcRandomGenerator"); public static readonly KnownType Org_BouncyCastle_Security_SecureRandom = new("Org.BouncyCastle.Security.SecureRandom"); public static readonly KnownType Serilog_Events_LogEventLevel = new("Serilog.Events.LogEventLevel"); public static readonly KnownType Serilog_ILogger = new("Serilog.ILogger"); public static readonly KnownType Serilog_LoggerConfiguration = new("Serilog.LoggerConfiguration"); public static readonly KnownType Serilog_Log = new("Serilog.Log"); public static readonly KnownType ServiceStack_OrmLite_OrmLiteReadApi = new("ServiceStack.OrmLite.OrmLiteReadApi"); public static readonly KnownType ServiceStack_OrmLite_OrmLiteReadApiAsync = new("ServiceStack.OrmLite.OrmLiteReadApiAsync"); public static readonly KnownType System_Action = new("System.Action"); public static readonly KnownType System_Action_T = new("System.Action", "T"); public static readonly KnownType System_Action_T1_T2 = new("System.Action", "T1", "T2"); public static readonly KnownType System_Action_T1_T2_T3 = new("System.Action", "T1", "T2", "T3"); public static readonly KnownType System_Action_T1_T2_T3_T4 = new("System.Action", "T1", "T2", "T3", "T4"); public static readonly KnownType System_Activator = new("System.Activator"); public static readonly KnownType System_ApplicationException = new("System.ApplicationException"); public static readonly KnownType System_AppDomain = new("System.AppDomain"); public static readonly KnownType System_ArgumentException = new("System.ArgumentException"); public static readonly KnownType System_ArgumentNullException = new("System.ArgumentNullException"); public static readonly KnownType System_ArgumentOutOfRangeException = new("System.ArgumentOutOfRangeException"); public static readonly KnownType System_Array = new("System.Array"); public static readonly KnownType System_Attribute = new("System.Attribute"); public static readonly KnownType System_AttributeUsageAttribute = new("System.AttributeUsageAttribute"); public static readonly KnownType System_Boolean = new("System.Boolean"); public static readonly KnownType System_Byte = new("System.Byte"); public static readonly KnownType System_Byte_Array = new("System.Byte") { IsArray = true }; public static readonly KnownType System_Char = new("System.Char"); public static readonly KnownType System_Char_Array = new("System.Char") { IsArray = true }; public static readonly KnownType System_Convert = new("System.Convert"); public static readonly KnownType System_CLSCompliantAttribute = new("System.CLSCompliantAttribute"); public static readonly KnownType System_CodeDom_Compiler_GeneratedCodeAttribute = new("System.CodeDom.Compiler.GeneratedCodeAttribute"); public static readonly KnownType System_Collections_CollectionBase = new("System.Collections.CollectionBase"); public static readonly KnownType System_Collections_Concurrent_BlockingCollection_T = new("System.Collections.Concurrent.BlockingCollection", "T"); public static readonly KnownType System_Collections_Concurrent_ConcurrentDictionary_TKey_TValue = new("System.Collections.Concurrent.ConcurrentDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Concurrent_IProducerConsumerCollection_T = new("System.Collections.Concurrent.IProducerConsumerCollection", "T"); public static readonly KnownType System_Collections_DictionaryBase = new("System.Collections.DictionaryBase"); public static readonly KnownType System_Collections_Frozen_FrozenDictionary = new("System.Collections.Frozen.FrozenDictionary"); public static readonly KnownType System_Collections_Frozen_FrozenDictionary_TKey_TValue = new("System.Collections.Frozen.FrozenDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Frozen_FrozenSet = new("System.Collections.Frozen.FrozenSet"); public static readonly KnownType System_Collections_Frozen_FrozenSet_T = new("System.Collections.Frozen.FrozenSet", "T"); public static readonly KnownType System_Collections_Generic_CollectionExtensions = new("System.Collections.Generic.CollectionExtensions"); public static readonly KnownType System_Collections_Generic_Comparer_T = new("System.Collections.Generic.Comparer", "T"); public static readonly KnownType System_Collections_Generic_Dictionary_TKey_TValue = new("System.Collections.Generic.Dictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Generic_HashSet_T = new("System.Collections.Generic.HashSet", "T"); public static readonly KnownType System_Collections_Generic_IAsyncEnumerable_T = new("System.Collections.Generic.IAsyncEnumerable", "T"); public static readonly KnownType System_Collections_Generic_ICollection_T = new("System.Collections.Generic.ICollection", "T"); public static readonly KnownType System_Collections_Generic_IComparer_T = new("System.Collections.Generic.IComparer", "T"); public static readonly KnownType System_Collections_Generic_IDictionary_TKey_TValue = new("System.Collections.Generic.IDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Generic_IEnumerable_T = new("System.Collections.Generic.IEnumerable", "T"); public static readonly KnownType System_Collections_Generic_IEqualityComparer_T = new("System.Collections.Generic.IEqualityComparer", "T"); public static readonly KnownType System_Collections_Generic_IList_T = new("System.Collections.Generic.IList", "T"); public static readonly KnownType System_Collections_Generic_IReadOnlyCollection_T = new("System.Collections.Generic.IReadOnlyCollection", "T"); public static readonly KnownType System_Collections_Generic_IReadOnlyList_T = new("System.Collections.Generic.IReadOnlyList", "T"); public static readonly KnownType System_Collections_Generic_ISet_T = new("System.Collections.Generic.ISet", "T"); public static readonly KnownType System_Collections_Generic_KeyValuePair_TKey_TValue = new("System.Collections.Generic.KeyValuePair", "TKey", "TValue"); public static readonly KnownType System_Collections_Generic_List_T = new("System.Collections.Generic.List", "T"); public static readonly KnownType System_Collections_Generic_Queue_T = new("System.Collections.Generic.Queue", "T"); public static readonly KnownType System_Collections_Generic_SortedDictionary_TKey_TValue = new("System.Collections.Generic.SortedDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Generic_SortedList_TKey_TValue = new("System.Collections.Generic.SortedList", "TKey", "TValue"); public static readonly KnownType System_Collections_Generic_SortedSet_T = new("System.Collections.Generic.SortedSet", "T"); public static readonly KnownType System_Collections_Generic_Stack_T = new("System.Collections.Generic.Stack", "T"); public static readonly KnownType System_Collections_Generic_LinkedList_T = new("System.Collections.Generic.LinkedList", "T"); public static readonly KnownType System_Collections_Generic_OrderedDictionary_TKey_TValue = new("System.Collections.Generic.OrderedDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_ICollection = new("System.Collections.ICollection"); public static readonly KnownType System_Collections_IEnumerable = new("System.Collections.IEnumerable"); public static readonly KnownType System_Collections_IEnumerator = new("System.Collections.IEnumerator"); public static readonly KnownType System_Collections_IList = new("System.Collections.IList"); public static readonly KnownType System_Collections_Immutable_IImmutableDictionary_TKey_TValue = new("System.Collections.Immutable.IImmutableDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Immutable_IImmutableList_T = new("System.Collections.Immutable.IImmutableList", "T"); public static readonly KnownType System_Collections_Immutable_IImmutableQueue_T = new("System.Collections.Immutable.IImmutableQueue", "T"); public static readonly KnownType System_Collections_Immutable_IImmutableSet_T = new("System.Collections.Immutable.IImmutableSet", "T"); public static readonly KnownType System_Collections_Immutable_IImmutableStack_T = new("System.Collections.Immutable.IImmutableStack", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableArray = new("System.Collections.Immutable.ImmutableArray"); public static readonly KnownType System_Collections_Immutable_ImmutableArray_T = new("System.Collections.Immutable.ImmutableArray", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableArray_T_Builder = new("System.Collections.Immutable.ImmutableArray+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableDictionary = new("System.Collections.Immutable.ImmutableDictionary"); public static readonly KnownType System_Collections_Immutable_ImmutableDictionary_TKey_TValue = new("System.Collections.Immutable.ImmutableDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Immutable_ImmutableDictionary_TKey_TValue_Builder = new("System.Collections.Immutable.ImmutableDictionary+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableHashSet = new("System.Collections.Immutable.ImmutableHashSet"); public static readonly KnownType System_Collections_Immutable_ImmutableHashSet_T = new("System.Collections.Immutable.ImmutableHashSet", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableHashSet_T_Builder = new("System.Collections.Immutable.ImmutableHashSet+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableList = new("System.Collections.Immutable.ImmutableList"); public static readonly KnownType System_Collections_Immutable_ImmutableList_T = new("System.Collections.Immutable.ImmutableList", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableList_T_Builder = new("System.Collections.Immutable.ImmutableList+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableQueue = new("System.Collections.Immutable.ImmutableQueue"); public static readonly KnownType System_Collections_Immutable_ImmutableQueue_T = new("System.Collections.Immutable.ImmutableQueue", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedDictionary = new("System.Collections.Immutable.ImmutableSortedDictionary"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedDictionary_TKey_TValue = new("System.Collections.Immutable.ImmutableSortedDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedDictionary_TKey_TValue_Builder = new("System.Collections.Immutable.ImmutableSortedDictionary+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedSet = new("System.Collections.Immutable.ImmutableSortedSet"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedSet_T = new("System.Collections.Immutable.ImmutableSortedSet", "T"); public static readonly KnownType System_Collections_Immutable_ImmutableSortedSet_T_Builder = new("System.Collections.Immutable.ImmutableSortedSet+Builder"); public static readonly KnownType System_Collections_Immutable_ImmutableStack = new("System.Collections.Immutable.ImmutableStack"); public static readonly KnownType System_Collections_Immutable_ImmutableStack_T = new("System.Collections.Immutable.ImmutableStack", "T"); public static readonly KnownType System_Collections_ObjectModel_Collection_T = new("System.Collections.ObjectModel.Collection", "T"); public static readonly KnownType System_Collections_ObjectModel_ObservableCollection_T = new("System.Collections.ObjectModel.ObservableCollection", "T"); public static readonly KnownType System_Collections_ObjectModel_ReadOnlyCollection_T = new("System.Collections.ObjectModel.ReadOnlyCollection", "T"); public static readonly KnownType System_Collections_ObjectModel_ReadOnlyDictionary_TKey_TValue = new("System.Collections.ObjectModel.ReadOnlyDictionary", "TKey", "TValue"); public static readonly KnownType System_Collections_ObjectModel_ReadOnlySet_T = new("System.Collections.ObjectModel.ReadOnlySet", "T"); public static readonly KnownType System_Collections_Queue = new("System.Collections.Queue"); public static readonly KnownType System_Collections_ReadOnlyCollectionBase = new("System.Collections.ReadOnlyCollectionBase"); public static readonly KnownType System_Collections_SortedList = new("System.Collections.SortedList"); public static readonly KnownType System_Collections_Stack = new("System.Collections.Stack"); public static readonly KnownType System_Collections_Specialized_NameObjectCollectionBase = new("System.Collections.Specialized.NameObjectCollectionBase"); public static readonly KnownType System_Collections_Specialized_NameValueCollection = new("System.Collections.Specialized.NameValueCollection"); public static readonly KnownType System_Composition_ExportAttribute = new("System.Composition.ExportAttribute"); public static readonly KnownType System_ComponentModel_Composition_CreationPolicy = new("System.ComponentModel.Composition.CreationPolicy"); public static readonly KnownType System_ComponentModel_Composition_ExportAttribute = new("System.ComponentModel.Composition.ExportAttribute"); public static readonly KnownType System_ComponentModel_Composition_InheritedExportAttribute = new("System.ComponentModel.Composition.InheritedExportAttribute"); public static readonly KnownType System_ComponentModel_Composition_PartCreationPolicyAttribute = new("System.ComponentModel.Composition.PartCreationPolicyAttribute"); public static readonly KnownType System_ComponentModel_DataAnnotations_KeyAttribute = new("System.ComponentModel.DataAnnotations.KeyAttribute"); public static readonly KnownType System_ComponentModel_DataAnnotations_RegularExpressionAttribute = new("System.ComponentModel.DataAnnotations.RegularExpressionAttribute"); public static readonly KnownType System_ComponentModel_DataAnnotations_RangeAttribute = new("System.ComponentModel.DataAnnotations.RangeAttribute"); public static readonly KnownType System_ComponentModel_DataAnnotations_IValidatableObject = new("System.ComponentModel.DataAnnotations.IValidatableObject"); public static readonly KnownType System_ComponentModel_DataAnnotations_RequiredAttribute = new("System.ComponentModel.DataAnnotations.RequiredAttribute"); public static readonly KnownType System_ComponentModel_DataAnnotations_ValidationAttribute = new("System.ComponentModel.DataAnnotations.ValidationAttribute"); public static readonly KnownType System_ComponentModel_DefaultValueAttribute = new("System.ComponentModel.DefaultValueAttribute"); public static readonly KnownType System_ComponentModel_EditorBrowsableAttribute = new("System.ComponentModel.EditorBrowsableAttribute"); public static readonly KnownType System_ComponentModel_LocalizableAttribute = new("System.ComponentModel.LocalizableAttribute"); public static readonly KnownType System_Configuration_ConfigXmlDocument = new("System.Configuration.ConfigXmlDocument"); public static readonly KnownType System_Configuration_ConfigurationManager = new("System.Configuration.ConfigurationManager"); public static readonly KnownType System_Console = new("System.Console"); public static readonly KnownType System_Data_Common_CommandTrees_DbExpression = new("System.Data.Common.CommandTrees.DbExpression"); public static readonly KnownType System_Data_DataSet = new("System.Data.DataSet"); public static readonly KnownType System_Data_DataTable = new("System.Data.DataTable"); public static readonly KnownType System_Data_Entity_Core_Objects_ObjectQuery = new("System.Data.Entity.Core.Objects.ObjectQuery"); public static readonly KnownType System_Data_Entity_Database = new("System.Data.Entity.Database"); public static readonly KnownType System_Data_Entity_DbSet = new("System.Data.Entity.DbSet"); public static readonly KnownType System_Data_Entity_DbSet_TEntity = new("System.Data.Entity.DbSet", "TEntity"); public static readonly KnownType System_Data_Entity_Infrastructure_DbQuery = new("System.Data.Entity.Infrastructure.DbQuery"); public static readonly KnownType System_Data_Entity_Infrastructure_DbQuery_TResult = new("System.Data.Entity.Infrastructure.DbQuery", "TResult"); public static readonly KnownType System_Data_Entity_Migrations_DbMigration = new("System.Data.Entity.Migrations.DbMigration"); public static readonly KnownType System_Data_IDbCommand = new("System.Data.IDbCommand"); public static readonly KnownType System_Data_Linq_ITable = new("System.Data.Linq.ITable"); public static readonly KnownType System_Data_Odbc_OdbcCommand = new("System.Data.Odbc.OdbcCommand"); public static readonly KnownType System_Data_Odbc_OdbcDataAdapter = new("System.Data.Odbc.OdbcDataAdapter"); public static readonly KnownType System_Data_OracleClient_OracleCommand = new("System.Data.OracleClient.OracleCommand"); public static readonly KnownType System_Data_OracleClient_OracleDataAdapter = new("System.Data.OracleClient.OracleDataAdapter"); public static readonly KnownType System_Data_SqlClient_SqlCommand = new("System.Data.SqlClient.SqlCommand"); public static readonly KnownType System_Data_SqlClient_SqlDataAdapter = new("System.Data.SqlClient.SqlDataAdapter"); public static readonly KnownType Microsoft_Data_SqlClient_SqlCommand = new("Microsoft.Data.SqlClient.SqlCommand"); public static readonly KnownType Microsoft_Data_SqlClient_SqlDataAdapter = new("Microsoft.Data.SqlClient.SqlDataAdapter"); public static readonly KnownType System_Data_Sqlite_SqliteCommand = new("System.Data.SQLite.SQLiteCommand"); public static readonly KnownType System_Data_Sqlite_SQLiteDataAdapter = new("System.Data.SQLite.SQLiteDataAdapter"); public static readonly KnownType System_Data_SqlServerCe_SqlCeCommand = new("System.Data.SqlServerCe.SqlCeCommand"); public static readonly KnownType System_Data_SqlServerCe_SqlCeDataAdapter = new("System.Data.SqlServerCe.SqlCeDataAdapter"); public static readonly KnownType System_DateOnly = new("System.DateOnly"); public static readonly KnownType System_DateTime = new("System.DateTime"); public static readonly KnownType System_DateTimeKind = new("System.DateTimeKind"); public static readonly KnownType System_DateTimeOffset = new("System.DateTimeOffset"); public static readonly KnownType System_Decimal = new("System.Decimal"); public static readonly KnownType System_Delegate = new("System.Delegate"); public static readonly KnownType System_Diagnostics_CodeAnalysis_DynamicallyAccessedMembersAttribute = new("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute"); public static readonly KnownType System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute = new("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute"); public static readonly KnownType System_Diagnostics_CodeAnalysis_SuppressMessageAttribute = new("System.Diagnostics.CodeAnalysis.SuppressMessageAttribute"); public static readonly KnownType System_Diagnostics_CodeAnalysis_StringSyntaxAttribute = new("System.Diagnostics.CodeAnalysis.StringSyntaxAttribute"); public static readonly KnownType System_Diagnostics_ConditionalAttribute = new("System.Diagnostics.ConditionalAttribute"); public static readonly KnownType System_Diagnostics_Contracts_PureAttribute = new("System.Diagnostics.Contracts.PureAttribute"); public static readonly KnownType System_Diagnostics_Debug = new("System.Diagnostics.Debug"); public static readonly KnownType System_Diagnostics_Debugger = new("System.Diagnostics.Debugger"); public static readonly KnownType System_Diagnostics_DebuggerDisplayAttribute = new("System.Diagnostics.DebuggerDisplayAttribute"); public static readonly KnownType System_Diagnostics_Process = new("System.Diagnostics.Process"); public static readonly KnownType System_Diagnostics_ProcessStartInfo = new("System.Diagnostics.ProcessStartInfo"); public static readonly KnownType System_Diagnostics_Trace = new("System.Diagnostics.Trace"); public static readonly KnownType System_Diagnostics_TraceSource = new("System.Diagnostics.TraceSource"); public static readonly KnownType System_Diagnostics_TraceSwitch = new("System.Diagnostics.TraceSwitch"); public static readonly KnownType System_DirectoryServices_AuthenticationTypes = new("System.DirectoryServices.AuthenticationTypes"); public static readonly KnownType System_DirectoryServices_DirectoryEntry = new("System.DirectoryServices.DirectoryEntry"); public static readonly KnownType System_Double = new("System.Double"); public static readonly KnownType System_Drawing_Bitmap = new("System.Drawing.Bitmap"); public static readonly KnownType System_Drawing_Image = new("System.Drawing.Image"); public static readonly KnownType System_DuplicateWaitObjectException = new("System.DuplicateWaitObjectException"); public static readonly KnownType System_Enum = new("System.Enum"); public static readonly KnownType System_Environment = new("System.Environment"); public static readonly KnownType System_EventArgs = new("System.EventArgs"); public static readonly KnownType System_EventHandler = new("System.EventHandler"); public static readonly KnownType System_EventHandler_TEventArgs = new("System.EventHandler", "TEventArgs"); public static readonly KnownType System_Exception = new("System.Exception"); public static readonly KnownType System_ExecutionEngineException = new("System.ExecutionEngineException"); public static readonly KnownType System_FlagsAttribute = new("System.FlagsAttribute"); public static readonly KnownType System_FormattableString = new("System.FormattableString"); public static readonly KnownType System_Func_TResult = new("System.Func", "TResult"); public static readonly KnownType System_Func_T_TResult = new("System.Func", "T", "TResult"); public static readonly KnownType System_Func_T1_T2_TResult = new("System.Func", "T1", "T2", "TResult"); public static readonly KnownType System_Func_T1_T2_T3_TResult = new("System.Func", "T1", "T2", "T3", "TResult"); public static readonly KnownType System_Func_T1_T2_T3_T4_TResult = new("System.Func", "T1", "T2", "T3", "T4", "TResult"); public static readonly KnownType System_GC = new("System.GC"); public static readonly KnownType System_Globalization_CompareOptions = new("System.Globalization.CompareOptions"); public static readonly KnownType System_Globalization_CultureInfo = new("System.Globalization.CultureInfo"); public static readonly KnownType System_Guid = new("System.Guid"); public static readonly KnownType System_Half = new("System.Half"); public static readonly KnownType System_IAsyncDisposable = new("System.IAsyncDisposable"); public static readonly KnownType System_IComparable = new("System.IComparable"); public static readonly KnownType System_IComparable_T = new("System.IComparable", "T"); public static readonly KnownType System_IdentityModel_Tokens_SecurityTokenHandler = new("System.IdentityModel.Tokens.SecurityTokenHandler"); public static readonly KnownType System_IdentityModel_Tokens_SymmetricSecurityKey = new("System.IdentityModel.Tokens.SymmetricSecurityKey"); public static readonly KnownType System_IDisposable = new("System.IDisposable"); public static readonly KnownType System_IEquatable_T = new("System.IEquatable", "T"); public static readonly KnownType System_IFormatProvider = new("System.IFormatProvider"); public static readonly KnownType System_Index = new("System.Index"); public static readonly KnownType System_IndexOutOfRangeException = new("System.IndexOutOfRangeException"); public static readonly KnownType System_Int16 = new("System.Int16"); public static readonly KnownType System_Int32 = new("System.Int32"); public static readonly KnownType System_Int64 = new("System.Int64"); public static readonly KnownType System_Int128 = new("System.Int128"); public static readonly KnownType System_IntPtr = new("System.IntPtr"); public static readonly KnownType System_InvalidOperationException = new("System.InvalidOperationException"); public static readonly KnownType System_IO_Compression_ZipFile = new("System.IO.Compression.ZipFile"); public static readonly KnownType System_IO_Compression_ZipFileExtensions = new("System.IO.Compression.ZipFileExtensions"); public static readonly KnownType System_IO_FileStream = new("System.IO.FileStream"); public static readonly KnownType System_IO_Path = new("System.IO.Path"); public static readonly KnownType System_IO_Stream = new("System.IO.Stream"); public static readonly KnownType System_IO_StreamReader = new("System.IO.StreamReader"); public static readonly KnownType System_IO_StreamWriter = new("System.IO.StreamWriter"); public static readonly KnownType System_IO_TextWriter = new("System.IO.TextWriter"); public static readonly KnownType System_Lazy = new("System.Lazy", "T"); public static readonly KnownType System_Linq_Enumerable = new("System.Linq.Enumerable"); public static readonly KnownType System_Linq_Expressions_Expression = new("System.Linq.Expressions.Expression"); public static readonly KnownType System_Linq_Expressions_Expression_T = new("System.Linq.Expressions.Expression", "TDelegate"); public static readonly KnownType System_Linq_ImmutableArrayExtensions = new("System.Linq.ImmutableArrayExtensions"); public static readonly KnownType System_Linq_IQueryable = new("System.Linq.IQueryable", "T"); public static readonly KnownType System_Linq_Queryable = new("System.Linq.Queryable"); public static readonly KnownType System_MarshalByRefObject = new("System.MarshalByRefObject"); public static readonly KnownType System_MemoryExtensions = new("System.MemoryExtensions"); public static readonly KnownType System_MTAThreadAttribute = new("System.MTAThreadAttribute"); public static readonly KnownType System_Net_FtpWebRequest = new("System.Net.FtpWebRequest"); public static readonly KnownType System_Net_Http_Headers_HttpHeaders = new("System.Net.Http.Headers.HttpHeaders"); public static readonly KnownType System_Net_Http_HttpClient = new("System.Net.Http.HttpClient"); public static readonly KnownType System_Net_Http_HttpClientHandler = new("System.Net.Http.HttpClientHandler"); public static readonly KnownType System_Net_IPAddress = new("System.Net.IPAddress"); public static readonly KnownType System_Net_Mail_SmtpClient = new("System.Net.Mail.SmtpClient"); public static readonly KnownType System_Net_NetworkCredential = new("System.Net.NetworkCredential"); public static readonly KnownType System_Net_SecurityProtocolType = new("System.Net.SecurityProtocolType"); public static readonly KnownType System_Net_Security_RemoteCertificateValidationCallback = new("System.Net.Security.RemoteCertificateValidationCallback"); public static readonly KnownType System_Net_Security_SslPolicyErrors = new("System.Net.Security.SslPolicyErrors"); public static readonly KnownType System_Net_Sockets_Socket = new("System.Net.Sockets.Socket"); public static readonly KnownType System_Net_Sockets_SocketTaskExtensions = new("System.Net.Sockets.SocketTaskExtensions"); public static readonly KnownType System_Net_Sockets_TcpClient = new("System.Net.Sockets.TcpClient"); public static readonly KnownType System_Net_Sockets_UdpClient = new("System.Net.Sockets.UdpClient"); public static readonly KnownType System_Net_WebClient = new("System.Net.WebClient"); public static readonly KnownType System_NonSerializedAttribute = new("System.NonSerializedAttribute"); public static readonly KnownType System_NotImplementedException = new("System.NotImplementedException"); public static readonly KnownType System_NotSupportedException = new("System.NotSupportedException"); public static readonly KnownType System_Nullable_T = new("System.Nullable", "T"); public static readonly KnownType System_NullReferenceException = new("System.NullReferenceException"); public static readonly KnownType System_Numerics_IEqualityOperators_TSelf_TOther_TResult = new("System.Numerics.IEqualityOperators", "TSelf", "TOther", "TResult"); public static readonly KnownType System_Numerics_IFloatingPointIeee754_TSelf = new("System.Numerics.IFloatingPointIeee754", "TSelf"); public static readonly KnownType System_Object = new("System.Object"); public static readonly KnownType System_Object_Array = new("System.Object") { IsArray = true }; public static readonly KnownType System_ObsoleteAttribute = new("System.ObsoleteAttribute"); public static readonly KnownType System_OutOfMemoryException = new("System.OutOfMemoryException"); public static readonly KnownType System_Random = new("System.Random"); public static readonly KnownType System_Range = new("System.Range"); public static readonly KnownType System_ReadOnlySpan_T = new("System.ReadOnlySpan", "T"); public static readonly KnownType System_Reflection_Assembly = new("System.Reflection.Assembly"); public static readonly KnownType System_Reflection_AssemblyVersionAttribute = new("System.Reflection.AssemblyVersionAttribute"); public static readonly KnownType System_Reflection_BindingFlags = new("System.Reflection.BindingFlags"); public static readonly KnownType System_Reflection_MemberInfo = new("System.Reflection.MemberInfo"); public static readonly KnownType System_Reflection_Module = new("System.Reflection.Module"); public static readonly KnownType System_Reflection_MethodImplAttributes = new("System.Reflection.MethodImplAttributes"); public static readonly KnownType System_Reflection_ParameterInfo = new("System.Reflection.ParameterInfo"); public static readonly KnownType System_ResolveEventArgs = new("System.ResolveEventArgs"); public static readonly KnownType System_Resources_NeutralResourcesLanguageAttribute = new("System.Resources.NeutralResourcesLanguageAttribute"); public static readonly KnownType System_Runtime_CompilerServices_ExtensionAttribute = new("System.Runtime.CompilerServices.ExtensionAttribute"); public static readonly KnownType System_Runtime_CompilerServices_CallerArgumentExpressionAttribute = new("System.Runtime.CompilerServices.CallerArgumentExpressionAttribute"); public static readonly KnownType System_Runtime_CompilerServices_CallerFilePathAttribute = new("System.Runtime.CompilerServices.CallerFilePathAttribute"); public static readonly KnownType System_Runtime_CompilerServices_CallerLineNumberAttribute = new("System.Runtime.CompilerServices.CallerLineNumberAttribute"); public static readonly KnownType System_Runtime_CompilerServices_CallerMemberNameAttribute = new("System.Runtime.CompilerServices.CallerMemberNameAttribute"); public static readonly KnownType System_Runtime_CompilerServices_InternalsVisibleToAttribute = new("System.Runtime.CompilerServices.InternalsVisibleToAttribute"); public static readonly KnownType System_Runtime_CompilerServices_ModuleInitializerAttribute = new("System.Runtime.CompilerServices.ModuleInitializerAttribute"); public static readonly KnownType System_Runtime_CompilerServices_ValueTaskAwaiter = new("System.Runtime.CompilerServices.ValueTaskAwaiter"); public static readonly KnownType System_Runtime_CompilerServices_ValueTaskAwaiter_TResult = new("System.Runtime.CompilerServices.ValueTaskAwaiter", "TResult"); public static readonly KnownType System_Runtime_CompilerServices_TaskAwaiter = new("System.Runtime.CompilerServices.TaskAwaiter"); public static readonly KnownType System_Runtime_CompilerServices_TaskAwaiter_TResult = new("System.Runtime.CompilerServices.TaskAwaiter", "TResult"); public static readonly KnownType System_Runtime_InteropServices_ComImportAttribute = new("System.Runtime.InteropServices.ComImportAttribute"); public static readonly KnownType System_Runtime_InteropServices_ComVisibleAttribute = new("System.Runtime.InteropServices.ComVisibleAttribute"); public static readonly KnownType System_Runtime_InteropServices_DefaultParameterValueAttribute = new("System.Runtime.InteropServices.DefaultParameterValueAttribute"); public static readonly KnownType System_Runtime_InteropServices_DllImportAttribute = new("System.Runtime.InteropServices.DllImportAttribute"); public static readonly KnownType System_Runtime_InteropServices_Exception = new("System.Runtime.InteropServices._Exception"); public static readonly KnownType System_Runtime_InteropServices_HandleRef = new("System.Runtime.InteropServices.HandleRef"); public static readonly KnownType System_Runtime_InteropServices_InterfaceTypeAttribute = new("System.Runtime.InteropServices.InterfaceTypeAttribute"); public static readonly KnownType System_Runtime_InteropServices_LibraryImportAttribute = new("System.Runtime.InteropServices.LibraryImportAttribute"); public static readonly KnownType System_Runtime_InteropServices_NFloat = new("System.Runtime.InteropServices.NFloat"); public static readonly KnownType System_Runtime_InteropServices_OptionalAttribute = new("System.Runtime.InteropServices.OptionalAttribute"); public static readonly KnownType System_Runtime_InteropServices_SafeHandle = new("System.Runtime.InteropServices.SafeHandle"); public static readonly KnownType System_Runtime_InteropServices_StructLayoutAttribute = new("System.Runtime.InteropServices.StructLayoutAttribute"); public static readonly KnownType System_Runtime_Serialization_DataMemberAttribute = new("System.Runtime.Serialization.DataMemberAttribute"); public static readonly KnownType System_Runtime_Serialization_Formatters_Binary_BinaryFormatter = new("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter"); public static readonly KnownType System_Runtime_Serialization_Formatters_Soap_SoapFormatter = new("System.Runtime.Serialization.Formatters.Soap.SoapFormatter"); public static readonly KnownType System_Runtime_Serialization_ISerializable = new("System.Runtime.Serialization.ISerializable"); public static readonly KnownType System_Runtime_Serialization_IDeserializationCallback = new("System.Runtime.Serialization.IDeserializationCallback"); public static readonly KnownType System_Runtime_Serialization_NetDataContractSerializer = new("System.Runtime.Serialization.NetDataContractSerializer"); public static readonly KnownType System_Runtime_Serialization_OnDeserializedAttribute = new("System.Runtime.Serialization.OnDeserializedAttribute"); public static readonly KnownType System_Runtime_Serialization_OnDeserializingAttribute = new("System.Runtime.Serialization.OnDeserializingAttribute"); public static readonly KnownType System_Runtime_Serialization_OnSerializedAttribute = new("System.Runtime.Serialization.OnSerializedAttribute"); public static readonly KnownType System_Runtime_Serialization_OnSerializingAttribute = new("System.Runtime.Serialization.OnSerializingAttribute"); public static readonly KnownType System_Runtime_Serialization_OptionalFieldAttribute = new("System.Runtime.Serialization.OptionalFieldAttribute"); public static readonly KnownType System_Runtime_Serialization_SerializationInfo = new("System.Runtime.Serialization.SerializationInfo"); public static readonly KnownType System_Runtime_Serialization_StreamingContext = new("System.Runtime.Serialization.StreamingContext"); public static readonly KnownType System_SByte = new("System.SByte"); public static readonly KnownType System_Security_AccessControl_FileSystemAccessRule = new("System.Security.AccessControl.FileSystemAccessRule"); public static readonly KnownType System_Security_AccessControl_FileSystemSecurity = new("System.Security.AccessControl.FileSystemSecurity"); public static readonly KnownType System_Security_AllowPartiallyTrustedCallersAttribute = new("System.Security.AllowPartiallyTrustedCallersAttribute"); public static readonly KnownType System_Security_Authentication_SslProtocols = new("System.Security.Authentication.SslProtocols"); public static readonly KnownType System_Security_Cryptography_AesManaged = new("System.Security.Cryptography.AesManaged"); public static readonly KnownType System_Security_Cryptography_AsymmetricAlgorithm = new("System.Security.Cryptography.AsymmetricAlgorithm"); public static readonly KnownType System_Security_Cryptography_AsymmetricKeyExchangeDeformatter = new("System.Security.Cryptography.AsymmetricKeyExchangeDeformatter"); public static readonly KnownType System_Security_Cryptography_AsymmetricKeyExchangeFormatter = new("System.Security.Cryptography.AsymmetricKeyExchangeFormatter"); public static readonly KnownType System_Security_Cryptography_AsymmetricSignatureDeformatter = new("System.Security.Cryptography.AsymmetricSignatureDeformatter"); public static readonly KnownType System_Security_Cryptography_AsymmetricSignatureFormatter = new("System.Security.Cryptography.AsymmetricSignatureFormatter"); public static readonly KnownType System_Security_Cryptography_CryptoConfig = new("System.Security.Cryptography.CryptoConfig"); public static readonly KnownType System_Security_Cryptography_CryptographicOperations = new("System.Security.Cryptography.CryptographicOperations"); public static readonly KnownType System_Security_Cryptography_CspParameters = new("System.Security.Cryptography.CspParameters"); public static readonly KnownType System_Security_Cryptography_DES = new("System.Security.Cryptography.DES"); public static readonly KnownType System_Security_Cryptography_DeriveBytes = new("System.Security.Cryptography.DeriveBytes"); public static readonly KnownType System_Security_Cryptography_DSA = new("System.Security.Cryptography.DSA"); public static readonly KnownType System_Security_Cryptography_DSACryptoServiceProvider = new("System.Security.Cryptography.DSACryptoServiceProvider"); public static readonly KnownType System_Security_Cryptography_ECDiffieHellman = new("System.Security.Cryptography.ECDiffieHellman"); public static readonly KnownType System_Security_Cryptography_ECDsa = new("System.Security.Cryptography.ECDsa"); public static readonly KnownType System_Security_Cryptography_ECAlgorythm = new("System.Security.Cryptography.ECAlgorithm"); public static readonly KnownType System_Security_Cryptography_HashAlgorithm = new("System.Security.Cryptography.HashAlgorithm"); public static readonly KnownType System_Security_Cryptography_HashAlgorithmName = new("System.Security.Cryptography.HashAlgorithmName"); public static readonly KnownType System_Security_Cryptography_HMAC = new("System.Security.Cryptography.HMAC"); public static readonly KnownType System_Security_Cryptography_HMACMD5 = new("System.Security.Cryptography.HMACMD5"); public static readonly KnownType System_Security_Cryptography_HMACRIPEMD160 = new("System.Security.Cryptography.HMACRIPEMD160"); public static readonly KnownType System_Security_Cryptography_HMACSHA1 = new("System.Security.Cryptography.HMACSHA1"); public static readonly KnownType System_Security_Cryptography_ICryptoTransform = new("System.Security.Cryptography.ICryptoTransform"); public static readonly KnownType System_Security_Cryptography_KeyedHashAlgorithm = new("System.Security.Cryptography.KeyedHashAlgorithm"); public static readonly KnownType System_Security_Cryptography_MD5 = new("System.Security.Cryptography.MD5"); public static readonly KnownType System_Security_Cryptography_PasswordDeriveBytes = new("System.Security.Cryptography.PasswordDeriveBytes"); public static readonly KnownType System_Security_Cryptography_RC2 = new("System.Security.Cryptography.RC2"); public static readonly KnownType System_Security_Cryptography_RandomNumberGenerator = new("System.Security.Cryptography.RandomNumberGenerator"); public static readonly KnownType System_Security_Cryptography_Rfc2898DeriveBytes = new("System.Security.Cryptography.Rfc2898DeriveBytes"); public static readonly KnownType System_Security_Cryptography_RIPEMD160 = new("System.Security.Cryptography.RIPEMD160"); public static readonly KnownType System_Security_Cryptography_RNGCryptoServiceProvider = new("System.Security.Cryptography.RNGCryptoServiceProvider"); public static readonly KnownType System_Security_Cryptography_RSA = new("System.Security.Cryptography.RSA"); public static readonly KnownType System_Security_Cryptography_RSACryptoServiceProvider = new("System.Security.Cryptography.RSACryptoServiceProvider"); public static readonly KnownType System_Security_Cryptography_SHA1 = new("System.Security.Cryptography.SHA1"); public static readonly KnownType System_Security_Cryptography_SymmetricAlgorithm = new("System.Security.Cryptography.SymmetricAlgorithm"); public static readonly KnownType System_Security_Cryptography_TripleDES = new("System.Security.Cryptography.TripleDES"); public static readonly KnownType System_Security_Cryptography_X509Certificates_X509Certificate2 = new("System.Security.Cryptography.X509Certificates.X509Certificate2"); public static readonly KnownType System_Security_Cryptography_X509Certificates_X509Chain = new("System.Security.Cryptography.X509Certificates.X509Chain"); public static readonly KnownType System_Security_Cryptography_Xml_SignedXml = new("System.Security.Cryptography.Xml.SignedXml"); public static readonly KnownType System_Security_Permissions_CodeAccessSecurityAttribute = new("System.Security.Permissions.CodeAccessSecurityAttribute"); public static readonly KnownType System_Security_Permissions_PrincipalPermission = new("System.Security.Permissions.PrincipalPermission"); public static readonly KnownType System_Security_Permissions_PrincipalPermissionAttribute = new("System.Security.Permissions.PrincipalPermissionAttribute"); public static readonly KnownType System_Security_PermissionSet = new("System.Security.PermissionSet"); public static readonly KnownType System_Security_Principal_IIdentity = new("System.Security.Principal.IIdentity"); public static readonly KnownType System_Security_Principal_IPrincipal = new("System.Security.Principal.IPrincipal"); public static readonly KnownType System_Security_Principal_NTAccount = new("System.Security.Principal.NTAccount"); public static readonly KnownType System_Security_Principal_SecurityIdentifier = new("System.Security.Principal.SecurityIdentifier"); public static readonly KnownType System_Security_Principal_WindowsIdentity = new("System.Security.Principal.WindowsIdentity"); public static readonly KnownType System_Security_SecureString = new("System.Security.SecureString"); public static readonly KnownType System_Security_SecurityCriticalAttribute = new("System.Security.SecurityCriticalAttribute"); public static readonly KnownType System_Security_SecuritySafeCriticalAttribute = new("System.Security.SecuritySafeCriticalAttribute"); public static readonly KnownType System_SerializableAttribute = new("System.SerializableAttribute"); public static readonly KnownType System_ServiceModel_OperationContractAttribute = new("System.ServiceModel.OperationContractAttribute"); public static readonly KnownType System_ServiceModel_ServiceContractAttribute = new("System.ServiceModel.ServiceContractAttribute"); public static readonly KnownType System_Single = new("System.Single"); public static readonly KnownType System_StackOverflowException = new("System.StackOverflowException"); public static readonly KnownType System_STAThreadAttribute = new("System.STAThreadAttribute"); public static readonly KnownType System_String = new("System.String"); public static readonly KnownType System_String_Array = new("System.String") { IsArray = true }; public static readonly KnownType System_StringComparison = new("System.StringComparison"); public static readonly KnownType System_SystemException = new("System.SystemException"); public static readonly KnownType System_Text_Encoding = new("System.Text.Encoding"); public static readonly KnownType System_Text_Json_Serialization_JsonIgnoreAttribute = new("System.Text.Json.Serialization.JsonIgnoreAttribute"); public static readonly KnownType System_Text_Json_Serialization_JsonRequiredAttribute = new("System.Text.Json.Serialization.JsonRequiredAttribute"); public static readonly KnownType System_Text_RegularExpressions_Regex = new("System.Text.RegularExpressions.Regex"); public static readonly KnownType System_Text_RegularExpressions_RegexOptions = new("System.Text.RegularExpressions.RegexOptions"); public static readonly KnownType System_Text_StringBuilder = new("System.Text.StringBuilder"); public static readonly KnownType System_Threading_CancellationToken = new("System.Threading.CancellationToken"); public static readonly KnownType System_Threading_CancellationTokenSource = new("System.Threading.CancellationTokenSource"); public static readonly KnownType System_Threading_Lock = new("System.Threading.Lock"); public static readonly KnownType System_Threading_Lock_Scope = new("System.Threading.Lock+Scope"); public static readonly KnownType System_Threading_Monitor = new("System.Threading.Monitor"); public static readonly KnownType System_Threading_Mutex = new("System.Threading.Mutex"); public static readonly KnownType System_Threading_ReaderWriterLock = new("System.Threading.ReaderWriterLock"); public static readonly KnownType System_Threading_ReaderWriterLockSlim = new("System.Threading.ReaderWriterLockSlim"); public static readonly KnownType System_Threading_Semaphore = new("System.Threading.Semaphore"); public static readonly KnownType System_Threading_SemaphoreSlim = new("System.Threading.SemaphoreSlim"); public static readonly KnownType System_Threading_SpinLock = new("System.Threading.SpinLock"); public static readonly KnownType System_Threading_Tasks_Task = new("System.Threading.Tasks.Task"); public static readonly KnownType System_Threading_Tasks_Task_T = new("System.Threading.Tasks.Task", "TResult"); public static readonly KnownType System_Threading_Tasks_TaskFactory = new("System.Threading.Tasks.TaskFactory"); public static readonly KnownType System_Threading_Tasks_ValueTask = new("System.Threading.Tasks.ValueTask"); public static readonly KnownType System_Threading_Tasks_ValueTask_TResult = new("System.Threading.Tasks.ValueTask", "TResult"); public static readonly KnownType System_Threading_Thread = new("System.Threading.Thread"); public static readonly KnownType System_Threading_WaitHandle = new("System.Threading.WaitHandle"); public static readonly KnownType System_ThreadStaticAttribute = new("System.ThreadStaticAttribute"); public static readonly KnownType System_TimeOnly = new("System.TimeOnly"); public static readonly KnownType System_TimeSpan = new("System.TimeSpan"); public static readonly KnownType System_Type = new("System.Type"); public static readonly KnownType System_UInt16 = new("System.UInt16"); public static readonly KnownType System_UInt32 = new("System.UInt32"); public static readonly KnownType System_UInt64 = new("System.UInt64"); public static readonly KnownType System_UIntPtr = new("System.UIntPtr"); public static readonly KnownType System_Uri = new("System.Uri"); public static readonly KnownType System_ValueTuple = new("System.ValueTuple"); public static readonly KnownType System_ValueType = new("System.ValueType"); public static readonly KnownType System_Web_HttpApplication = new("System.Web.HttpApplication"); public static readonly KnownType System_Web_HttpCookie = new("System.Web.HttpCookie"); public static readonly KnownType System_Web_HttpContext = new("System.Web.HttpContext"); public static readonly KnownType System_Web_HttpResponse = new("System.Web.HttpResponse"); public static readonly KnownType System_Web_HttpResponseBase = new("System.Web.HttpResponseBase"); public static readonly KnownType System_Web_Http_ApiController = new("System.Web.Http.ApiController"); public static readonly KnownType System_Web_Http_Cors_EnableCorsAttribute = new("System.Web.Http.Cors.EnableCorsAttribute"); public static readonly KnownType System_Web_Mvc_Controller = new("System.Web.Mvc.Controller"); public static readonly KnownType System_Web_Mvc_HttpPostAttribute = new("System.Web.Mvc.HttpPostAttribute"); public static readonly KnownType System_Web_Mvc_NonActionAttribute = new("System.Web.Mvc.NonActionAttribute"); public static readonly KnownType System_Web_Mvc_RouteAttribute = new("System.Web.Mvc.RouteAttribute"); public static readonly KnownType System_Web_Mvc_Routing_IRouteInfoProvider = new("System.Web.Mvc.Routing.IRouteInfoProvider"); public static readonly KnownType System_Web_Mvc_RoutePrefixAttribute = new("System.Web.Mvc.RoutePrefixAttribute"); public static readonly KnownType System_Web_Mvc_ValidateInputAttribute = new("System.Web.Mvc.ValidateInputAttribute"); public static readonly KnownType System_Web_Routing_RouteValueDictionary = new("System.Web.Routing.RouteValueDictionary"); public static readonly KnownType System_Web_Script_Serialization_JavaScriptSerializer = new("System.Web.Script.Serialization.JavaScriptSerializer"); public static readonly KnownType System_Web_Script_Serialization_JavaScriptTypeResolver = new("System.Web.Script.Serialization.JavaScriptTypeResolver"); public static readonly KnownType System_Web_Script_Serialization_SimpleTypeResolver = new("System.Web.Script.Serialization.SimpleTypeResolver"); public static readonly KnownType System_Web_UI_LosFormatter = new("System.Web.UI.LosFormatter"); public static readonly KnownType System_Windows_DependencyObject = new("System.Windows.DependencyObject"); public static readonly KnownType System_Windows_Forms_Application = new("System.Windows.Forms.Application"); public static readonly KnownType System_Windows_Forms_IContainerControl = new("System.Windows.Forms.IContainerControl"); public static readonly KnownType System_Windows_FrameworkElement = new("System.Windows.FrameworkElement"); public static readonly KnownType System_Windows_Markup_ConstructorArgumentAttribute = new("System.Windows.Markup.ConstructorArgumentAttribute"); public static readonly KnownType System_Windows_Markup_XmlnsPrefixAttribute = new("System.Windows.Markup.XmlnsPrefixAttribute"); public static readonly KnownType System_Windows_Markup_XmlnsDefinitionAttribute = new("System.Windows.Markup.XmlnsDefinitionAttribute"); public static readonly KnownType System_Windows_Markup_XmlnsCompatibleWithAttribute = new("System.Windows.Markup.XmlnsCompatibleWithAttribute"); public static readonly KnownType System_Xml_Resolvers_XmlPreloadedResolver = new("System.Xml.Resolvers.XmlPreloadedResolver"); public static readonly KnownType System_Xml_Serialization_XmlElementAttribute = new("System.Xml.Serialization.XmlElementAttribute"); public static readonly KnownType System_Xml_XmlDocument = new("System.Xml.XmlDocument"); public static readonly KnownType System_Xml_XmlDataDocument = new("System.Xml.XmlDataDocument"); public static readonly KnownType System_Xml_XmlNode = new("System.Xml.XmlNode"); public static readonly KnownType System_Xml_XPath_XPathDocument = new("System.Xml.XPath.XPathDocument"); public static readonly KnownType System_Xml_XmlReader = new("System.Xml.XmlReader"); public static readonly KnownType System_Xml_XmlReaderSettings = new("System.Xml.XmlReaderSettings"); public static readonly KnownType System_Xml_XmlUrlResolver = new("System.Xml.XmlUrlResolver"); public static readonly KnownType System_Xml_XmlTextReader = new("System.Xml.XmlTextReader"); public static readonly KnownType System_Xml_XmlWriter = new("System.Xml.XmlWriter"); public static readonly KnownType Void = new("System.Void"); public static readonly KnownType NSubstitute_SubstituteExtensions = new("NSubstitute.SubstituteExtensions"); public static readonly KnownType NSubstitute_Received = new("NSubstitute.Received"); public static readonly KnownType NSubstitute_ReceivedExtensions_ReceivedExtensions = new("NSubstitute.ReceivedExtensions.ReceivedExtensions"); public static readonly ImmutableArray SystemActionVariants = ImmutableArray.Create( new("System.Action"), new("System.Action", "T"), new("System.Action", "T1", "T2"), new("System.Action", "T1", "T2", "T3"), new("System.Action", "T1", "T2", "T3", "T4"), new("System.Action", "T1", "T2", "T3", "T4", "T5"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15"), new("System.Action", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15", "T16")); public static readonly ImmutableArray SystemFuncVariants = ImmutableArray.Create( new("System.Func", "TResult"), new("System.Func", "T", "TResult"), new("System.Func", "T1", "T2", "TResult"), new("System.Func", "T1", "T2", "T3", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15", "TResult"), new("System.Func", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15", "T16", "TResult")); public static readonly ImmutableArray SystemTasks = ImmutableArray.Create( System_Threading_Tasks_Task, System_Threading_Tasks_Task_T, System_Threading_Tasks_ValueTask_TResult); public static readonly KnownType System_Resources_ResourceManager = new("System.Resources.ResourceManager"); public static readonly KnownType TimeZoneConverter_TZConvert = new("TimeZoneConverter.TZConvert"); public static readonly KnownType UnityEditor_AssetModificationProcessor = new("UnityEditor.AssetModificationProcessor"); public static readonly KnownType UnityEditor_AssetPostprocessor = new("UnityEditor.AssetPostprocessor"); public static readonly KnownType UnityEngine_MonoBehaviour = new("UnityEngine.MonoBehaviour"); public static readonly KnownType UnityEngine_ScriptableObject = new("UnityEngine.ScriptableObject"); public static readonly KnownType Xunit_Assert = new("Xunit.Assert"); public static readonly KnownType Xunit_Sdk_AssertException = new("Xunit.Sdk.AssertException"); public static readonly KnownType Xunit_FactAttribute = new("Xunit.FactAttribute"); public static readonly KnownType Xunit_Sdk_XunitException = new("Xunit.Sdk.XunitException"); public static readonly KnownType Xunit_TheoryAttribute = new("Xunit.TheoryAttribute"); public static readonly KnownType LegacyXunit_TheoryAttribute = new("Xunit.Extensions.TheoryAttribute"); public static readonly ImmutableArray CallerInfoAttributes = ImmutableArray.Create( System_Runtime_CompilerServices_CallerArgumentExpressionAttribute, System_Runtime_CompilerServices_CallerFilePathAttribute, System_Runtime_CompilerServices_CallerLineNumberAttribute, System_Runtime_CompilerServices_CallerMemberNameAttribute); public static readonly ImmutableArray DatabaseBaseQueryTypes = ImmutableArray.Create( System_Data_Entity_Infrastructure_DbQuery, System_Data_Entity_Infrastructure_DbQuery_TResult, Microsoft_EntityFrameworkCore_DbSet_TEntity, System_Data_Linq_ITable, System_Data_Entity_Core_Objects_ObjectQuery); public static readonly ImmutableArray FloatingPointNumbers = ImmutableArray.Create( System_Half, System_Single, System_Double, System_Runtime_InteropServices_NFloat); public static readonly ImmutableArray IntegralNumbers = ImmutableArray.Create( System_Int16, System_Int32, System_Int64, System_UInt16, System_UInt32, System_UInt64, System_Char, System_Byte, System_SByte); public static readonly ImmutableArray IntegralNumbersIncludingNative = ImmutableArray.Create( System_Int16, System_Int32, System_Int64, System_UInt16, System_UInt32, System_UInt64, System_Char, System_Byte, System_SByte, System_IntPtr, System_UIntPtr); public static readonly ImmutableArray NonIntegralNumbers = ImmutableArray.Create( System_Single, System_Double, System_Decimal); public static readonly ImmutableArray PointerTypes = ImmutableArray.Create( System_IntPtr, System_UIntPtr); public static readonly ImmutableArray UnsignedIntegers = ImmutableArray.Create( System_UInt64, System_UInt32, System_UInt16); public static readonly ImmutableArray RouteAttributes = ImmutableArray.Create( Microsoft_AspNetCore_Mvc_RouteAttribute, System_Web_Mvc_RouteAttribute); public static readonly ImmutableArray TestMethodAttributesOfMSTest = ImmutableArray.Create( Microsoft_VisualStudio_TestTools_UnitTesting_TestMethodAttribute, Microsoft_VisualStudio_TestTools_UnitTesting_DataTestMethodAttribute); public static readonly ImmutableArray TestMethodAttributesOfNUnit = ImmutableArray.Create( NUnit_Framework_TestAttribute, NUnit_Framework_TestCaseAttribute, NUnit_Framework_TestCaseSourceAttribute, NUnit_Framework_TheoryAttribute, NUnit_Framework_ITestBuilderInterface); public static readonly ImmutableArray TestMethodAttributesOfxUnit = ImmutableArray.Create( Xunit_TheoryAttribute, LegacyXunit_TheoryAttribute, Xunit_FactAttribute); // In order for the FindFirstTestMethodType to work, FactAttribute should go last as the Theory attribute derives from it. public static readonly ImmutableArray ExpectedExceptionAttributes = ImmutableArray.Create( // Note: XUnit doesn't have a exception attribute Microsoft_VisualStudio_TestTools_UnitTesting_ExpectedExceptionAttribute, NUnit_Framework_ExpectedExceptionAttribute); public static readonly ImmutableArray IgnoreAttributes = ImmutableArray.Create( // Note: XUnit doesn't have a separate "Ignore" attribute. It has a "Skip" parameter on the test attribute Microsoft_VisualStudio_TestTools_UnitTesting_IgnoreAttribute, NUnit_Framework_IgnoreAttribute); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Semantics/NetFrameworkVersionProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics; public enum NetFrameworkVersion { // cannot tell Unknown, // probably .NET 3.5 Probably35, // between .NET 4.0 (inclusive) and .NET 4.5.1 (inclusive) Between4And451, // after .NET 4.5.2 (inclusive) After452 } /// /// This class provides an approximation of the .NET Framework version of the Compilation. /// /// /// This class has been added for the requirements of the S2755 C# implementation, so it is quite limited. /// public class NetFrameworkVersionProvider { public virtual NetFrameworkVersion Version(Compilation compilation) { if (compilation is null) { return NetFrameworkVersion.Unknown; } /// See https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ee471421(v=vs.100) var debuggerSymbol = compilation.GetTypeByMetadataName(KnownType.System_Diagnostics_Debugger); var mscorlibAssembly = debuggerSymbol?.ContainingAssembly; if (mscorlibAssembly is null || !mscorlibAssembly.Identity.Name.Equals("mscorlib", StringComparison.OrdinalIgnoreCase)) { return NetFrameworkVersion.Unknown; // it could be .NET Core or .NET Standard } var debuggerConstructorSymbol = debuggerSymbol.GetMembers(".ctor").FirstOrDefault(); if (debuggerConstructorSymbol is null) { return NetFrameworkVersion.Unknown; // e.g. .NET Standard or maybe another .NET distribution } else if (!debuggerConstructorSymbol.GetAttributes().Any(x => x.AttributeClass.Name.Equals("ObsoleteAttribute"))) { return NetFrameworkVersion.Probably35; // the constructor was still not deprecated in .NET Framework 3.5 } else if (mscorlibAssembly.GetTypeByMetadataName("System.IO.UnmanagedMemoryStream") is { } typeSymbol && !typeSymbol.GetMembers("FlushAsync").IsEmpty) { return NetFrameworkVersion.After452; // FlushAsync was not present in .NET Framework 4.5.1 and became present in 4.5.2 } else { return NetFrameworkVersion.Between4And451; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/SonarAnalyzer.Core.csproj ================================================  netstandard2.0 all runtime; build; native; contentfiles; analyzers; buildtransitive NU1701 NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/AccessibilityExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class AccessibilityExtensions { /// /// Beware of Accessibility members: /// ProtectedOrInternal = C# "protected internal" or VB.NET "Protected Friend" syntax. Accessible from inheriting class OR the same assembly. /// ProtectedAndInternal = C# "private protected" or VB.NET "Private Protected" syntax. Accessible only from inheriting class in the same assembly. /// public static bool IsAccessibleOutsideTheType(this Accessibility accessibility) => accessibility == Accessibility.Public || accessibility == Accessibility.Internal || accessibility == Accessibility.ProtectedOrInternal; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/AttributeDataExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class AttributeDataExtensions { private static readonly ImmutableArray RouteTemplateProviders = ImmutableArray.Create( KnownType.Microsoft_AspNetCore_Mvc_Routing_IRouteTemplateProvider, KnownType.System_Web_Mvc_Routing_IRouteInfoProvider); public static bool HasName(this AttributeData attribute, string name) => attribute is { AttributeClass.Name: { } attributeClassName } && attributeClassName == name; public static bool HasAnyName(this AttributeData attribute, params string[] names) => names.Any(x => attribute.HasName(x)); public static string GetAttributeRouteTemplate(this AttributeData attribute) => attribute.AttributeClass.DerivesOrImplementsAny(RouteTemplateProviders) && attribute.TryGetAttributeValue("template", out var template) ? template : null; public static bool TryGetAttributeValue(this AttributeData attribute, string valueName, out T value) { // named arguments take precedence over constructor arguments of the same name. For [Attr(valueName: false, valueName = true)] "true" is returned. if (attribute.NamedArguments.IndexOf(x => x.Key.Equals(valueName, StringComparison.OrdinalIgnoreCase)) is var namedAgumentIndex and >= 0) { return TryConvertConstant(attribute.NamedArguments[namedAgumentIndex].Value, out value); } else if (attribute.AttributeConstructor.Parameters.IndexOf(x => x.Name.Equals(valueName, StringComparison.OrdinalIgnoreCase)) is var constructorParameterIndex and >= 0) { return TryConvertConstant(attribute.ConstructorArguments[constructorParameterIndex], out value); } else { value = default; return false; } } public static bool HasAttributeUsageInherited(this AttributeData attribute) => attribute.AttributeClass.GetAttributes() .Where(x => x.AttributeClass.Is(KnownType.System_AttributeUsageAttribute)) .SelectMany(x => x.NamedArguments.Where(x => x.Key == nameof(AttributeUsageAttribute.Inherited))) .Where(x => x.Value is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_Boolean }) .Select(x => (bool?)x.Value.Value) .FirstOrDefault() ?? true; // Default value of Inherited is true private static bool TryConvertConstant(TypedConstant constant, out T value) { value = default; if (constant.IsNull) { return true; } else if (constant.Value is T result) { value = result; return true; } else if (constant.Value is IConvertible) { try { value = (T)Convert.ChangeType(constant.Value, typeof(T)); return true; } catch { return false; } } else { return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/BinaryOperatorKindExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class BinaryOperatorKindExtensions { public static bool IsAnyEquality(this BinaryOperatorKind kind) => kind.IsEquals() || kind.IsNotEquals(); public static bool IsEquals(this BinaryOperatorKind kind) => kind is BinaryOperatorKind.Equals or BinaryOperatorKind.ObjectValueEquals; public static bool IsNotEquals(this BinaryOperatorKind kind) => kind is BinaryOperatorKind.NotEquals or BinaryOperatorKind.ObjectValueNotEquals; public static bool IsAnyRelational(this BinaryOperatorKind kind) => kind is BinaryOperatorKind.GreaterThan or BinaryOperatorKind.GreaterThanOrEqual or BinaryOperatorKind.LessThan or BinaryOperatorKind.LessThanOrEqual; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/ComparisonKindExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class ComparisonKindExtensions { public static ComparisonKind Mirror(this ComparisonKind comparison) => comparison switch { ComparisonKind.GreaterThan => ComparisonKind.LessThan, ComparisonKind.GreaterThanOrEqual => ComparisonKind.LessThanOrEqual, ComparisonKind.LessThan => ComparisonKind.GreaterThan, ComparisonKind.LessThanOrEqual => ComparisonKind.GreaterThanOrEqual, _ => comparison, }; public static string ToDisplayString(this ComparisonKind kind, AnalyzerLanguage language) { if (language != AnalyzerLanguage.CSharp && language != AnalyzerLanguage.VisualBasic) { throw new NotSupportedException($"Language {language} is not supported."); } return kind switch { ComparisonKind.Equals when language == AnalyzerLanguage.CSharp => "==", ComparisonKind.Equals => "=", ComparisonKind.NotEquals when language == AnalyzerLanguage.CSharp => "!=", ComparisonKind.NotEquals => "<>", ComparisonKind.LessThan => "<", ComparisonKind.LessThanOrEqual => "<=", ComparisonKind.GreaterThan => ">", ComparisonKind.GreaterThanOrEqual => ">=", _ => throw new InvalidOperationException(), }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/CountComparisonResultExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class CountComparisonResultExtensions { public static bool IsEmptyOrNotEmpty(this CountComparisonResult comparison) => comparison == CountComparisonResult.Empty || comparison == CountComparisonResult.NotEmpty; public static bool IsInvalid(this CountComparisonResult comparison) => comparison == CountComparisonResult.AlwaysFalse || comparison == CountComparisonResult.AlwaysTrue; public static CountComparisonResult Compare(this ComparisonKind comparison, int count) => comparison switch { ComparisonKind.Equals => Equals(count), ComparisonKind.NotEquals => NotEquals(count), ComparisonKind.GreaterThanOrEqual => GreaterThanOrEqual(count), ComparisonKind.GreaterThan => GreaterThan(count), ComparisonKind.LessThan => LessThan(count), ComparisonKind.LessThanOrEqual => LessThanOrEqual(count), _ => CountComparisonResult.None, }; private static CountComparisonResult Equals(int count) => Check(count, 0, CountComparisonResult.AlwaysFalse, CountComparisonResult.Empty); private static CountComparisonResult NotEquals(int count) => Check(count, 0, CountComparisonResult.AlwaysTrue, CountComparisonResult.NotEmpty); private static CountComparisonResult GreaterThan(int count) => Check(count, 0, CountComparisonResult.AlwaysTrue, CountComparisonResult.NotEmpty); private static CountComparisonResult GreaterThanOrEqual(int count) => Check(count, 1, CountComparisonResult.AlwaysTrue, CountComparisonResult.NotEmpty); private static CountComparisonResult LessThan(int count) => Check(count, 1, CountComparisonResult.AlwaysFalse, CountComparisonResult.Empty); private static CountComparisonResult LessThanOrEqual(int count) => Check(count, 0, CountComparisonResult.AlwaysFalse, CountComparisonResult.Empty); private static CountComparisonResult Check(int count, int threshold, CountComparisonResult belowThreshold, CountComparisonResult onThreshold) { if (count == threshold) { return onThreshold; } else { return count < threshold ? belowThreshold : CountComparisonResult.SizeDepedendent; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/FileLinePositionSpanExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class FileLinePositionSpanExtensions { public static IEnumerable LineNumbers(this FileLinePositionSpan lineSpan, bool isZeroBasedCount = true) { var offset = isZeroBasedCount ? 0 : 1; var start = lineSpan.StartLinePosition.Line + offset; var end = lineSpan.EndLinePosition.Line + offset; return Enumerable.Range(start, end - start + 1); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/LinePositionExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Syntax.Extensions; public static class LinePositionExtensions { public static int LineNumberToReport(this LinePosition position) => position.Line + 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/LocationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class LocationExtensions { public static FileLinePositionSpan GetMappedLineSpanIfAvailable(this Location location) => GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree) ? location.GetMappedLineSpan() : location.GetLineSpan(); public static Location EnsureMappedLocation(this Location location) { if (location is null || !GeneratedCodeRecognizer.IsRazorGeneratedFile(location.SourceTree)) { return location; } var lineSpan = location.GetMappedLineSpan(); return Location.Create(lineSpan.Path, location.SourceSpan, lineSpan.Span); } public static int StartLine(this Location location) => location.GetLineSpan().StartLinePosition.Line; public static int StartColumn(this Location location) => location.GetLineSpan().StartLinePosition.Character; public static int EndLine(this Location location) => location.GetLineSpan().EndLinePosition.Line; public static bool IsValid(this Location location, Compilation compilation) => location.Kind != LocationKind.SourceFile || compilation.ContainsSyntaxTree(location.SourceTree); public static SecondaryLocation ToSecondary(this Location location, string message = null, params string[] messageArgs) => message is not null && messageArgs?.Length > 0 ? new(location, string.Format(message, messageArgs)) : new(location, message); public static int LineNumberToReport(this Location location) => location.GetMappedLineSpan().StartLinePosition.LineNumberToReport(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/SyntaxNodeExtensions.Roslyn.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.Shared.Extensions; [ExcludeFromCodeCoverage] public static class SyntaxNodeExtensions { /// /// Returns true if is a given token is a child token of a certain type of parent node. /// /// The type of the parent node. /// The node that we are testing. /// A function that, when given the parent node, returns the child token we are interested in. /// /// Copied from /// public static bool IsChildNode(this SyntaxNode node, Func childGetter) where TParent : SyntaxNode { var ancestor = node.GetAncestor(); if (ancestor == null) { return false; } var ancestorNode = childGetter(ancestor); return node == ancestorNode; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs#L56 public static TNode GetAncestor(this SyntaxNode node) where TNode : SyntaxNode { var current = node.Parent; while (current != null) { if (current is TNode tNode) { return tNode; } current = current.GetParent(ascendOutOfTrivia: true); } return null; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/SyntaxNodeExtensions.cs#L811 public static SyntaxNode GetParent(this SyntaxNode node, bool ascendOutOfTrivia) { var parent = node.Parent; if (parent == null && ascendOutOfTrivia) { if (node is IStructuredTriviaSyntax structuredTrivia) { parent = structuredTrivia.ParentTrivia.Token.Parent; } } return parent; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/SyntaxNodeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Syntax.Extensions; public static class SyntaxNodeExtensions { public static bool IsKnownType(this SyntaxNode node, KnownType knownType, SemanticModel model) { var type = model.GetSymbolInfo(node).Symbol.GetSymbolType(); return type.Is(knownType) || type?.OriginalDefinition?.Is(knownType) == true; } public static bool IsDeclarationKnownType(this SyntaxNode node, KnownType knownType, SemanticModel model) => model.GetDeclaredSymbol(node)?.GetSymbolType().Is(knownType) ?? false; public static SemanticModel EnsureCorrectSemanticModelOrDefault(this SyntaxNode node, SemanticModel model) => node.SyntaxTree.SemanticModelOrDefault(model); public static bool ToStringContains(this SyntaxNode node, string s) => node.ToString().Contains(s); public static bool ToStringContains(this SyntaxNode node, string s, StringComparison comparison) => node.ToString().IndexOf(s, comparison) != -1; public static bool ToStringContainsEitherOr(this SyntaxNode node, string a, string b) { var toString = node.ToString(); return toString.Contains(a) || toString.Contains(b); } public static string GetMappedFilePathFromRoot(this SyntaxNode root) { if (root.DescendantTrivia().FirstOrDefault() is { RawKind: (ushort)Microsoft.CodeAnalysis.CSharp.SyntaxKind.PragmaChecksumDirectiveTrivia } pragmaChecksum) { // The format is: #pragma checksum "filename" "{guid}" "checksum bytes" // See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives#pragma-checksum var content = pragmaChecksum.ToString(); var firstIndex = content.IndexOf('"'); var start = firstIndex + 1; var secondIndex = content.IndexOf('"', start); return content.Substring(start, secondIndex - start); } return root.SyntaxTree.FilePath; } public static TSyntaxKind Kind(this SyntaxNode node) where TSyntaxKind : struct, Enum => node is null ? default : (TSyntaxKind)Enum.ToObject(typeof(TSyntaxKind), node.RawKind); public static SecondaryLocation ToSecondaryLocation(this SyntaxNode node, string message = null, params string[] messageArgs) => message is not null && messageArgs?.Length > 0 ? new(node.GetLocation(), string.Format(message, messageArgs)) : new(node.GetLocation(), message); public static int LineNumberToReport(this SyntaxNode node) => node.GetLocation().LineNumberToReport(); public static bool HasFlagsAttribute(this SyntaxNode node, SemanticModel model) => model.GetDeclaredSymbol(node).HasAttribute(KnownType.System_FlagsAttribute); public static Location CreateLocation(this SyntaxNode from, SyntaxNode to) => Location.Create(from.SyntaxTree, TextSpan.FromBounds(from.SpanStart, to.Span.End)); public static Location CreateLocation(this SyntaxNode from, SyntaxToken to) => Location.Create(from.SyntaxTree, TextSpan.FromBounds(from.SpanStart, to.Span.End)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/SyntaxTokenExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Syntax.Extensions; public static class SyntaxTokenExtensions { public static int Line(this SyntaxToken token) => token.GetLocation().StartLine(); public static SecondaryLocation ToSecondaryLocation(this SyntaxToken token, string message = null, params string[] messageArgs) => message is not null && messageArgs?.Length > 0 ? new(token.GetLocation(), string.Format(message, messageArgs)) : new(token.GetLocation(), message); public static Location CreateLocation(this SyntaxToken from, SyntaxToken to) => Location.Create(from.SyntaxTree, TextSpan.FromBounds(from.SpanStart, to.Span.End)); public static Location CreateLocation(this SyntaxToken from, SyntaxNode to) => Location.Create(from.SyntaxTree, TextSpan.FromBounds(from.SpanStart, to.Span.End)); public static IEnumerable LineNumbers(this SyntaxToken token, bool isZeroBasedCount = true) => token.GetLocation().GetLineSpan().LineNumbers(isZeroBasedCount); public static bool IsFirstTokenOnLine(this SyntaxToken token) => token.Line() != token.GetPreviousToken().Line(); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/SyntaxTreeExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; using Roslyn.Utilities; namespace SonarAnalyzer.Core.Syntax.Extensions; internal static class SyntaxTreeExtensions { private static readonly ConditionalWeakTable GeneratedCodeCache = new(); [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/7439", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] public static bool IsGenerated(this SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) { if (tree is null) { return false; } else { return GeneratedCodeCache.TryGetValue(tree, out var result) ? (bool)result : IsGeneratedGetOrAdd(tree, generatedCodeRecognizer); } } public static bool IsConsideredGenerated(this SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer, bool isRazorAnalysisEnabled) => isRazorAnalysisEnabled ? tree.IsGenerated(generatedCodeRecognizer) && !GeneratedCodeRecognizer.IsRazorGeneratedFile(tree) : tree.IsGenerated(generatedCodeRecognizer); public static string GetOriginalFilePath(this SyntaxTree tree) => // Currently we support only C# based generated files. tree.GetRoot().DescendantNodes(_ => true, true).OfType().FirstOrDefault() is { } pragmaChecksum ? pragmaChecksum.File.ValueText : tree.FilePath; public static bool EndsWith(this SyntaxTree tree, string suffix) => tree.FilePath.EndsWith(suffix, StringComparison.OrdinalIgnoreCase); private static bool IsGeneratedGetOrAdd(SyntaxTree tree, GeneratedCodeRecognizer generatedCodeRecognizer) => (bool)GeneratedCodeCache.GetValue(tree, x => generatedCodeRecognizer.IsGenerated(x)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Extensions/SyntaxTriviaExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions; public static class SyntaxTriviaExtensions { public static IEnumerable LineNumbers(this SyntaxTrivia trivia, bool isZeroBasedCount = true) => trivia.GetLocation().GetLineSpan().LineNumbers(isZeroBasedCount); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/AccessorAccess.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; [Flags] public enum AccessorAccess { None = 0, Get = 1, Set = 2, Both = Get | Set } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/ComparisonKind.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public enum ComparisonKind { None = 0, Equals, NotEquals, LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual, } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/CountComparisionResult.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public enum CountComparisonResult { None, SizeDepedendent, Empty, NotEmpty, AlwaysTrue, AlwaysFalse, } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/EquivalenceChecker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public static class EquivalenceChecker { public static bool AreEquivalent(SyntaxNode node1, SyntaxNode node2, Func nodeComparator) => node1.Language == node2.Language && nodeComparator(node1, node2); public static bool AreEquivalent(SyntaxList nodeList1, SyntaxList nodeList2, Func nodeComparator) { if (nodeList1.Count != nodeList2.Count) { return false; } for (var i = 0; i < nodeList1.Count; i++) { if (!AreEquivalent(nodeList1[i], nodeList2[i], nodeComparator)) { return false; } } return true; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/ExpressionNumericConverterBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public abstract class ExpressionNumericConverterBase : IExpressionNumericConverter where TLiteralExpressionSyntax : SyntaxNode where TUnaryExpressionSyntax : SyntaxNode { protected abstract object TokenValue(TLiteralExpressionSyntax literalExpression); protected abstract SyntaxNode Operand(TUnaryExpressionSyntax unaryExpression); protected abstract bool IsSupportedOperator(TUnaryExpressionSyntax unaryExpression); protected abstract bool IsMinusOperator(TUnaryExpressionSyntax unaryExpression); protected abstract SyntaxNode RemoveParentheses(SyntaxNode expression); public int? ConstantIntValue(SemanticModel model, SyntaxNode expression) => ConstantValue(model, expression, Convert.ToInt32, (multiplier, v) => multiplier * v); public int? ConstantIntValue(SyntaxNode expression) => ConstantValue(null, expression, Convert.ToInt32, (multiplier, v) => multiplier * v); public double? ConstantDoubleValue(SyntaxNode expression) => ConstantValue(null, expression, Convert.ToDouble, (multiplier, v) => multiplier * v); public decimal? ConstantDecimalValue(SyntaxNode expression) => ConstantValue(null, expression, Convert.ToDecimal, (multiplier, v) => multiplier * v); private T? ConstantValue(SemanticModel model, SyntaxNode expression, Func converter, Func multiplierCalculator) where T : struct { expression = RemoveParentheses(expression); if (Multiplier(expression, out var internalExpression) is int multiplier && internalExpression is TLiteralExpressionSyntax literalExpression && Conversions.ConvertWith(TokenValue(literalExpression), converter) is { } value) { return multiplierCalculator(multiplier, value); } else if (model?.GetConstantValue(expression) is { HasValue: true } optional && optional.Value is T constantValue) { return constantValue; } else { return null; } } private int? Multiplier(SyntaxNode expression, out SyntaxNode internalExpression) { var multiplier = 1; internalExpression = expression; var unary = internalExpression as TUnaryExpressionSyntax; while (unary is not null) { if (!IsSupportedOperator(unary)) { return null; } if (IsMinusOperator(unary)) { multiplier *= -1; } internalExpression = Operand(unary); unary = internalExpression as TUnaryExpressionSyntax; } return multiplier; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/GeneratedCodeRecognizer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Syntax.Utilities; public abstract class GeneratedCodeRecognizer { private static readonly ImmutableArray GeneratedFileParts = ImmutableArray.Create( ".g.", ".generated.", ".designer.", "_generated.", "temporarygeneratedfile_", ".assemblyattributes.vb"); // The C# version of this file can already be detected because it contains special comments private static readonly ImmutableArray AutoGeneratedCommentParts = ImmutableArray.Create( " GeneratedCodeAttributes = ImmutableArray.Create( "DebuggerNonUserCode", "DebuggerNonUserCodeAttribute", "GeneratedCode", "GeneratedCodeAttribute", "CompilerGenerated", "CompilerGeneratedAttribute"); private static readonly Regex RazorPattern = new(@".*razor(?!.cshtml)(\.[-\w]*)?(\.ide)?\.g\.cs$", RegexOptions.Compiled | RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout); private static readonly Regex CshtmlPattern = new(@".*cshtml(?!.razor)(\.[-\w]*)?(\.ide)?\.g\.cs$", RegexOptions.Compiled | RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout); protected abstract bool IsTriviaComment(SyntaxTrivia trivia); protected abstract string GetAttributeName(SyntaxNode node); public bool IsGenerated(SyntaxTree tree) => !string.IsNullOrEmpty(tree.FilePath) && (HasGeneratedFileName(tree) || HasGeneratedCommentOrAttribute(tree)); /// /// Detects if the file has been generated by the the compiler for Razor. /// public static bool IsBuildTimeRazorGeneratedFile(SyntaxTree tree) => tree is not null && (tree.EndsWith("razor.g.cs") || tree.EndsWith("cshtml.g.cs")); /// /// Detects if the file has been generated by the IDE (VS only for now) for Razor. /// public static bool IsDesignTimeRazorGeneratedFile(SyntaxTree tree) => tree is not null && tree.EndsWith("ide.g.cs") && IsRazorGeneratedFile(tree); public static bool IsRazorGeneratedFile(SyntaxTree tree) => tree is not null && (IsRazor(tree) || IsCshtml(tree)); public static bool IsRazor(SyntaxTree tree) => tree.EndsWith(".g.cs") // Performance && RazorPattern.SafeIsMatch(tree.FilePath); public static bool IsCshtml(SyntaxTree tree) => tree.EndsWith(".g.cs") // Performance && CshtmlPattern.SafeIsMatch(tree.FilePath); private bool HasGeneratedCommentOrAttribute(SyntaxTree tree) { var root = tree.GetRoot(); if (root is null) { return false; } return HasAutoGeneratedComment(root) || HasGeneratedCodeAttribute(root); } private bool HasAutoGeneratedComment(SyntaxNode root) { var firstToken = root.GetFirstToken(true); if (!firstToken.HasLeadingTrivia) { return false; } return firstToken.LeadingTrivia .Where(IsTriviaComment) .Any(x => AutoGeneratedCommentParts.Any(part => x.ToString().IndexOf(part, StringComparison.OrdinalIgnoreCase) >= 0)); } private bool HasGeneratedCodeAttribute(SyntaxNode root) { var attributeNames = root .DescendantNodesAndSelf() .Select(GetAttributeName) .Where(x => !string.IsNullOrEmpty(x)); return attributeNames.Any(x => GeneratedCodeAttributes.Any(attribute => x.EndsWith(attribute, StringComparison.Ordinal))); } private static bool HasGeneratedFileName(SyntaxTree tree) { var fileName = Path.GetFileName(tree.FilePath); return GeneratedFileParts.Any(x => fileName.IndexOf(x, StringComparison.OrdinalIgnoreCase) >= 0); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/IExpressionNumericConverter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public interface IExpressionNumericConverter { int? ConstantIntValue(SemanticModel model, SyntaxNode expression); int? ConstantIntValue(SyntaxNode expression); double? ConstantDoubleValue(SyntaxNode expression); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/MethodParameterLookupBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; public interface IMethodParameterLookup { IMethodSymbol MethodSymbol { get; } bool TryGetSymbol(SyntaxNode argument, out IParameterSymbol parameter); bool TryGetSyntax(IParameterSymbol parameter, out ImmutableArray expressions); bool TryGetSyntax(string parameterName, out ImmutableArray expressions); bool TryGetNonParamsSyntax(IParameterSymbol parameter, out SyntaxNode expression); } // This should come from the Roslyn API (https://github.com/dotnet/roslyn/issues/9) public abstract class MethodParameterLookupBase : IMethodParameterLookup where TArgumentSyntax : SyntaxNode { private readonly SeparatedSyntaxList argumentList; protected abstract SyntaxToken? GetNameColonIdentifier(TArgumentSyntax argument); protected abstract SyntaxToken? GetNameEqualsIdentifier(TArgumentSyntax argument); protected abstract SyntaxNode Expression(TArgumentSyntax argument); public IMethodSymbol MethodSymbol { get; } private ImmutableArray MethodSymbolOrCandidates { get; } protected MethodParameterLookupBase(SeparatedSyntaxList argumentList, SymbolInfo? methodSymbolInfo) : this(argumentList, methodSymbolInfo?.Symbol as IMethodSymbol, methodSymbolInfo?.AllSymbols().OfType()) { } protected MethodParameterLookupBase(SeparatedSyntaxList argumentList, IMethodSymbol methodSymbol) : this(argumentList, methodSymbol, [methodSymbol]) { } private MethodParameterLookupBase(SeparatedSyntaxList argumentList, IMethodSymbol methodSymbol, IEnumerable methodSymbolOrCandidates) { this.argumentList = argumentList; MethodSymbol = methodSymbol; MethodSymbolOrCandidates = methodSymbolOrCandidates?.ToImmutableArray() ?? ImmutableArray.Create(); } /// /// Method returns array of argument syntaxes that represents all syntaxes passed to the parameter. /// /// There could be multiple syntaxes for ParamArray/params. /// There could be zero or one result for optional parameters. /// There will be single result for normal parameters. /// public bool TryGetSyntax(IParameterSymbol parameter, out ImmutableArray expressions) => TryGetSyntax(parameter.Name, out expressions); /// /// Method returns array of argument syntaxes that represents all syntaxes passed to the parameter. /// /// There could be multiple syntaxes for ParamArray/params. /// There could be zero or one result for optional parameters. /// There will be single result for normal parameters. public bool TryGetSyntax(string parameterName, out ImmutableArray expressions) { var candidateArgumentLists = MethodSymbolOrCandidates .Select(x => GetAllArgumentParameterMappings(x).Where(x => x.Symbol.Name == parameterName).Select(x => Expression(x.Node)).ToImmutableArray()).ToImmutableArray(); expressions = candidateArgumentLists.Any() && AllArgumentsAreTheSame(candidateArgumentLists) ? candidateArgumentLists[0] : Enumerable.Empty().ToImmutableArray(); return !expressions.IsEmpty; static bool AllArgumentsAreTheSame(ImmutableArray> candidateArgumentLists) => candidateArgumentLists.Skip(1).All(x => x.SequenceEqual(candidateArgumentLists[0])); } /// /// Method returns zero or one argument syntax that represents syntax passed to the parameter. /// /// Caller must ensure that given parameter is not ParamArray/params. /// public bool TryGetNonParamsSyntax(IParameterSymbol parameter, out SyntaxNode expression) { if (parameter.IsParams) { throw new InvalidOperationException("Cannot call TryGetNonParamsSyntax on ParamArray/params parameters."); } if (TryGetSyntax(parameter, out var all)) { expression = all.Single(); return true; } expression = null; return false; } public IEnumerable> GetAllArgumentParameterMappings() => GetAllArgumentParameterMappings(MethodSymbol); public bool TryGetSymbol(SyntaxNode argument, out IParameterSymbol parameter) => TryGetSymbol(argument, MethodSymbol, out parameter); private bool TryGetSymbol(SyntaxNode argument, IMethodSymbol methodSymbol, out IParameterSymbol parameter) { parameter = null; var arg = argument as TArgumentSyntax ?? throw new ArgumentException($"{nameof(argument)} must be of type {typeof(TArgumentSyntax)}", nameof(argument)); if (!argumentList.Contains(arg) || methodSymbol is null || methodSymbol.IsVararg) { return false; } if (GetNameColonIdentifier(arg) is { } nameColonIdentifier) { parameter = methodSymbol.Parameters.FirstOrDefault(x => x.Name == nameColonIdentifier.ValueText); return parameter is not null; } if (GetNameEqualsIdentifier(arg) is { } nameEqualsIdentifier && methodSymbol.ContainingType.GetMembers(nameEqualsIdentifier.ValueText) is { Length: 1 } properties && properties[0] is IPropertySymbol { SetMethod: { } setter } property && property.Name == nameEqualsIdentifier.ValueText && setter.Parameters is { Length: 1 } parameters) { parameter = parameters[0]; return parameter is not null; } var index = argumentList.IndexOf(arg); if (index >= methodSymbol.Parameters.Length) { var lastParameter = methodSymbol.Parameters.Last(); parameter = lastParameter.IsParams ? lastParameter : null; return parameter is not null; } parameter = methodSymbol.Parameters[index]; return true; } private IEnumerable> GetAllArgumentParameterMappings(IMethodSymbol methodSymbol) { foreach (var argument in argumentList) { if (TryGetSymbol(argument, methodSymbol, out var parameter)) { yield return new NodeAndSymbol(argument, parameter); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/RemovableDeclarationCollectorBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using NodeSymbolAndModel = SonarAnalyzer.Core.Common.NodeSymbolAndModel; namespace SonarAnalyzer.Core.Syntax.Utilities; // For C#, TOwnerOfSubnodes == TDeclaration == BaseTypeDeclarationSyntax // For VB, TOwnerOfSubnodes == TypeBlockSyntax, TDeclaration = TypeStatementSyntax public abstract class RemovableDeclarationCollectorBase where TOwnerOfSubnodes : SyntaxNode where TDeclaration : SyntaxNode { private readonly Compilation compilation; private readonly INamedTypeSymbol namedType; public abstract IEnumerable RemovableFieldLikeDeclarations(ISet kinds, Accessibility maxAccessibility); public abstract TOwnerOfSubnodes OwnerOfSubnodes(TDeclaration node); protected abstract IEnumerable MatchingDeclarations(NodeAndModel container, ISet kinds); public IEnumerable> TypeDeclarations => field ??= namedType.DeclaringSyntaxReferences .Select(x => x.GetSyntax()) .OfType() .Select(x => new NodeAndModel(OwnerOfSubnodes(x), compilation.GetSemanticModel(x.SyntaxTree))) .Where(x => x.Model is not null); protected RemovableDeclarationCollectorBase(INamedTypeSymbol namedType, Compilation compilation) { this.namedType = namedType; this.compilation = compilation; } public IEnumerable RemovableDeclarations(ISet kinds, Accessibility maxAccessibility) => TypeDeclarations .SelectMany(x => MatchingDeclarations(x, kinds).Select(declaration => CreateNodeSymbolAndModel(declaration, x.Model))) .Where(x => IsRemovable(x.Symbol, maxAccessibility)); public static bool IsRemovable(IMethodSymbol methodSymbol, Accessibility maxAccessibility) => IsRemovable((ISymbol)methodSymbol, maxAccessibility) && (methodSymbol.MethodKind == MethodKind.Ordinary || methodSymbol.MethodKind == MethodKind.Constructor) && !methodSymbol.IsMainMethod() && !methodSymbol.IsEventHandler() && !methodSymbol.IsSerializationConstructor(); protected static bool IsRemovable(ISymbol symbol, Accessibility maxAccessibility) => symbol is not null && symbol.GetEffectiveAccessibility() <= maxAccessibility && !symbol.IsImplicitlyDeclared && !symbol.IsAbstract && !symbol.IsVirtual && symbol.GetAttributes().IsEmpty() && !symbol.ContainingType.IsInterface() && symbol.InterfaceMembers().IsEmpty() && symbol.GetOverriddenMember() is null; protected static NodeSymbolAndModel CreateNodeSymbolAndModel(SyntaxNode node, SemanticModel model) => new(node, model.GetDeclaredSymbol(node), model); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/StringInterpolationConstantValueResolver.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.Core.Syntax.Utilities; public abstract class StringInterpolationConstantValueResolver where TSyntaxKind : struct where TInterpolatedStringExpressionSyntax : SyntaxNode where TInterpolatedStringContentSyntax : SyntaxNode where TInterpolationSyntax : SyntaxNode where TInterpolatedStringTextSyntax : SyntaxNode { protected abstract ILanguageFacade Language { get; } protected abstract IEnumerable Contents(TInterpolatedStringExpressionSyntax interpolatedStringExpression); protected abstract SyntaxToken TextToken(TInterpolatedStringTextSyntax interpolatedStringText); public string InterpolatedTextValue(TInterpolatedStringExpressionSyntax interpolatedStringExpression, SemanticModel model) { var resolvedContent = new StringBuilder(); foreach (var interpolatedStringContent in Contents(interpolatedStringExpression)) { if (interpolatedStringContent is TInterpolationSyntax interpolation) { if (Language.Syntax.NodeExpression(interpolation) is TInterpolatedStringExpressionSyntax nestedInterpolatedString && InterpolatedTextValue(nestedInterpolatedString, model) is { } innerInterpolatedValue) { resolvedContent.Append(innerInterpolatedValue); } else if (Language.FindConstantValue(model, Language.Syntax.NodeExpression(interpolation)) is string constantValue) { resolvedContent.Append(constantValue); } else { return null; } } else if (interpolatedStringContent is TInterpolatedStringTextSyntax interpolatedText) { resolvedContent.Append(TextToken(interpolatedText).Text); } else { return null; } } return resolvedContent.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Syntax/Utilities/VisualIndentComparer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities; /// /// Class to determine if visually one line is more indented than another. /// /// /// If the strings contain a mix of tab and non-tab characters then the text that appears most indented will depend on what the tab spacing used by IDE is. /// internal static class VisualIndentComparer { public static bool IsSecondIndentLonger(SyntaxNode node1, SyntaxNode node2) { var node1LinePosition = node1.GetLocation().GetLineSpan().StartLinePosition; var node2LinePosition = node2.GetLocation().GetLineSpan().StartLinePosition; var lines = node1.SyntaxTree.GetText().Lines; var indentText1 = lines[node1LinePosition.Line].ToString().Substring(0, node1LinePosition.Character); var indentText2 = lines[node2LinePosition.Line].ToString().Substring(0, node2LinePosition.Character); return IsSecondIndentLonger(indentText1, indentText2); } /// /// Returns true if it seems likely that the second indent will appear visually longer than the first. The method will only return false if it there is a high /// degree of confidence that the second input is definitely the same length or shorter (i.e. low number of false positives). /// public static bool IsSecondIndentLonger(string indent1, string indent2) { var tabCount1 = TabCount(indent1); var tabCount2 = TabCount(indent2); // If the number of indent tabs is the same then it's safe just to use the absolute number of charaters if (tabCount1 == tabCount2) { return indent2.Length > indent1.Length; } else if (tabCount1 > tabCount2 && indent1.Length >= indent2.Length) { // More tabs in first and same or more characters overall -> second definitely shorter return false; } else { // The second string has more tabs. If it is also longer overall then we'll return true. // However, if it has more tabs but fewer letters, we're not sure - it depends on what the tab setting is. // Since we're only returning false if we're sure, we'll return true in that case too. return true; } } private static int TabCount(string text) => text.Count(x => x == '\t'); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ArgumentContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class ArgumentContext : SyntaxBaseContext { public IParameterSymbol Parameter { get; internal set; } public ArgumentContext(SonarSyntaxNodeReportingContext context) : base(context) { } public ArgumentContext(SyntaxNode node, SemanticModel model) : base(node, model) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ArgumentDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public enum MemberKind { Method, Constructor, Indexer, Attribute } public class ArgumentDescriptor { public MemberKind MemberKind { get; } public Func, int?, bool> ArgumentListConstraint { get; } public RefKind? RefKind { get; } public Func ParameterConstraint { get; } public Func InvokedMemberNameConstraint { get; } public Func InvokedMemberNodeConstraint { get; } public Func InvokedMemberConstraint { get; } private ArgumentDescriptor(MemberKind memberKind, Func invokedMemberConstraint, Func invokedMemberNameConstraint, Func invokedMemberNodeConstraint, Func, int?, bool> argumentListConstraint, Func parameterConstraint, RefKind? refKind) { MemberKind = memberKind; ArgumentListConstraint = argumentListConstraint; RefKind = refKind; ParameterConstraint = parameterConstraint; InvokedMemberNameConstraint = invokedMemberNameConstraint; InvokedMemberNodeConstraint = invokedMemberNodeConstraint; InvokedMemberConstraint = invokedMemberConstraint; } public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, int argumentPosition) => MethodInvocation(invokedType, methodName, parameterName, x => x == argumentPosition); public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, Func argumentPosition) => MethodInvocation(invokedType, methodName, x => x.Name == parameterName, argumentPosition, null); public static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, string parameterName, Func argumentPosition, RefKind refKind) => MethodInvocation(invokedType, methodName, x => x.Name == parameterName, argumentPosition, refKind); public static ArgumentDescriptor MethodInvocation(KnownType invokedType, Func invokedMemberNameConstraint, Func parameterConstraint, Func argumentPosition, RefKind? refKind) => MethodInvocation(x => invokedType.Matches(x.ContainingType), invokedMemberNameConstraint, parameterConstraint, argumentPosition, refKind); public static ArgumentDescriptor MethodInvocation(Func invokedMemberConstraint, Func invokedMemberNameConstraint, Func parameterConstraint, Func argumentPosition, RefKind? refKind) => MethodInvocation(invokedMemberConstraint, invokedMemberNameConstraint, (_, _, _) => true, parameterConstraint, (_, position) => position is null || argumentPosition is null || argumentPosition(position.Value), refKind); public static ArgumentDescriptor MethodInvocation(Func invokedMemberConstraint, Func invokedMemberNameConstraint, Func invokedMemberNodeConstraint, Func parameterConstraint, Func, int?, bool> argumentListConstraint, RefKind? refKind) => new(MemberKind.Method, invokedMemberConstraint, invokedMemberNameConstraint, invokedMemberNodeConstraint, argumentListConstraint, parameterConstraint, refKind); public static ArgumentDescriptor ConstructorInvocation(KnownType constructedType, string parameterName, int argumentPosition) => ConstructorInvocation( x => constructedType.Matches(x.ContainingType), (x, c) => x.Equals(constructedType.TypeName, c), static (_, _, _) => true, x => x.Name == parameterName, (_, x) => x is null || x == argumentPosition, null); public static ArgumentDescriptor ConstructorInvocation(Func invokedMethodSymbol, Func invokedMemberNameConstraint, Func invokedMemberNodeConstraint, Func parameterConstraint, Func, int?, bool> argumentListConstraint, RefKind? refKind) => new(MemberKind.Constructor, invokedMemberConstraint: invokedMethodSymbol, invokedMemberNameConstraint, invokedMemberNodeConstraint, argumentListConstraint, parameterConstraint, refKind); public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, Func parameterConstraint, int argumentPosition) => ElementAccess(invokedIndexerContainer, null, parameterConstraint, x => x == argumentPosition); public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, string invokedIndexerExpression, Func parameterConstraint, int argumentPosition) => ElementAccess(invokedIndexerContainer, invokedIndexerExpression, parameterConstraint, x => x == argumentPosition); public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, Func parameterConstraint, Func argumentPositionConstraint) => ElementAccess(invokedIndexerContainer, null, parameterConstraint, argumentPositionConstraint); public static ArgumentDescriptor ElementAccess(KnownType invokedIndexerContainer, string invokedIndexerExpression, Func parameterConstraint, Func argumentPositionConstraint) => ElementAccess( x => x is { ContainingSymbol: INamedTypeSymbol container } && invokedIndexerContainer.Matches(container), (s, c) => invokedIndexerExpression is null || s.Equals(invokedIndexerExpression, c), (_, _, _) => true, parameterConstraint, (_, p) => argumentPositionConstraint is null || p is null || argumentPositionConstraint(p.Value)); public static ArgumentDescriptor ElementAccess(Func invokedIndexerPropertyMethod, Func invokedIndexerExpression, Func invokedIndexerExpressionNodeConstraint, Func parameterConstraint, Func, int?, bool> argumentListConstraint) => new(MemberKind.Indexer, invokedMemberConstraint: invokedIndexerPropertyMethod, invokedMemberNameConstraint: invokedIndexerExpression, invokedMemberNodeConstraint: invokedIndexerExpressionNodeConstraint, argumentListConstraint: argumentListConstraint, parameterConstraint: parameterConstraint, refKind: null); public static ArgumentDescriptor AttributeArgument(KnownType attributeType, string constructorParameterName, int argumentPosition) => AttributeArgument( x => x is { MethodKind: MethodKind.Constructor, ContainingType: { Name: { } name } type } && attributeType.Matches(type) && AttributeClassNameConstraint(attributeType.TypeName, name, StringComparison.Ordinal), (x, c) => AttributeClassNameConstraint(attributeType.TypeName, x, c), (_, _, _) => true, x => x.Name == constructorParameterName, (_, i) => i is null || i.Value == argumentPosition); public static ArgumentDescriptor AttributeArgument(string attributeName, string parameterName, int argumentPosition) => AttributeArgument( x => x is { MethodKind: MethodKind.Constructor, ContainingType.Name: { } name } && AttributeClassNameConstraint(attributeName, name, StringComparison.Ordinal), (x, c) => AttributeClassNameConstraint(attributeName, x, c), (_, _, _) => true, x => x.Name == parameterName, (_, i) => i is null || i.Value == argumentPosition); public static ArgumentDescriptor AttributeArgument(Func attributeConstructorConstraint, Func attributeNameConstraint, Func attributeNodeConstraint, Func parameterConstraint, Func, int?, bool> argumentListConstraint) => new(MemberKind.Attribute, invokedMemberConstraint: attributeConstructorConstraint, invokedMemberNameConstraint: attributeNameConstraint, invokedMemberNodeConstraint: attributeNodeConstraint, argumentListConstraint: argumentListConstraint, parameterConstraint: parameterConstraint, refKind: null); public static ArgumentDescriptor AttributeProperty(KnownType attributeType, string propertyName) => AttributeArgument( attributeConstructorConstraint: x => x is { MethodKind: MethodKind.PropertySet, AssociatedSymbol: { Name: { } name, ContainingType: { } type } } && name == propertyName && attributeType.Matches(type), attributeNameConstraint: (s, c) => AttributeClassNameConstraint(attributeType.TypeName, s, c), (_, _, _) => true, parameterConstraint: _ => true, argumentListConstraint: (_, _) => true); public static ArgumentDescriptor AttributeProperty(string attributeName, string propertyName) => AttributeArgument( attributeConstructorConstraint: x => x is { MethodKind: MethodKind.PropertySet, AssociatedSymbol.Name: { } name } && name == propertyName, attributeNameConstraint: (s, c) => AttributeClassNameConstraint(attributeName, s, c), (_, _, _) => true, parameterConstraint: _ => true, argumentListConstraint: (_, _) => true); private static bool AttributeClassNameConstraint(string attributeTypeName, string nodeClassName, StringComparison c) => nodeClassName.Equals(attributeTypeName, c) || attributeTypeName.Equals($"{nodeClassName}Attribute", c); private static ArgumentDescriptor MethodInvocation(KnownType invokedType, string methodName, Func parameterConstraint, Func argumentPosition, RefKind? refKind) => MethodInvocation(invokedType, (n, c) => n.Equals(methodName, c), parameterConstraint, argumentPosition, refKind); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ArgumentTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class ArgumentTracker : SyntaxTrackerBase where TSyntaxKind : struct { protected abstract RefKind? ArgumentRefKind(SyntaxNode argumentNode); protected abstract IReadOnlyCollection ArgumentList(SyntaxNode argumentNode); protected abstract int? Position(SyntaxNode argumentNode); protected abstract bool InvocationMatchesMemberKind(SyntaxNode invokedExpression, MemberKind memberKind); protected abstract bool InvokedMemberMatches(SemanticModel model, SyntaxNode invokedExpression, MemberKind memberKind, Func invokedMemberNameConstraint); public Condition MatchArgument(ArgumentDescriptor descriptor) => trackingContext => { if (trackingContext.Node is { } argumentNode && argumentNode is { Parent.Parent: { } invoked } && SyntacticChecks(trackingContext.Model, descriptor, argumentNode, invoked) && (descriptor.InvokedMemberNodeConstraint?.Invoke(trackingContext.Model, Language, invoked) ?? true) && MethodSymbol(trackingContext.Model, invoked) is { } methodSymbol && Language.MethodParameterLookup(invoked, methodSymbol).TryGetSymbol(argumentNode, out var parameter) && ParameterMatches(parameter, descriptor.ParameterConstraint, descriptor.InvokedMemberConstraint)) { trackingContext.Parameter = parameter; return true; } return false; }; protected override ArgumentContext CreateContext(SonarSyntaxNodeReportingContext context) => new(context); private IMethodSymbol MethodSymbol(SemanticModel model, SyntaxNode invoked) => model.GetSymbolInfo(invoked).Symbol switch { IMethodSymbol x => x, IPropertySymbol propertySymbol => Language.Syntax.IsWrittenTo(invoked, model, CancellationToken.None) ? propertySymbol.SetMethod : propertySymbol.GetMethod, _ => null, }; // SemanticModel is needed for target-typed-new only. private bool SyntacticChecks(SemanticModel model, ArgumentDescriptor descriptor, SyntaxNode argumentNode, SyntaxNode invokedExpression) => InvocationMatchesMemberKind(invokedExpression, descriptor.MemberKind) && RefKindMatches(descriptor, argumentNode) && (descriptor.ArgumentListConstraint is null || (ArgumentList(argumentNode) is { } argList && descriptor.ArgumentListConstraint(argList, Position(argumentNode)))) && (descriptor.InvokedMemberNameConstraint is null || InvokedMemberMatches(model, invokedExpression, descriptor.MemberKind, x => descriptor.InvokedMemberNameConstraint(x, Language.NameComparison))); private bool RefKindMatches(ArgumentDescriptor descriptor, SyntaxNode argumentNode) => descriptor.RefKind is not { } expectedRefKind // When null: No RefKind constraint was specified || ArgumentRefKind(argumentNode) is not { } actualRefKind // When null: In VB, the argument does not need ref/out keywords on the call side || expectedRefKind == actualRefKind // For parameter ref kind "in", on the call side "in" is optional or can be "ref" instead // For "ref readonly" parameters, "none", "in" and "ref" are allowed on the call side || (expectedRefKind is RefKindEx.In && actualRefKind is RefKind.None or RefKind.Ref) || (expectedRefKind is RefKindEx.RefReadOnlyParameter && actualRefKind is RefKind.None or RefKind.Ref or RefKindEx.In); private static bool ParameterMatches(IParameterSymbol parameter, Func parameterConstraint, Func invokedMemberConstraint) { if (parameter.ContainingSymbol is IMethodSymbol method && method.Parameters.IndexOf(parameter) is >= 0 and int position) { do { if (invokedMemberConstraint?.Invoke(method) is null or true && parameterConstraint?.Invoke(method.Parameters[position]) is null or true) { return true; } } while ((method = method.OverriddenMethod) is not null); } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/AssignmentFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class AssignmentFinder { /// 'true' will find any AssignmentExpressionSyntax like =, +=, -=, &=. 'false' will find only '=' SimpleAssignmentExpression. protected abstract bool IsAssignmentToIdentifier(SyntaxNode node, string identifierName, bool anyAssignmentKind, out SyntaxNode rightExpression); protected abstract bool IsIdentifierDeclaration(SyntaxNode node, string identifierName, out SyntaxNode initializer); protected abstract bool IsLoop(SyntaxNode node); protected abstract SyntaxNode GetTopMostContainingMethod(SyntaxNode node); public SyntaxNode FindLinearPrecedingAssignmentExpression(string identifierName, SyntaxNode current, Func defaultValue = null) { var method = GetTopMostContainingMethod(current); while (current != method && current?.Parent is not null) { if (IsLoop(current) && ContainsNestedAssignmentToIdentifier(current)) { return null; // There's assignment inside this loop, value can be altered by each iteration } foreach (var statement in current.Parent.ChildNodes().TakeWhile(x => x != current).Reverse()) { if (IsAssignmentToIdentifier(statement, identifierName, false, out var right)) { return right; } else if (IsIdentifierDeclaration(statement, identifierName, out var initializer)) { return initializer; } else if (ContainsNestedAssignmentToIdentifier(statement)) { return null; // Assignment inside nested statement (if, try, for, ...) } } current = current.Parent; } return defaultValue?.Invoke(); bool ContainsNestedAssignmentToIdentifier(SyntaxNode node) => node.DescendantNodes().Any(x => IsAssignmentToIdentifier(x, identifierName, true, out _)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/BaseContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class BaseContext { public IList SecondaryLocations { get; } = new List(); public void AddSecondaryLocation(Location location, string message, params string[] formatArgs) { if (location is not null && location != Location.None) { SecondaryLocations.Add(new SecondaryLocation(location, string.Format(message, formatArgs))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/BaseTypeContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; /// /// Syntax and semantic information about an inheritance relationship. /// public class BaseTypeContext : SyntaxBaseContext { /// /// A list of all type syntax nodes for node being analyzed. /// public IEnumerable AllBaseTypeNodes { get; } public BaseTypeContext(SonarSyntaxNodeReportingContext context, IEnumerable allBaseTypeNodes) : base(context) => AllBaseTypeNodes = allBaseTypeNodes ?? []; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/BaseTypeTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; /// /// Tracker class for rules that check the inheritance tree for e.g. disallowed base classes. /// /// The syntax type. public abstract class BaseTypeTracker : SyntaxTrackerBase where TSyntaxKind : struct { /// /// Extract the list of type syntax nodes for the base types/interface types. /// protected abstract IEnumerable GetBaseTypeNodes(SyntaxNode contextNode); internal Condition MatchSubclassesOf(params KnownType[] types) { var immutableTypes = types.ToImmutableArray(); return context => { foreach (var baseTypeNode in context.AllBaseTypeNodes) { if (context.Model.GetTypeInfo(baseTypeNode).Type.DerivesOrImplementsAny(immutableTypes)) { context.PrimaryLocation = baseTypeNode.GetLocation(); return true; // assume there won't be more than one matching node } } return false; }; } protected override BaseTypeContext CreateContext(SonarSyntaxNodeReportingContext context) => GetBaseTypeNodes(context.Node) is { } baseTypeList && baseTypeList.Any() ? new BaseTypeContext(context, baseTypeList) : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/BuilderPatternCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class BuilderPatternCondition where TSyntaxKind : struct where TInvocationSyntax : SyntaxNode { private readonly bool constructorIsSafe; private readonly BuilderPatternDescriptor[] descriptors; private readonly AssignmentFinder assignmentFinder; protected abstract ILanguageFacade Language { get; } protected abstract SyntaxNode GetExpression(TInvocationSyntax node); protected abstract string GetIdentifierName(TInvocationSyntax node); protected abstract bool IsMemberAccess(SyntaxNode node, out SyntaxNode memberAccessExpression); protected abstract bool IsObjectCreation(SyntaxNode node); protected abstract bool IsIdentifier(SyntaxNode node, out string identifierName); protected BuilderPatternCondition(bool constructorIsSafe, BuilderPatternDescriptor[] descriptors, AssignmentFinder assignmentFinder) { this.constructorIsSafe = constructorIsSafe; this.descriptors = descriptors; this.assignmentFinder = assignmentFinder; } public bool IsInvalidBuilderInitialization(InvocationContext context) { var current = context.Node; while (current is not null) { current = Language.Syntax.RemoveParentheses(current); if (current is TInvocationSyntax invocation) { var invocationContext = new InvocationContext(invocation, GetIdentifierName(invocation), context.Model); if (descriptors.FirstOrDefault(x => x.IsMatch(invocationContext)) is { } descriptor) { return !descriptor.IsValid(invocation); } current = GetExpression(invocation); } else if (IsMemberAccess(current, out var memberAccessExpression)) { current = memberAccessExpression; } else if (IsObjectCreation(current)) { // We're sure that full invocation chain started here => we've seen all configuration invocations. return !constructorIsSafe; } else if (IsIdentifier(current, out var identifierName)) { if (!(context.Model.GetSymbolInfo(current).Symbol is ILocalSymbol)) { return false; } // When tracking reaches the local variable in invocation chain 'variable.MethodA().MethodB()' // we'll try to find preceding assignment to that variable to continue inspection of initialization chain. current = assignmentFinder.FindLinearPrecedingAssignmentExpression(identifierName, current); } else { return false; } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/BuilderPatternDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class BuilderPatternDescriptor where TSyntaxKind : struct where TInvocationSyntax : SyntaxNode { private readonly TrackerBase.Condition[] invocationConditions; private readonly Func isValid; public BuilderPatternDescriptor(bool isValid, params TrackerBase.Condition[] invocationConditions) : this(_ => isValid, invocationConditions) { } public BuilderPatternDescriptor(Func isValid, params TrackerBase.Condition[] invocationConditions) { this.isValid = isValid; this.invocationConditions = invocationConditions; } public bool IsMatch(InvocationContext context) => invocationConditions.All(x => x(context)); public bool IsValid(TInvocationSyntax invocation) => isValid(invocation); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ConstantValueFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class ConstantValueFinder where TIdentifierNameSyntax : SyntaxNode where TVariableDeclaratorSyntax : SyntaxNode { protected readonly SemanticModel Model; private readonly AssignmentFinder assignmentFinder; private readonly int nullLiteralExpressionSyntaxKind; protected abstract string IdentifierName(TIdentifierNameSyntax node); protected abstract SyntaxNode InitializerValue(TVariableDeclaratorSyntax node); protected abstract TVariableDeclaratorSyntax VariableDeclarator(SyntaxNode node); protected abstract bool IsPtrZero(SyntaxNode node); protected ConstantValueFinder(SemanticModel model, AssignmentFinder assignmentFinder, int nullLiteralExpressionSyntaxKind) { Model = model; this.assignmentFinder = assignmentFinder; this.nullLiteralExpressionSyntaxKind = nullLiteralExpressionSyntaxKind; } public object FindConstant(SyntaxNode node) => FindConstant(node, null); private object FindConstant(SyntaxNode node, HashSet visitedVariables) { if (node is null || node.RawKind == nullLiteralExpressionSyntaxKind) // Performance shortcut { return null; } if (IsPtrZero(node)) { return 0; } return node.EnsureCorrectSemanticModelOrDefault(Model) is { } nodeModel ? nodeModel.GetConstantValue(node).Value ?? FindAssignedConstant(node, nodeModel, visitedVariables) : null; } private object FindAssignedConstant(SyntaxNode node, SemanticModel model, HashSet visitedVariables) { return node is TIdentifierNameSyntax identifier ? FindConstant(assignmentFinder.FindLinearPrecedingAssignmentExpression(IdentifierName(identifier), node, FindFieldInitializer), visitedVariables) : null; SyntaxNode FindFieldInitializer() { if (model.GetSymbolInfo(identifier).Symbol is IFieldSymbol fieldSymbol && VariableDeclarator(fieldSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax()) is { } variable && (visitedVariables is null || !visitedVariables.Contains(variable))) { visitedVariables ??= new(); visitedVariables.Add(variable); return InitializerValue(variable); } else { return null; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ElementAccessContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class ElementAccessContext : SyntaxBaseContext { public Lazy InvokedPropertySymbol { get; } public ElementAccessContext(SonarSyntaxNodeReportingContext context) : base(context) => InvokedPropertySymbol = new Lazy(() => context.Model.GetSymbolInfo(context.Node).Symbol as IPropertySymbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ElementAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class ElementAccessTracker : SyntaxTrackerBase where TSyntaxKind : struct { public abstract object AssignedValue(ElementAccessContext context); public abstract Condition ArgumentAtIndexEquals(int index, string value); public abstract Condition MatchSetter(); public abstract Condition MatchProperty(MemberDescriptor member); internal Condition ArgumentAtIndexIs(int index, params KnownType[] types) => context => context.InvokedPropertySymbol.Value is { } property && property.Parameters.Length > index && property.Parameters[0].Type.DerivesOrImplements(types[index]); internal Condition MatchIndexerIn(params KnownType[] types) => context => context.InvokedPropertySymbol.Value is { } property && property.ContainingType.DerivesOrImplementsAny(types.ToImmutableArray()); protected override ElementAccessContext CreateContext(SonarSyntaxNodeReportingContext context) => new(context); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/FieldAccessContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class FieldAccessContext : SyntaxBaseContext { public string FieldName { get; } public Lazy InvokedFieldSymbol { get; } public FieldAccessContext(SonarSyntaxNodeReportingContext context, string fieldName) : base(context) { FieldName = fieldName; InvokedFieldSymbol = new Lazy(() => Model.GetSymbolInfo(Node).Symbol as IFieldSymbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/FieldAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class FieldAccessTracker : SyntaxTrackerBase where TSyntaxKind : struct { public abstract Condition WhenRead(); public abstract Condition MatchSet(); public abstract Condition AssignedValueIsConstant(); protected abstract bool IsIdentifierWithinMemberAccess(SyntaxNode expression); public Condition MatchField(params MemberDescriptor[] fields) => context => MemberDescriptor.MatchesAny(context.FieldName, context.InvokedFieldSymbol, false, Language.NameComparison, fields); protected override FieldAccessContext CreateContext(SonarSyntaxNodeReportingContext context) { // We register for both MemberAccess and IdentifierName and we want to avoid raising two times for the same identifier. if (IsIdentifierWithinMemberAccess(context.Node)) { return null; } return Language.Syntax.NodeIdentifier(context.Node) is { } fieldIdentifier ? new FieldAccessContext(context, fieldIdentifier.ValueText) : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/InvocationContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class InvocationContext : SyntaxBaseContext { public string MethodName { get; } public Lazy MethodSymbol { get; } public InvocationContext(SonarSyntaxNodeReportingContext context, string methodName) : this(context.Node, methodName, context.Model) { } public InvocationContext(SyntaxNode node, string methodName, SemanticModel model) : base(node, model) { MethodName = methodName; MethodSymbol = new Lazy(() => Model.GetSymbolInfo(Node).Symbol as IMethodSymbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/InvocationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class InvocationTracker : SyntaxTrackerBase where TSyntaxKind : struct { public abstract Condition ArgumentAtIndexIsStringConstant(int index); public abstract Condition ArgumentAtIndexIsAny(int index, params string[] values); public abstract Condition ArgumentAtIndexIs(int index, Func predicate); public abstract Condition MatchProperty(MemberDescriptor member); public abstract object ConstArgumentForParameter(InvocationContext context, string parameterName); protected abstract SyntaxToken? ExpectedExpressionIdentifier(SyntaxNode expression); public Condition MatchMethod(params MemberDescriptor[] methods) => context => MemberDescriptor.MatchesAny(context.MethodName, context.MethodSymbol, true, Language.NameComparison, methods); public Condition MethodNameIs(string methodName) => context => context.MethodName == methodName; public Condition MethodIsStatic() => context => context.MethodSymbol.Value is { IsStatic: true }; public Condition MethodIsExtension() => context => context.MethodSymbol.Value is { IsExtensionMethod: true }; public Condition MethodHasParameters(int count) => context => context.MethodSymbol.Value is { } method && method.Parameters.Length == count; public Condition IsInvalidBuilderInitialization(BuilderPatternCondition condition) where TInvocationSyntax : SyntaxNode => condition.IsInvalidBuilderInitialization; internal Condition MethodReturnTypeIs(KnownType returnType) => context => context.MethodSymbol.Value is { } method && method.ReturnType.DerivesFrom(returnType); internal Condition ArgumentIsBoolConstant(string parameterName, bool expectedValue) => context => ConstArgumentForParameter(context, parameterName) is bool boolValue && boolValue == expectedValue; internal Condition IsIHeadersDictionary() => context => context.MethodSymbol.Value.ContainingType.TypeArguments is var typeArguments && typeArguments.Length == 2 && typeArguments[0].Is(KnownType.System_String) && typeArguments[1].Is(KnownType.Microsoft_Extensions_Primitives_StringValues); protected virtual SyntaxNode NodeExpression(SyntaxNode node) => Language.Syntax.NodeExpression(node); protected override InvocationContext CreateContext(SonarSyntaxNodeReportingContext context) => NodeExpression(context.Node) is { } expression && ExpectedExpressionIdentifier(expression) is { } identifier ? new InvocationContext(context, identifier.ValueText) : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/MemberDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class MemberDescriptor { internal KnownType ContainingType { get; } internal string Name { get; } internal MemberDescriptor(KnownType containingType, string name) { ContainingType = containingType; Name = name; } public override string ToString() => $"{ContainingType.TypeName}.{Name}"; public bool IsMatch(string memberName, ITypeSymbol containingType, StringComparison nameComparison) => HasSameName(memberName, Name, nameComparison) && containingType.Is(ContainingType); public bool IsMatch(string memberName, Lazy memberSymbol, StringComparison nameComparison) where TSymbolType : class, ISymbol => HasSameName(memberName, Name, nameComparison) && memberSymbol.Value is { } symbol && HasSameContainingType(symbol, checkOverriddenMethods: true); public static bool MatchesAny(string memberName, Lazy memberSymbol, bool checkOverriddenMethods, StringComparison nameComparison, params MemberDescriptor[] members) where TSymbolType : class, ISymbol => memberName != null // avoid calling the semantic model if no name matches && members.Any(x => memberName.Equals(x.Name, nameComparison)) && memberSymbol.Value is { } symbol // we need to check both Name and Type to make sure the right method on the right type is called && members.Any(x => memberName.Equals(x.Name, nameComparison) && x.HasSameContainingType(symbol, checkOverriddenMethods)); private static bool HasSameName(string name1, string name2, StringComparison comparison) => name1 != null && name1.Equals(name2, comparison); private bool HasSameContainingType(TSymbolType memberSymbol, bool checkOverriddenMethods) where TSymbolType : class, ISymbol { var containingType = memberSymbol.ContainingType?.ConstructedFrom; return containingType != null && checkOverriddenMethods ? containingType.DerivesOrImplements(ContainingType) : containingType.Is(ContainingType); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/MethodDeclarationContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class MethodDeclarationContext : BaseContext { private readonly Compilation compilation; public IMethodSymbol MethodSymbol { get; } public MethodDeclarationContext(IMethodSymbol methodSymbol, Compilation compilation) { MethodSymbol = methodSymbol; this.compilation = compilation; } public SemanticModel GetSemanticModel(SyntaxNode node) => compilation.GetSemanticModel(node.SyntaxTree); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/MethodDeclarationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Trackers; public abstract class MethodDeclarationTracker : TrackerBase where TSyntaxKind : struct { public abstract Condition ParameterAtIndexIsUsed(int index); protected abstract SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration); public void Track(TrackerInput input, params Condition[] conditions) { input.Context.RegisterCompilationStartAction(c => { if (input.IsEnabled(c.Options)) { c.RegisterSymbolAction(TrackMethodDeclaration, SymbolKind.Method); } }); void TrackMethodDeclaration(SonarSymbolReportingContext c) { if (!IsTrackedMethod((IMethodSymbol)c.Symbol, c.Compilation)) { return; } foreach (var declaration in c.Symbol.DeclaringSyntaxReferences) { if (c.Symbol.IsTopLevelMain()) { c.ReportIssue(Language.GeneratedCodeRecognizer, input.Rule, Location.Create(declaration.SyntaxTree, TextSpan.FromBounds(0, 0))); } else { var methodIdentifier = GetMethodIdentifier(declaration.GetSyntax()); if (methodIdentifier.HasValue) { c.ReportIssue(Language.GeneratedCodeRecognizer, input.Rule, methodIdentifier.Value); } } } } bool IsTrackedMethod(IMethodSymbol methodSymbol, Compilation compilation) { var conditionContext = new MethodDeclarationContext(methodSymbol, compilation); return conditions.All(c => c(conditionContext)); } } public Condition MatchMethodName(params string[] methodNames) => context => methodNames.Contains(context.MethodSymbol.Name); public Condition IsOrdinaryMethod() => context => context.MethodSymbol.MethodKind == MethodKind.Ordinary; public Condition IsMainMethod() => context => context.MethodSymbol.IsMainMethod() || context.MethodSymbol.IsTopLevelMain(); internal Condition AnyParameterIsOfType(params KnownType[] types) { var typesArray = types.ToImmutableArray(); return context => context.MethodSymbol.Parameters.Length > 0 && context.MethodSymbol.Parameters.Any(parameter => parameter.Type.DerivesOrImplementsAny(typesArray)); } internal Condition DecoratedWithAnyAttribute(params KnownType[] attributeTypes) => context => context.MethodSymbol.GetAttributes().Any(a => a.AttributeClass.IsAny(attributeTypes)); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ObjectCreationContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class ObjectCreationContext : SyntaxBaseContext { public Lazy InvokedConstructorSymbol { get; } public ObjectCreationContext(SonarSyntaxNodeReportingContext context) : base(context) => InvokedConstructorSymbol = new Lazy(() => context.Model.GetSymbolInfo(context.Node).Symbol as IMethodSymbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/ObjectCreationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class ObjectCreationTracker : SyntaxTrackerBase where TSyntaxKind : struct { public abstract Condition ArgumentAtIndexIsConst(int index); public abstract object ConstArgumentForParameter(ObjectCreationContext context, string parameterName); protected override TSyntaxKind[] TrackedSyntaxKinds => Language.SyntaxKind.ObjectCreationExpressions; internal Condition ArgumentIsBoolConstant(string parameterName, bool expectedValue) => context => ConstArgumentForParameter(context, parameterName) is bool boolValue && boolValue == expectedValue; internal Condition ArgumentAtIndexIs(int index, KnownType type) => context => context.InvokedConstructorSymbol.Value is { } constructor && constructor.Parameters.Length > index && constructor.Parameters[index].Type.Is(type); internal Condition WhenDerivesOrImplementsAny(params KnownType[] types) => context => context.InvokedConstructorSymbol.Value is { } constructor && constructor.IsConstructor() && constructor.ContainingType.DerivesOrImplementsAny(types.ToImmutableArray()); internal Condition MatchConstructor(params KnownType[] types) => // We cannot do a syntax check first because a type name can be aliased with // a using Alias = Fully.Qualified.Name and we will generate false negative // for new Alias() context => context.InvokedConstructorSymbol.Value is { } constructor && constructor.IsConstructor() && constructor.ContainingType.IsAny(types); internal Condition WhenDerivesFrom(KnownType baseType) => context => context.InvokedConstructorSymbol.Value is { } constructor && constructor.IsConstructor() && constructor.ContainingType.DerivesFrom(baseType); internal Condition WhenImplements(KnownType baseType) => context => context.InvokedConstructorSymbol.Value is { } constructor && constructor.IsConstructor() && constructor.ContainingType.Implements(baseType); protected override ObjectCreationContext CreateContext(SonarSyntaxNodeReportingContext context) => new ObjectCreationContext(context); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/PropertyAccessContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class PropertyAccessContext : SyntaxBaseContext { public string PropertyName { get; } public Lazy PropertySymbol { get; } public PropertyAccessContext(SonarSyntaxNodeReportingContext context, string propertyName) : base(context) { PropertyName = propertyName; PropertySymbol = new Lazy(() => context.Model.GetSymbolInfo(context.Node).Symbol as IPropertySymbol); } public PropertyAccessContext(SyntaxNode node, SemanticModel model, string propertyName) : base(node, model) { PropertyName = propertyName; PropertySymbol = new Lazy(() => model.GetSymbolInfo(node).Symbol as IPropertySymbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/PropertyAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class PropertyAccessTracker : SyntaxTrackerBase where TSyntaxKind : struct { public abstract object AssignedValue(PropertyAccessContext context); public abstract Condition MatchGetter(); public abstract Condition MatchSetter(); public abstract Condition AssignedValueIsConstant(); protected abstract bool IsIdentifierWithinMemberAccess(SyntaxNode expression); public Condition MatchProperty(params MemberDescriptor[] properties) => MatchProperty(false, properties); public Condition MatchProperty(bool checkOverridenProperties, params MemberDescriptor[] properties) => context => MemberDescriptor.MatchesAny(context.PropertyName, context.PropertySymbol, checkOverridenProperties, Language.NameComparison, properties); protected override PropertyAccessContext CreateContext(SonarSyntaxNodeReportingContext context) { // We register for both MemberAccess and IdentifierName and we want to // avoid raising two times for the same identifier. if (IsIdentifierWithinMemberAccess(context.Node)) { return null; } return Language.Syntax.NodeIdentifier(context.Node) is { } propertyIdentifier ? new PropertyAccessContext(context, propertyIdentifier.ValueText) : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/SyntaxBaseContext.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class SyntaxBaseContext : BaseContext { public SemanticModel Model { get; } public SyntaxNode Node { get; } public Location PrimaryLocation { get; set; } public SyntaxBaseContext(SonarSyntaxNodeReportingContext context) : this(context.Node, context.Model) { } public SyntaxBaseContext(SyntaxNode node, SemanticModel model) { Model = model; Node = node; PrimaryLocation = Node.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/SyntaxTrackerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class SyntaxTrackerBase : TrackerBase where TSyntaxKind : struct where TContext : SyntaxBaseContext { protected abstract TSyntaxKind[] TrackedSyntaxKinds { get; } protected abstract TContext CreateContext(SonarSyntaxNodeReportingContext context); public void Track(TrackerInput input, params Condition[] conditions) => Track(input, [], conditions); public void Track(TrackerInput input, string[] diagnosticMessageArgs, params Condition[] conditions) { input.Context.RegisterCompilationStartAction(c => { if (input.IsEnabled(c.Options)) { c.RegisterNodeAction(Language.GeneratedCodeRecognizer, TrackAndReportIfNecessary, TrackedSyntaxKinds); } }); void TrackAndReportIfNecessary(SonarSyntaxNodeReportingContext c) { if (CreateContext(c) is { } trackingContext && Array.TrueForAll(conditions, x => x(trackingContext)) && trackingContext.PrimaryLocation is not null && trackingContext.PrimaryLocation != Location.None) { c.ReportIssue(input.Rule, trackingContext.PrimaryLocation, trackingContext.SecondaryLocations, diagnosticMessageArgs); } } } public Condition ExceptWhen(Condition condition) => x => !condition(x); public Condition And(Condition condition1, Condition condition2) => x => condition1(x) && condition2(x); public Condition Or(Condition condition1, Condition condition2) => x => condition1(x) || condition2(x); public Condition Or(Condition condition1, Condition condition2, Condition condition3) => x => condition1(x) || condition2(x) || condition3(x); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/TrackerBase.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public abstract class TrackerBase where TSyntaxKind : struct where TContext : BaseContext { protected abstract ILanguageFacade Language { get; } public delegate bool Condition(TContext trackingContext); } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/Trackers/TrackerInput.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Trackers; public class TrackerInput { private readonly IAnalyzerConfiguration configuration; public DiagnosticDescriptor Rule { get; } public SonarAnalysisContext Context { get; } public TrackerInput(SonarAnalysisContext context, IAnalyzerConfiguration configuration, DiagnosticDescriptor rule) { Context = context; this.configuration = configuration; Rule = rule; } public bool IsEnabled(AnalyzerOptions options) { configuration.Initialize(options); return configuration.IsEnabled(Rule.Id); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Core/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Google.Protobuf": { "type": "Direct", "requested": "[3.6.1, )", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Grpc.Tools": { "type": "Direct", "requested": "[2.26.0, )", "resolved": "2.26.0", "contentHash": "qTsydkJmYauTeKxetCUSJKh25u5Z3oSs7UokdBrpS+tiR8zZmI46mddfj0Bd7GChBvcbnbWtDnZcmEZFzRaF0w==" }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Direct", "requested": "[1.0.27, )", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/LoggingFrameworkMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace Roslyn.Utilities.SonarAnalyzer.Shared; public static class LoggingFrameworkMethods { public static readonly HashSet MicrosoftExtensionsLogging = [ "Log", "LogCritical", "LogDebug", "LogError", "LogInformation", "LogTrace", "LogWarning" ]; public static readonly HashSet CastleCoreOrCommonCore = [ "Debug", "DebugFormat", "Error", "ErrorFormat", "Fatal", "FatalFormat", "Info", "InfoFormat", "Trace", "TraceFormat", "Warn", "WarnFormat" ]; public static readonly HashSet Log4NetILog = [ "Debug", "Error", "Fatal", "Info", "Warn" ]; public static readonly HashSet Log4NetILogExtensions = [ "DebugExt", "ErrorExt", "FatalExt", "InfoExt", "WarnExt" ]; public static readonly HashSet Serilog = [ "Debug", "Error", "Information", "Fatal", "Warning", "Write", "Verbose", ]; public static readonly HashSet NLogLoggingMethods = [ "Debug", "ConditionalDebug", "Error", "Fatal", "Info", "Trace", "ConditionalTrace", "Warn" ]; public static readonly HashSet NLogILoggerBase = ["Log"]; } ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/SonarAnalyzer.Shared.projitems ================================================  $(MSBuildAllProjects);$(MSBuildThisFileFullPath) true 892cf3fa-3be2-42e8-a7e6-76ae72864dec SonarAnalyzer.Shared ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/SonarAnalyzer.Shared.shproj ================================================ 892cf3fa-3be2-42e8-a7e6-76ae72864dec 14.0 ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/Syntax/Extensions/MemberAccessExpressionSyntaxExtensionsShared.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if CS namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; #else namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; #endif internal static class MemberAccessExpressionSyntaxExtensionsShared { public static bool IsPtrZero(this MemberAccessExpressionSyntax memberAccess, SemanticModel model) => memberAccess.Name.Identifier.Text == nameof(IntPtr.Zero) && memberAccess.EnsureCorrectSemanticModelOrDefault(model) is { } maModel && maModel.GetTypeInfo(memberAccess).Type is var type && type.IsAny(KnownType.System_IntPtr, KnownType.System_UIntPtr); } ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/Syntax/Extensions/StatementSyntaxExtensionsShared.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if CS namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; #else namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; #endif public static class StatementSyntaxExtensionsShared { /// /// Returns all statements before the specified statement within the containing method. /// This method recursively traverses all parent blocks of the provided statement. /// public static IEnumerable GetPreviousStatements(this StatementSyntax statement) { var previousStatements = statement.GetPreviousStatementsCurrentBlock(); return statement.Parent is StatementSyntax parentStatement ? previousStatements.Union(GetPreviousStatements(parentStatement)) : previousStatements; } } ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/Syntax/Extensions/SyntaxNodeExtensionsShared.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if CS namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; #else namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; #endif public static class SyntaxNodeExtensionsShared { public static bool ContainsGetOrSetOnDependencyProperty(this SyntaxNode node, Compilation compilation) { var model = compilation.GetSemanticModel(node.SyntaxTree); // Ignore the accessor if it calls System.Windows.DependencyObject.GetValue or System.Windows.DependencyObject.SetValue return node .DescendantNodes() .OfType() .Where(x => x.Expression.NameIs("GetValue") || x.Expression.NameIs("SetValue")) .Any(x => model.GetSymbolInfo(x).Symbol?.ContainingType?.DerivesFrom(KnownType.System_Windows_DependencyObject) is true); } public static IEnumerable GetPreviousStatementsCurrentBlock(this SyntaxNode expression) { var statement = expression.FirstAncestorOrSelf(); return statement is null ? [] : statement.Parent.ChildNodes().OfType().TakeWhile(x => x != statement).Reverse(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.Shared/Syntax/Extensions/SyntaxTokenExtensionsShared.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if CS namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions; #else namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; #endif public static class SyntaxTokenExtensionsShared { // Based on Roslyn: https://github.com/dotnet/roslyn/blob/09903da31892a30f71eab67d2fd83232cfbf0cea/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs#L1187-L1254 public static SyntaxNode GetBindableParent(this SyntaxToken token) { var node = token.Parent; while (node is not null) { var parent = node.Parent; switch (parent) { // If this node is on the left side of a member access expression, don't ascend // further or we'll end up binding to something else. case MemberAccessExpressionSyntax memberAccess when memberAccess.Expression == node: return node; // If this node is on the left side of a qualified name, don't ascend // further or we'll end up binding to something else. case QualifiedNameSyntax qualifiedName when qualifiedName.Left == node: return node; // If this node is the type of an object creation expression, return the // object creation expression. case ObjectCreationExpressionSyntax objectCreation when objectCreation.Type == node: return parent; // The inside of an interpolated string is treated as its own token so we // need to force navigation to the parent expression syntax. case InterpolatedStringExpressionSyntax _ when node is InterpolatedStringTextSyntax: return parent; case NameSyntax _: node = parent; break; // If this node is not parented by a name, we're done. default: return node; } } return null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/Common/ISyntaxWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; namespace SonarAnalyzer.ShimLayer; public interface ISyntaxWrapper where T : SyntaxNode { T SyntaxNode { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/Common/SyntaxNodeTypes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.ShimLayer; internal static class SyntaxNodeTypes { public static Type LatestType(Type wrapper) { return Load(wrapper, nameof(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName)) ?? Load(wrapper, nameof(BaseNamespaceDeclarationSyntaxWrapper.FallbackWrappedTypeName)); static Type Load(Type wrapper, string fieldName) => wrapper.GetField(fieldName, BindingFlags.Static | BindingFlags.Public) is { } field && field.GetValue(null) is string name ? typeof(CSharpSyntaxNode).Assembly.GetType(name) // This may need to be extended to other assemblies if needed. See TypeLoader.LoadBaseline and .LoadLatest : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/PartialTypes/BaseNamespaceDeclarationSyntaxWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.ShimLayer; public partial struct BaseNamespaceDeclarationSyntaxWrapper { public const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax"; private static Func, MemberDeclarationSyntax> withUsingsAccessor; public BaseNamespaceDeclarationSyntaxWrapper WithUsings(SyntaxList usings) // This should be removed once we Shim methods { withUsingsAccessor ??= LightupHelpers.CreateSyntaxWithPropertyAccessor>(WrappedType, nameof(Usings)); return new BaseNamespaceDeclarationSyntaxWrapper(withUsingsAccessor(Node, usings)); } public static implicit operator BaseNamespaceDeclarationSyntaxWrapper(NamespaceDeclarationSyntax node) => new(node); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/PartialTypes/BaseObjectCreationExpressionSyntaxWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.ShimLayer; public partial struct BaseObjectCreationExpressionSyntaxWrapper { public const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ObjectCreationExpressionSyntax"; public static implicit operator BaseObjectCreationExpressionSyntaxWrapper(ObjectCreationExpressionSyntax node) => new(node); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/PartialTypes/CommonForEachStatementSyntaxWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.ShimLayer; public partial struct CommonForEachStatementSyntaxWrapper { public const string FallbackWrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax"; public static implicit operator CommonForEachStatementSyntaxWrapper(ForEachStatementSyntax node) => new(node); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/SonarAnalyzer.ShimLayer.csproj ================================================  netstandard2.0 NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/TemporaryLightUp/LightupHelpers.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace SonarAnalyzer.ShimLayer { using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Roslyn.Utilities; internal static class LightupHelpers { private static readonly ConcurrentDictionary> SupportedObjectWrappers = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SupportedSyntaxWrappers = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SupportedOperationWrappers = new ConcurrentDictionary>(); public static bool SupportsCSharp7 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7)); public static bool SupportsCSharp71 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_1)); public static bool SupportsCSharp72 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_2)); public static bool SupportsCSharp73 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_3)); public static bool SupportsCSharp8 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp8)); public static bool SupportsCSharp9 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp9)); public static bool SupportsCSharp10 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp10)); public static bool SupportsCSharp11 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp11)); public static bool SupportsCSharp12 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp12)); public static bool SupportsIOperation => SupportsCSharp73; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapObject(object obj, Type underlyingType) { if (obj == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class if (!wrappedObject.TryGetValue(obj.GetType(), out var canCast)) { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo()); wrappedObject.TryAdd(obj.GetType(), canCast); } return canCast; } [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapNode(SyntaxNode node, Type underlyingType) { if (node == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class if (!wrappedSyntax.TryGetValue(node.Kind(), out var canCast)) { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(node.GetType().GetTypeInfo()); wrappedSyntax.TryAdd(node.Kind(), canCast); } return canCast; } [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapOperation(IOperation operation, Type underlyingType) { if (operation == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class // Sonar: https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.operationkind Loop && CaseClause are further differentiated by LoopKind & CaseKind, but are not castable between different Kinds which can result in InvalidCast Exceptions // ToDo: Commented out for now, needs OperationKindEx to be generated if (!wrappedSyntax.TryGetValue(operation.Kind, out var canCast)) //if (!wrappedSyntax.TryGetValue(operation.Kind, out var canCast) || operation.Kind is OperationKindEx.Loop or OperationKindEx.CaseClause) // Sonar { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(operation.GetType().GetTypeInfo()); wrappedSyntax.TryAdd(operation.Kind, canCast); } return canCast; } internal static Func CreateOperationPropertyAccessor(Type type, string propertyName) { TProperty FallbackAccessor(TOperation syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TOperation).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } var operationParameter = Expression.Parameter(typeof(TOperation), "operation"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TOperation).GetTypeInfo()) ? (Expression)operationParameter : Expression.Convert(operationParameter, type); // ToDo: Commented out for now //if (property.PropertyType.FullName == "Microsoft.CodeAnalysis.FlowAnalysis.CaptureId") // Sonar - begin //{ // var constructor = typeof(CaptureId).GetConstructors().Single(); // Expression> expression = // Expression.Lambda>( // Expression.New(constructor, Expression.Convert(Expression.Call(instance, property.GetMethod), typeof(object))), // operationParameter); // return expression.Compile(); //} //else if (typeof(TProperty).IsEnum && property.PropertyType.IsEnum) { Expression> expression = Expression.Lambda>( Expression.Convert(Expression.Call(instance, property.GetMethod), typeof(TProperty)), operationParameter); return expression.Compile(); } else if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } else { Expression> expression = Expression.Lambda>( Expression.Call(instance, property.GetMethod), operationParameter); return expression.Compile(); } // Sonar - end } internal static Func> CreateOperationListPropertyAccessor(Type type, string propertyName) { ImmutableArray FallbackAccessor(TOperation syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return ImmutableArray.Empty; } if (type == null) { return FallbackAccessor; } if (!typeof(TOperation).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(ImmutableArray<>)) { throw new InvalidOperationException(); } var propertyOperationType = property.PropertyType.GenericTypeArguments[0]; if (!typeof(IOperation).GetTypeInfo().IsAssignableFrom(propertyOperationType.GetTypeInfo())) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TOperation), "syntax"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TOperation).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression propertyAccess = Expression.Call(instance, property.GetMethod); var unboundCastUpMethod = typeof(ImmutableArray).GetTypeInfo().GetDeclaredMethod(nameof(ImmutableArray.CastUp)); var boundCastUpMethod = unboundCastUpMethod.MakeGenericMethod(propertyOperationType); Expression>> expression = Expression.Lambda>>( Expression.Call(boundCastUpMethod, propertyAccess), syntaxParameter); return expression.Compile(); } internal static Func CreateStaticPropertyAccessor(Type type, string propertyName) { static TProperty FallbackAccessor() { return default; } if (type == null) { return FallbackAccessor; } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } Expression> expression = Expression.Lambda>( Expression.Call(null, property.GetMethod)); return expression.Compile(); } public static Func CreateSyntaxPropertyAccessor(Type type, string propertyName) { TProperty FallbackAccessor(TSyntax syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); // Sonar - begin Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression body = Expression.Call(instance, property.GetMethod); if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { body = Expression.Convert(body, typeof(TProperty)); } return Expression.Lambda>(body, syntaxParameter).Compile(); // Sonar - end } internal static Func CreateSyntaxPropertyAccessor(Type type, Type argumentType, string accessorMethodName) { static TProperty FallbackAccessor(TSyntax syntax, TArg argument) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TArg).GetTypeInfo().IsAssignableFrom(argumentType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(accessorMethodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 1) { continue; } if (Equals(argumentType, parameters[0].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var argParameter = Expression.Parameter(typeof(TArg), "arg"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression argument = argumentType.GetTypeInfo().IsAssignableFrom(typeof(TArg).GetTypeInfo()) ? (Expression)argParameter : Expression.Convert(argParameter, argumentType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, argument), syntaxParameter, argParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type keyType, string methodName) { static bool FallbackAccessor(TSyntax syntax, TKey key, out TValue value) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TKey).GetTypeInfo().IsAssignableFrom(keyType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 2) { continue; } if (Equals(keyType, parameters[0].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[1].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var keyParameter = Expression.Parameter(typeof(TKey), "key"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression key = keyType.GetTypeInfo().IsAssignableFrom(typeof(TKey).GetTypeInfo()) ? (Expression)keyParameter : Expression.Convert(keyParameter, keyType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, key, valueParameter), syntaxParameter, keyParameter, valueParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type firstType, Type secondType, string methodName) // Sonar { static bool FallbackAccessor(TSender sender, TFirst first, TSecond second, out TValue value) { if (sender == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSender).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TFirst).GetTypeInfo().IsAssignableFrom(firstType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TSecond).GetTypeInfo().IsAssignableFrom(secondType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 4) { continue; } if (Equals(firstType, parameters[0].ParameterType) && Equals(secondType, parameters[1].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[2].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var senderParameter = Expression.Parameter(typeof(TSender), "sender"); var firstParameter = Expression.Parameter(typeof(TFirst), "first"); var secondParameter = Expression.Parameter(typeof(TSecond), "second"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSender).GetTypeInfo()) ? (Expression)senderParameter : Expression.Convert(senderParameter, type); Expression first = firstType.GetTypeInfo().IsAssignableFrom(typeof(TFirst).GetTypeInfo()) ? (Expression)firstParameter : Expression.Convert(firstParameter, firstType); Expression second = secondType.GetTypeInfo().IsAssignableFrom(typeof(TSecond).GetTypeInfo()) ? (Expression)secondParameter : Expression.Convert(secondParameter, secondType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, first, second, valueParameter), senderParameter, firstParameter, secondParameter, valueParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type firstType, Type secondType, Type thirdType, string methodName) // Sonar { static bool FallbackAccessor(TSender sender, TFirst first, TSecond second, TThird third, out TValue value) { if (sender == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSender).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TFirst).GetTypeInfo().IsAssignableFrom(firstType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TSecond).GetTypeInfo().IsAssignableFrom(secondType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TThird).GetTypeInfo().IsAssignableFrom(thirdType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 4) { continue; } if (Equals(firstType, parameters[0].ParameterType) && Equals(secondType, parameters[1].ParameterType) && Equals(thirdType, parameters[2].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[3].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var senderParameter = Expression.Parameter(typeof(TSender), "sender"); var firstParameter = Expression.Parameter(typeof(TFirst), "first"); var secondParameter = Expression.Parameter(typeof(TSecond), "second"); var thirdParameter = Expression.Parameter(typeof(TThird), "third"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSender).GetTypeInfo()) ? (Expression)senderParameter : Expression.Convert(senderParameter, type); Expression first = firstType.GetTypeInfo().IsAssignableFrom(typeof(TFirst).GetTypeInfo()) ? (Expression)firstParameter : Expression.Convert(firstParameter, firstType); Expression second = secondType.GetTypeInfo().IsAssignableFrom(typeof(TSecond).GetTypeInfo()) ? (Expression)secondParameter : Expression.Convert(secondParameter, secondType); Expression third = thirdType.GetTypeInfo().IsAssignableFrom(typeof(TThird).GetTypeInfo()) ? (Expression)thirdParameter : Expression.Convert(thirdParameter, thirdType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, first, second, third, valueParameter), senderParameter, firstParameter, secondParameter, thirdParameter, valueParameter); return expression.Compile(); } internal static Func> CreateSeparatedSyntaxListPropertyAccessor(Type type, string propertyName) { SeparatedSyntaxListWrapper FallbackAccessor(TSyntax syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return SeparatedSyntaxListWrapper.UnsupportedEmpty; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>)) { throw new InvalidOperationException(); } var propertySyntaxType = property.PropertyType.GenericTypeArguments[0]; if (!ValidatePropertyType(typeof(TProperty), propertySyntaxType)) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression propertyAccess = Expression.Call(instance, property.GetMethod); var unboundWrapperType = typeof(SeparatedSyntaxListWrapper<>.AutoWrapSeparatedSyntaxList<>); var boundWrapperType = unboundWrapperType.MakeGenericType(typeof(TProperty), propertySyntaxType); var constructorInfo = boundWrapperType.GetTypeInfo().DeclaredConstructors.Single(constructor => constructor.GetParameters().Length == 1); Expression>> expression = Expression.Lambda>>( Expression.New(constructorInfo, propertyAccess), syntaxParameter); return expression.Compile(); } internal static Func CreateSyntaxWithPropertyAccessor(Type type, string propertyName) { TSyntax FallbackAccessor(TSyntax syntax, TProperty newValue) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } if (Equals(newValue, default(TProperty))) { return syntax; } throw new NotSupportedException(); } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName) .SingleOrDefault(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType)); if (methodInfo is null) { return FallbackAccessor; } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var valueParameter = Expression.Parameter(typeof(TProperty), methodInfo.GetParameters()[0].Name); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression value = property.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(TProperty).GetTypeInfo()) ? (Expression)valueParameter : Expression.Convert(valueParameter, property.PropertyType); Expression> expression = Expression.Lambda>( Expression.Call(instance, methodInfo, value), syntaxParameter, valueParameter); return expression.Compile(); } internal static Func, TSyntax> CreateSeparatedSyntaxListWithPropertyAccessor(Type type, string propertyName) { TSyntax FallbackAccessor(TSyntax syntax, SeparatedSyntaxListWrapper newValue) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } if (newValue is null) { return syntax; } throw new NotSupportedException(); } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>)) { throw new InvalidOperationException(); } var propertySyntaxType = property.PropertyType.GenericTypeArguments[0]; if (!ValidatePropertyType(typeof(TProperty), propertySyntaxType)) { throw new InvalidOperationException(); } var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName) .Single(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType)); var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var valueParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), methodInfo.GetParameters()[0].Name); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression value = Expression.Convert( Expression.Call(valueParameter, underlyingListProperty.GetMethod), property.PropertyType); Expression, TSyntax>> expression = Expression.Lambda, TSyntax>>( Expression.Call(instance, methodInfo, value), syntaxParameter, valueParameter); return expression.Compile(); } private static bool ValidatePropertyType(Type returnType, Type actualType) { var requiredType = SyntaxNodeTypes.LatestType(returnType) ?? returnType; return requiredType == actualType; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/TemporaryLightUp/SeparatedSyntaxListWrapper`1.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace SonarAnalyzer.ShimLayer { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; public abstract class SeparatedSyntaxListWrapper : IEquatable>, IReadOnlyList { private static readonly SyntaxWrapper SyntaxWrapper = SyntaxWrapper.Default; public static SeparatedSyntaxListWrapper UnsupportedEmpty { get; } = new UnsupportedSyntaxList(); public abstract int Count { get; } public abstract TextSpan FullSpan { get; } public abstract int SeparatorCount { get; } public abstract TextSpan Span { get; } [EditorBrowsable(EditorBrowsableState.Never)] public abstract object UnderlyingList { get; } public abstract TNode this[int index] { get; } public static bool operator ==(SeparatedSyntaxListWrapper left, SeparatedSyntaxListWrapper right) { // Currently unused _ = left; _ = right; throw new NotImplementedException(); } public static bool operator !=(SeparatedSyntaxListWrapper left, SeparatedSyntaxListWrapper right) { // Currently unused _ = left; _ = right; throw new NotImplementedException(); } // Summary: // Creates a new list with the specified node added to the end. // // Parameters: // node: // The node to add. public SeparatedSyntaxListWrapper Add(TNode node) => this.Insert(this.Count, node); // Summary: // Creates a new list with the specified nodes added to the end. // // Parameters: // nodes: // The nodes to add. public SeparatedSyntaxListWrapper AddRange(IEnumerable nodes) => this.InsertRange(this.Count, nodes); public abstract bool Any(); public abstract bool Contains(TNode node); public bool Equals(SeparatedSyntaxListWrapper other) { throw new NotImplementedException(); } public override bool Equals(object obj) { throw new NotImplementedException(); } public abstract TNode First(); public abstract TNode FirstOrDefault(); public Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } public override abstract int GetHashCode(); public abstract SyntaxToken GetSeparator(int index); public abstract IEnumerable GetSeparators(); public abstract SyntaxNodeOrTokenList GetWithSeparators(); public abstract int IndexOf(Func predicate); public abstract int IndexOf(TNode node); public abstract SeparatedSyntaxListWrapper Insert(int index, TNode node); public abstract SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes); public abstract TNode Last(); public abstract int LastIndexOf(Func predicate); public abstract int LastIndexOf(TNode node); public abstract TNode LastOrDefault(); public abstract SeparatedSyntaxListWrapper Remove(TNode node); public abstract SeparatedSyntaxListWrapper RemoveAt(int index); public abstract SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode); public abstract SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes); public abstract SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator); public abstract string ToFullString(); public override abstract string ToString(); public struct Enumerator : IEnumerator { private readonly SeparatedSyntaxListWrapper wrapper; private int index; private TNode current; public Enumerator(SeparatedSyntaxListWrapper wrapper) { this.wrapper = wrapper; this.index = -1; this.current = default; } public TNode Current => this.current; object IEnumerator.Current => this.Current; public override bool Equals(object obj) { Enumerator? otherOpt = obj as Enumerator?; if (!otherOpt.HasValue) { return false; } Enumerator other = otherOpt.GetValueOrDefault(); return other.wrapper == this.wrapper && other.index == this.index; } public override int GetHashCode() { if (this.wrapper == null) { return 0; } return this.wrapper.GetHashCode() ^ this.index; } public void Dispose() { } public bool MoveNext() { if (this.index < -1) { return false; } if (this.index == this.wrapper.Count - 1) { this.index = int.MinValue; return false; } this.index++; this.current = this.wrapper[this.index]; return true; } public void Reset() { this.index = -1; this.current = default; } } internal sealed class AutoWrapSeparatedSyntaxList : SeparatedSyntaxListWrapper where TSyntax : SyntaxNode { private readonly SeparatedSyntaxList syntaxList; public AutoWrapSeparatedSyntaxList() : this(default) { } public AutoWrapSeparatedSyntaxList(SeparatedSyntaxList syntaxList) { this.syntaxList = syntaxList; } public override int Count => this.syntaxList.Count; public override TextSpan FullSpan => this.syntaxList.FullSpan; public override int SeparatorCount => this.syntaxList.SeparatorCount; public override TextSpan Span => this.syntaxList.Span; public override object UnderlyingList => this.syntaxList; public override TNode this[int index] => SyntaxWrapper.Wrap(this.syntaxList[index]); public override bool Any() => this.syntaxList.Any(); public override bool Contains(TNode node) => this.syntaxList.Contains(SyntaxWrapper.Unwrap(node)); public override TNode First() => SyntaxWrapper.Wrap(this.syntaxList.First()); public override TNode FirstOrDefault() => SyntaxWrapper.Wrap(this.syntaxList.FirstOrDefault()); public override int GetHashCode() => this.syntaxList.GetHashCode(); public override SyntaxToken GetSeparator(int index) => this.syntaxList.GetSeparator(index); public override IEnumerable GetSeparators() => this.syntaxList.GetSeparators(); public override SyntaxNodeOrTokenList GetWithSeparators() => this.syntaxList.GetWithSeparators(); public override int IndexOf(TNode node) => this.syntaxList.IndexOf((TSyntax)SyntaxWrapper.Unwrap(node)); public override int IndexOf(Func predicate) => this.syntaxList.IndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override SeparatedSyntaxListWrapper Insert(int index, TNode node) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Insert(index, (TSyntax)SyntaxWrapper.Unwrap(node))); public override SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes) => new AutoWrapSeparatedSyntaxList(this.syntaxList.InsertRange(index, nodes.Select(node => (TSyntax)SyntaxWrapper.Unwrap(node)))); public override TNode Last() => SyntaxWrapper.Wrap(this.syntaxList.Last()); public override int LastIndexOf(TNode node) => this.syntaxList.LastIndexOf((TSyntax)SyntaxWrapper.Unwrap(node)); public override int LastIndexOf(Func predicate) => this.syntaxList.LastIndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override TNode LastOrDefault() => SyntaxWrapper.Wrap(this.syntaxList.LastOrDefault()); public override SeparatedSyntaxListWrapper Remove(TNode node) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Remove((TSyntax)SyntaxWrapper.Unwrap(node))); public override SeparatedSyntaxListWrapper RemoveAt(int index) => new AutoWrapSeparatedSyntaxList(this.syntaxList.RemoveAt(index)); public override SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Replace((TSyntax)SyntaxWrapper.Unwrap(nodeInList), (TSyntax)SyntaxWrapper.Unwrap(newNode))); public override SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes) => new AutoWrapSeparatedSyntaxList(this.syntaxList.ReplaceRange((TSyntax)SyntaxWrapper.Unwrap(nodeInList), newNodes.Select(node => (TSyntax)SyntaxWrapper.Unwrap(node)))); public override SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator) => new AutoWrapSeparatedSyntaxList(this.syntaxList.ReplaceSeparator(separatorToken, newSeparator)); public override string ToFullString() => this.syntaxList.ToFullString(); public override string ToString() => this.syntaxList.ToString(); } private sealed class UnsupportedSyntaxList : SeparatedSyntaxListWrapper { private static readonly SeparatedSyntaxList SyntaxList = default; public UnsupportedSyntaxList() { } public override int Count => 0; public override TextSpan FullSpan => SyntaxList.FullSpan; public override int SeparatorCount => 0; public override TextSpan Span => SyntaxList.Span; public override object UnderlyingList => null; public override TNode this[int index] => SyntaxWrapper.Wrap(SyntaxList[index]); public override bool Any() => false; public override bool Contains(TNode node) => false; public override TNode First() => SyntaxWrapper.Wrap(SyntaxList.First()); public override TNode FirstOrDefault() => SyntaxWrapper.Wrap(default); public override int GetHashCode() => SyntaxList.GetHashCode(); public override SyntaxToken GetSeparator(int index) => SyntaxList.GetSeparator(index); public override IEnumerable GetSeparators() => SyntaxList.GetSeparators(); public override SyntaxNodeOrTokenList GetWithSeparators() => SyntaxList.GetWithSeparators(); public override int IndexOf(TNode node) => SyntaxList.IndexOf(SyntaxWrapper.Unwrap(node)); public override int IndexOf(Func predicate) => SyntaxList.IndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override SeparatedSyntaxListWrapper Insert(int index, TNode node) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes) { throw new NotSupportedException(); } public override TNode Last() => SyntaxWrapper.Wrap(SyntaxList.Last()); public override int LastIndexOf(TNode node) => SyntaxList.LastIndexOf(SyntaxWrapper.Unwrap(node)); public override int LastIndexOf(Func predicate) => SyntaxList.LastIndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override TNode LastOrDefault() => SyntaxWrapper.Wrap(default); public override SeparatedSyntaxListWrapper Remove(TNode node) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper RemoveAt(int index) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator) { throw new NotSupportedException(); } public override string ToFullString() => SyntaxList.ToFullString(); public override string ToString() => SyntaxList.ToString(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/TemporaryLightUp/SyntaxWrapper`1.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace SonarAnalyzer.ShimLayer { using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; using SonarAnalyzer.ShimLayer; public abstract class SyntaxWrapper { public static SyntaxWrapper Default { get; } = FindDefaultSyntaxWrapper(); public abstract TNode Wrap(SyntaxNode node); public abstract SyntaxNode Unwrap(TNode node); private static SyntaxWrapper FindDefaultSyntaxWrapper() { if (typeof(SyntaxNode).GetTypeInfo().IsAssignableFrom(typeof(TNode).GetTypeInfo())) { return new DirectCastSyntaxWrapper(); } return new ConversionSyntaxWrapper(); } private sealed class DirectCastSyntaxWrapper : SyntaxWrapper { public override SyntaxNode Unwrap(TNode node) { return (SyntaxNode)(object)node; } public override TNode Wrap(SyntaxNode node) { return (TNode)(object)node; } } private sealed class ConversionSyntaxWrapper : SyntaxWrapper { private readonly Func unwrapAccessor; private readonly Func wrapAccessor; public ConversionSyntaxWrapper() { this.unwrapAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(TNode), nameof(ISyntaxWrapper.SyntaxNode)); var explicitOperator = typeof(TNode).GetTypeInfo().GetDeclaredMethods("op_Explicit") .Single(m => m.ReturnType == typeof(TNode) && m.GetParameters()[0].ParameterType == typeof(SyntaxNode)); var syntaxParameter = Expression.Parameter(typeof(SyntaxNode), "syntax"); Expression> wrapAccessorExpression = Expression.Lambda>( Expression.Call(explicitOperator, syntaxParameter), syntaxParameter); this.wrapAccessor = wrapAccessorExpression.Compile(); } public override SyntaxNode Unwrap(TNode node) { return this.unwrapAccessor(node); } public override TNode Wrap(SyntaxNode node) { return this.wrapAccessor(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/TemporaryLightUp/TryGetValueAccessor`3.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace SonarAnalyzer.ShimLayer { internal delegate bool TryGetValueAccessor(T instance, TKey key, out TValue value); internal delegate bool TryGetValueAccessor(T instance, TFirst first, TSecond second, out TValue value); // Sonar internal delegate bool TryGetValueAccessor(T instance, TFirst first, TSecond second, TThird third, out TValue value); // Sonar } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/GeneratorSyntaxExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.CodeGeneration { using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; internal static class GeneratorSyntaxExtensions { public static TSyntax WithLeadingBlankLine(this TSyntax syntax) where TSyntax : SyntaxNode { return syntax.WithLeadingTrivia(SyntaxFactory.TriviaList( SyntaxFactory.PreprocessingMessage(XmlSyntaxFactory.CrLf))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/LICENSE ================================================ MIT License Copyright (c) Tunnel Vision Laboratories, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/OperationLightupGenerator.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.CodeGeneration { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; [Generator] internal sealed class OperationLightupGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { var operationInterfacesFiles = context.AdditionalTextsProvider.Where(static x => Path.GetFileName(x.Path) == "OperationInterfaces.xml"); context.RegisterSourceOutput(operationInterfacesFiles, this.Execute); } private void Execute(SourceProductionContext context, AdditionalText operationInterfacesFile) { var operationInterfacesText = operationInterfacesFile.GetText(context.CancellationToken); if (operationInterfacesText is null) { throw new InvalidOperationException("Failed to read OperationInterfaces.xml"); } var operationInterfaces = XDocument.Parse(operationInterfacesText.ToString()); this.GenerateOperationInterfaces(in context, operationInterfaces); } private void GenerateOperationInterfaces(in SourceProductionContext context, XDocument operationInterfaces) { var tree = operationInterfaces.XPathSelectElement("/Tree"); if (tree is null) { throw new InvalidOperationException("Failed to find the IOperation root."); } var documentData = new DocumentData(operationInterfaces); foreach (var pair in documentData.Interfaces) { this.GenerateOperationInterface(in context, pair.Value); } this.GenerateOperationWrapperHelper(in context, documentData.Interfaces.Values.ToImmutableArray()); // this.GenerateOperationKindEx(in context, documentData.Interfaces.Values.ToImmutableArray()); } private void GenerateOperationInterface(in SourceProductionContext context, InterfaceData node) { var members = SyntaxFactory.List(); // internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Operations.IArgumentOperation"; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)), // Sonar declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("WrappedTypeName"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal($"{node.Namespace}.{node.InterfaceName}")))))))); if (node.InterfaceName != "IOperation") { // private static readonly Type WrappedType; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("Type"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("WrappedType"))))); } foreach (var property in node.Properties) { if (property.IsSkipped || !property.NeedsAccessor) { continue; } // private static readonly Func ConstructorAccessor; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("Func"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("IOperation"), property.AccessorResultType, }))), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(property.AccessorName))))); } // private readonly IOperation operation; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("IOperation"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("operation"))))); var staticCtorStatements = SyntaxFactory.List(); if (node.InterfaceName != "IOperation") { staticCtorStatements = staticCtorStatements.Add( SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName("WrappedType"), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("OperationWrapperHelper"), name: SyntaxFactory.IdentifierName("GetWrappedType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))))))))); } foreach (var property in node.Properties) { if (property.IsSkipped || !property.NeedsAccessor) { continue; } SimpleNameSyntax helperName; if (property.IsDerivedOperationArray) { // CreateOperationListPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateOperationListPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.IdentifierName("IOperation")))); } else { // CreateOperationPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateOperationPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("IOperation"), property.AccessorResultType, }))); } // ConstructorAccessor = LightupHelpers.CreateOperationPropertyAccessor(WrappedType, nameof(Constructor)); ExpressionSyntax wrappedType = node.InterfaceName == "IOperation" ? SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName("IOperation")) : SyntaxFactory.IdentifierName("WrappedType"); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName(property.AccessorName), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("LightupHelpers"), name: helperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(wrappedType), SyntaxFactory.Argument(SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("nameof"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName(property.Name)))))), })))))); } // static IArgumentOperationWrapper() // { // WrappedType = OperationWrapperHelper.GetWrappedType(typeof(IObjectCreationOperationWrapper)); // } members = members.Add(SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), identifier: SyntaxFactory.Identifier(node.WrapperName), parameterList: SyntaxFactory.ParameterList(), initializer: null, body: SyntaxFactory.Block(staticCtorStatements), expressionBody: null)); // private IArgumentOperationWrapper(IOperation operation) // { // this.operation = operation; // } members = members.Add(SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)), identifier: SyntaxFactory.Identifier(node.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("IOperation"), identifier: SyntaxFactory.Identifier("operation"), @default: null))), initializer: null, body: SyntaxFactory.Block( SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("operation")), right: SyntaxFactory.IdentifierName("operation")))), expressionBody: null)); // public IOperation WrappedOperation => this.operation; members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: SyntaxFactory.IdentifierName("IOperation"), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("WrappedOperation"), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("operation"))), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); if (node.InterfaceName != "IOperation") { // public ITypeSymbol Type => this.WrappedOperation.Type; members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: SyntaxFactory.IdentifierName("ITypeSymbol"), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("Type"), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("WrappedOperation")), name: SyntaxFactory.IdentifierName("Type"))), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } foreach (var property in node.Properties) { if (property.IsSkipped) { // Generate a NotImplementedException for public properties that do not have a supported type if (property.IsPublicProperty && !property.IsOverride) // Sonar { // public object Constructor => throw new NotImplementedException("Property 'Type.Property' has unsupported type 'Type'"); members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier(property.Name), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.ThrowExpression(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(nameof(NotImplementedException)), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.LiteralExpression( SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal($"Property '{node.InterfaceName}.{property.Name}' has unsupported type '{property.Type}'"))))), initializer: null))), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } continue; } var propertyType = property.NeedsWrapper ? SyntaxFactory.IdentifierName(property.WrappedType) : property.AccessorResultType; // Sonar // The value is accessed in one of the following ways: // ConstructorAccessor(this.WrappedOperation) // this.WrappedOperation.Constructor ExpressionSyntax evaluatedAccessor; if (property.NeedsAccessor) { evaluatedAccessor = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName(property.AccessorName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("WrappedOperation")))))); } else { evaluatedAccessor = SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("WrappedOperation")), name: SyntaxFactory.IdentifierName(property.Name)); } ExpressionSyntax convertedResult; if (property.NeedsWrapper) { // IObjectOrCollectionInitializerOperationWrapper.FromOperation(...) convertedResult = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: propertyType, name: SyntaxFactory.IdentifierName("FromOperation")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(evaluatedAccessor)))); } else { convertedResult = evaluatedAccessor; } // public IMethodSymbol Constructor => ConstructorAccessor(this.WrappedOperation); members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: propertyType, explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier(property.Name), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(convertedResult), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } foreach (var baseDefinition in node.InheritedInterfaces) { // For now, don't inherit properties from IOperationWrapper var inheritedProperties = baseDefinition.InterfaceName != "IOperation" ? baseDefinition.Properties : ImmutableArray.Empty; foreach (var property in inheritedProperties) { if (node.Properties.Any(derivedProperty => derivedProperty.Name == property.Name && derivedProperty.IsNew)) { continue; } if (!property.IsPublicProperty) { continue; } var propertyType = property.IsSkipped ? SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)) : property.NeedsWrapper ? SyntaxFactory.IdentifierName(property.WrappedType) : property.AccessorResultType; // Sonar // public IOperation Instance => ((IMemberReferenceOperationWrapper)this).Instance; members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: propertyType, explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier(property.Name), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ParenthesizedExpression(SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName(baseDefinition.WrapperName), expression: SyntaxFactory.ThisExpression())), name: SyntaxFactory.IdentifierName(property.Name))), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } // public static explicit operator IFieldReferenceOperationWrapper(IMemberReferenceOperationWrapper wrapper) // => FromOperation(wrapper.WrappedOperation); members = members.Add(SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ExplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(node.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(baseDefinition.WrapperName), identifier: SyntaxFactory.Identifier("wrapper"), @default: null))), body: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("FromOperation"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("wrapper"), name: SyntaxFactory.IdentifierName("WrappedOperation"))))))), semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); // public static implicit operator IMemberReferenceOperationWrapper(IFieldReferenceOperationWrapper wrapper) // => IMemberReferenceOperationWrapper.FromUpcast(wrapper.WrappedOperation); members = members.Add(SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ImplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(baseDefinition.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(node.WrapperName), identifier: SyntaxFactory.Identifier("wrapper"), @default: null))), body: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(baseDefinition.WrapperName), name: SyntaxFactory.IdentifierName("FromUpcast")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("wrapper"), name: SyntaxFactory.IdentifierName("WrappedOperation"))))))), semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); } // public static IArgumentOperationWrapper FromOperation(IOperation operation) // { // if (operation == null) // { // return default; // } // // if (!IsInstance(operation)) // { // throw new InvalidCastException($"Cannot cast '{operation.GetType().FullName}' to '{WrappedTypeName}'"); // } // // return new IArgumentOperationWrapper(operation); // } var fromOperationStatements = new List(); fromOperationStatements.Add(SyntaxFactory.IfStatement( condition: SyntaxFactory.BinaryExpression( SyntaxKind.EqualsExpression, left: SyntaxFactory.IdentifierName("operation"), right: SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), statement: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression))))); if (node.InterfaceName != "IOperation") { fromOperationStatements.Add(SyntaxFactory.IfStatement( condition: SyntaxFactory.PrefixUnaryExpression( SyntaxKind.LogicalNotExpression, operand: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("IsInstance"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName("operation")))))), statement: SyntaxFactory.Block( SyntaxFactory.ThrowStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName("InvalidCastException"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.InterpolatedStringExpression( SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken), SyntaxFactory.List(new InterpolatedStringContentSyntax[] { SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "Cannot cast '", "Cannot cast '", trailing: default)), SyntaxFactory.Interpolation(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("operation"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList()), name: SyntaxFactory.IdentifierName("FullName"))), SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "' to '", "' to '", trailing: default)), SyntaxFactory.Interpolation(SyntaxFactory.IdentifierName("WrappedTypeName")), SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "'", "'", trailing: default)), }))))), initializer: null))))); } fromOperationStatements.Add(SyntaxFactory.ReturnStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(node.WrapperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName("operation")))), initializer: null))); members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), returnType: SyntaxFactory.IdentifierName(node.WrapperName), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("FromOperation"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("IOperation"), identifier: SyntaxFactory.Identifier("operation"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block(fromOperationStatements), expressionBody: null)); // public static bool IsInstance(IOperation operation) // { // return operation != null && LightupHelpers.CanWrapOperation(operation, WrappedType); // } ExpressionSyntax isInstanceExpression = SyntaxFactory.BinaryExpression( SyntaxKind.NotEqualsExpression, left: SyntaxFactory.IdentifierName("operation"), right: SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)); if (node.InterfaceName != "IOperation") { isInstanceExpression = SyntaxFactory.BinaryExpression( SyntaxKind.LogicalAndExpression, left: isInstanceExpression, right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("LightupHelpers"), name: SyntaxFactory.IdentifierName("CanWrapOperation")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("operation")), SyntaxFactory.Argument(SyntaxFactory.IdentifierName("WrappedType")), })))); } members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), returnType: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("IsInstance"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("IOperation"), identifier: SyntaxFactory.Identifier("operation"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block(SyntaxFactory.ReturnStatement(isInstanceExpression)), expressionBody: null)); if (node.IsAbstract) { // internal static IMemberReferenceOperationWrapper FromUpcast(IOperation operation) // { // return new IMemberReferenceOperationWrapper(operation); // } members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar returnType: SyntaxFactory.IdentifierName(node.WrapperName), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("FromUpcast"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("IOperation"), identifier: SyntaxFactory.Identifier("operation"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(node.WrapperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName("operation")))), initializer: null))), expressionBody: null)); } var wrapperStruct = SyntaxFactory.StructDeclaration( attributeLists: default, modifiers: SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)).Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), // Sonar identifier: SyntaxFactory.Identifier(node.WrapperName), typeParameterList: null, baseList: SyntaxFactory.BaseList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.SimpleBaseType(SyntaxFactory.IdentifierName("IOperationWrapper")))), // Sonar constraintClauses: default, members: members); var usingDirectives = new List(); usingDirectives.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); if (node.InterfaceName == "IOperation") { usingDirectives.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Generic"))); } usingDirectives.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Immutable"))); usingDirectives.Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))); var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), externs: default, usings: SyntaxFactory.List(usingDirectives), members: SyntaxFactory.SingletonList(wrapperStruct)); wrapperNamespace = wrapperNamespace .NormalizeWhitespace() .WithLeadingTrivia( SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed) .WithTrailingTrivia( SyntaxFactory.CarriageReturnLineFeed); context.AddSource(node.WrapperName + ".g.cs", wrapperNamespace.GetText(Encoding.UTF8)); } private void GenerateOperationWrapperHelper(in SourceProductionContext context, ImmutableArray wrapperTypes) { // private static readonly ImmutableDictionary WrappedTypes; var wrappedTypes = SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("ImmutableDictionary"), typeArgumentList: SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("Type"), SyntaxFactory.IdentifierName("Type"), }))), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier("WrappedTypes"))))); // var codeAnalysisAssembly = typeof(SyntaxNode).GetTypeInfo().Assembly; // var builder = ImmutableDictionary.CreateBuilder(); var staticCtorStatements = SyntaxFactory.List() .Add(SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("codeAnalysisAssembly"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName("SyntaxNode")), name: SyntaxFactory.IdentifierName("GetTypeInfo"))), name: SyntaxFactory.IdentifierName("Assembly")))))))) .Add(SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("builder"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("ImmutableDictionary"), name: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateBuilder"), typeArgumentList: SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("Type"), SyntaxFactory.IdentifierName("Type"), }))))))))))); foreach (var node in wrapperTypes) { // For the base IOperation node: // builder.Add(typeof(IArgumentOperationWrapper), typeof(IOperation)); // // For all other nodes: // builder.Add(typeof(IArgumentOperationWrapper), codeAnalysisAssembly.GetType(IArgumentOperationWrapper.WrappedTypeName)); ArgumentSyntax typeArgument; if (node.InterfaceName == "IOperation") { typeArgument = SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName("IOperation"))); } else { typeArgument = SyntaxFactory.Argument( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("codeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("WrappedTypeName"))))))); } staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("Add")), argumentList: SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), typeArgument, }))))); } // WrappedTypes = builder.ToImmutable(); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName("WrappedTypes"), right: SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("ToImmutable")))))); var staticCtor = SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), identifier: SyntaxFactory.Identifier("OperationWrapperHelper"), parameterList: SyntaxFactory.ParameterList(), initializer: null, body: SyntaxFactory.Block(staticCtorStatements), expressionBody: null); // /// // /// Gets the type that is wrapped by the given wrapper. // /// // /// Type of the wrapper for which the wrapped type should be retrieved. // /// The wrapped type, or null if there is no info. // internal static Type GetWrappedType(Type wrapperType) // { // if (WrappedTypes.TryGetValue(wrapperType, out Type wrappedType)) // { // return wrappedType; // } // // return null; // } var getWrappedType = SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar returnType: SyntaxFactory.IdentifierName("Type"), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("GetWrappedType"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("Type"), identifier: SyntaxFactory.Identifier("wrapperType"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block( SyntaxFactory.IfStatement( condition: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("WrappedTypes"), name: SyntaxFactory.IdentifierName("TryGetValue")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("wrapperType")), SyntaxFactory.Argument( nameColon: null, refKindKeyword: SyntaxFactory.Token(SyntaxKind.OutKeyword), expression: SyntaxFactory.DeclarationExpression( type: SyntaxFactory.IdentifierName("Type"), designation: SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("wrappedType")))), }))), statement: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("wrappedType")))), SyntaxFactory.ReturnStatement(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))), expressionBody: null); getWrappedType = getWrappedType.WithLeadingTrivia(SyntaxFactory.TriviaList( SyntaxFactory.Trivia(SyntaxFactory.DocumentationComment( SyntaxFactory.XmlText(" "), SyntaxFactory.XmlSummaryElement( SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" Gets the type that is wrapped by the given wrapper."), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" ")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" "), SyntaxFactory.XmlParamElement( "wrapperType", SyntaxFactory.XmlText("Type of the wrapper for which the wrapped type should be retrieved.")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" "), SyntaxFactory.XmlReturnsElement( SyntaxFactory.XmlText("The wrapped type, or null if there is no info.")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation).WithoutTrailingTrivia())))); var wrapperHelperClass = SyntaxFactory.ClassDeclaration( attributeLists: default, modifiers: SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)).Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar identifier: SyntaxFactory.Identifier("OperationWrapperHelper"), typeParameterList: null, baseList: null, constraintClauses: default, members: SyntaxFactory.List() .Add(wrappedTypes) .Add(staticCtor) .Add(getWrappedType)); var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), externs: default, usings: SyntaxFactory.List() .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Immutable"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Reflection"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))), members: SyntaxFactory.SingletonList(wrapperHelperClass)); wrapperNamespace = wrapperNamespace .NormalizeWhitespace() .WithLeadingTrivia( SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed) .WithTrailingTrivia( SyntaxFactory.CarriageReturnLineFeed); context.AddSource("OperationWrapperHelper.g.cs", wrapperNamespace.GetText(Encoding.UTF8)); } private void GenerateOperationKindEx(in SourceProductionContext context, ImmutableArray wrapperTypes) { var operationKinds = wrapperTypes .SelectMany(type => type.OperationKinds) .OrderBy(kind => kind.value) .ToImmutableArray(); var members = SyntaxFactory.List(); foreach (var operationKind in operationKinds) { // public const OperationKind FieldReference = (OperationKind)26; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("OperationKind"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier(operationKind.name), argumentList: null, initializer: SyntaxFactory.EqualsValueClause(SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName("OperationKind"), expression: SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal($"0x{operationKind.value:x}", operationKind.value))))))))); } var operationKindExClass = SyntaxFactory.ClassDeclaration( attributeLists: default, modifiers: SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)).Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar identifier: SyntaxFactory.Identifier("OperationKindEx"), typeParameterList: null, baseList: null, constraintClauses: default, members: members); var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), externs: default, usings: SyntaxFactory.List() .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))), members: SyntaxFactory.SingletonList(operationKindExClass)); wrapperNamespace = wrapperNamespace .NormalizeWhitespace() .WithLeadingTrivia( SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed) .WithTrailingTrivia( SyntaxFactory.CarriageReturnLineFeed); context.AddSource("OperationKindEx.g.cs", wrapperNamespace.GetText(Encoding.UTF8)); } private sealed class DocumentData { public DocumentData(XDocument document) { var operationKinds = GetOperationKinds(document); var interfaces = new Dictionary(); foreach (var node in document.XPathSelectElements("/Tree/AbstractNode")) { if (node.Attribute("Internal")?.Value == "true") { continue; } if (!operationKinds.TryGetValue(node.RequiredAttribute("Name").Value, out var kinds)) { kinds = ImmutableArray<(string name, int value, string? extraDescription)>.Empty; } var interfaceData = new InterfaceData(this, node, kinds); interfaces.Add(interfaceData.InterfaceName, interfaceData); } foreach (var node in document.XPathSelectElements("/Tree/Node")) { if (node.Attribute("Internal")?.Value == "true") { continue; } if (!operationKinds.TryGetValue(node.RequiredAttribute("Name").Value, out var kinds)) { kinds = ImmutableArray<(string name, int value, string? extraDescription)>.Empty; } var interfaceData = new InterfaceData(this, node, kinds); interfaces.Add(interfaceData.InterfaceName, interfaceData); } this.Interfaces = new ReadOnlyDictionary(interfaces); } public ReadOnlyDictionary Interfaces { get; } private static ImmutableDictionary> GetOperationKinds(XDocument document) { var skippedOperationKinds = GetSkippedOperationKinds(document); var builder = ImmutableDictionary.CreateBuilder>(); int operationKind = 0; foreach (var node in document.XPathSelectElements("/Tree/AbstractNode|/Tree/Node")) { if (node.Attribute("Internal")?.Value == "true") { continue; } if (node.XPathSelectElement("OperationKind") is { } explicitKind) { if (node.Name == "AbstractNode" && explicitKind.Attribute("Include")?.Value != "true") { continue; } else if (explicitKind.Attribute("Include")?.Value == "false") { // The node is explicitly excluded continue; } else if (explicitKind.XPathSelectElements("Entry").Any()) { var nodeBuilder = ImmutableArray.CreateBuilder<(string name, int value, string? extraDescription)>(); foreach (var entry in explicitKind.XPathSelectElements("Entry")) { if (entry.Attribute("EditorBrowsable")?.Value == "false") { // Skip code generation for this operation kind continue; } int parsedValue = ParsePrefixHexValue(entry.RequiredAttribute("Value").Value); nodeBuilder.Add((entry.RequiredAttribute("Name").Value, parsedValue, entry.Attribute("ExtraDescription")?.Value)); } builder.Add(node.RequiredAttribute("Name").Value, nodeBuilder.ToImmutable()); continue; } } else if (node.Name == "AbstractNode") { // Abstract nodes without explicit Include="true" are skipped continue; } // Implicit operation kind operationKind++; while (skippedOperationKinds.Contains(operationKind)) { operationKind++; } var nodeName = node.RequiredAttribute("Name").Value; var kindName = nodeName.Substring("I".Length, nodeName.Length - "I".Length - "Operation".Length); builder.Add(nodeName, ImmutableArray.Create((kindName, operationKind, (string?)null))); } return builder.ToImmutable(); } private static ImmutableHashSet GetSkippedOperationKinds(XDocument document) { var builder = ImmutableHashSet.CreateBuilder(); foreach (var skippedKind in document.XPathSelectElements("/Tree/UnusedOperationKinds/Entry")) { builder.Add(ParsePrefixHexValue(skippedKind.RequiredAttribute("Value").Value)); } foreach (var explicitKind in document.XPathSelectElements("/Tree/*/OperationKind/Entry")) { builder.Add(ParsePrefixHexValue(explicitKind.RequiredAttribute("Value").Value)); } return builder.ToImmutable(); } private static int ParsePrefixHexValue(string value) { if (!value.StartsWith("0x")) { throw new InvalidOperationException($"Unexpected number format: '{value}'"); } return int.Parse(value.Substring("0x".Length), NumberStyles.AllowHexSpecifier); } } private sealed class InterfaceData { private readonly DocumentData documentData; public InterfaceData(DocumentData documentData, XElement node, ImmutableArray<(string name, int value, string? extraDescription)> operationKinds) { this.documentData = documentData; this.OperationKinds = operationKinds; this.InterfaceName = node.RequiredAttribute("Name").Value; if (node.Attribute("Namespace") is { } namespaceNode) { if (namespaceNode.Value == string.Empty) { this.Namespace = "Microsoft.CodeAnalysis"; } else { this.Namespace = $"Microsoft.CodeAnalysis.{namespaceNode.Value}"; } } else { this.Namespace = "Microsoft.CodeAnalysis.Operations"; } this.Name = this.InterfaceName.Substring("I".Length, this.InterfaceName.Length - "I".Length - "Operation".Length); this.WrapperName = this.InterfaceName + "Wrapper"; this.BaseInterfaceName = node.Attribute("Base")?.Value; this.IsAbstract = node.Name == "AbstractNode"; this.Properties = node.XPathSelectElements("Property").Select(property => new PropertyData(property)).ToImmutableArray(); } public ImmutableArray<(string name, int value, string? extraDescription)> OperationKinds { get; } public string InterfaceName { get; } public string Namespace { get; } public string Name { get; } public string WrapperName { get; } public string? BaseInterfaceName { get; } public bool IsAbstract { get; } public ImmutableArray Properties { get; } public InterfaceData? BaseInterface { get { if (this.BaseInterfaceName is not null && this.documentData.Interfaces.TryGetValue(this.BaseInterfaceName, out var baseInterface)) { return baseInterface; } return null; } } public IEnumerable InheritedInterfaces { get { var inheritedInterfaces = new List(); for (var baseDefinition = this.BaseInterface; baseDefinition is not null; baseDefinition = baseDefinition.BaseInterface) { inheritedInterfaces.Add(baseDefinition); } inheritedInterfaces.Reverse(); return inheritedInterfaces; } } } private sealed class PropertyData { public PropertyData(XElement node) { this.Name = node.RequiredAttribute("Name").Value; this.AccessorName = this.Name + "Accessor"; this.Type = node.RequiredAttribute("Type").Value; this.TypeNonNullable = Type.TrimEnd('?'); // Sonar - When comparing types as strings, the nullable suffix should be ignored. this.WrappedType = TypeNonNullable + "Wrapper"; // Sonar this.IsNew = node.Attribute("New")?.Value == "true"; this.IsPublicProperty = node.Attribute("Internal")?.Value != "true"; this.IsOverride = node.Attribute("Override")?.Value == "true"; // Sonar this.IsSkipped = this.TypeNonNullable switch // Sonar { "ArgumentKind" => true, "BranchKind" => true, "CaseKind" => true, "CommonConversion" => true, "ForEachLoopOperationInfo" => true, "IDiscardSymbol" => true, "InstanceReferenceKind" => true, "InterpolatedStringArgumentPlaceholderKind" => true, // Sonar: Skipped because it's not available "LoopKind" => true, "PlaceholderKind" => true, _ => !this.IsPublicProperty || this.IsOverride, // Sonar }; this.NeedsAccessor = this.Name switch { nameof(IOperation.Kind) => false, nameof(IOperation.Syntax) => false, nameof(IOperation.Type) => false, nameof(IOperation.ConstantValue) => false, _ => true, }; this.NeedsWrapper = IsAnyOperation(TypeNonNullable) && TypeNonNullable != "IOperation"; // Sonar this.IsDerivedOperationArray = IsAnyOperationArray(TypeNonNullable) && TypeNonNullable != "ImmutableArray"; // Sonar if (this.IsDerivedOperationArray) { this.AccessorResultType = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("ImmutableArray"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.IdentifierName("IOperation")))); } else if (IsAnyOperation(TypeNonNullable)) // Sonar { this.AccessorResultType = SyntaxFactory.IdentifierName("IOperation"); } else { this.AccessorResultType = SyntaxFactory.ParseTypeName(this.Type); } } public bool IsNew { get; } public bool IsPublicProperty { get; } public bool IsOverride { get; } // Added by Sonar. Usages are also by Sonar. public bool IsSkipped { get; } public string Name { get; } public string AccessorName { get; } public string Type { get; } public bool NeedsAccessor { get; } public string TypeNonNullable { get; } // Sonar public string WrappedType { get; } // Sonar public bool NeedsWrapper { get; } public bool IsDerivedOperationArray { get; } public TypeSyntax AccessorResultType { get; } private static bool IsAnyOperation(string type) { return type.StartsWith("I") && type.EndsWith("Operation"); } private static bool IsAnyOperationArray(string type) { return type.StartsWith("ImmutableArray"); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/RoslynHashCode.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. // #nullable enable // NOTE: This code is derived from an implementation originally in dotnet/runtime: // https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/HashCode.cs // // See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the // reference implementation. /* The xxHash32 implementation is based on the code published by Yann Collet: https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c xxHash - Fast Hash algorithm Copyright (C) 2012-2016, Yann Collet BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You can contact the author at : - xxHash homepage: http://www.xxhash.com - xxHash source repository : https://github.com/Cyan4973/xxHash */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Security.Cryptography; namespace Analyzer.Utilities { // xxHash32 is used for the hash code. // https://github.com/Cyan4973/xxHash [SuppressMessage("Design", "CA1066:Implement IEquatable when overriding Object.Equals", Justification = "This type is not equatable.")] [SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "This type is not equatable.")] [SuppressMessage("Usage", "CA2231:Overload operator equals on overriding value type Equals", Justification = "This type is not equatable.")] internal struct RoslynHashCode { private static readonly uint s_seed = GenerateGlobalSeed(); private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 668265263U; private const uint Prime5 = 374761393U; private uint _v1, _v2, _v3, _v4; private uint _queue1, _queue2, _queue3; private uint _length; private static uint GenerateGlobalSeed() { using var randomNumberGenerator = RandomNumberGenerator.Create(); var array = new byte[sizeof(uint)]; randomNumberGenerator.GetBytes(array); return BitConverter.ToUInt32(array, 0); } public static int Combine(T1 value1) { // Provide a way of diffusing bits from something with a limited // input hash space. For example, many enums only have a few // possible hashes, only using the bottom few bits of the code. Some // collections are built on the assumption that hashes are spread // over a larger space, so diffusing the bits may help the // collection work more efficiently. var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hash = MixEmptyState(); hash += 4; hash = QueueRound(hash, hc1); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hash = MixEmptyState(); hash += 8; hash = QueueRound(hash, hc1); hash = QueueRound(hash, hc2); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hash = MixEmptyState(); hash += 12; hash = QueueRound(hash, hc1); hash = QueueRound(hash, hc2); hash = QueueRound(hash, hc3); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hc4 = (uint)(value4?.GetHashCode() ?? 0); Initialize(out var v1, out var v2, out var v3, out var v4); v1 = Round(v1, hc1); v2 = Round(v2, hc2); v3 = Round(v3, hc3); v4 = Round(v4, hc4); var hash = MixState(v1, v2, v3, v4); hash += 16; hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hc4 = (uint)(value4?.GetHashCode() ?? 0); var hc5 = (uint)(value5?.GetHashCode() ?? 0); Initialize(out var v1, out var v2, out var v3, out var v4); v1 = Round(v1, hc1); v2 = Round(v2, hc2); v3 = Round(v3, hc3); v4 = Round(v4, hc4); var hash = MixState(v1, v2, v3, v4); hash += 20; hash = QueueRound(hash, hc5); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hc4 = (uint)(value4?.GetHashCode() ?? 0); var hc5 = (uint)(value5?.GetHashCode() ?? 0); var hc6 = (uint)(value6?.GetHashCode() ?? 0); Initialize(out var v1, out var v2, out var v3, out var v4); v1 = Round(v1, hc1); v2 = Round(v2, hc2); v3 = Round(v3, hc3); v4 = Round(v4, hc4); var hash = MixState(v1, v2, v3, v4); hash += 24; hash = QueueRound(hash, hc5); hash = QueueRound(hash, hc6); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hc4 = (uint)(value4?.GetHashCode() ?? 0); var hc5 = (uint)(value5?.GetHashCode() ?? 0); var hc6 = (uint)(value6?.GetHashCode() ?? 0); var hc7 = (uint)(value7?.GetHashCode() ?? 0); Initialize(out var v1, out var v2, out var v3, out var v4); v1 = Round(v1, hc1); v2 = Round(v2, hc2); v3 = Round(v3, hc3); v4 = Round(v4, hc4); var hash = MixState(v1, v2, v3, v4); hash += 28; hash = QueueRound(hash, hc5); hash = QueueRound(hash, hc6); hash = QueueRound(hash, hc7); hash = MixFinal(hash); return (int)hash; } public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) { var hc1 = (uint)(value1?.GetHashCode() ?? 0); var hc2 = (uint)(value2?.GetHashCode() ?? 0); var hc3 = (uint)(value3?.GetHashCode() ?? 0); var hc4 = (uint)(value4?.GetHashCode() ?? 0); var hc5 = (uint)(value5?.GetHashCode() ?? 0); var hc6 = (uint)(value6?.GetHashCode() ?? 0); var hc7 = (uint)(value7?.GetHashCode() ?? 0); var hc8 = (uint)(value8?.GetHashCode() ?? 0); Initialize(out var v1, out var v2, out var v3, out var v4); v1 = Round(v1, hc1); v2 = Round(v2, hc2); v3 = Round(v3, hc3); v4 = Round(v4, hc4); v1 = Round(v1, hc5); v2 = Round(v2, hc6); v3 = Round(v3, hc7); v4 = Round(v4, hc8); var hash = MixState(v1, v2, v3, v4); hash += 32; hash = MixFinal(hash); return (int)hash; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) { v1 = s_seed + Prime1 + Prime2; v2 = s_seed + Prime2; v3 = s_seed; v4 = s_seed - Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Round(uint hash, uint input) { return BitOperations.RotateLeft(hash + (input * Prime2), 13) * Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint QueueRound(uint hash, uint queuedValue) { return BitOperations.RotateLeft(hash + (queuedValue * Prime3), 17) * Prime4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixState(uint v1, uint v2, uint v3, uint v4) { return BitOperations.RotateLeft(v1, 1) + BitOperations.RotateLeft(v2, 7) + BitOperations.RotateLeft(v3, 12) + BitOperations.RotateLeft(v4, 18); } private static uint MixEmptyState() { return s_seed + Prime5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixFinal(uint hash) { hash ^= hash >> 15; hash *= Prime2; hash ^= hash >> 13; hash *= Prime3; hash ^= hash >> 16; return hash; } public void Add(T value) { Add(value?.GetHashCode() ?? 0); } public void Add(T value, IEqualityComparer? comparer) { Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); } private void Add(int value) { // The original xxHash works as follows: // 0. Initialize immediately. We can't do this in a struct (no // default ctor). // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. // 2. Accumulate remaining blocks of length 4 (1 uint) into the // hash. // 3. Accumulate remaining blocks of length 1 into the hash. // There is no need for #3 as this type only accepts ints. _queue1, // _queue2 and _queue3 are basically a buffer so that when // ToHashCode is called we can execute #2 correctly. // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see // #0) nd the last place that can be done if you look at the // original code is just before the first block of 16 bytes is mixed // in. The xxHash32 state is never used for streams containing fewer // than 16 bytes. // To see what's really going on here, have a look at the Combine // methods. var val = (uint)value; // Storing the value of _length locally shaves of quite a few bytes // in the resulting machine code. var previousLength = _length++; var position = previousLength % 4; // Switch can't be inlined. if (position == 0) { _queue1 = val; } else if (position == 1) { _queue2 = val; } else if (position == 2) { _queue3 = val; } else // position == 3 { if (previousLength == 3) Initialize(out _v1, out _v2, out _v3, out _v4); _v1 = Round(_v1, _queue1); _v2 = Round(_v2, _queue2); _v3 = Round(_v3, _queue3); _v4 = Round(_v4, val); } } public int ToHashCode() { // Storing the value of _length locally shaves of quite a few bytes // in the resulting machine code. var length = _length; // position refers to the *next* queue position in this method, so // position == 1 means that _queue1 is populated; _queue2 would have // been populated on the next call to Add. var position = length % 4; // If the length is less than 4, _v1 to _v4 don't contain anything // yet. xxHash32 treats this differently. var hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); // _length is incremented once per Add(Int32) and is therefore 4 // times too small (xxHash length is in bytes, not ints). hash += length * 4; // Mix what remains in the queue // Switch can't be inlined right now, so use as few branches as // possible by manually excluding impossible scenarios (position > 1 // is always false if position is not > 0). if (position > 0) { hash = QueueRound(hash, _queue1); if (position > 1) { hash = QueueRound(hash, _queue2); if (position > 2) hash = QueueRound(hash, _queue3); } } hash = MixFinal(hash); return (int)hash; } #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member #pragma warning disable CA1065 // Do not raise exceptions in unexpected locations // Obsolete member 'memberA' overrides non-obsolete member 'memberB'. // Disallowing GetHashCode and Equals is by design // * We decided to not override GetHashCode() to produce the hash code // as this would be weird, both naming-wise as well as from a // behavioral standpoint (GetHashCode() should return the object's // hash code, not the one being computed). // * Even though ToHashCode() can be called safely multiple times on // this implementation, it is not part of the contract. If the // implementation has to change in the future we don't want to worry // about people who might have incorrectly used this type. [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(SR.HashCode_HashCodeNotSupported); [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object? obj) => throw new NotSupportedException(SR.HashCode_EqualityNotSupported); #pragma warning restore CA1065 // Do not raise exceptions in unexpected locations #pragma warning restore CS0809 // Obsolete member overrides non-obsolete member private static class SR { public static string HashCode_HashCodeNotSupported = "HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code."; public static string HashCode_EqualityNotSupported = "HashCode is a mutable struct and should not be compared with other HashCodes."; } private static class BitOperations { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint RotateLeft(uint value, int offset) => (value << offset) | (value >> (32 - offset)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong RotateLeft(ulong value, int offset) => (value << offset) | (value >> (64 - offset)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/SonarAnalyzer.ShimLayer.CodeGeneration.csproj ================================================  netstandard2.0 enable true Internal.SonarAnalyzer.ShimLayer.CodeGeneration ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/SyntaxLightupGenerator.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.CodeGeneration { using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; using System.Xml.XPath; using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; [Generator] internal sealed class SyntaxLightupGenerator : IIncrementalGenerator { private enum NodeKind { Predefined, Abstract, Concrete, } public void Initialize(IncrementalGeneratorInitializationContext context) { var referencedAssemblies = context.CompilationProvider.SelectMany( static (compilation, cancellationToken) => { return compilation.SourceModule.ReferencedAssemblySymbols .Where(reference => reference.Name is "Microsoft.CodeAnalysis" or "Microsoft.CodeAnalysis.CSharp"); }); var existingTypesInReferences = referencedAssemblies.SelectMany( static (reference, cancellationToken) => { var microsoftNamespace = reference.GlobalNamespace.GetNamespaceMembers().SingleOrDefault(symbol => symbol.Name == nameof(Microsoft)); var microsoftCodeAnalysisNamespace = microsoftNamespace?.GetNamespaceMembers().SingleOrDefault(symbol => symbol.Name == nameof(Microsoft.CodeAnalysis)); var microsoftCodeAnalysisCSharpNamespace = microsoftCodeAnalysisNamespace?.GetNamespaceMembers().SingleOrDefault(symbol => symbol.Name == nameof(Microsoft.CodeAnalysis.CSharp)); var microsoftCodeAnalysisCSharpSyntaxNamespace = microsoftCodeAnalysisCSharpNamespace?.GetNamespaceMembers().SingleOrDefault(symbol => symbol.Name == nameof(Microsoft.CodeAnalysis.CSharp.Syntax)); var existingTypesBuilder = ImmutableArray.CreateBuilder(); AddPublicTypesFromNamespace(microsoftNamespace, existingTypesBuilder); AddPublicTypesFromNamespace(microsoftCodeAnalysisNamespace, existingTypesBuilder); AddPublicTypesFromNamespace(microsoftCodeAnalysisCSharpNamespace, existingTypesBuilder); AddPublicTypesFromNamespace(microsoftCodeAnalysisCSharpSyntaxNamespace, existingTypesBuilder); return existingTypesBuilder.ToImmutable(); static void AddPublicTypesFromNamespace(INamespaceSymbol? namespaceSymbol, ImmutableArray.Builder existingTypesBuilder) { if (namespaceSymbol is null) { return; } foreach (var type in namespaceSymbol.GetTypeMembers()) { if (type is not { DeclaredAccessibility: Accessibility.Public }) { continue; } existingTypesBuilder.Add(ExistingTypeData.FromNamedType(type, type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat))); } } }); var compilationData = existingTypesInReferences.Collect().Select( static (existingTypes, cancellationToken) => { return new CompilationData( ExistingTypesWrapper: new EquatableValue>( existingTypes.ToImmutableDictionary(type => type.TypeName), ImmutableDictionaryEqualityComparer.Default)); }); var syntaxFiles = context.AdditionalTextsProvider.Where(static x => Path.GetFileName(x.Path) == "Syntax.xml"); context.RegisterSourceOutput( syntaxFiles.Combine(compilationData), (context, value) => this.Execute(in context, value.Right, value.Left)); } private void Execute(in SourceProductionContext context, CompilationData compilationData, AdditionalText syntaxFile) { var syntaxText = syntaxFile.GetText(context.CancellationToken); if (syntaxText is null) { throw new InvalidOperationException("Failed to read Syntax.xml"); } var syntaxData = new SyntaxData(compilationData, XDocument.Parse(syntaxText.ToString())); //this.GenerateSyntaxWrappers(in context, syntaxData); this.GenerateSyntaxWrapperHelper(in context, syntaxData.Nodes); } private void GenerateSyntaxWrappers(in SourceProductionContext context, SyntaxData syntaxData) { foreach (var node in syntaxData.Nodes) { this.GenerateSyntaxWrapper(in context, syntaxData, node); } } private void GenerateSyntaxWrapper(in SourceProductionContext context, SyntaxData syntaxData, NodeData nodeData) { if (nodeData.WrapperName is null) { // No need to generate a wrapper for this type return; } var concreteBase = syntaxData.TryGetConcreteBase(nodeData)?.Name ?? nameof(SyntaxNode); var members = SyntaxFactory.List(); // internal const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax"; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.ConstKeyword)), // Sonar: changed to PublicKeyword declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("WrappedTypeName"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("Microsoft.CodeAnalysis.CSharp.Syntax." + nodeData.Name)))))))); // private static readonly Type WrappedType; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("Type"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("WrappedType"))))); bool first = true; foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } if (field.IsOverride) { // The 'get' accessor is skipped for override fields continue; } // private static readonly Func FieldAccessor; FieldDeclarationSyntax fieldAccessor = SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("Func"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)), }))), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(field.AccessorName)))); if (first) { fieldAccessor = fieldAccessor.WithLeadingBlankLine(); first = false; } members = members.Add(fieldAccessor); } foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } // private static readonly Func WithFieldAccessor; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("Func"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)), SyntaxFactory.IdentifierName(concreteBase), }))), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(field.WithAccessorName))))); } // private readonly SyntaxNode node; members = members.Add(SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName(concreteBase), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator("node")))).WithLeadingBlankLine()); // WrappedType = SyntaxWrapperHelper.GetWrappedType(typeof(SyntaxWrapper)); var staticCtorStatements = SyntaxFactory.SingletonList( SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName("WrappedType"), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("SyntaxWrapperHelper"), name: SyntaxFactory.IdentifierName("GetWrappedType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(nodeData.WrapperName))))))))); foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } if (field.IsOverride) { // The 'get' accessor is skipped for override fields continue; } SimpleNameSyntax helperName; if (field.IsWrappedSeparatedSyntaxList(syntaxData, out var elementNode)) { Debug.Assert(elementNode.WrapperName is not null, $"Assertion failed: {nameof(elementNode)}.{nameof(elementNode.WrapperName)} is not null"); // CreateSeparatedSyntaxListPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateSeparatedSyntaxListPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.IdentifierName(elementNode.WrapperName), }))); } else { // CreateSyntaxPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateSyntaxPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)), }))); } // ReturnTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, nameof(ReturnType)); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName(field.AccessorName), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("LightupHelpers"), name: helperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("WrappedType")), SyntaxFactory.Argument(SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("nameof"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName(field.Name)))))), })))))); } foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } SimpleNameSyntax helperName; if (field.IsWrappedSeparatedSyntaxList(syntaxData, out var elementNode)) { Debug.Assert(elementNode.WrapperName is not null, $"Assertion failed: {nameof(elementNode)}.{nameof(elementNode.WrapperName)} is not null"); // CreateSeparatedSyntaxListWithPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateSeparatedSyntaxListWithPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.IdentifierName(elementNode.WrapperName), }))); } else { // CreateSyntaxWithPropertyAccessor helperName = SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateSyntaxWithPropertyAccessor"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName(concreteBase), SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)), }))); } // WithReturnTypeAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor(WrappedType, nameof(ReturnType)); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName(field.WithAccessorName), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("LightupHelpers"), name: helperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("WrappedType")), SyntaxFactory.Argument(SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("nameof"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName(field.Name)))))), })))))); } // static SyntaxWrapper() // { // WrappedType = SyntaxWrapperHelper.GetWrappedType(typeof(SyntaxWrapper)); // } members = members.Add(SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), identifier: SyntaxFactory.Identifier(nodeData.WrapperName), parameterList: SyntaxFactory.ParameterList(), initializer: null, body: SyntaxFactory.Block(staticCtorStatements), expressionBody: null).WithLeadingBlankLine()); // private SyntaxNodeWrapper(SyntaxNode node) // { // this.node = node; // } members = members.Add(SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)), identifier: SyntaxFactory.Identifier(nodeData.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(concreteBase), identifier: SyntaxFactory.Identifier("node"), @default: null))), initializer: null, body: SyntaxFactory.Block( SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("node")), right: SyntaxFactory.IdentifierName("node")))), expressionBody: null)); // public SyntaxNode SyntaxNode => this.node; members = members.Add(SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: SyntaxFactory.IdentifierName(concreteBase), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("SyntaxNode"), accessorList: null, expressionBody: SyntaxFactory.ArrowExpressionClause(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("node"))), initializer: null, semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))); // public T Field // { // get // { // return ...; // } // } first = true; foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } TypeSyntax propertyType = SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)); ExpressionSyntax returnExpression; if (field.IsOverride) { var declaringNode = field.GetDeclaringNode(syntaxData); if (declaringNode.WrapperName is not null) { // ((CommonForEachStatementSyntaxWrapper)this).OpenParenToken returnExpression = SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ParenthesizedExpression( SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName(declaringNode.WrapperName ?? declaringNode.Name), expression: SyntaxFactory.ThisExpression())), name: SyntaxFactory.IdentifierName(field.Name)); } else { // this.SyntaxNode.OpenParenToken returnExpression = SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("SyntaxNode")), name: SyntaxFactory.IdentifierName(field.Name)); if (declaringNode.TryGetField(field.Name) is { IsExtensionField: true }) { // this.SyntaxNode.OpenParenToken() returnExpression = SyntaxFactory.InvocationExpression( expression: returnExpression, argumentList: SyntaxFactory.ArgumentList()); } } } else if (field.IsWrappedSeparatedSyntaxList(syntaxData, out var elementNode)) { // PatternAccessor(this.SyntaxNode) returnExpression = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName(field.AccessorName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("SyntaxNode")))))); } else if (syntaxData.TryGetNode(field.Type) is { } fieldNodeType) { // PatternAccessor(this.SyntaxNode) returnExpression = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName(field.AccessorName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("SyntaxNode")))))); if (fieldNodeType.WrapperName is not null) { // (PatternSyntaxWrapper)... propertyType = SyntaxFactory.IdentifierName(fieldNodeType.WrapperName); returnExpression = SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName(fieldNodeType.WrapperName), expression: returnExpression); } } else { // PatternAccessor(this.SyntaxNode) returnExpression = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName(field.AccessorName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("SyntaxNode")))))); } // public T Field // { // get // { // return ...; // } // } PropertyDeclarationSyntax property = SyntaxFactory.PropertyDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), type: propertyType, explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier(field.Name), accessorList: SyntaxFactory.AccessorList(SyntaxFactory.SingletonList(SyntaxFactory.AccessorDeclaration( SyntaxKind.GetAccessorDeclaration, SyntaxFactory.Block( SyntaxFactory.ReturnStatement(returnExpression))))), expressionBody: null, initializer: null, semicolonToken: default); if (first) { property = property.WithLeadingBlankLine(); first = false; } members = members.Add(property); } for (var baseNode = syntaxData.TryGetNode(nodeData.BaseName); baseNode?.WrapperName is not null; baseNode = syntaxData.TryGetNode(baseNode.BaseName)) { // public static explicit operator SyntaxWrapper(BaseSyntaxWrapper node) // { // return (SyntaxWrapper)node.SyntaxNode; // } ConversionOperatorDeclarationSyntax wrapperConversion = SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ExplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(nodeData.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(baseNode.WrapperName), identifier: SyntaxFactory.Identifier("node"), @default: null))), body: SyntaxFactory.Block(SyntaxFactory.ReturnStatement( SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName(nodeData.WrapperName), expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("node"), name: SyntaxFactory.IdentifierName("SyntaxNode"))))), expressionBody: null, semicolonToken: default); if (first) { wrapperConversion = wrapperConversion.WithLeadingBlankLine(); first = false; } members = members.Add(wrapperConversion); } // public static explicit operator WhenClauseSyntaxWrapper(SyntaxNode node) // { // if (node == null) // { // return default; // } // // if (!IsInstance(node)) // { // throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); // } // // return new WhenClauseSyntaxWrapper((CSharpSyntaxNode)node); // } var nodeConversion = SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ExplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(nodeData.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("SyntaxNode"), identifier: SyntaxFactory.Identifier("node"), @default: null))), body: SyntaxFactory.Block( SyntaxFactory.IfStatement( condition: SyntaxFactory.BinaryExpression( SyntaxKind.EqualsExpression, left: SyntaxFactory.IdentifierName("node"), right: SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), statement: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)))), SyntaxFactory.IfStatement( condition: SyntaxFactory.PrefixUnaryExpression( SyntaxKind.LogicalNotExpression, operand: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName("IsInstance"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName("node")))))), statement: SyntaxFactory.Block( SyntaxFactory.ThrowStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName("InvalidCastException"), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.InterpolatedStringExpression( SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken), SyntaxFactory.List(new InterpolatedStringContentSyntax[] { SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "Cannot cast '", "Cannot cast '", trailing: default)), SyntaxFactory.Interpolation(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("node"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList()), name: SyntaxFactory.IdentifierName("FullName"))), SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "' to '", "' to '", trailing: default)), SyntaxFactory.Interpolation(SyntaxFactory.IdentifierName("WrappedTypeName")), SyntaxFactory.InterpolatedStringText(SyntaxFactory.Token( leading: default, SyntaxKind.InterpolatedStringTextToken, "'", "'", trailing: default)), }))))), initializer: null)))), SyntaxFactory.ReturnStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(nodeData.WrapperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.CastExpression( type: SyntaxFactory.IdentifierName(concreteBase), expression: SyntaxFactory.IdentifierName("node"))))), initializer: null))), expressionBody: null, semicolonToken: default); if (first) { nodeConversion = nodeConversion.WithLeadingBlankLine(); first = false; } members = members.Add(nodeConversion); for (var baseNode = syntaxData.TryGetNode(nodeData.BaseName); baseNode?.WrapperName is not null; baseNode = syntaxData.TryGetNode(baseNode.BaseName)) { // public static implicit operator BaseSyntaxWrapper(SyntaxWrapper wrapper) // { // return BaseSyntaxWrapper.FromUpcast(wrapper.node); // } members = members.Add(SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ImplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(baseNode.WrapperName), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(nodeData.WrapperName), identifier: SyntaxFactory.Identifier("wrapper"), @default: null))), body: SyntaxFactory.Block(SyntaxFactory.ReturnStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(baseNode.WrapperName), name: SyntaxFactory.IdentifierName("FromUpcast")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("wrapper"), name: SyntaxFactory.IdentifierName("node")))))))), expressionBody: null, semicolonToken: default)); } // public static implicit operator CSharpSyntaxNode(SyntaxWrapper wrapper) // { // return wrapper.node; // } members = members.Add(SyntaxFactory.ConversionOperatorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), implicitOrExplicitKeyword: SyntaxFactory.Token(SyntaxKind.ImplicitKeyword), operatorKeyword: SyntaxFactory.Token(SyntaxKind.OperatorKeyword), type: SyntaxFactory.IdentifierName(concreteBase), parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(nodeData.WrapperName), identifier: SyntaxFactory.Identifier("wrapper"), @default: null))), body: SyntaxFactory.Block(SyntaxFactory.ReturnStatement(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("wrapper"), name: SyntaxFactory.IdentifierName("node")))), expressionBody: null, semicolonToken: default)); // public static bool IsInstance(SyntaxNode node) // { // return node != null && LightupHelpers.CanWrapNode(node, WrappedType); // } members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), returnType: SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("IsInstance"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("SyntaxNode"), identifier: SyntaxFactory.Identifier("node"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.BinaryExpression( SyntaxKind.LogicalAndExpression, left: SyntaxFactory.BinaryExpression( SyntaxKind.NotEqualsExpression, left: SyntaxFactory.IdentifierName("node"), right: SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression)), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("LightupHelpers"), name: SyntaxFactory.IdentifierName("CanWrapNode")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("node")), SyntaxFactory.Argument(SyntaxFactory.IdentifierName("WrappedType")), })))))), expressionBody: null)); foreach (var field in nodeData.Fields) { if (field.IsSkipped) { continue; } string valueName = char.ToLowerInvariant(field.Name[0]) + field.Name.Substring(1); ExpressionSyntax convertedValue = SyntaxFactory.IdentifierName(valueName); TypeSyntax propertyType = SyntaxFactory.ParseTypeName(field.GetAccessorResultType(syntaxData)); if (syntaxData.TryGetNode(field.Type) is { WrapperName: { } wrapperName }) { propertyType = SyntaxFactory.IdentifierName(wrapperName); } ExpressionSyntax returnExpression = SyntaxFactory.InvocationExpression( expression: SyntaxFactory.IdentifierName(field.WithAccessorName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.ThisExpression(), name: SyntaxFactory.IdentifierName("SyntaxNode"))), SyntaxFactory.Argument(convertedValue), }))); // public SyntaxWrapper WithField(T value) // { // return new SyntaxWrapper(...); // } members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword)), returnType: SyntaxFactory.IdentifierName(nodeData.WrapperName), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("With" + field.Name), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: propertyType, identifier: SyntaxFactory.Identifier(valueName), @default: null))), constraintClauses: default, body: SyntaxFactory.Block(SyntaxFactory.ReturnStatement( SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(nodeData.WrapperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(returnExpression))), initializer: null))), expressionBody: null, semicolonToken: default)); } if (nodeData.Kind == NodeKind.Abstract) { // internal static SyntaxWrapper FromUpcast(CSharpSyntaxNode node) // { // return new SyntaxWrapper(node); // } members = members.Add(SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar: changed to PublicKeyword returnType: SyntaxFactory.IdentifierName(nodeData.WrapperName), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("FromUpcast"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName(concreteBase), identifier: SyntaxFactory.Identifier("node"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.ObjectCreationExpression( type: SyntaxFactory.IdentifierName(nodeData.WrapperName), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(SyntaxFactory.IdentifierName("node")))), initializer: null))), expressionBody: null)); } var wrapperStruct = SyntaxFactory.StructDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword)), // Sonar: changed to PublicKeyword identifier: SyntaxFactory.Identifier(nodeData.WrapperName), typeParameterList: null, baseList: SyntaxFactory.BaseList(SyntaxFactory.SingletonSeparatedList( SyntaxFactory.SimpleBaseType(SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("ISyntaxWrapper"), typeArgumentList: SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.IdentifierName(concreteBase))))))), constraintClauses: default, members: members); var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), externs: default, usings: SyntaxFactory.List() .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Immutable"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis.CSharp"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis.CSharp.Syntax"))), members: SyntaxFactory.SingletonList(wrapperStruct)); wrapperNamespace = wrapperNamespace .NormalizeWhitespace() .WithLeadingTrivia( SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed) .WithTrailingTrivia( SyntaxFactory.CarriageReturnLineFeed); context.AddSource(nodeData.WrapperName + ".g.cs", wrapperNamespace.GetText(Encoding.UTF8)); } private void GenerateSyntaxWrapperHelper(in SourceProductionContext context, ImmutableArray wrapperTypes) { // private static readonly ImmutableDictionary WrappedTypes; var wrappedTypes = SyntaxFactory.FieldDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), declaration: SyntaxFactory.VariableDeclaration( type: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("ImmutableDictionary"), typeArgumentList: SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("Type"), SyntaxFactory.IdentifierName("Type"), }))), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(SyntaxFactory.Identifier("WrappedTypes"))))); // var csharpCodeAnalysisAssembly = typeof(CSharpSyntaxNode).GetTypeInfo().Assembly; // var builder = ImmutableDictionary.CreateBuilder(); var staticCtorStatements = SyntaxFactory.List() .Add(SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("csharpCodeAnalysisAssembly"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName("CSharpSyntaxNode")), name: SyntaxFactory.IdentifierName("GetTypeInfo"))), name: SyntaxFactory.IdentifierName("Assembly")))))))) .Add(SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("builder"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("ImmutableDictionary"), name: SyntaxFactory.GenericName( identifier: SyntaxFactory.Identifier("CreateBuilder"), typeArgumentList: SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.IdentifierName("Type"), SyntaxFactory.IdentifierName("Type"), }))))))))))); foreach (var node in wrapperTypes.OrderBy(node => node.Name, StringComparer.OrdinalIgnoreCase)) { if (node.WrapperName is null || node.Name is nameof(AnonymousFunctionExpressionSyntax) or nameof(ClassDeclarationSyntax) or nameof(LambdaExpressionSyntax) or nameof(ParenthesizedLambdaExpressionSyntax) or nameof(SimpleLambdaExpressionSyntax) or nameof(StructDeclarationSyntax)) { continue; } if (node.Name == nameof(CommonForEachStatementSyntax)) { // Prior to C# 7, ForEachStatementSyntax was the base type for all foreach statements. If // the CommonForEachStatementSyntax type isn't found at runtime, we fall back to using this type instead. // // var forEachStatementSyntaxType = csharpCodeAnalysisAssembly.GetType(CommonForEachStatementSyntaxWrapper.WrappedTypeName) // ?? csharpCodeAnalysisAssembly.GetType(CommonForEachStatementSyntaxWrapper.FallbackWrappedTypeName); staticCtorStatements = staticCtorStatements.Add( SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("forEachStatementSyntaxType"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.BinaryExpression( SyntaxKind.CoalesceExpression, left: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("WrappedTypeName")))))), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("FallbackWrappedTypeName"))))))))))))); // builder.Add(typeof(CommonForEachStatementSyntaxWrapper), forEachStatementSyntaxType); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("Add")), argumentList: SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), SyntaxFactory.Argument(SyntaxFactory.IdentifierName("forEachStatementSyntaxType")), }))))); continue; } if (node.Name == nameof(BaseObjectCreationExpressionSyntax)) { // Prior to C# 9, ObjectCreationExpressionSyntax was the base type for all object creation // statements. If the BaseObjectCreationExpressionSyntax type isn't found at runtime, we fall back // to using this type instead. // // var objectCreationExpressionSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.WrappedTypeName) // ?? csharpCodeAnalysisAssembly.GetType(BaseObjectCreationExpressionSyntaxWrapper.FallbackWrappedTypeName); LocalDeclarationStatementSyntax localStatement = SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("objectCreationExpressionSyntaxType"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.BinaryExpression( SyntaxKind.CoalesceExpression, left: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("WrappedTypeName")))))), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("FallbackWrappedTypeName")))))))))))); // This is the first line of the statements that initialize 'builder', so start it with a blank line staticCtorStatements = staticCtorStatements.Add(localStatement.WithLeadingBlankLine()); // builder.Add(typeof(BaseObjectCreationExpressionSyntaxWrapper), objectCreationExpressionSyntaxType); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("Add")), argumentList: SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), SyntaxFactory.Argument(SyntaxFactory.IdentifierName("objectCreationExpressionSyntaxType")), }))))); continue; } if (node.Name == nameof(BaseNamespaceDeclarationSyntax)) { // Prior to C# 10, NamespaceDeclarationSyntax was the base type for all namespace declarations. // If the BaseNamespaceDeclarationSyntax type isn't found at runtime, we fall back // to using this type instead. // // var baseNamespaceDeclarationSyntaxType = csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName) // ?? csharpCodeAnalysisAssembly.GetType(BaseNamespaceDeclarationSyntaxWrapper.WrappedTypeName); LocalDeclarationStatementSyntax localStatement = SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration( type: SyntaxFactory.IdentifierName("var"), variables: SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator( identifier: SyntaxFactory.Identifier("baseNamespaceDeclarationSyntaxType"), argumentList: null, initializer: SyntaxFactory.EqualsValueClause( SyntaxFactory.BinaryExpression( SyntaxKind.CoalesceExpression, left: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("WrappedTypeName")))))), right: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("FallbackWrappedTypeName")))))))))))); // This is the first line of the statements that initialize 'builder', so start it with a blank line staticCtorStatements = staticCtorStatements.Add(localStatement.WithLeadingBlankLine()); // builder.Add(typeof(BaseNamespaceDeclarationSyntaxWrapper), baseNamespaceDeclarationSyntaxType); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("Add")), argumentList: SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), SyntaxFactory.Argument(SyntaxFactory.IdentifierName("baseNamespaceDeclarationSyntaxType")), }))))); continue; } // builder.Add(typeof(ConstantPatternSyntaxWrapper), csharpCodeAnalysisAssembly.GetType(ConstantPatternSyntaxWrapper.WrappedTypeName)); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("Add")), argumentList: SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.TypeOfExpression(SyntaxFactory.IdentifierName(node.WrapperName))), SyntaxFactory.Argument( SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("csharpCodeAnalysisAssembly"), name: SyntaxFactory.IdentifierName("GetType")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName(node.WrapperName), name: SyntaxFactory.IdentifierName("WrappedTypeName"))))))), }))))); } // WrappedTypes = builder.ToImmutable(); staticCtorStatements = staticCtorStatements.Add(SyntaxFactory.ExpressionStatement( SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, left: SyntaxFactory.IdentifierName("WrappedTypes"), right: SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("builder"), name: SyntaxFactory.IdentifierName("ToImmutable"))))).WithLeadingBlankLine()); var staticCtor = SyntaxFactory.ConstructorDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), identifier: SyntaxFactory.Identifier("SyntaxWrapperHelper"), parameterList: SyntaxFactory.ParameterList(), initializer: null, body: SyntaxFactory.Block(staticCtorStatements), expressionBody: null).WithLeadingBlankLine(); // /// // /// Gets the type that is wrapped by the given wrapper. // /// // /// Type of the wrapper for which the wrapped type should be retrieved. // /// The wrapped type, or null if there is no info. // internal static Type GetWrappedType(Type wrapperType) // { // if (WrappedTypes.TryGetValue(wrapperType, out Type wrappedType)) // { // return wrappedType; // } // // return null; // } var getWrappedType = SyntaxFactory.MethodDeclaration( attributeLists: default, modifiers: SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar: changed to PublicKeyword returnType: SyntaxFactory.IdentifierName("Type"), explicitInterfaceSpecifier: null, identifier: SyntaxFactory.Identifier("GetWrappedType"), typeParameterList: null, parameterList: SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Parameter( attributeLists: default, modifiers: default, type: SyntaxFactory.IdentifierName("Type"), identifier: SyntaxFactory.Identifier("wrapperType"), @default: null))), constraintClauses: default, body: SyntaxFactory.Block( SyntaxFactory.IfStatement( condition: SyntaxFactory.InvocationExpression( expression: SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, expression: SyntaxFactory.IdentifierName("WrappedTypes"), name: SyntaxFactory.IdentifierName("TryGetValue")), argumentList: SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { SyntaxFactory.Argument(SyntaxFactory.IdentifierName("wrapperType")), SyntaxFactory.Argument( nameColon: null, refKindKeyword: SyntaxFactory.Token(SyntaxKind.OutKeyword), expression: SyntaxFactory.DeclarationExpression( type: SyntaxFactory.IdentifierName("Type"), designation: SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("wrappedType")))), }))), statement: SyntaxFactory.Block( SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName("wrappedType")))), SyntaxFactory.ReturnStatement(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))), expressionBody: null); getWrappedType = getWrappedType.WithLeadingTrivia(SyntaxFactory.TriviaList( SyntaxFactory.Trivia(SyntaxFactory.DocumentationComment( SyntaxFactory.XmlText(" "), SyntaxFactory.XmlSummaryElement( SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" Gets the type that is wrapped by the given wrapper."), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" ")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" "), SyntaxFactory.XmlParamElement( "wrapperType", SyntaxFactory.XmlText("Type of the wrapper for which the wrapped type should be retrieved.")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation), SyntaxFactory.XmlText(" "), SyntaxFactory.XmlReturnsElement( SyntaxFactory.XmlText("The wrapped type, or null if there is no info.")), SyntaxFactory.XmlText(XmlSyntaxFactory.XmlCarriageReturnLineFeedWithContinuation).WithoutTrailingTrivia())))); var wrapperHelperClass = SyntaxFactory.ClassDeclaration( attributeLists: default, modifiers: SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword)).Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)), // Sonar: changed to PublicKeyword identifier: SyntaxFactory.Identifier("SyntaxWrapperHelper"), typeParameterList: null, baseList: null, constraintClauses: default, members: SyntaxFactory.List() .Add(wrappedTypes) .Add(staticCtor) .Add(getWrappedType)); var wrapperNamespace = SyntaxFactory.NamespaceDeclaration( name: SyntaxFactory.ParseName("StyleCop.Analyzers.Lightup"), externs: default, usings: SyntaxFactory.List() .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Collections.Immutable"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Reflection"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis"))) .Add(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("Microsoft.CodeAnalysis.CSharp"))), members: SyntaxFactory.SingletonList(wrapperHelperClass)); wrapperNamespace = wrapperNamespace .NormalizeWhitespace() .WithLeadingTrivia( SyntaxFactory.Comment("// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment("// Licensed under the MIT License. See LICENSE in the project root for license information."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed) .WithTrailingTrivia( SyntaxFactory.CarriageReturnLineFeed); context.AddSource("SyntaxWrapperHelper.g.cs", wrapperNamespace.GetText(Encoding.UTF8)); } private sealed class SyntaxData { private readonly Dictionary nameToNode; public SyntaxData(CompilationData compilationData, XDocument document) { var nodesBuilder = ImmutableArray.CreateBuilder(); if (document.XPathSelectElement("/Tree[@Root='SyntaxNode']") is { } tree) { foreach (var element in tree.XPathSelectElements("PredefinedNode|AbstractNode|Node")) { nodesBuilder.Add(new NodeData(compilationData, element)); } } this.Nodes = nodesBuilder.ToImmutable(); this.nameToNode = this.Nodes.ToDictionary(node => node.Name); } public ImmutableArray Nodes { get; } public NodeData? TryGetConcreteType(NodeData? node) { for (var current = node; current is not null; current = this.TryGetNode(current.BaseName)) { if (current.WrapperName is null) { // This is not a wrapper return current; } } return null; } public NodeData? TryGetConcreteBase(NodeData node) { return this.TryGetConcreteType(this.TryGetNode(node.BaseName)); } public NodeData? TryGetNode(string name) { this.nameToNode.TryGetValue(name, out var node); return node; } } private sealed class NodeData { private static readonly ImmutableHashSet ClassesThatShouldBeAlwaysGenerated = ImmutableHashSet.Create( // Sonar nameof(AnonymousFunctionExpressionSyntax), nameof(LambdaExpressionSyntax), nameof(ParenthesizedLambdaExpressionSyntax), nameof(SimpleLambdaExpressionSyntax), // TypeDeclarationSyntaxWrapper causes compatibility problems with ParameterList because the property was first declared in RecordDeclarationSyntax (C#9) and later // moved upwards into TypeDeclarationSyntax (C#12). The forwarding of the accessor in RecordDeclarationSyntax to the base wrapper does not work for SDK versions // .Net5 to .Net7 as the property is not found in TypeDeclarationSyntax. // Use the `ParameterList(this TypeDeclarationSyntax)` extension method for a unified access instead of adding TypeDeclarationSyntax here. nameof(ClassDeclarationSyntax), nameof(StructDeclarationSyntax)); public NodeData(CompilationData compilationData, XElement element) { this.Kind = element.Name.LocalName switch { "PredefinedNode" => NodeKind.Predefined, "AbstractNode" => NodeKind.Abstract, "Node" => NodeKind.Concrete, _ => throw new NotSupportedException($"Unknown element name '{element.Name}'"), }; this.Name = element.RequiredAttribute("Name").Value; this.ExistingType = compilationData.ExistingTypes.GetValueOrDefault($"Microsoft.CodeAnalysis.CSharp.Syntax.{this.Name}") ?? compilationData.ExistingTypes.GetValueOrDefault($"Microsoft.CodeAnalysis.CSharp.{this.Name}") ?? compilationData.ExistingTypes.GetValueOrDefault($"Microsoft.CodeAnalysis.{this.Name}"); if (this.ExistingType is not null && !ClassesThatShouldBeAlwaysGenerated.Contains(this.Name)) // Sonar { this.WrapperName = null; } else { this.WrapperName = this.Name + "Wrapper"; } this.BaseName = element.RequiredAttribute("Base").Value; this.Fields = element.XPathSelectElements("descendant::Field").Select(field => new FieldData(this, field)).ToImmutableArray(); } public NodeKind Kind { get; } public string Name { get; } public ExistingTypeData? ExistingType { get; } public string? WrapperName { get; } public string BaseName { get; } public ImmutableArray Fields { get; } internal FieldData? TryGetField(string name) { return this.Fields.SingleOrDefault(field => field.Name == name); } } private sealed class FieldData { private readonly NodeData nodeData; public FieldData(NodeData nodeData, XElement element) { this.nodeData = nodeData; this.Name = element.RequiredAttribute("Name").Value; var type = element.RequiredAttribute("Type").Value; this.Type = type switch { "SyntaxList" => nameof(SyntaxTokenList), _ => type, }; this.IsOverride = element.Attribute("Override")?.Value == "true"; this.AccessorName = this.Name + "Accessor"; this.WithAccessorName = "With" + this.Name + "Accessor"; } public bool IsSkipped => false; public string Name { get; } public string AccessorName { get; } public string WithAccessorName { get; } public string Type { get; } public bool IsOverride { get; } /// /// Gets a value indicating whether this field is implemented as an extension method in the lightup layer. /// public bool IsExtensionField { get { return this.nodeData.ExistingType is not null && !this.nodeData.ExistingType.MemberNames.Contains(this.Name); } } public NodeData GetDeclaringNode(SyntaxData syntaxData) { for (var current = this.nodeData; current is not null; current = syntaxData.TryGetNode(current.BaseName)) { var currentField = current.TryGetField(this.Name); if (currentField is { IsOverride: false }) { return currentField.nodeData; } } throw new NotSupportedException("Unable to find declaring node."); } public bool IsWrappedSeparatedSyntaxList(SyntaxData syntaxData, [NotNullWhen(true)] out NodeData? element) { if (this.Type.StartsWith("SeparatedSyntaxList<") && this.Type.EndsWith(">")) { var elementTypeName = this.Type.Substring("SeparatedSyntaxList<".Length, this.Type.Length - "SeparatedSyntaxList<".Length - ">".Length); var elementTypeNode = syntaxData.TryGetNode(elementTypeName); if (elementTypeNode is { WrapperName: not null }) { element = elementTypeNode; return true; } } element = null; return false; } public string GetAccessorResultType(SyntaxData syntaxData) { var typeNode = syntaxData.TryGetNode(this.Type); if (typeNode is not null) { return syntaxData.TryGetConcreteType(typeNode)?.Name ?? nameof(SyntaxNode); } if (this.IsWrappedSeparatedSyntaxList(syntaxData, out var elementTypeNode)) { return $"SeparatedSyntaxListWrapper<{elementTypeNode.WrapperName}>"; } return this.Type; } public string? GetAccessorResultElementType(SyntaxData syntaxData) { if (this.IsWrappedSeparatedSyntaxList(syntaxData, out var elementTypeNode)) { return elementTypeNode.WrapperName; } return null; } } private sealed record CompilationData { public EquatableValue> ExistingTypesWrapper { get; } // Sonar public ImmutableDictionary ExistingTypes => this.ExistingTypesWrapper.Value; public CompilationData(EquatableValue> ExistingTypesWrapper) // Sonar { this.ExistingTypesWrapper = ExistingTypesWrapper; } } private record class ExistingTypeData { public string TypeName { get; } // Sonar public EquatableValue> MemberNamesWrapper { get; } // Sonar public ImmutableArray MemberNames => this.MemberNamesWrapper.Value; public ExistingTypeData(string TypeName, EquatableValue> MemberNamesWrapper) // Sonar { this.TypeName = TypeName; this.MemberNamesWrapper = MemberNamesWrapper; } public static ExistingTypeData FromNamedType(INamedTypeSymbol namedType, string typeName) { var memberNames = ImmutableArray.CreateRange(namedType.GetMembers(), member => member.Name); return new ExistingTypeData( TypeName: typeName, MemberNamesWrapper: new EquatableValue>(memberNames, ImmutableArrayEqualityComparer.Default)); } } private sealed class EquatableValue : IEquatable?> { public EquatableValue(T value, IEqualityComparer comparer) { this.Value = value; this.Comparer = comparer; } public T Value { get; } public IEqualityComparer Comparer { get; } public bool Equals(EquatableValue? other) { if (other is null) { return false; } return this.Comparer.Equals(this.Value, other.Value); } } private sealed class ImmutableDictionaryEqualityComparer : IEqualityComparer?> where TKey : notnull { public static ImmutableDictionaryEqualityComparer Default { get; } = new ImmutableDictionaryEqualityComparer(); public bool Equals(ImmutableDictionary? x, ImmutableDictionary? y) { if (x == y) { return true; } else if (x is null || y is null) { return false; } else if (x.Count != y.Count) { return false; } var keyEqualityComparer = EqualityComparer.Default; var valueEqualityComparer = EqualityComparer.Default; using var first = x.GetEnumerator(); using var second = y.GetEnumerator(); while (first.MoveNext() && second.MoveNext()) { if (!keyEqualityComparer.Equals(first.Current.Key, second.Current.Key) || !valueEqualityComparer.Equals(first.Current.Value, second.Current.Value)) { return false; } } return true; } public int GetHashCode(ImmutableDictionary? obj) { if (obj is null) { return 0; } var hashCode = default(RoslynHashCode); var keyEqualityComparer = EqualityComparer.Default; var valueEqualityComparer = EqualityComparer.Default; foreach (var i in obj) { hashCode.Add(keyEqualityComparer.GetHashCode(i.Key)); hashCode.Add(valueEqualityComparer.GetHashCode(i.Value)); } return hashCode.ToHashCode(); } } private sealed class ImmutableArrayEqualityComparer : IEqualityComparer> { public static ImmutableArrayEqualityComparer Default { get; } = new ImmutableArrayEqualityComparer(); public bool Equals(ImmutableArray x, ImmutableArray y) { if (x == y) { return true; } else if (x == null || y == null) { return false; } else if (x.Length != y.Length) { return false; } var equalityComparer = EqualityComparer.Default; for (var i = 0; i < x.Length; i++) { if (!equalityComparer.Equals(x[i], y[i])) { return false; } } return true; } public int GetHashCode(ImmutableArray obj) { if (obj == null) { return 0; } var hashCode = default(RoslynHashCode); var equalityComparer = EqualityComparer.Default; foreach (var i in obj) { hashCode.Add(equalityComparer.GetHashCode(i)); } return hashCode.ToHashCode(); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/XElementExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.CodeGeneration { using System; using System.Xml.Linq; internal static class XElementExtensions { public static XAttribute RequiredAttribute(this XElement element, XName name) => element.Attribute(name) ?? throw new InvalidOperationException($"Expected attribute '{name}'"); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/XmlSyntaxFactory.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.CodeGeneration { using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; internal static class XmlSyntaxFactory { public const string CrLf = "\r\n"; public static SyntaxToken XmlCarriageReturnLineFeedWithContinuation { get; } = SyntaxFactory.XmlTextNewLine(CrLf, continueXmlDocumentationComment: true); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.CodeGeneration/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", "requested": "[5.0.0, )", "resolved": "5.0.0", "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "TunnelVisionLabs.ReferenceAssemblyAnnotator": { "type": "Direct", "requested": "[1.0.0-alpha.160, )", "resolved": "1.0.0-alpha.160", "contentHash": "ktxB8PGoPpIaYKjLk/+P94Fi2Qw2E1Dw7atBQRrKnHA57sk8WwmkI4RJmg6s5ph4k1RIaaAZMus05ah/AikEkA==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "3.11.0", "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Memory": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==", "dependencies": { "System.Buffers": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "t+SoieZsRuEyiw/J+qXUbolyO219tKQQI0+2/YI+Qv7YdGValA6WiuokrNKqjrTNsy5ABWU11bdKOzUdheteXg==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", "dependencies": { "System.Collections.Immutable": "9.0.0", "System.Memory": "4.5.5" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.1.0", "contentHash": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg==" }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.1.0" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Factory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.ShimLayer.Generator; public static class Factory { // Match 3 or more consecutive newlines (with optional whitespace-only lines between them) and replace with exactly 2 newlines. private static readonly Regex ExcessiveNewLines = new(@"\n(\s*\n){2,}", RegexOptions.ExplicitCapture, TimeSpan.FromMilliseconds(100)); public static IEnumerable CreateAllFiles() { using var typeLoader = new TypeLoader(); var model = ModelBuilder.Build(typeLoader.LoadLatest(), typeLoader.LoadBaseline()); foreach (var strategy in model.ToArray()) { if (strategy.Generate(model) is { } content) { var shortened = ExcessiveNewLines.Replace(content, "\n\n"); yield return new($"{strategy.Latest.Name}.g.cs", shortened); } } } } public record GeneratedFile(string Name, string Content); ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Model/MemberDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Model; public record MemberDescriptor(MemberInfo Member, bool IsPassthrough); ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Model/ModelBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Model; public static class ModelBuilder { private static readonly TypeDescriptor ObjectTypeDescriptor = new(typeof(object), typeof(object).GetMembers()); public static StrategyModel Build(TypeDescriptor[] latest, TypeDescriptor[] baseline) { var baselineMap = baseline.ToDictionary(x => x.Type.FullName, x => x); return new(latest.ToDictionary(x => x.Type, x => CreateStrategy(x, baselineMap.TryGetValue(x.Type.FullName, out var baselineType) ? baselineType : null, baselineMap))); } private static Strategy CreateStrategy(TypeDescriptor latest, TypeDescriptor baseline, IReadOnlyDictionary baselineMap) { if (IsSkipped(latest.Type)) { return new SkipStrategy(latest.Type); } else if (baseline is not null && latest.Members.Select(x => x.ToString()).OrderBy(x => x).SequenceEqual(baseline.Members.Select(x => x.ToString()).OrderBy(x => x))) { return new NoChangeStrategy(latest.Type); } else if (latest.Type.IsEnum) { var fields = CreateEnumFields(latest, baseline); return baseline is null ? new NewEnumStrategy(latest.Type, fields) : new PartialEnumStrategy(latest.Type, fields); } else if (IsAssignableTo(latest.Type, "Microsoft.CodeAnalysis.SyntaxNode")) { if (baseline is null) { var commonBase = FindCommonBaseType(latest, baselineMap); return new SyntaxNodeWrapStrategy(latest.Type, commonBase.Type, CreateMembers(latest, commonBase)); } else { return new SyntaxNodeExtendStrategy(latest.Type, CreateMembers(latest, baseline)); } } else if (IsAssignableTo(latest.Type, "Microsoft.CodeAnalysis.IOperation")) { return new IOperationStrategy(latest.Type, CreateMembers(latest, baseline)); } // ToDo: TypeStrategy, or ClassStrategy / StructStrategy / InterfaceStrategy? else { // ToDo: Throw NotSupportedException instead of skip, there should be nothing left after explicitly handling all known cases return baseline is null ? new SkipStrategy(latest.Type) : new NoChangeStrategy(latest.Type); } } private static TypeDescriptor FindCommonBaseType(TypeDescriptor latest, IReadOnlyDictionary baselineMap) { var current = latest.Type; while (current is not null) { if (baselineMap.TryGetValue(current.FullName, out var baselineType)) { return baselineType; } current = current.BaseType; } return ObjectTypeDescriptor; } private static bool IsAssignableTo(Type type, string fullName) // We can't use typeof(Xxx).IsAssignableFrom(type) because it's loaded into a different metadata context { if (type.GetInterface(fullName) is not null) { return true; } while (type is not null) { if (type.FullName == fullName) { return true; } type = type.BaseType; } return false; } private static MemberDescriptor[] CreateMembers(TypeDescriptor latestType, TypeDescriptor baselineType) { var baseline = new HashSet(baselineType?.Members.Select(x => x.ToString()) ?? []); return latestType.Members.Where(IsValid).Select(x => new MemberDescriptor(x, baseline.Contains(x.ToString()))).ToArray(); } private static FieldInfo[] CreateEnumFields(TypeDescriptor latestType, TypeDescriptor baselineType) { // IOperation has changed significantly compared to Roslyn 1.3.2, including changes of values => we need to (re)generate everything var baseline = latestType.Type.Name == nameof(OperationKind) ? [] : new HashSet(baselineType?.Members.OfType().Select(x => x.Name) ?? []); return latestType.Members.OfType().Where(x => !x.IsSpecialName && !baseline.Contains(x.Name)).ToArray(); } private static bool IsSkipped(Type type) => type.IsNested || type.IsGenericType || typeof(Delegate).IsAssignableFrom(type); private static bool IsValid(MemberInfo member) => member switch { MethodInfo method => !method.IsSpecialName && !(method.Name is nameof(GetType) or nameof(Equals) or nameof(GetHashCode) or nameof(ToString)), // Struct methods that would need override PropertyInfo => true, _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Model/StrategyModel.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; namespace SonarAnalyzer.ShimLayer.Generator.Model; public class StrategyModel : IEnumerable { private readonly Dictionary strategies; public Strategy this[Type key] { get { if (strategies.TryGetValue(key, out var strategy)) { return strategy; } else { Strategy newStrategy = key.Name == "SeparatedSyntaxList`1" && this[key.GenericTypeArguments.Single()] is SyntaxNodeWrapStrategy typeArgument ? new SeparatedSyntaxListStrategy(key, typeArgument) : new NoChangeStrategy(key); Add(key, newStrategy); return newStrategy; } } } public StrategyModel() => strategies = []; public StrategyModel(Dictionary strategies) => this.strategies = strategies; public void Add(Type type, Strategy strategy) => strategies.Add(type, strategy); public IEnumerator GetEnumerator() => strategies.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Model/TypeDescriptor.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Model; public record TypeDescriptor(Type Type, MemberInfo[] Members); ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Model/TypeLoader.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.ShimLayer.Generator.Model; public sealed class TypeLoader : IDisposable { private readonly MetadataLoadContext metadataContext = new(new CustomAssemblyResolver(), Path.GetFileNameWithoutExtension(typeof(object).Assembly.Location)); public void Dispose() => metadataContext.Dispose(); public TypeDescriptor[] LoadBaseline() { var assembly = typeof(TypeLoader).Assembly; using var common = assembly.GetManifestResourceStream("Microsoft.CodeAnalysis.1.3.2.dll"); using var csharp = assembly.GetManifestResourceStream("Microsoft.CodeAnalysis.CSharp.1.3.2.dll"); return [ .. Load(metadataContext.LoadFromStream(common)), .. Load(metadataContext.LoadFromStream(csharp)) ]; } public TypeDescriptor[] LoadLatest() => [ ..Load(metadataContext.LoadFromAssemblyPath(typeof(SyntaxNode).Assembly.Location)), // Microsoft.CodeAnalysis ..Load(metadataContext.LoadFromAssemblyPath(typeof(CSharpSyntaxNode).Assembly.Location)) // Microsoft.CodeAnalysis.CSharp ]; private static TypeDescriptor[] Load(Assembly assembly) => assembly.GetExportedTypes().Select(x => new TypeDescriptor(x, FindMembers(x).ToArray())).ToArray(); private static IEnumerable FindMembers(Type type) { foreach (var member in type.GetMembers()) { yield return member; } if (type.IsInterface) // Members from inherited interfaces are not present in type.GetMembers() { foreach (var member in type.GetInterfaces().SelectMany(x => x.GetMembers())) { yield return member; } } } } file sealed class CustomAssemblyResolver : PathAssemblyResolver { public CustomAssemblyResolver() : base(Directory.GetFiles(Path.GetDirectoryName(typeof(object).Assembly.Location), "*.dll")) { } public override Assembly Resolve(MetadataLoadContext context, AssemblyName assemblyName) => base.Resolve(context, assemblyName) // Microsoft.CodeAnalysis does not resolve automatically ?? context.GetAssemblies().Single(x => x.GetName().Name == assemblyName.Name && x.GetName().Version.Major == assemblyName.Version.Major); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/ShimLayerGenerator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; namespace SonarAnalyzer.ShimLayer.Generator; [Generator] [ExcludeFromCodeCoverage] public class ShimLayerGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) => context.RegisterSourceOutput( context.ParseOptionsProvider.Select((_, _) => context.GetType().Assembly.FullName), // Any simple provider, return Roslyn version (context, _) => { foreach (var file in Factory.CreateAllFiles()) { context.AddSource(file.Name, file.Content); } }); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/SonarAnalyzer.ShimLayer.Generator.csproj ================================================  netstandard2.0 Internal.SonarAnalyzer.ShimLayer.Generator Microsoft.CodeAnalysis.CSharp.1.3.2.dll Microsoft.CodeAnalysis.1.3.2.dll IsExternalInit.cs $(GetTargetPathDependsOn);GetDependencyTargetPaths ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/IOperationStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class IOperationStrategy : Strategy { public IReadOnlyList Members { get; } public IOperationStrategy(Type latest, IReadOnlyList members) : base(latest) => Members = members; public override string Generate(StrategyModel model) => $"namespace SonarAnalyzer.ShimLayer; // {Latest.Name}"; // NET-2729 public override string ReturnTypeSnippet() => throw new NotImplementedException(); public override string ToConversionSnippet(string from) => throw new NotImplementedException(); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/NewEnumStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class NewEnumStrategy : Strategy { public FieldInfo[] Fields { get; } public NewEnumStrategy(Type latest, FieldInfo[] fields) : base(latest) => Fields = fields; public override string Generate(StrategyModel model) { var sb = new StringBuilder(); sb.AppendLine(Preamble()); sb.AppendLine($"{SerializeAttributes(Latest.GetCustomAttributesData(), 0)}public enum {Latest.Name} : {Enum.GetUnderlyingType(Latest)}"); sb.AppendLine("{"); foreach (var field in Fields) { sb.AppendLine($" {field.Name} = {field.GetRawConstantValue()},"); } sb.AppendLine("}"); return sb.ToString(); } public override string ReturnTypeSnippet() => Latest.Name; public override string ToConversionSnippet(string from) => $"({Latest.Name}){from}"; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/NoChangeStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class NoChangeStrategy : Strategy { private readonly string type; public NoChangeStrategy(Type latest) : base(latest) => type = latest.IsGenericType ? latest.Name.Replace("`1", null) + "<" + string.Join(", ", latest.GetGenericArguments().Select(x => x.Name)) + ">" : latest.Name; public override string Generate(StrategyModel model) => null; public override string ReturnTypeSnippet() => type; public override string CompiletimeTypeSnippet() => type; public override string ToConversionSnippet(string from) => $"({type}){from}"; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/PartialEnumStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class PartialEnumStrategy : Strategy { public FieldInfo[] Fields { get; } public PartialEnumStrategy(Type latest, FieldInfo[] fields) : base(latest) => Fields = fields; public override string Generate(StrategyModel model) { var sb = new StringBuilder(); sb.Append(Preamble($"using {Latest.Namespace};")); sb.AppendLine(); sb.AppendLine($"public static class {Latest.Name}Ex"); sb.AppendLine("{"); foreach (var field in Fields) { sb.AppendLine($" public const {Latest.Name} {field.Name} = ({Latest.Name}){field.GetRawConstantValue()};"); } sb.AppendLine("}"); return sb.ToString(); } public override string ReturnTypeSnippet() => Latest.Name; public override string ToConversionSnippet(string from) => $"({Latest.Name}){from}"; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/SeparatedSyntaxListStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class SeparatedSyntaxListStrategy : Strategy { private readonly string type; private readonly Strategy typeArgument; public SeparatedSyntaxListStrategy(Type latest, Strategy typeArgument) : base(latest) { type = latest.Name.Replace("`1", "Wrapper"); this.typeArgument = typeArgument; } public override string ReturnTypeSnippet() => $"{type}<{typeArgument.ReturnTypeSnippet()}>"; public override string ToConversionSnippet(string from) => from; public override string CompiletimeTypeSnippet() => ReturnTypeSnippet(); public override string PropertyAccessorInitializerSnippet(string compiletimeType, string propertyName) => $"LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor<{compiletimeType}, {typeArgument.ReturnTypeSnippet()}>(WrappedType, nameof({propertyName}))"; public override string Generate(StrategyModel model) => null; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/SkipStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class SkipStrategy : Strategy { public override bool IsSupported => false; public SkipStrategy(Type latest) : base(latest) { } public override string ReturnTypeSnippet() => throw new NotSupportedException(); public override string ToConversionSnippet(string from) => throw new NotSupportedException(); public override string Generate(StrategyModel model) => null; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/Strategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public abstract class Strategy { public abstract string Generate(StrategyModel model); public abstract string ReturnTypeSnippet(); public abstract string ToConversionSnippet(string from); public virtual bool IsSupported => true; public Type Latest { get; } protected Strategy(Type latest) => Latest = latest; public virtual string CompiletimeTypeSnippet() => Latest.Name; protected static string JoinLines(IEnumerable lines) => string.Join("\n", lines.Where(x => x is not null)); protected static string Preamble(params IEnumerable additionalUsing) => $""" // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ {JoinLines((SortedSet)[ "using Microsoft.CodeAnalysis;", "using Microsoft.CodeAnalysis.CSharp;", "using Microsoft.CodeAnalysis.CSharp.Syntax;", "using Microsoft.CodeAnalysis.Text;", "using System;", "using System.Collections.Immutable;", .. additionalUsing])} namespace SonarAnalyzer.ShimLayer; """; public virtual string PropertyAccessorInitializerSnippet(string compiletimeType, string propertyName) => $"""LightupHelpers.CreateSyntaxPropertyAccessor<{compiletimeType}, {CompiletimeTypeSnippet()}>(WrappedType, "{propertyName}")"""; protected static string SerializeAttributes(IEnumerable attributes, int indentSize) { var sb = new StringBuilder(); var indent = new string(' ', indentSize); foreach (var attribute in attributes.Where(x => x.AttributeType.Name is not "ExperimentalAttribute" and not "NullableAttribute")) { sb.Append("[").Append(attribute.AttributeType.FullName); if (attribute.ConstructorArguments.Any()) { sb.Append("("); sb.Append(string.Join(", ", attribute.ConstructorArguments.Select(SerializeArgument))); sb.Append(")"); } sb.AppendLine("]"); sb.Append(indent); } return sb.ToString(); } private static string SerializeArgument(CustomAttributeTypedArgument arg) { if (arg.ArgumentType.Name == nameof(String)) { return $@"""{arg.Value}"""; } else if (arg.ArgumentType.Name == nameof(Boolean)) { return arg.Value.ToString().ToLower(); } else if (arg.ArgumentType.IsEnum) // If the Enum is not in Roslyn 1.3.2, or netstandard2.0, consider excluding the entire attribute { return $"{arg.ArgumentType.FullName}.{Enum.GetName(arg.ArgumentType, arg.Value)}"; } else { return arg.Value?.ToString() ?? "null"; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/SyntaxNodeExtendStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public sealed class SyntaxNodeExtendStrategy : Strategy { public IReadOnlyList Members { get; } public SyntaxNodeExtendStrategy(Type latest, MemberDescriptor[] members) : base(latest) => Members = members.Where(x => !x.IsPassthrough).Select(x => x.Member).ToArray(); public override string Generate(StrategyModel model) => Members.Select(x => GenerateMemberAccessor(x, model)).Where(x => x is not null).ToArray() is { Length: > 0 } accessors ? $$""" {{Preamble($"using {Latest.Namespace};")}} public static partial class {{Latest.Name}}ShimExtensions { private static readonly Type WrappedType = typeof({{CompiletimeTypeSnippet()}}); {{JoinLines(accessors)}} extension({{CompiletimeTypeSnippet()}} @this) { {{JoinLines(Members.Select(x => GenerateMemberExtension(x, model)))}} } } """ : null; public override string ReturnTypeSnippet() => Latest.Name; public override string ToConversionSnippet(string from) => from; private string GenerateMemberAccessor(MemberInfo member, StrategyModel model) => member switch { PropertyInfo prop when model[prop.PropertyType] is var propertyTypeStrategy => $""" private static readonly Func<{CompiletimeTypeSnippet()}, {propertyTypeStrategy.CompiletimeTypeSnippet()}> {prop.Name}Accessor = {propertyTypeStrategy.PropertyAccessorInitializerSnippet(CompiletimeTypeSnippet(), prop.Name)}; """, _ => null, }; private static string GenerateMemberExtension(MemberInfo member, StrategyModel model) => member switch { PropertyInfo { GetMethod: not null } prop when model[prop.PropertyType] is var propertyTypeStrategy => $""" public {propertyTypeStrategy.ReturnTypeSnippet()} {prop.Name} => {propertyTypeStrategy.ToConversionSnippet($"{prop.Name}Accessor(@this)")}; """, _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/Strategies/SyntaxNodeWrapStrategy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies; public class SyntaxNodeWrapStrategy : Strategy { public Type BaseType { get; } public IReadOnlyList Members { get; } public SyntaxNodeWrapStrategy(Type latest, Type baseType, IReadOnlyList members) : base(latest) { BaseType = baseType; Members = members; } public override string ReturnTypeSnippet() => $"{Latest.Name}Wrapper"; public override string ToConversionSnippet(string from) => $"({Latest.Name}Wrapper){from}"; public override string CompiletimeTypeSnippet() => BaseType.Name; public override string Generate(StrategyModel model) => $$""" {{Preamble()}} public readonly partial struct {{Latest.Name}}Wrapper: ISyntaxWrapper<{{CompiletimeTypeSnippet()}}> { public const string WrappedTypeName = "{{Latest.FullName}}"; private static readonly Type WrappedType; private readonly {{CompiletimeTypeSnippet()}} node; static {{Latest.Name}}Wrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof({{Latest.Name}}Wrapper)); {{JoinLines(Members.Where(x => !x.IsPassthrough).Select(x => MemberAccessorInitialization(x.Member, model)))}} } private {{Latest.Name}}Wrapper({{CompiletimeTypeSnippet()}} node) => this.node = node; public {{CompiletimeTypeSnippet()}} Node => this.node; [Obsolete("Use Node instead")] public {{CompiletimeTypeSnippet()}} SyntaxNode => this.node; {{JoinLines(Members.Select(x => MemberDeclaration(x, model)))}} public static explicit operator {{Latest.Name}}Wrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new {{Latest.Name}}Wrapper(({{CompiletimeTypeSnippet()}})node); } public static implicit operator {{CompiletimeTypeSnippet()}}({{Latest.Name}}Wrapper wrapper) => wrapper.node; {{WrapperToWrapperConversions(model)}} public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """; private string WrapperToWrapperConversions(StrategyModel model) { StringBuilder sb = null; var baseType = Latest.BaseType; while (baseType is not null && model[baseType] is SyntaxNodeWrapStrategy) // BaseType is also wrapped { sb ??= new StringBuilder(); sb.AppendLine($""" public static implicit operator {baseType.Name}Wrapper({Latest.Name}Wrapper up) => ({baseType.Name}Wrapper)up.SyntaxNode; public static explicit operator {Latest.Name}Wrapper({baseType.Name}Wrapper down) => ({Latest.Name}Wrapper)down.SyntaxNode; """); baseType = baseType.BaseType; } return sb?.ToString() ?? string.Empty; } private string MemberAccessorInitialization(MemberInfo member, StrategyModel model) => member is PropertyInfo property && model[property.PropertyType] is { IsSupported: true } propertyTypeStrategy ? $""" {member.Name}Accessor = {propertyTypeStrategy.PropertyAccessorInitializerSnippet(CompiletimeTypeSnippet(), member.Name)}; """ : null; private string MemberDeclaration(MemberDescriptor member, StrategyModel model) { var attributes = SerializeAttributes(member.Member.GetCustomAttributesData(), 4); return member switch { { IsPassthrough: true, Member: PropertyInfo pi } => $""" {attributes}public {model[pi.PropertyType].CompiletimeTypeSnippet()} {member.Member.Name} => this.node.{member.Member.Name}; """, { IsPassthrough: false, Member: PropertyInfo pi } when model[pi.PropertyType] is { IsSupported: true } propertyTypeStrategy => $""" private static readonly Func<{BaseType.Name}, {propertyTypeStrategy.CompiletimeTypeSnippet()}> {member.Member.Name}Accessor; {attributes}public {propertyTypeStrategy.ReturnTypeSnippet()} {member.Member.Name} => {propertyTypeStrategy.ToConversionSnippet($"{member.Member.Name}Accessor(this.node)")}; """, _ => null, }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Generator/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.Common": { "type": "Direct", "requested": "[5.0.0, )", "resolved": "5.0.0", "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", "requested": "[5.0.0, )", "resolved": "5.0.0", "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Reflection.MetadataLoadContext": { "type": "Direct", "requested": "[10.0.5, )", "resolved": "10.0.5", "contentHash": "z9yyFZcuCwtZXrxxDc2nBfB5lTHf96gS/rsD38Lcv6Ok25TOYPjNKlqpTZUqu2HTLPAM4w+/WjGL6gi9cIJO6w==", "dependencies": { "System.Collections.Immutable": "10.0.5", "System.Memory": "4.6.3", "System.Reflection.Metadata": "10.0.5" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "3.11.0", "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.1", "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "10.0.5", "contentHash": "nozrOKfEgfrvvswkCfxaumY68RA/x1F3ZYwtwRvva8ul91NCnUzb6MKpl5/P7u9v/nIagVL4OXXj8d007tucxw==", "dependencies": { "System.Memory": "4.6.3", "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.Memory": { "type": "Transitive", "resolved": "4.6.3", "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", "dependencies": { "System.Buffers": "4.6.1", "System.Numerics.Vectors": "4.6.1", "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.6.1", "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "10.0.5", "contentHash": "fEAXJCtauNLYr5ESg3t6HE2Av6urWdJdymxZbuSt/DDqhtNtLtUtXTEpKbp0vkTdyBJwmaha8d2454vSzS/lcQ==", "dependencies": { "System.Collections.Immutable": "10.0.5" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.1.2", "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.1.0" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/AnalysisContext/CompilationStartAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using static System.Linq.Expressions.Expression; namespace SonarAnalyzer.ShimLayer.AnalysisContext; public static class CompilationStartAnalysisContextExtensions { private static readonly Action, SymbolKind> RegisterSymbolStartActionWrapper = CreateRegisterSymbolStartAnalysisWrapper(); public static void RegisterSymbolStartAction(this CompilationStartAnalysisContext context, Action action, SymbolKind symbolKind) => RegisterSymbolStartActionWrapper(context, action, symbolKind); // Code is executed in static initializers and is not detected by the coverage tool // See the SonarAnalysisContextTest.SonarCompilationStartAnalysisContext_RegisterSymbolStartAction family of tests to check test coverage manually [ExcludeFromCodeCoverage] private static Action, SymbolKind> CreateRegisterSymbolStartAnalysisWrapper() { if (typeof(CompilationStartAnalysisContext).GetMethod(nameof(RegisterSymbolStartAction)) is not { } registerMethod) { return static (_, _, _) => { }; } var contextParameter = Parameter(typeof(CompilationStartAnalysisContext)); var shimmedActionParameter = Parameter(typeof(Action)); var symbolKindParameter = Parameter(typeof(SymbolKind)); var roslynSymbolStartAnalysisContextType = typeof(CompilationStartAnalysisContext).Assembly.GetType("Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext"); var roslynSymbolStartAnalysisActionType = typeof(Action<>).MakeGenericType(roslynSymbolStartAnalysisContextType); var roslynSymbolStartAnalysisContextParameter = Parameter(roslynSymbolStartAnalysisContextType); var sonarSymbolStartAnalysisContextCtor = typeof(SymbolStartAnalysisContextWrapper).GetConstructors().Single(); // Action registerAction = roslynSymbolStartAnalysisContextParameter => // shimmedActionParameter.Invoke(new Sonar.SymbolStartAnalysisContextWrapper(roslynSymbolStartAnalysisContextParameter)) var registerAction = Lambda( delegateType: roslynSymbolStartAnalysisActionType, body: Call(shimmedActionParameter, nameof(Action.Invoke), [], New(sonarSymbolStartAnalysisContextCtor, roslynSymbolStartAnalysisContextParameter)), parameters: roslynSymbolStartAnalysisContextParameter); // (contextParameter, shimmedActionParameter, symbolKindParameter) => contextParameter.RegisterSymbolStartAction(registerAction, symbolKindParameter) return Lambda, SymbolKind>>( Call(contextParameter, registerMethod, registerAction, symbolKindParameter), contextParameter, shimmedActionParameter, symbolKindParameter).Compile(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/AnalysisContext/SymbolStartAnalysisContextWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using static System.Linq.Expressions.Expression; using CS = Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.ShimLayer.AnalysisContext; public readonly struct SymbolStartAnalysisContextWrapper { private const string VBSyntaxKind = "Microsoft.CodeAnalysis.VisualBasic.SyntaxKind"; private static readonly Func CancellationTokenAccessor; private static readonly Func CompilationAccessor; private static readonly Func OptionsAccessor; private static readonly Func SymbolAccessor; private static readonly Action> RegisterCodeBlockActionMethod; private static readonly Action>> RegisterCodeBlockStartActionCS; private static readonly Action> RegisterCodeBlockStartActionVB; private static readonly Action, ImmutableArray> RegisterOperationActionMethod; private static readonly Action> RegisterOperationBlockActionMethod; private static readonly Action> RegisterOperationBlockStartActionMethod; private static readonly Action> RegisterSymbolEndActionMethod; private static readonly Action, ImmutableArray> RegisterSyntaxNodeActionCS; private static readonly Action, object> RegisterSyntaxNodeActionVB; public CancellationToken CancellationToken => CancellationTokenAccessor(RoslynSymbolStartAnalysisContext); public Compilation Compilation => CompilationAccessor(RoslynSymbolStartAnalysisContext); public AnalyzerOptions Options => OptionsAccessor(RoslynSymbolStartAnalysisContext); public ISymbol Symbol => SymbolAccessor(RoslynSymbolStartAnalysisContext); private object RoslynSymbolStartAnalysisContext { get; } // Code is executed in static initializers and is not detected by the coverage tool // See the RegisterSymbolStartActionWrapperTest family of tests to check test coverage manually [ExcludeFromCodeCoverage] static SymbolStartAnalysisContextWrapper() { var symbolStartAnalysisContextType = LoadSymbolStartAnalysisContextType(); var languageKindEnumVBType = LoadLanguageKindEnumVBType(); CancellationTokenAccessor = CreatePropertyAccessor(nameof(CancellationToken)); CompilationAccessor = CreatePropertyAccessor(nameof(Compilation)); OptionsAccessor = CreatePropertyAccessor(nameof(Options)); SymbolAccessor = CreatePropertyAccessor(nameof(Symbol)); RegisterCodeBlockActionMethod = CreateRegistrationMethod(nameof(RegisterCodeBlockAction)); RegisterCodeBlockStartActionCS = CreateRegistrationMethod>(nameof(RegisterCodeBlockStartAction), typeof(CS.SyntaxKind)); RegisterCodeBlockStartActionVB = CreateRegistrationMethodCodeBlockStart(languageKindEnumVBType); RegisterOperationActionMethod = CreateRegistrationMethodWithAdditionalParameter>(nameof(RegisterOperationAction)); RegisterOperationBlockActionMethod = CreateRegistrationMethod(nameof(RegisterOperationBlockAction)); RegisterOperationBlockStartActionMethod = CreateRegistrationMethod(nameof(RegisterOperationBlockStartAction)); RegisterSymbolEndActionMethod = CreateRegistrationMethod(nameof(RegisterSymbolEndAction)); RegisterSyntaxNodeActionCS = CreateRegistrationMethodWithAdditionalParameter>(nameof(RegisterSyntaxNodeAction), typeof(CS.SyntaxKind)); RegisterSyntaxNodeActionVB = CreateRegistrationMethodSyntaxNode(languageKindEnumVBType); // receiverParameter => ((symbolStartAnalysisContextType)receiverParameter)."propertyName" Func CreatePropertyAccessor(string propertyName) { if (symbolStartAnalysisContextType is null) { return static _ => default; } var receiverParameter = Parameter(typeof(object)); return Lambda>( Property(Convert(receiverParameter, symbolStartAnalysisContextType), propertyName), receiverParameter).Compile(); } // (object receiverParameter, Action registerActionParameter) => // ((symbolStartAnalysisContextType)receiverParameter)."registrationMethodName"(registerActionParameter) Action> CreateRegistrationMethod(string registrationMethodName, params Type[] typeArguments) { if (symbolStartAnalysisContextType is null) { return static (_, _) => { }; } var receiverParameter = Parameter(typeof(object)); var registerActionParameter = Parameter(typeof(Action)); return Lambda>>( Call(Convert(receiverParameter, symbolStartAnalysisContextType), registrationMethodName, typeArguments, registerActionParameter), receiverParameter, registerActionParameter).Compile(); } // (object receiverParameter, Action actionObjectParameter) => // ((symbolStartAnalysisContextType)receiverParameter).RegisterCodeBlockStartAction(contextLanguageParameter => actionObjectParameter.Invoke(contextLanguageParameter)) Action> CreateRegistrationMethodCodeBlockStart(Type languageKindEnumType) { if (symbolStartAnalysisContextType is null || languageKindEnumType is null) { return static (_, _) => { }; } var receiverParameter = Parameter(typeof(object)); var actionObjectParameter = Parameter(typeof(Action)); var contextLanguageType = typeof(CodeBlockStartAnalysisContext<>).MakeGenericType(languageKindEnumType); var actionContextLanguageType = typeof(Action<>).MakeGenericType(contextLanguageType); var contextLanguageParameter = Parameter(contextLanguageType); var registerActionParameter = Parameter(actionContextLanguageType); // Action> innerRegistration = contextLanguageParameter => actionObjectParameter.Invoke(contextLanguageParameter) var innerRegistration = Lambda(actionContextLanguageType, Call(actionObjectParameter, nameof(Action.Invoke), [], contextLanguageParameter), contextLanguageParameter); return Lambda>>( Call(Convert(receiverParameter, symbolStartAnalysisContextType), nameof(RegisterCodeBlockStartAction), [languageKindEnumType], innerRegistration), receiverParameter, actionObjectParameter).Compile(); } // (object receiverParameter, Action registerActionParameter, TParameter additionalParameter) => // ((symbolStartAnalysisContextType)receiverParameter)."registrationMethodName"(registerActionParameter, additionalParameter) Action, TParameter> CreateRegistrationMethodWithAdditionalParameter(string registrationMethodName, params Type[] typeArguments) { if (symbolStartAnalysisContextType is null) { return static (_, _, _) => { }; } var receiverParameter = Parameter(typeof(object)); var registerActionParameter = Parameter(typeof(Action)); var additionalParameter = Parameter(typeof(TParameter)); return Lambda, TParameter>>( Call(Convert(receiverParameter, symbolStartAnalysisContextType), registrationMethodName, typeArguments, registerActionParameter, additionalParameter), receiverParameter, registerActionParameter, additionalParameter).Compile(); } // (object receiverParameter, Action registerActionParameter, object syntaxKindArrayParameter) => // ((symbolStartAnalysisContextType)receiverParameter).RegisterSyntaxNodeAction(contextParameter => registerActionParameter.Invoke(contextParameter), (languageKindEnumType[])syntaxKindArrayParameter) Action, object> CreateRegistrationMethodSyntaxNode(Type languageKindEnumType) { if (symbolStartAnalysisContextType is null || languageKindEnumType is null) { return static (_, _, _) => { }; } var receiverParameter = Parameter(typeof(object)); var registerActionParameter = Parameter(typeof(Action)); var syntaxKindArrayParameter = Parameter(typeof(object)); var syntaxKindArrayType = languageKindEnumType.MakeArrayType(); return Lambda, object>>( Call(Convert(receiverParameter, symbolStartAnalysisContextType), nameof(RegisterSyntaxNodeAction), [languageKindEnumType], registerActionParameter, Convert(syntaxKindArrayParameter, syntaxKindArrayType)), receiverParameter, registerActionParameter, syntaxKindArrayParameter).Compile(); } static Type LoadSymbolStartAnalysisContextType() { try { return typeof(CompilationStartAnalysisContext).Assembly.GetType("Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext", throwOnError: false); } // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.gettype?view=net-8.0#system-reflection-assembly-gettype(system-string-system-boolean) catch { return null; } } static Type LoadLanguageKindEnumVBType() { try { return Type.GetType($"{VBSyntaxKind}, Microsoft.CodeAnalysis.VisualBasic, Culture=neutral, PublicKeyToken=31bf3856ad364e35", throwOnError: false); } // https://learn.microsoft.com/en-us/dotnet/api/system.type.gettype?view=net-8.0#system-type-gettype(system-string-system-boolean) catch { return null; } } } public SymbolStartAnalysisContextWrapper(object roslynSymbolStartAnalysisContext) => RoslynSymbolStartAnalysisContext = roslynSymbolStartAnalysisContext; public void RegisterCodeBlockAction(Action action) => RegisterCodeBlockActionMethod(RoslynSymbolStartAnalysisContext, action); public void RegisterCodeBlockStartAction(Action> action) where TLanguageKindEnum : struct { var languageKindType = typeof(TLanguageKindEnum); if (languageKindType == typeof(CS.SyntaxKind)) { RegisterCodeBlockStartActionCS(RoslynSymbolStartAnalysisContext, (Action>)action); } else if (languageKindType.FullName == VBSyntaxKind) { RegisterCodeBlockStartActionVB(RoslynSymbolStartAnalysisContext, x => action((CodeBlockStartAnalysisContext)x)); } else { throw new ArgumentException("Invalid type parameter.", nameof(TLanguageKindEnum)); } } public void RegisterOperationAction(Action action, ImmutableArray operationKinds) => RegisterOperationActionMethod(RoslynSymbolStartAnalysisContext, action, operationKinds); public void RegisterOperationBlockAction(Action action) => RegisterOperationBlockActionMethod(RoslynSymbolStartAnalysisContext, action); public void RegisterOperationBlockStartAction(Action action) => RegisterOperationBlockStartActionMethod(RoslynSymbolStartAnalysisContext, action); public void RegisterSymbolEndAction(Action action) => RegisterSymbolEndActionMethod(RoslynSymbolStartAnalysisContext, action); public void RegisterSyntaxNodeAction(Action action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct { var languageKindType = typeof(TLanguageKindEnum); if (languageKindType == typeof(CS.SyntaxKind)) { RegisterSyntaxNodeActionCS(RoslynSymbolStartAnalysisContext, action, syntaxKinds.Cast().ToImmutableArray()); } else if (languageKindType.FullName == VBSyntaxKind) { RegisterSyntaxNodeActionVB(RoslynSymbolStartAnalysisContext, action, syntaxKinds); } else { throw new ArgumentException("Invalid type parameter.", nameof(TLanguageKindEnum)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/AnalyzerConfigOptionsProviderWrapper.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; public readonly struct AnalyzerConfigOptionsProviderWrapper { internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider"; private static readonly Type WrappedType; private static readonly Func GlobalOptionsAccessor; private static readonly Func GetOptionsSyntaxTreeAccessor; private static readonly Func GetOptionsAdditionalTextAccessor; private readonly object node; static AnalyzerConfigOptionsProviderWrapper() { WrappedType = WrapperHelper.GetWrappedType(typeof(AnalyzerConfigOptionsProviderWrapper)); GlobalOptionsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, nameof(GlobalOptions)); GetOptionsSyntaxTreeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, typeof(SyntaxTree), nameof(GetOptions)); GetOptionsAdditionalTextAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, typeof(AdditionalText), nameof(GetOptions)); } private AnalyzerConfigOptionsProviderWrapper(object node) { this.node = node; } public AnalyzerConfigOptionsWrapper GlobalOptions { get { if (this.node == null && WrappedType == null) { // Gracefully fall back to a collection with no values return AnalyzerConfigOptionsWrapper.FromObject(null); } return AnalyzerConfigOptionsWrapper.FromObject(GlobalOptionsAccessor(this.node)); } } public static AnalyzerConfigOptionsProviderWrapper FromObject(object node) { if (node == null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new AnalyzerConfigOptionsProviderWrapper(node); } public static bool IsInstance(object obj) { return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType); } public AnalyzerConfigOptionsWrapper GetOptions(SyntaxTree tree) { if (this.node == null && WrappedType == null) { // Gracefully fall back to a collection with no values if (tree == null) { throw new ArgumentNullException(nameof(tree)); } return AnalyzerConfigOptionsWrapper.FromObject(null); } return AnalyzerConfigOptionsWrapper.FromObject(GetOptionsSyntaxTreeAccessor(this.node, tree)); } public AnalyzerConfigOptionsWrapper GetOptions(AdditionalText textFile) { if (this.node == null && WrappedType == null) { // Gracefully fall back to a collection with no values if (textFile == null) { throw new ArgumentNullException(nameof(textFile)); } return AnalyzerConfigOptionsWrapper.FromObject(null); } return AnalyzerConfigOptionsWrapper.FromObject(GetOptionsAdditionalTextAccessor(this.node, textFile)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/AnalyzerConfigOptionsWrapper.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; public readonly struct AnalyzerConfigOptionsWrapper { internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions"; private static readonly Type WrappedType; private static readonly Func KeyComparerAccessor; private static readonly TryGetValueAccessor TryGetValueAccessor; private readonly object node; static AnalyzerConfigOptionsWrapper() { WrappedType = WrapperHelper.GetWrappedType(typeof(AnalyzerConfigOptionsWrapper)); KeyComparerAccessor = LightupHelpers.CreateStaticPropertyAccessor(WrappedType, nameof(KeyComparer)); TryGetValueAccessor = LightupHelpers.CreateTryGetValueAccessor(WrappedType, typeof(string), nameof(TryGetValue)); } private AnalyzerConfigOptionsWrapper(object node) { this.node = node; } public static StringComparer KeyComparer { get { if (WrappedType is null) { // Gracefully fall back to a collection with no values return StringComparer.Ordinal; } return KeyComparerAccessor(); } } public static AnalyzerConfigOptionsWrapper FromObject(object node) { if (node == null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new AnalyzerConfigOptionsWrapper(node); } public static bool IsInstance(object obj) { return obj != null && LightupHelpers.CanWrapObject(obj, WrappedType); } public bool TryGetValue(string key, out string value) { if (this.node is null && WrappedType is null) { // Gracefully fall back to a collection with no values value = null; return false; } return TryGetValueAccessor(this.node, key, out value); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/AnalyzerOptionsExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis.Diagnostics; public static class AnalyzerOptionsExtensions { private static readonly Func AnalyzerConfigOptionsProviderAccessor; static AnalyzerOptionsExtensions() { AnalyzerConfigOptionsProviderAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(AnalyzerOptions), nameof(AnalyzerConfigOptionsProvider)); } public static AnalyzerConfigOptionsProviderWrapper AnalyzerConfigOptionsProvider(this AnalyzerOptions options) { return AnalyzerConfigOptionsProviderWrapper.FromObject(AnalyzerConfigOptionsProviderAccessor(options)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/ArgumentSyntaxExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; public static class ArgumentSyntaxExtensions { private static readonly Func RefKindKeywordAccessor; private static readonly Func WithRefKindKeywordAccessor; static ArgumentSyntaxExtensions() { RefKindKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(ArgumentSyntax), nameof(RefKindKeyword)); WithRefKindKeywordAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor(typeof(ArgumentSyntax), nameof(RefKindKeyword)); } public static SyntaxToken RefKindKeyword(this ArgumentSyntax syntax) { return RefKindKeywordAccessor(syntax); } public static ArgumentSyntax WithRefKindKeyword(this ArgumentSyntax syntax, SyntaxToken refKindKeyword) { return WithRefKindKeywordAccessor(syntax, refKindKeyword); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/BaseMethodDeclarationSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace StyleCop.Analyzers.Lightup; public static class BaseMethodDeclarationSyntaxExtensions { public static ArrowExpressionClauseSyntax ExpressionBody(this BaseMethodDeclarationSyntax syntax) => // Prior to C# 7, the ExpressionBody properties did not have ExpressionBody on BaseMethodDeclarationSyntax but on // some of the derived types only. Therefore we need to special case the derived types here. syntax.Kind() switch { SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)syntax).ExpressionBody, SyntaxKind.OperatorDeclaration => ((OperatorDeclarationSyntax)syntax).ExpressionBody, SyntaxKind.ConversionOperatorDeclaration => ((ConversionOperatorDeclarationSyntax)syntax).ExpressionBody, _ => BaseMethodDeclarationSyntaxShimExtensions.get_ExpressionBody(syntax), }; } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/CSharp7.md ================================================ # C# 7 APIs supported via light-up See [dotnet/roslyn@c2af711](https://github.com/dotnet/roslyn/commit/c2af71127234e2b6df231fad3b9f7db6cc7cb444). ## Semantics * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.DisplayVersion.get -> bool` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.EmbeddedFiles.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.SourceLink.get -> string` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateAnonymousTypeSymbol(System.Collections.Immutable.ImmutableArray memberTypes, System.Collections.Immutable.ImmutableArray memberNames, System.Collections.Immutable.ImmutableArray memberIsReadOnly = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray memberLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateErrorNamespaceSymbol(Microsoft.CodeAnalysis.INamespaceSymbol container, string name) -> Microsoft.CodeAnalysis.INamespaceSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(Microsoft.CodeAnalysis.INamedTypeSymbol underlyingType, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(System.Collections.Immutable.ImmutableArray elementTypes, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.Emit(System.IO.Stream peStream, System.IO.Stream pdbStream = null, System.IO.Stream xmlDocumentationStream = null, System.IO.Stream win32Resources = null, System.Collections.Generic.IEnumerable manifestResources = null, Microsoft.CodeAnalysis.Emit.EmitOptions options = null, Microsoft.CodeAnalysis.IMethodSymbol debugEntryPoint = null, System.IO.Stream sourceLinkStream = null, System.Collections.Generic.IEnumerable embeddedTexts = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitResult` * [ ] `Microsoft.CodeAnalysis.Compilation.Emit(System.IO.Stream peStream, System.IO.Stream pdbStream, System.IO.Stream xmlDocumentationStream, System.IO.Stream win32Resources, System.Collections.Generic.IEnumerable manifestResources, Microsoft.CodeAnalysis.Emit.EmitOptions options, Microsoft.CodeAnalysis.IMethodSymbol debugEntryPoint, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.Emit.EmitResult` * [ ] `Microsoft.CodeAnalysis.Compilation.GetUnreferencedAssemblyIdentities(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithConcurrentBuild(bool concurrent) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithCryptoKeyContainer(string cryptoKeyContainer) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithCryptoKeyFile(string cryptoKeyFile) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithCryptoPublicKey(System.Collections.Immutable.ImmutableArray cryptoPublicKey) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithDelaySign(bool? delaySign) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithMainTypeName(string mainTypeName) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithModuleName(string moduleName) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithOverflowChecks(bool checkOverflow) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithScriptClassName(string scriptClassName) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.DesktopStrongNameProvider.DesktopStrongNameProvider(System.Collections.Immutable.ImmutableArray keyFileSearchPaths = default(System.Collections.Immutable.ImmutableArray), string tempPath = null) -> void` * [ ] `Microsoft.CodeAnalysis.DesktopStrongNameProvider.DesktopStrongNameProvider(System.Collections.Immutable.ImmutableArray keyFileSearchPaths) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.AnalyzerTelemetryInfo() -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CodeBlockActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CodeBlockEndActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CodeBlockStartActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CompilationActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CompilationEndActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.CompilationStartActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.ExecutionTime.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockEndActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockEndActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockStartActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockStartActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SemanticModelActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SymbolActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SyntaxNodeActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SyntaxTreeActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.EmbeddedText` * [ ] `Microsoft.CodeAnalysis.EmbeddedText.Checksum.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.EmbeddedText.ChecksumAlgorithm.get -> Microsoft.CodeAnalysis.Text.SourceHashAlgorithm` * [ ] `Microsoft.CodeAnalysis.EmbeddedText.FilePath.get -> string` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly = false, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat = (Microsoft.CodeAnalysis.Emit.DebugInformationFormat)0, string pdbFilePath = null, string outputNameOverride = null, int fileAlignment = 0, ulong baseAddress = 0, bool highEntropyVirtualAddressSpace = false, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion = default(Microsoft.CodeAnalysis.SubsystemVersion), string runtimeMetadataVersion = null, bool tolerateErrors = false, bool includePrivateMembers = false, System.Collections.Immutable.ImmutableArray instrumentationKinds = default(System.Collections.Immutable.ImmutableArray)) -> void` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat, string pdbFilePath, string outputNameOverride, int fileAlignment, ulong baseAddress, bool highEntropyVirtualAddressSpace, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion, string runtimeMetadataVersion, bool tolerateErrors, bool includePrivateMembers) -> void` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.InstrumentationKinds.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.WithInstrumentationKinds(System.Collections.Immutable.ImmutableArray instrumentationKinds) -> Microsoft.CodeAnalysis.Emit.EmitOptions` * [ ] `Microsoft.CodeAnalysis.Emit.InstrumentationKind` * [ ] `Microsoft.CodeAnalysis.Emit.InstrumentationKind.None = 0 -> Microsoft.CodeAnalysis.Emit.InstrumentationKind` * [ ] `Microsoft.CodeAnalysis.Emit.InstrumentationKind.TestCoverage = 1 -> Microsoft.CodeAnalysis.Emit.InstrumentationKind` * [ ] `Microsoft.CodeAnalysis.IArrayTypeSymbol.IsSZArray.get -> bool` * [ ] `Microsoft.CodeAnalysis.IArrayTypeSymbol.LowerBounds.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IArrayTypeSymbol.Sizes.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IDiscardSymbol` * [ ] `Microsoft.CodeAnalysis.IDiscardSymbol.Type.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [x] `Microsoft.CodeAnalysis.IFieldSymbol.CorrespondingTupleField.get -> Microsoft.CodeAnalysis.IFieldSymbol` * [ ] `Microsoft.CodeAnalysis.ILocalSymbol.IsRef.get -> bool` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.RefCustomModifiers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.ReturnsByRef.get -> bool` * [ ] `Microsoft.CodeAnalysis.INamedTypeSymbol.GetTypeArgumentCustomModifiers(int ordinal) -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.INamedTypeSymbol.IsComImport.get -> bool` * [x] `Microsoft.CodeAnalysis.INamedTypeSymbol.TupleElements.get -> System.Collections.Immutable.ImmutableArray` * [x] `Microsoft.CodeAnalysis.INamedTypeSymbol.TupleUnderlyingType.get -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.IParameterSymbol.RefCustomModifiers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IPropertySymbol.RefCustomModifiers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IPropertySymbol.ReturnsByRef.get -> bool` * [x] `Microsoft.CodeAnalysis.ITypeSymbol.IsTupleType.get -> bool` * [x] `Microsoft.CodeAnalysis.MethodKind.LocalFunction = 17 -> Microsoft.CodeAnalysis.MethodKind` * [ ] `Microsoft.CodeAnalysis.PortableExecutableReference.GetMetadataId() -> Microsoft.CodeAnalysis.MetadataId` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayFormat.RemoveGenericsOptions(Microsoft.CodeAnalysis.SymbolDisplayGenericsOptions options) -> Microsoft.CodeAnalysis.SymbolDisplayFormat` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayFormat.RemoveLocalOptions(Microsoft.CodeAnalysis.SymbolDisplayLocalOptions options) -> Microsoft.CodeAnalysis.SymbolDisplayFormat` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayFormat.RemoveMiscellaneousOptions(Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions options) -> Microsoft.CodeAnalysis.SymbolDisplayFormat` * [x] `Microsoft.CodeAnalysis.SymbolDisplayLocalOptions.IncludeRef = 4 -> Microsoft.CodeAnalysis.SymbolDisplayLocalOptions` * [x] `Microsoft.CodeAnalysis.SymbolDisplayMemberOptions.IncludeRef = 128 -> Microsoft.CodeAnalysis.SymbolDisplayMemberOptions` * [x] `Microsoft.CodeAnalysis.SymbolKind.Discard = 19 -> Microsoft.CodeAnalysis.SymbolKind` * [ ] `Microsoft.CodeAnalysis.Text.SourceText.CanBeEmbedded.get -> bool` * [ ] `Microsoft.CodeAnalysis.Text.SourceText.GetChecksum() -> System.Collections.Immutable.ImmutableArray` * [ ] `abstract Microsoft.CodeAnalysis.CompilationOptions.Language.get -> string` * [ ] `abstract Microsoft.CodeAnalysis.ParseOptions.Language.get -> string` * [x] ~~`override Microsoft.CodeAnalysis.SyntaxNode.ToString() -> string`~~ * [ ] `static Microsoft.CodeAnalysis.CaseInsensitiveComparison.StartsWith(string value, string possibleStart) -> bool` * [ ] `static Microsoft.CodeAnalysis.EmbeddedText.FromBytes(string filePath, System.ArraySegment bytes, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm = Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha1) -> Microsoft.CodeAnalysis.EmbeddedText` * [ ] `static Microsoft.CodeAnalysis.EmbeddedText.FromSource(string filePath, Microsoft.CodeAnalysis.Text.SourceText text) -> Microsoft.CodeAnalysis.EmbeddedText` * [ ] `static Microsoft.CodeAnalysis.EmbeddedText.FromStream(string filePath, System.IO.Stream stream, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm = Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha1) -> Microsoft.CodeAnalysis.EmbeddedText` * [ ] `static Microsoft.CodeAnalysis.SeparatedSyntaxList.implicit operator Microsoft.CodeAnalysis.SeparatedSyntaxList(Microsoft.CodeAnalysis.SeparatedSyntaxList nodes) -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [ ] `static Microsoft.CodeAnalysis.SeparatedSyntaxList.implicit operator Microsoft.CodeAnalysis.SeparatedSyntaxList(Microsoft.CodeAnalysis.SeparatedSyntaxList nodes) -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [ ] `static Microsoft.CodeAnalysis.SyntaxNodeExtensions.WithoutTrivia(this Microsoft.CodeAnalysis.SyntaxToken token) -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `static Microsoft.CodeAnalysis.Text.SourceText.From(System.IO.Stream stream, System.Text.Encoding encoding = null, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm = Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha1, bool throwIfBinaryDetected = false, bool canBeEmbedded = false) -> Microsoft.CodeAnalysis.Text.SourceText` * [ ] `static Microsoft.CodeAnalysis.Text.SourceText.From(System.IO.Stream stream, System.Text.Encoding encoding, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm, bool throwIfBinaryDetected) -> Microsoft.CodeAnalysis.Text.SourceText` * [ ] `static Microsoft.CodeAnalysis.Text.SourceText.From(System.IO.TextReader reader, int length, System.Text.Encoding encoding = null, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm = Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha1) -> Microsoft.CodeAnalysis.Text.SourceText` * [ ] `static Microsoft.CodeAnalysis.Text.SourceText.From(byte[] buffer, int length, System.Text.Encoding encoding = null, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm = Microsoft.CodeAnalysis.Text.SourceHashAlgorithm.Sha1, bool throwIfBinaryDetected = false, bool canBeEmbedded = false) -> Microsoft.CodeAnalysis.Text.SourceText` * [ ] `static Microsoft.CodeAnalysis.Text.SourceText.From(byte[] buffer, int length, System.Text.Encoding encoding, Microsoft.CodeAnalysis.Text.SourceHashAlgorithm checksumAlgorithm, bool throwIfBinaryDetected) -> Microsoft.CodeAnalysis.Text.SourceText` * [ ] `virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitDiscard(Microsoft.CodeAnalysis.IDiscardSymbol symbol) -> void` * [ ] `virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitDiscard(Microsoft.CodeAnalysis.IDiscardSymbol symbol) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxNode.ChildThatContainsPosition(int position) -> Microsoft.CodeAnalysis.SyntaxNodeOrToken` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxNode.SerializeTo(System.IO.Stream stream, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxNode.ToFullString() -> string` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxNode.WriteTo(System.IO.TextWriter writer) -> void` ## Syntax * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpParseOptions.CSharpParseOptions(Microsoft.CodeAnalysis.CSharp.LanguageVersion languageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.Default, Microsoft.CodeAnalysis.DocumentationMode documentationMode = Microsoft.CodeAnalysis.DocumentationMode.Parse, Microsoft.CodeAnalysis.SourceCodeKind kind = Microsoft.CodeAnalysis.SourceCodeKind.Regular, System.Collections.Generic.IEnumerable preprocessorSymbols = null) -> void` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpParseOptions.SpecifiedLanguageVersion.get -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.IsThrow.get -> bool` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.IsTupleConversion.get -> bool` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.IsTupleLiteralConversion.get -> bool` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7 = 7 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.Default = 0 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.Latest = 2147483647 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax.WithExpressionBody(Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.Pattern.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.WhenClause.get -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.WithColonToken(Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.WithKeyword(Microsoft.CodeAnalysis.SyntaxToken keyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.WithPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.WithWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax.WithExpressionBody(Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.Designation.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.WithDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.Designation.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.WithDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken tildeToken, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax.WithExpressionBody(Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax.UnderscoreToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax.WithUnderscoreToken(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Variable.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithForEachKeyword(Microsoft.CodeAnalysis.SyntaxToken forEachKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithInKeyword(Microsoft.CodeAnalysis.SyntaxToken inKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithStatement(Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithVariable(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.IsKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.Pattern.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken isKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.WithIsKeyword(Microsoft.CodeAnalysis.SyntaxToken isKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.WithPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.AddBodyStatements(params Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.AddConstraintClauses(params Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.AddParameterListParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.AddTypeParameterListParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Body.get -> Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.ConstraintClauses.get -> Microsoft.CodeAnalysis.SyntaxList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Identifier.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.ParameterList.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.ReturnType.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.SemicolonToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.TypeParameterList.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithBody(Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithConstraintClauses(Microsoft.CodeAnalysis.SyntaxList constraintClauses) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithExpressionBody(Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithParameterList(Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithReturnType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.WithTypeParameterList(Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.AddVariables(params Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList variables, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Variables.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.WithVariables(Microsoft.CodeAnalysis.SeparatedSyntaxList variables) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.RefKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.WithRefKeyword(Microsoft.CodeAnalysis.SyntaxToken refKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.RefKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.WithRefKeyword(Microsoft.CodeAnalysis.SyntaxToken refKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax.Identifier.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.ThrowKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken throwKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.WithThrowKeyword(Microsoft.CodeAnalysis.SyntaxToken throwKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Identifier.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.AddArguments(params Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.Arguments.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList arguments, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.WithArguments(Microsoft.CodeAnalysis.SeparatedSyntaxList arguments) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.AddElements(params Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Elements.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList elements, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.WithElements(Microsoft.CodeAnalysis.SeparatedSyntaxList elements) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Condition.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken whenKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax condition) -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.WhenKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.WithCondition(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax condition) -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.WithWhenKeyword(Microsoft.CodeAnalysis.SyntaxToken whenKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.CasePatternSwitchLabel = 9009 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ConstantPattern = 9002 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DeclarationExpression = 9040 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DeclarationPattern = 9000 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DiscardDesignation = 9014 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ForEachVariableStatement = 8929 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.IsPatternExpression = 8657 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.LocalFunctionStatement = 8830 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ParenthesizedVariableDesignation = 8928 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.RefExpression = 9050 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.RefType = 9051 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.SingleVariableDesignation = 8927 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ThrowExpression = 9052 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.TupleElement = 8925 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.TupleExpression = 8926 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.TupleType = 8924 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.UnderscoreToken = 8491 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.WhenClause = 9013 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.ForEachKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.InKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.Statement.get -> Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.Language.get -> string` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpParseOptions.Language.get -> string` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitCasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitConstantPattern(Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDeclarationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDeclarationPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDiscardDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitForEachVariableStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitIsPatternExpression(Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitLocalFunctionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitParenthesizedVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitRefExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitRefType(Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitSingleVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitThrowExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitTupleElement(Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitTupleExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitTupleType(Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.ColonToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax.Keyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ConversionOperatorDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.ForEachKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.InKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.Statement.get -> Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.ForEachKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.InKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Statement.get -> Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.OperatorDeclarationSyntax.ExpressionBody.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax declaratorSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.ISymbol` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax designationSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.ISymbol` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax declarationSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.ISymbol` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax declaratorSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetForEachStatementInfo(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax forEachStatement) -> Microsoft.CodeAnalysis.CSharp.ForEachStatementInfo` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.AccessorDeclaration(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CasePatternSwitchLabel(Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstantPattern(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax initializer, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax initializer, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ConstructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorInitializerSyntax initializer, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConstructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DeclarationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DeclarationPattern(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DestructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DestructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DestructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken tildeToken, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DestructorDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken tildeToken, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DestructorDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DiscardDesignation() -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DiscardDesignation(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ForEachVariableStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ForEachVariableStatement(Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.IsPatternExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.IsPatternExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken isKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.LocalFunctionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.LocalFunctionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, string identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.LocalFunctionStatement(Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.LocalFunctionStatement(Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body, Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedVariableDesignation(Microsoft.CodeAnalysis.SeparatedSyntaxList variables = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedVariableDesignation(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList variables, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefExpression(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefType(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SingleVariableDesignation(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ThrowExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ThrowExpression(Microsoft.CodeAnalysis.SyntaxToken throwKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleElement(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleElement(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleExpression(Microsoft.CodeAnalysis.SeparatedSyntaxList arguments = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleExpression(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList arguments, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleType(Microsoft.CodeAnalysis.SeparatedSyntaxList elements = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.TupleType(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList elements, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax condition) -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.WhenClause(Microsoft.CodeAnalysis.SyntaxToken whenKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax condition) -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitCasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitConstantPattern(Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDeclarationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDeclarationPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDiscardDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitForEachVariableStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitIsPatternExpression(Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitLocalFunctionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitParenthesizedVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefType(Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSingleVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitThrowExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleElement(Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleType(Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitCasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitConstantPattern(Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDeclarationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDeclarationPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDiscardDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitForEachVariableStatement(Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitIsPatternExpression(Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitLocalFunctionStatement(Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitParenthesizedVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefType(Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSingleVariableDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitThrowExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleElement(Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitTupleType(Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax node) -> TResult` ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/CSharp71.md ================================================ # C# 7.1 APIs supported via light-up See [dotnet/roslyn@5520eac](https://github.com/dotnet/roslyn/commit/5520eaccd5d22ae98a39a5f88120277f02097dbf). ## Semantics * [ ] `Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts` * [ ] `const Microsoft.CodeAnalysis.LanguageNames.FSharp = "F#" -> string` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.OutputRefFilePath.get -> string` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.RuleSetPath.get -> string` * [ ] `Microsoft.CodeAnalysis.CommandLineReference.CommandLineReference(string reference, Microsoft.CodeAnalysis.MetadataReferenceProperties properties) -> void` * [ ] `Microsoft.CodeAnalysis.CommandLineSourceFile.CommandLineSourceFile(string path, bool isScript) -> void` * [ ] `Microsoft.CodeAnalysis.Compilation.Emit(System.IO.Stream peStream, System.IO.Stream pdbStream = null, System.IO.Stream xmlDocumentationStream = null, System.IO.Stream win32Resources = null, System.Collections.Generic.IEnumerable manifestResources = null, Microsoft.CodeAnalysis.Emit.EmitOptions options = null, Microsoft.CodeAnalysis.IMethodSymbol debugEntryPoint = null, System.IO.Stream sourceLinkStream = null, System.Collections.Generic.IEnumerable embeddedTexts = null, System.IO.Stream metadataPEStream = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitResult` * [ ] `Microsoft.CodeAnalysis.Compilation.Emit(System.IO.Stream peStream, System.IO.Stream pdbStream, System.IO.Stream xmlDocumentationStream, System.IO.Stream win32Resources, System.Collections.Generic.IEnumerable manifestResources, Microsoft.CodeAnalysis.Emit.EmitOptions options, Microsoft.CodeAnalysis.IMethodSymbol debugEntryPoint, System.IO.Stream sourceLinkStream, System.Collections.Generic.IEnumerable embeddedTexts, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.Emit.EmitResult` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly = false, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat = (Microsoft.CodeAnalysis.Emit.DebugInformationFormat)0, string pdbFilePath = null, string outputNameOverride = null, int fileAlignment = 0, ulong baseAddress = 0, bool highEntropyVirtualAddressSpace = false, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion = default(Microsoft.CodeAnalysis.SubsystemVersion), string runtimeMetadataVersion = null, bool tolerateErrors = false, bool includePrivateMembers = true, System.Collections.Immutable.ImmutableArray instrumentationKinds = default(System.Collections.Immutable.ImmutableArray)) -> void` * [ ] `Microsoft.CodeAnalysis.ParseOptions.Errors.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.ParseOptions.SpecifiedKind.get -> Microsoft.CodeAnalysis.SourceCodeKind` * [ ] `static Microsoft.CodeAnalysis.Compilation.GetRequiredLanguageVersion(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.MapSpecifiedToEffectiveVersion(this Microsoft.CodeAnalysis.CSharp.LanguageVersion version) -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [ ] `static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.ToDisplayString(this Microsoft.CodeAnalysis.CSharp.LanguageVersion version) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(this string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool` ## Syntax * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_1 = 701 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ConflictMarkerTrivia = 8564 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DefaultLiteralExpression = 8755 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName) -> bool` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.TryGetInferredMemberName(this Microsoft.CodeAnalysis.SyntaxNode syntax) -> string` ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/CSharp72.md ================================================ # C# 7.2 APIs supported via light-up See [dotnet/roslyn@30a68f1](https://github.com/dotnet/roslyn/commit/30a68f16a0cb3d154a0fca41df38ec509dfe703c). ## Semantics * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationAction(System.Action action, System.Collections.Immutable.ImmutableArray operationKinds) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationBlockEndAction(System.Action action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.SemanticModel.GetOperationCore(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.IOperation` * [ ] `abstract Microsoft.CodeAnalysis.SemanticModel.RootCore.get -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.DisplayLangVersions.get -> bool` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action action, params Microsoft.CodeAnalysis.OperationKind[] operationKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationAction(System.Action action, params Microsoft.CodeAnalysis.OperationKind[] operationKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.ContainingSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.OperationAnalysisContext(Microsoft.CodeAnalysis.IOperation operation, Microsoft.CodeAnalysis.ISymbol containingSymbol, Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action reportDiagnostic, System.Func isSupportedDiagnostic, System.Threading.CancellationToken cancellationToken) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.OperationBlockAnalysisContext(System.Collections.Immutable.ImmutableArray operationBlocks, Microsoft.CodeAnalysis.ISymbol owningSymbol, Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action reportDiagnostic, System.Func isSupportedDiagnostic, System.Threading.CancellationToken cancellationToken) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.OperationBlocks.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.OwningSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.OperationBlockStartAnalysisContext(System.Collections.Immutable.ImmutableArray operationBlocks, Microsoft.CodeAnalysis.ISymbol owningSymbol, Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Threading.CancellationToken cancellationToken) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.OperationBlocks.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.OwningSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationAction(System.Action action, params Microsoft.CodeAnalysis.OperationKind[] operationKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockEndActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockEndActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockStartActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockStartActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.ILocalSymbol.RefKind.get -> Microsoft.CodeAnalysis.RefKind` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.RefKind.get -> Microsoft.CodeAnalysis.RefKind` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.ReturnsByRefReadonly.get -> bool` * [ ] `Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.IOperation.Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor visitor) -> void` * [ ] `Microsoft.CodeAnalysis.IOperation.Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor visitor, TArgument argument) -> TResult` * [ ] `Microsoft.CodeAnalysis.IOperation.Children.get -> System.Collections.Generic.IEnumerable` * [ ] `Microsoft.CodeAnalysis.IOperation.ConstantValue.get -> Microsoft.CodeAnalysis.Optional` * [ ] `Microsoft.CodeAnalysis.IOperation.IsImplicit.get -> bool` * [ ] `Microsoft.CodeAnalysis.IOperation.Kind.get -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.IOperation.Language.get -> string` * [ ] `Microsoft.CodeAnalysis.IOperation.Parent.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.IOperation.Syntax.get -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `Microsoft.CodeAnalysis.IOperation.Type.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.IPropertySymbol.RefKind.get -> Microsoft.CodeAnalysis.RefKind` * [ ] `Microsoft.CodeAnalysis.IPropertySymbol.ReturnsByRefReadonly.get -> bool` * [ ] `Microsoft.CodeAnalysis.ModuleMetadata.IsDisposed.get -> bool` * [ ] `Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.AddressOf = 64 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.AnonymousFunction = 35 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.AnonymousObjectCreation = 49 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Argument = 79 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ArrayCreation = 38 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ArrayElementReference = 23 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ArrayInitializer = 76 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Await = 41 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.BinaryOperator = 32 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Block = 2 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Branch = 7 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CaseClause = 82 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CatchClause = 80 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Coalesce = 34 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CollectionElementInitializer = 52 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CompoundAssignment = 43 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Conditional = 33 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ConditionalAccess = 46 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ConditionalAccessInstance = 47 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ConstantPattern = 85 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Conversion = 21 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DeclarationExpression = 70 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DeclarationPattern = 86 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DeconstructionAssignment = 69 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Decrement = 68 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DefaultValue = 61 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DelegateCreation = 60 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DynamicIndexerAccess = 58 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DynamicInvocation = 57 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DynamicMemberReference = 56 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DynamicObjectCreation = 55 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Empty = 8 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.End = 18 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.EventAssignment = 45 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.EventReference = 30 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ExpressionStatement = 15 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.FieldInitializer = 72 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.FieldReference = 26 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Increment = 66 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.InstanceReference = 39 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.InterpolatedString = 48 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.InterpolatedStringText = 83 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Interpolation = 84 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Invalid = 1 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Invocation = 22 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.IsPattern = 65 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.IsType = 40 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Labeled = 6 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Literal = 20 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.LocalFunction = 16 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.LocalReference = 24 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Lock = 11 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Loop = 5 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.MemberInitializer = 51 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.MethodReference = 27 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.NameOf = 53 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.None = 0 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ObjectCreation = 36 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ObjectOrCollectionInitializer = 50 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.OmittedArgument = 71 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ParameterInitializer = 75 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ParameterReference = 25 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Parenthesized = 44 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.PropertyInitializer = 74 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.PropertyReference = 28 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.RaiseEvent = 19 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Return = 9 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.SimpleAssignment = 42 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.SizeOf = 63 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Stop = 17 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Switch = 4 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.SwitchCase = 81 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Throw = 67 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.TranslatedQuery = 59 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Try = 12 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Tuple = 54 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.TypeOf = 62 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.TypeParameterObjectCreation = 37 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.UnaryOperator = 31 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Using = 13 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.VariableDeclaration = 78 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.VariableDeclarationGroup = 3 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.VariableDeclarator = 77 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.VariableInitializer = 73 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.YieldBreak = 10 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.YieldReturn = 14 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.ArgumentKind.DefaultValue = 3 -> Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.ArgumentKind.Explicit = 1 -> Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.ArgumentKind.None = 0 -> Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.ArgumentKind.ParamArray = 2 -> Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Add = 1 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.And = 10 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Concatenate = 15 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.ConditionalAnd = 13 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.ConditionalOr = 14 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Divide = 4 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Equals = 16 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.ExclusiveOr = 12 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.GreaterThan = 23 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.GreaterThanOrEqual = 22 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.IntegerDivide = 5 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.LeftShift = 8 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.LessThan = 20 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.LessThanOrEqual = 21 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Like = 24 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Multiply = 3 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.None = 0 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.NotEquals = 18 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.ObjectValueEquals = 17 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.ObjectValueNotEquals = 19 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Or = 11 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Power = 7 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Remainder = 6 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.RightShift = 9 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.Subtract = 2 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.BranchKind.Break = 2 -> Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.BranchKind.Continue = 1 -> Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.BranchKind.GoTo = 3 -> Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.BranchKind.None = 0 -> Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.Default = 4 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.None = 0 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.Pattern = 5 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.Range = 3 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.Relational = 2 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CaseKind.SingleValue = 1 -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.Exists.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.IsIdentity.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.IsNumeric.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.IsReference.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.IsUserDefined.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.MethodSymbol.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IAddressOfOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAddressOfOperation.Reference.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAnonymousFunctionOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAnonymousFunctionOperation.Body.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAnonymousFunctionOperation.Symbol.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IAnonymousObjectCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAnonymousObjectCreationOperation.Initializers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation.ArgumentKind.get -> Microsoft.CodeAnalysis.Operations.ArgumentKind` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation.InConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation.OutConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation.Parameter.get -> Microsoft.CodeAnalysis.IParameterSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IArgumentOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayCreationOperation.DimensionSizes.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayCreationOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IArrayInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayElementReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayElementReferenceOperation.ArrayReference.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayElementReferenceOperation.Indices.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IArrayInitializerOperation.ElementValues.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAssignmentOperation.Target.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAssignmentOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAwaitOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IAwaitOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.IsCompareText.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.IsLifted.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.LeftOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.OperatorKind.get -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.OperatorMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IBinaryOperation.RightOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBlockOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IBlockOperation.Operations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IBranchOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IBranchOperation.BranchKind.get -> Microsoft.CodeAnalysis.Operations.BranchKind` * [ ] `Microsoft.CodeAnalysis.Operations.IBranchOperation.Target.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ICaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICaseClauseOperation.CaseKind.get -> Microsoft.CodeAnalysis.Operations.CaseKind` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation.ExceptionDeclarationOrExpression.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation.ExceptionType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation.Filter.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation.Handler.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICatchClauseOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ICoalesceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICoalesceOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICoalesceOperation.WhenNull.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation.AddMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation.IsDynamic.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.InConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.IsLifted.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.OperatorKind.get -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.OperatorMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation.OutConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalAccessInstanceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalAccessOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalAccessOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalAccessOperation.WhenNotNull.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalOperation.Condition.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalOperation.IsRef.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalOperation.WhenFalse.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConditionalOperation.WhenTrue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConstantPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConstantPatternOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation.Conversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation.IsTryCast.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConversionOperation.OperatorMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationExpressionOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationExpressionOperation.Expression.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation.DeclaredSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IDeconstructionAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDefaultCaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDefaultValueOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDelegateCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDelegateCreationOperation.Target.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation.ContainingType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation.Instance.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation.MemberName.get -> string` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation.TypeArguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEmptyOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEndOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.Adds.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.EventReference.get -> Microsoft.CodeAnalysis.Operations.IEventReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.HandlerValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventReferenceOperation.Event.get -> Microsoft.CodeAnalysis.IEventSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation.InitializedFields.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation.Field.get -> Microsoft.CodeAnalysis.IFieldSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation.IsDeclaration.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IForEachLoopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.Collection.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.LoopControlVariable.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForEachLoopOperation.NextVariables.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IForLoopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForLoopOperation.AtLoopBottom.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IForLoopOperation.Before.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IForLoopOperation.Condition.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.InitialValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.LimitValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.LoopControlVariable.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.NextVariables.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.StepValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation.IsLifted.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation.IsPostfix.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation.OperatorMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation.Target.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInstanceReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolatedStringContentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolatedStringOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolatedStringOperation.Parts.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolatedStringTextOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolatedStringTextOperation.Text.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolationOperation.Alignment.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolationOperation.Expression.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInterpolationOperation.FormatString.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInvalidOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInvocationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInvocationOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IInvocationOperation.Instance.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IInvocationOperation.IsVirtual.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IInvocationOperation.TargetMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IIsPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIsPatternOperation.Pattern.get -> Microsoft.CodeAnalysis.Operations.IPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIsPatternOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIsTypeOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IIsTypeOperation.IsNegated.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IIsTypeOperation.TypeOperand.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IIsTypeOperation.ValueOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILabeledOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILabeledOperation.Label.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ILabeledOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILiteralOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation.Body.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation.Symbol.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalReferenceOperation.IsDeclaration.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalReferenceOperation.Local.get -> Microsoft.CodeAnalysis.ILocalSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ILockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILockOperation.Body.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILockOperation.LockedValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation.Body.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation.LoopKind.get -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberInitializerOperation.InitializedMember.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberInitializerOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberReferenceOperation.Instance.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMemberReferenceOperation.Member.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodReferenceOperation.IsVirtual.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodReferenceOperation.Method.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.INameOfOperation` * [ ] `Microsoft.CodeAnalysis.Operations.INameOfOperation.Argument.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectCreationOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectCreationOperation.Constructor.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectCreationOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation.Initializers.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IOmittedArgumentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IParameterInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IParameterInitializerOperation.Parameter.get -> Microsoft.CodeAnalysis.IParameterSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IParameterReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IParameterReferenceOperation.Parameter.get -> Microsoft.CodeAnalysis.IParameterSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IParenthesizedOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IParenthesizedOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation.Guard.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation.Label.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation.Pattern.get -> Microsoft.CodeAnalysis.Operations.IPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertyInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertyInitializerOperation.InitializedProperties.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertyReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertyReferenceOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertyReferenceOperation.Property.get -> Microsoft.CodeAnalysis.IPropertySymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRaiseEventOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRaiseEventOperation.Arguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IRaiseEventOperation.EventReference.get -> Microsoft.CodeAnalysis.Operations.IEventReferenceOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeCaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeCaseClauseOperation.MaximumValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeCaseClauseOperation.MinimumValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRelationalCaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRelationalCaseClauseOperation.Relation.get -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.IRelationalCaseClauseOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReturnOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReturnOperation.ReturnedValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISimpleAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISimpleAssignmentOperation.IsRef.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ISingleValueCaseClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISingleValueCaseClauseOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISizeOfOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISizeOfOperation.TypeOperand.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IStopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation.Body.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation.Clauses.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchOperation.Cases.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISymbolInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISymbolInitializerOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IThrowOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IThrowOperation.Exception.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITranslatedQueryOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITranslatedQueryOperation.Operation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITryOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITryOperation.Body.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITryOperation.Catches.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ITryOperation.Finally.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleOperation.Elements.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ITypeOfOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITypeOfOperation.TypeOperand.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ITypeParameterObjectCreationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITypeParameterObjectCreationOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation.IsLifted.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation.OperatorKind.get -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.IUnaryOperation.OperatorMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IUsingOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IUsingOperation.Body.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IUsingOperation.Resources.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationGroupOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationGroupOperation.Declarations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation.Declarators.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation.IgnoredArguments.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation.Symbol.get -> Microsoft.CodeAnalysis.ILocalSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IWhileLoopOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IWhileLoopOperation.Condition.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IWhileLoopOperation.ConditionIsTop.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IWhileLoopOperation.ConditionIsUntil.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IWhileLoopOperation.IgnoredCondition.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind.For = 2 -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind.ForEach = 4 -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind.ForTo = 3 -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind.None = 0 -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.LoopKind.While = 1 -> Microsoft.CodeAnalysis.Operations.LoopKind` * [ ] `Microsoft.CodeAnalysis.Operations.OperationExtensions` * [ ] `Microsoft.CodeAnalysis.Operations.OperationVisitor` * [ ] `Microsoft.CodeAnalysis.Operations.OperationVisitor.OperationVisitor() -> void` * [ ] `Microsoft.CodeAnalysis.Operations.OperationVisitor` * [ ] `Microsoft.CodeAnalysis.Operations.OperationVisitor.OperationVisitor() -> void` * [ ] `Microsoft.CodeAnalysis.Operations.OperationWalker` * [ ] `Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() -> void` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.BitwiseNegation = 1 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.False = 6 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Minus = 4 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.None = 0 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Not = 2 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Plus = 3 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.True = 5 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.RefKind.In = 3 -> Microsoft.CodeAnalysis.RefKind` * [ ] `Microsoft.CodeAnalysis.RefKind.RefReadOnly = 3 -> Microsoft.CodeAnalysis.RefKind` * [ ] `Microsoft.CodeAnalysis.SemanticModel.GetOperation(Microsoft.CodeAnalysis.SyntaxNode node, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.SyntaxList.SyntaxList(System.Collections.Generic.IEnumerable nodes) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxList.SyntaxList(TNode node) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxNodeOrTokenList.SyntaxNodeOrTokenList(System.Collections.Generic.IEnumerable nodesAndTokens) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxNodeOrTokenList.SyntaxNodeOrTokenList(params Microsoft.CodeAnalysis.SyntaxNodeOrToken[] nodesAndTokens) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTokenList.SyntaxTokenList(Microsoft.CodeAnalysis.SyntaxToken token) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTokenList.SyntaxTokenList(System.Collections.Generic.IEnumerable tokens) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTokenList.SyntaxTokenList(params Microsoft.CodeAnalysis.SyntaxToken[] tokens) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTriviaList.SyntaxTriviaList(Microsoft.CodeAnalysis.SyntaxTrivia trivia) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTriviaList.SyntaxTriviaList(System.Collections.Generic.IEnumerable trivias) -> void` * [ ] `Microsoft.CodeAnalysis.SyntaxTriviaList.SyntaxTriviaList(params Microsoft.CodeAnalysis.SyntaxTrivia[] trivias) -> void` * [ ] `override Microsoft.CodeAnalysis.Operations.OperationWalker.DefaultVisit(Microsoft.CodeAnalysis.IOperation operation) -> void` * [ ] `override Microsoft.CodeAnalysis.Operations.OperationWalker.Visit(Microsoft.CodeAnalysis.IOperation operation) -> void` * [ ] `override Microsoft.CodeAnalysis.Optional.ToString() -> string` * [ ] `static Microsoft.CodeAnalysis.Emit.EmitBaseline.CreateInitialBaseline(Microsoft.CodeAnalysis.ModuleMetadata module, System.Func debugInformationProvider, System.Func localSignatureProvider, bool hasPortableDebugInformation) -> Microsoft.CodeAnalysis.Emit.EmitBaseline` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.Descendants(this Microsoft.CodeAnalysis.IOperation operation) -> System.Collections.Generic.IEnumerable` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.DescendantsAndSelf(this Microsoft.CodeAnalysis.IOperation operation) -> System.Collections.Generic.IEnumerable` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentName(this Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation dynamicOperation, int index) -> string` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentName(this Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation dynamicOperation, int index) -> string` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentName(this Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation dynamicOperation, int index) -> string` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentRefKind(this Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation dynamicOperation, int index) -> Microsoft.CodeAnalysis.RefKind?` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentRefKind(this Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation dynamicOperation, int index) -> Microsoft.CodeAnalysis.RefKind?` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetArgumentRefKind(this Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation dynamicOperation, int index) -> Microsoft.CodeAnalysis.RefKind?` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetDeclaredVariables(this Microsoft.CodeAnalysis.Operations.IVariableDeclarationGroupOperation declarationGroup) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetDeclaredVariables(this Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation declaration) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetVariableInitializer(this Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation declarationOperation) -> Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action action, System.Collections.Immutable.ImmutableArray operationKinds) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationBlockAction(System.Action action) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationBlockStartAction(System.Action action) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationAction(System.Action action, System.Collections.Immutable.ImmutableArray operationKinds) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationBlockAction(System.Action action) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationBlockStartAction(System.Action action) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.DefaultVisit(Microsoft.CodeAnalysis.IOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.Visit(Microsoft.CodeAnalysis.IOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAddressOf(Microsoft.CodeAnalysis.Operations.IAddressOfOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAnonymousFunction(Microsoft.CodeAnalysis.Operations.IAnonymousFunctionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAnonymousObjectCreation(Microsoft.CodeAnalysis.Operations.IAnonymousObjectCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArgument(Microsoft.CodeAnalysis.Operations.IArgumentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayCreation(Microsoft.CodeAnalysis.Operations.IArrayCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayElementReference(Microsoft.CodeAnalysis.Operations.IArrayElementReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayInitializer(Microsoft.CodeAnalysis.Operations.IArrayInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAwait(Microsoft.CodeAnalysis.Operations.IAwaitOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryOperator(Microsoft.CodeAnalysis.Operations.IBinaryOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBlock(Microsoft.CodeAnalysis.Operations.IBlockOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBranch(Microsoft.CodeAnalysis.Operations.IBranchOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCatchClause(Microsoft.CodeAnalysis.Operations.ICatchClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCoalesce(Microsoft.CodeAnalysis.Operations.ICoalesceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionElementInitializer(Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCompoundAssignment(Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditional(Microsoft.CodeAnalysis.Operations.IConditionalOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditionalAccess(Microsoft.CodeAnalysis.Operations.IConditionalAccessOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditionalAccessInstance(Microsoft.CodeAnalysis.Operations.IConditionalAccessInstanceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConstantPattern(Microsoft.CodeAnalysis.Operations.IConstantPatternOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConversion(Microsoft.CodeAnalysis.Operations.IConversionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeclarationExpression(Microsoft.CodeAnalysis.Operations.IDeclarationExpressionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeclarationPattern(Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeconstructionAssignment(Microsoft.CodeAnalysis.Operations.IDeconstructionAssignmentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDefaultCaseClause(Microsoft.CodeAnalysis.Operations.IDefaultCaseClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDefaultValue(Microsoft.CodeAnalysis.Operations.IDefaultValueOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDelegateCreation(Microsoft.CodeAnalysis.Operations.IDelegateCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicIndexerAccess(Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicInvocation(Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicMemberReference(Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicObjectCreation(Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEmpty(Microsoft.CodeAnalysis.Operations.IEmptyOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEnd(Microsoft.CodeAnalysis.Operations.IEndOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEventAssignment(Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEventReference(Microsoft.CodeAnalysis.Operations.IEventReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitExpressionStatement(Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldInitializer(Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldReference(Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForEachLoop(Microsoft.CodeAnalysis.Operations.IForEachLoopOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForLoop(Microsoft.CodeAnalysis.Operations.IForLoopOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForToLoop(Microsoft.CodeAnalysis.Operations.IForToLoopOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIncrementOrDecrement(Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInstanceReference(Microsoft.CodeAnalysis.Operations.IInstanceReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedString(Microsoft.CodeAnalysis.Operations.IInterpolatedStringOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedStringText(Microsoft.CodeAnalysis.Operations.IInterpolatedStringTextOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolation(Microsoft.CodeAnalysis.Operations.IInterpolationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInvalid(Microsoft.CodeAnalysis.Operations.IInvalidOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInvocation(Microsoft.CodeAnalysis.Operations.IInvocationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsPattern(Microsoft.CodeAnalysis.Operations.IIsPatternOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsType(Microsoft.CodeAnalysis.Operations.IIsTypeOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLabeled(Microsoft.CodeAnalysis.Operations.ILabeledOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLiteral(Microsoft.CodeAnalysis.Operations.ILiteralOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLocalFunction(Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLocalReference(Microsoft.CodeAnalysis.Operations.ILocalReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLock(Microsoft.CodeAnalysis.Operations.ILockOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMemberInitializer(Microsoft.CodeAnalysis.Operations.IMemberInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMethodReference(Microsoft.CodeAnalysis.Operations.IMethodReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNameOf(Microsoft.CodeAnalysis.Operations.INameOfOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitObjectCreation(Microsoft.CodeAnalysis.Operations.IObjectCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitObjectOrCollectionInitializer(Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitOmittedArgument(Microsoft.CodeAnalysis.Operations.IOmittedArgumentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParameterInitializer(Microsoft.CodeAnalysis.Operations.IParameterInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParameterReference(Microsoft.CodeAnalysis.Operations.IParameterReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParenthesized(Microsoft.CodeAnalysis.Operations.IParenthesizedOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPatternCaseClause(Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPropertyInitializer(Microsoft.CodeAnalysis.Operations.IPropertyInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPropertyReference(Microsoft.CodeAnalysis.Operations.IPropertyReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRaiseEvent(Microsoft.CodeAnalysis.Operations.IRaiseEventOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRangeCaseClause(Microsoft.CodeAnalysis.Operations.IRangeCaseClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalCaseClause(Microsoft.CodeAnalysis.Operations.IRelationalCaseClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReturn(Microsoft.CodeAnalysis.Operations.IReturnOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSimpleAssignment(Microsoft.CodeAnalysis.Operations.ISimpleAssignmentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSingleValueCaseClause(Microsoft.CodeAnalysis.Operations.ISingleValueCaseClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSizeOf(Microsoft.CodeAnalysis.Operations.ISizeOfOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitStop(Microsoft.CodeAnalysis.Operations.IStopOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitch(Microsoft.CodeAnalysis.Operations.ISwitchOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchCase(Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitThrow(Microsoft.CodeAnalysis.Operations.IThrowOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTranslatedQuery(Microsoft.CodeAnalysis.Operations.ITranslatedQueryOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTry(Microsoft.CodeAnalysis.Operations.ITryOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTuple(Microsoft.CodeAnalysis.Operations.ITupleOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypeOf(Microsoft.CodeAnalysis.Operations.ITypeOfOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypeParameterObjectCreation(Microsoft.CodeAnalysis.Operations.ITypeParameterObjectCreationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUnaryOperator(Microsoft.CodeAnalysis.Operations.IUnaryOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUsing(Microsoft.CodeAnalysis.Operations.IUsingOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclaration(Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclarationGroup(Microsoft.CodeAnalysis.Operations.IVariableDeclarationGroupOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclarator(Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableInitializer(Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitWhileLoop(Microsoft.CodeAnalysis.Operations.IWhileLoopOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.DefaultVisit(Microsoft.CodeAnalysis.IOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.Visit(Microsoft.CodeAnalysis.IOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAddressOf(Microsoft.CodeAnalysis.Operations.IAddressOfOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAnonymousFunction(Microsoft.CodeAnalysis.Operations.IAnonymousFunctionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAnonymousObjectCreation(Microsoft.CodeAnalysis.Operations.IAnonymousObjectCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArgument(Microsoft.CodeAnalysis.Operations.IArgumentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayCreation(Microsoft.CodeAnalysis.Operations.IArrayCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayElementReference(Microsoft.CodeAnalysis.Operations.IArrayElementReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitArrayInitializer(Microsoft.CodeAnalysis.Operations.IArrayInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitAwait(Microsoft.CodeAnalysis.Operations.IAwaitOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryOperator(Microsoft.CodeAnalysis.Operations.IBinaryOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBlock(Microsoft.CodeAnalysis.Operations.IBlockOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBranch(Microsoft.CodeAnalysis.Operations.IBranchOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCatchClause(Microsoft.CodeAnalysis.Operations.ICatchClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCoalesce(Microsoft.CodeAnalysis.Operations.ICoalesceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionElementInitializer(Microsoft.CodeAnalysis.Operations.ICollectionElementInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCompoundAssignment(Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditional(Microsoft.CodeAnalysis.Operations.IConditionalOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditionalAccess(Microsoft.CodeAnalysis.Operations.IConditionalAccessOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConditionalAccessInstance(Microsoft.CodeAnalysis.Operations.IConditionalAccessInstanceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConstantPattern(Microsoft.CodeAnalysis.Operations.IConstantPatternOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConversion(Microsoft.CodeAnalysis.Operations.IConversionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeclarationExpression(Microsoft.CodeAnalysis.Operations.IDeclarationExpressionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeclarationPattern(Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDeconstructionAssignment(Microsoft.CodeAnalysis.Operations.IDeconstructionAssignmentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDefaultCaseClause(Microsoft.CodeAnalysis.Operations.IDefaultCaseClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDefaultValue(Microsoft.CodeAnalysis.Operations.IDefaultValueOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDelegateCreation(Microsoft.CodeAnalysis.Operations.IDelegateCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicIndexerAccess(Microsoft.CodeAnalysis.Operations.IDynamicIndexerAccessOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicInvocation(Microsoft.CodeAnalysis.Operations.IDynamicInvocationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicMemberReference(Microsoft.CodeAnalysis.Operations.IDynamicMemberReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDynamicObjectCreation(Microsoft.CodeAnalysis.Operations.IDynamicObjectCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEmpty(Microsoft.CodeAnalysis.Operations.IEmptyOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEnd(Microsoft.CodeAnalysis.Operations.IEndOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEventAssignment(Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitEventReference(Microsoft.CodeAnalysis.Operations.IEventReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitExpressionStatement(Microsoft.CodeAnalysis.Operations.IExpressionStatementOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldInitializer(Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFieldReference(Microsoft.CodeAnalysis.Operations.IFieldReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForEachLoop(Microsoft.CodeAnalysis.Operations.IForEachLoopOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForLoop(Microsoft.CodeAnalysis.Operations.IForLoopOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitForToLoop(Microsoft.CodeAnalysis.Operations.IForToLoopOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIncrementOrDecrement(Microsoft.CodeAnalysis.Operations.IIncrementOrDecrementOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInstanceReference(Microsoft.CodeAnalysis.Operations.IInstanceReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedString(Microsoft.CodeAnalysis.Operations.IInterpolatedStringOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolatedStringText(Microsoft.CodeAnalysis.Operations.IInterpolatedStringTextOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInterpolation(Microsoft.CodeAnalysis.Operations.IInterpolationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInvalid(Microsoft.CodeAnalysis.Operations.IInvalidOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitInvocation(Microsoft.CodeAnalysis.Operations.IInvocationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsPattern(Microsoft.CodeAnalysis.Operations.IIsPatternOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsType(Microsoft.CodeAnalysis.Operations.IIsTypeOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLabeled(Microsoft.CodeAnalysis.Operations.ILabeledOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLiteral(Microsoft.CodeAnalysis.Operations.ILiteralOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLocalFunction(Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLocalReference(Microsoft.CodeAnalysis.Operations.ILocalReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitLock(Microsoft.CodeAnalysis.Operations.ILockOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMemberInitializer(Microsoft.CodeAnalysis.Operations.IMemberInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMethodReference(Microsoft.CodeAnalysis.Operations.IMethodReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNameOf(Microsoft.CodeAnalysis.Operations.INameOfOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitObjectCreation(Microsoft.CodeAnalysis.Operations.IObjectCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitObjectOrCollectionInitializer(Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitOmittedArgument(Microsoft.CodeAnalysis.Operations.IOmittedArgumentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParameterInitializer(Microsoft.CodeAnalysis.Operations.IParameterInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParameterReference(Microsoft.CodeAnalysis.Operations.IParameterReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitParenthesized(Microsoft.CodeAnalysis.Operations.IParenthesizedOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPatternCaseClause(Microsoft.CodeAnalysis.Operations.IPatternCaseClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPropertyInitializer(Microsoft.CodeAnalysis.Operations.IPropertyInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitPropertyReference(Microsoft.CodeAnalysis.Operations.IPropertyReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRaiseEvent(Microsoft.CodeAnalysis.Operations.IRaiseEventOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRangeCaseClause(Microsoft.CodeAnalysis.Operations.IRangeCaseClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalCaseClause(Microsoft.CodeAnalysis.Operations.IRelationalCaseClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReturn(Microsoft.CodeAnalysis.Operations.IReturnOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSimpleAssignment(Microsoft.CodeAnalysis.Operations.ISimpleAssignmentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSingleValueCaseClause(Microsoft.CodeAnalysis.Operations.ISingleValueCaseClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSizeOf(Microsoft.CodeAnalysis.Operations.ISizeOfOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitStop(Microsoft.CodeAnalysis.Operations.IStopOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitch(Microsoft.CodeAnalysis.Operations.ISwitchOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchCase(Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitThrow(Microsoft.CodeAnalysis.Operations.IThrowOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTranslatedQuery(Microsoft.CodeAnalysis.Operations.ITranslatedQueryOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTry(Microsoft.CodeAnalysis.Operations.ITryOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTuple(Microsoft.CodeAnalysis.Operations.ITupleOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypeOf(Microsoft.CodeAnalysis.Operations.ITypeOfOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypeParameterObjectCreation(Microsoft.CodeAnalysis.Operations.ITypeParameterObjectCreationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUnaryOperator(Microsoft.CodeAnalysis.Operations.IUnaryOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUsing(Microsoft.CodeAnalysis.Operations.IUsingOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclaration(Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclarationGroup(Microsoft.CodeAnalysis.Operations.IVariableDeclarationGroupOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableDeclarator(Microsoft.CodeAnalysis.Operations.IVariableDeclaratorOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitVariableInitializer(Microsoft.CodeAnalysis.Operations.IVariableInitializerOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitWhileLoop(Microsoft.CodeAnalysis.Operations.IWhileLoopOperation operation, TArgument argument) -> TResult` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.IsStackAlloc.get -> bool` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.ToCommonConversion() -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetConversion(this Microsoft.CodeAnalysis.Operations.IConversionOperation conversionExpression) -> Microsoft.CodeAnalysis.CSharp.Conversion` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetInConversion(this Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation compoundAssignment) -> Microsoft.CodeAnalysis.CSharp.Conversion` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetOutConversion(this Microsoft.CodeAnalysis.Operations.ICompoundAssignmentOperation compoundAssignment) -> Microsoft.CodeAnalysis.CSharp.Conversion` ## Syntax * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_2 = 702 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefType(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax` ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/CSharp73.md ================================================ # C# 7.3 APIs supported via light-up See [dotnet/roslyn@e60c9fe](https://github.com/dotnet/roslyn/commit/e60c9fe8accc442218b7858ca79f07b115e493b2). ## Semantics * [ ] `abstract Microsoft.CodeAnalysis.DataFlowAnalysis.CapturedInside.get -> System.Collections.Immutable.ImmutableArray` * [ ] `abstract Microsoft.CodeAnalysis.DataFlowAnalysis.CapturedOutside.get -> System.Collections.Immutable.ImmutableArray` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.DeconstructMethodName = "Deconstruct" -> string` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.MetadataImportOptions.get -> Microsoft.CodeAnalysis.MetadataImportOptions` * [ ] `Microsoft.CodeAnalysis.CompilationOptions.WithMetadataImportOptions(Microsoft.CodeAnalysis.MetadataImportOptions value) -> Microsoft.CodeAnalysis.CompilationOptions` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly = false, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat = (Microsoft.CodeAnalysis.Emit.DebugInformationFormat)0, string pdbFilePath = null, string outputNameOverride = null, int fileAlignment = 0, ulong baseAddress = 0, bool highEntropyVirtualAddressSpace = false, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion = default(Microsoft.CodeAnalysis.SubsystemVersion), string runtimeMetadataVersion = null, bool tolerateErrors = false, bool includePrivateMembers = true, System.Collections.Immutable.ImmutableArray instrumentationKinds = default(System.Collections.Immutable.ImmutableArray), System.Security.Cryptography.HashAlgorithmName? pdbChecksumAlgorithm = null) -> void` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.EmitOptions(bool metadataOnly, Microsoft.CodeAnalysis.Emit.DebugInformationFormat debugInformationFormat, string pdbFilePath, string outputNameOverride, int fileAlignment, ulong baseAddress, bool highEntropyVirtualAddressSpace, Microsoft.CodeAnalysis.SubsystemVersion subsystemVersion, string runtimeMetadataVersion, bool tolerateErrors, bool includePrivateMembers, System.Collections.Immutable.ImmutableArray instrumentationKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.PdbChecksumAlgorithm.get -> System.Security.Cryptography.HashAlgorithmName` * [ ] `Microsoft.CodeAnalysis.Emit.EmitOptions.WithPdbChecksumAlgorithm(System.Security.Cryptography.HashAlgorithmName name) -> Microsoft.CodeAnalysis.Emit.EmitOptions` * [x] `Microsoft.CodeAnalysis.INamedTypeSymbol.IsSerializable.get -> bool` * [x] `Microsoft.CodeAnalysis.ITypeParameterSymbol.HasUnmanagedTypeConstraint.get -> bool` * [ ] `Microsoft.CodeAnalysis.MetadataImportOptions` * [ ] `Microsoft.CodeAnalysis.MetadataImportOptions.All = 2 -> Microsoft.CodeAnalysis.MetadataImportOptions` * [ ] `Microsoft.CodeAnalysis.MetadataImportOptions.Internal = 1 -> Microsoft.CodeAnalysis.MetadataImportOptions` * [ ] `Microsoft.CodeAnalysis.MetadataImportOptions.Public = 0 -> Microsoft.CodeAnalysis.MetadataImportOptions` * [ ] `Microsoft.CodeAnalysis.OperationKind.ConstructorBodyOperation = 89 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Discard = 90 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.MethodBodyOperation = 88 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.TupleBinaryOperator = 87 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation.Initializer.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IDiscardOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IDiscardOperation.DiscardSymbol.get -> Microsoft.CodeAnalysis.IDiscardSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ILocalFunctionOperation.IgnoredBody.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodBodyBaseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodBodyBaseOperation.BlockBody.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodBodyBaseOperation.ExpressionBody.get -> Microsoft.CodeAnalysis.Operations.IBlockOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IMethodBodyOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISymbolInitializerOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation.LeftOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation.OperatorKind.get -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation.RightOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ITupleOperation.NaturalType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Platform.Arm64 = 6 -> Microsoft.CodeAnalysis.Platform` * [ ] `static Microsoft.CodeAnalysis.Diagnostic.Create(Microsoft.CodeAnalysis.DiagnosticDescriptor descriptor, Microsoft.CodeAnalysis.Location location, Microsoft.CodeAnalysis.DiagnosticSeverity effectiveSeverity, System.Collections.Generic.IEnumerable additionalLocations, System.Collections.Immutable.ImmutableDictionary properties, params object[] messageArgs) -> Microsoft.CodeAnalysis.Diagnostic` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConstructorBodyOperation(Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDiscardOperation(Microsoft.CodeAnalysis.Operations.IDiscardOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMethodBodyOperation(Microsoft.CodeAnalysis.Operations.IMethodBodyOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTupleBinaryOperator(Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitConstructorBodyOperation(Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDiscardOperation(Microsoft.CodeAnalysis.Operations.IDiscardOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitMethodBodyOperation(Microsoft.CodeAnalysis.Operations.IMethodBodyOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTupleBinaryOperator(Microsoft.CodeAnalysis.Operations.ITupleBinaryOperation operation, TArgument argument) -> TResult` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind outputKind, bool reportSuppressedDiagnostics = false, string moduleName = null, string mainTypeName = null, string scriptClassName = null, System.Collections.Generic.IEnumerable usings = null, Microsoft.CodeAnalysis.OptimizationLevel optimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel.Debug, bool checkOverflow = false, bool allowUnsafe = false, string cryptoKeyContainer = null, string cryptoKeyFile = null, System.Collections.Immutable.ImmutableArray cryptoPublicKey = default(System.Collections.Immutable.ImmutableArray), bool? delaySign = null, Microsoft.CodeAnalysis.Platform platform = Microsoft.CodeAnalysis.Platform.AnyCpu, Microsoft.CodeAnalysis.ReportDiagnostic generalDiagnosticOption = Microsoft.CodeAnalysis.ReportDiagnostic.Default, int warningLevel = 4, System.Collections.Generic.IEnumerable> specificDiagnosticOptions = null, bool concurrentBuild = true, bool deterministic = false, Microsoft.CodeAnalysis.XmlReferenceResolver xmlReferenceResolver = null, Microsoft.CodeAnalysis.SourceReferenceResolver sourceReferenceResolver = null, Microsoft.CodeAnalysis.MetadataReferenceResolver metadataReferenceResolver = null, Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer = null, Microsoft.CodeAnalysis.StrongNameProvider strongNameProvider = null, bool publicSign = false, Microsoft.CodeAnalysis.MetadataImportOptions metadataImportOptions = Microsoft.CodeAnalysis.MetadataImportOptions.Public) -> void` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind outputKind, bool reportSuppressedDiagnostics, string moduleName, string mainTypeName, string scriptClassName, System.Collections.Generic.IEnumerable usings, Microsoft.CodeAnalysis.OptimizationLevel optimizationLevel, bool checkOverflow, bool allowUnsafe, string cryptoKeyContainer, string cryptoKeyFile, System.Collections.Immutable.ImmutableArray cryptoPublicKey, bool? delaySign, Microsoft.CodeAnalysis.Platform platform, Microsoft.CodeAnalysis.ReportDiagnostic generalDiagnosticOption, int warningLevel, System.Collections.Generic.IEnumerable> specificDiagnosticOptions, bool concurrentBuild, bool deterministic, Microsoft.CodeAnalysis.XmlReferenceResolver xmlReferenceResolver, Microsoft.CodeAnalysis.SourceReferenceResolver sourceReferenceResolver, Microsoft.CodeAnalysis.MetadataReferenceResolver metadataReferenceResolver, Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer, Microsoft.CodeAnalysis.StrongNameProvider strongNameProvider, bool publicSign) -> void` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.WithMetadataImportOptions(Microsoft.CodeAnalysis.MetadataImportOptions value) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions` * [ ] `Microsoft.CodeAnalysis.CSharp.DeconstructionInfo` * [ ] `Microsoft.CodeAnalysis.CSharp.DeconstructionInfo.Conversion.get -> Microsoft.CodeAnalysis.CSharp.Conversion?` * [ ] `Microsoft.CodeAnalysis.CSharp.DeconstructionInfo.Method.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.CSharp.DeconstructionInfo.Nested.get -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpCommandLineParser.Script.get -> Microsoft.CodeAnalysis.CSharp.CSharpCommandLineParser` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeconstructionInfo(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.AssignmentExpressionSyntax assignment) -> Microsoft.CodeAnalysis.CSharp.DeconstructionInfo` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeconstructionInfo(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax foreach) -> Microsoft.CodeAnalysis.CSharp.DeconstructionInfo` ## Syntax * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7_3 = 703 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.RefKindKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.WithRefKindKeyword(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.RefKindKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithRefKindKeyword(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.AddInitializerExpressions(params Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.CloseBracketToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.Initializer.get -> Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.OpenBracketToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.StackAllocKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken stackAllocKeyword, Microsoft.CodeAnalysis.SyntaxToken openBracketToken, Microsoft.CodeAnalysis.SyntaxToken closeBracketToken, Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.WithCloseBracketToken(Microsoft.CodeAnalysis.SyntaxToken closeBracketToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.WithInitializer(Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.WithOpenBracketToken(Microsoft.CodeAnalysis.SyntaxToken openBracketToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.WithStackAllocKeyword(Microsoft.CodeAnalysis.SyntaxToken stackAllocKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax.Initializer.get -> Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken stackAllocKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax.WithInitializer(Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax.IsUnmanaged.get -> bool` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.ImplicitStackAllocArrayCreationExpression = 9053 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitImplicitStackAllocArrayCreationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ImplicitStackAllocArrayCreationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ImplicitStackAllocArrayCreationExpression(Microsoft.CodeAnalysis.SyntaxToken stackAllocKeyword, Microsoft.CodeAnalysis.SyntaxToken openBracketToken, Microsoft.CodeAnalysis.SyntaxToken closeBracketToken, Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.StackAllocArrayCreationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.StackAllocArrayCreationExpression(Microsoft.CodeAnalysis.SyntaxToken stackAllocKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.InitializerExpressionSyntax initializer) -> Microsoft.CodeAnalysis.CSharp.Syntax.StackAllocArrayCreationExpressionSyntax` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitImplicitStackAllocArrayCreationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitImplicitStackAllocArrayCreationExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax node) -> TResult` ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/CSharp8.md ================================================ # C# 8 APIs supported via light-up ## Semantics ## Syntax ## Uncategorized See [Microsoft.CodeAnalysis release/dev16.3@c955f3c99b5698c906e0700ef691b5b1571c8136](https://raw.githubusercontent.com/dotnet/roslyn/c955f3c99b5698c906e0700ef691b5b1571c8136/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt) * [ ] `*REMOVED*Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.EventReference.get -> Microsoft.CodeAnalysis.Operations.IEventReferenceOperation` * [ ] `*REMOVED*Microsoft.CodeAnalysis.SpecialType.Count = 43 -> Microsoft.CodeAnalysis.SpecialType` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.EmitPdbFile.get -> bool` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.GetOutputFilePath(string outputFileName) -> string` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.GetPdbFilePath(string outputFileName) -> string` * [ ] `*REMOVED*Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(Microsoft.CodeAnalysis.INamedTypeSymbol underlyingType, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateAnonymousTypeSymbol(System.Collections.Immutable.ImmutableArray memberTypes, System.Collections.Immutable.ImmutableArray memberNames, System.Collections.Immutable.ImmutableArray memberIsReadOnly = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray memberLocations = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray memberNullableAnnotations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `*REMOVED*Microsoft.CodeAnalysis.Compilation.CreateAnonymousTypeSymbol(System.Collections.Immutable.ImmutableArray memberTypes, System.Collections.Immutable.ImmutableArray memberNames, System.Collections.Immutable.ImmutableArray memberIsReadOnly = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray memberLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateAnonymousTypeSymbol(System.Collections.Immutable.ImmutableArray memberTypes, System.Collections.Immutable.ImmutableArray memberNames, System.Collections.Immutable.ImmutableArray memberIsReadOnly, System.Collections.Immutable.ImmutableArray memberLocations) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateArrayTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol elementType, int rank = 1, Microsoft.CodeAnalysis.NullableAnnotation elementNullableAnnotation = Microsoft.CodeAnalysis.NullableAnnotation.None) -> Microsoft.CodeAnalysis.IArrayTypeSymbol` * [ ] `*REMOVED*Microsoft.CodeAnalysis.Compilation.CreateArrayTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol elementType, int rank = 1) -> Microsoft.CodeAnalysis.IArrayTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateArrayTypeSymbol(Microsoft.CodeAnalysis.ITypeSymbol elementType, int rank) -> Microsoft.CodeAnalysis.IArrayTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(Microsoft.CodeAnalysis.INamedTypeSymbol underlyingType, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementNullableAnnotations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(Microsoft.CodeAnalysis.INamedTypeSymbol underlyingType, System.Collections.Immutable.ImmutableArray elementNames, System.Collections.Immutable.ImmutableArray elementLocations) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `*REMOVED*Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(System.Collections.Immutable.ImmutableArray elementTypes, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(System.Collections.Immutable.ImmutableArray elementTypes, System.Collections.Immutable.ImmutableArray elementNames = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementLocations = default(System.Collections.Immutable.ImmutableArray), System.Collections.Immutable.ImmutableArray elementNullableAnnotations = default(System.Collections.Immutable.ImmutableArray)) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(System.Collections.Immutable.ImmutableArray elementTypes, System.Collections.Immutable.ImmutableArray elementNames, System.Collections.Immutable.ImmutableArray elementLocations) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.Compilation.HasImplicitConversion(Microsoft.CodeAnalysis.ITypeSymbol fromType, Microsoft.CodeAnalysis.ITypeSymbol toType) -> bool` * [ ] `Microsoft.CodeAnalysis.Compilation.IsSymbolAccessibleWithin(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.ISymbol within, Microsoft.CodeAnalysis.ITypeSymbol throughType = null) -> bool` * [ ] `Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor` * [ ] `Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor.DiagnosticSuppressor() -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Suppression` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Suppression.Descriptor.get -> Microsoft.CodeAnalysis.SuppressionDescriptor` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Suppression.SuppressedDiagnostic.get -> Microsoft.CodeAnalysis.Diagnostic` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.GetSemanticModel(Microsoft.CodeAnalysis.SyntaxTree syntaxTree) -> Microsoft.CodeAnalysis.SemanticModel` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.ReportSuppression(Microsoft.CodeAnalysis.Diagnostics.Suppression suppression) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext.ReportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SuppressionActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SuppressionActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.IArrayTypeSymbol.ElementNullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IDiscardSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IEventSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IFieldSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.ILocalSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.Construct(System.Collections.Immutable.ImmutableArray typeArguments, System.Collections.Immutable.ImmutableArray typeArgumentNullableAnnotations) -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.ReceiverNullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.ReturnNullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.TypeArgumentNullableAnnotations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.INamedTypeSymbol.Construct(System.Collections.Immutable.ImmutableArray typeArguments, System.Collections.Immutable.ImmutableArray typeArgumentNullableAnnotations) -> Microsoft.CodeAnalysis.INamedTypeSymbol` * [ ] `Microsoft.CodeAnalysis.INamedTypeSymbol.TypeArgumentNullableAnnotations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.IParameterSymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.IPropertySymbol.NullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.ISymbol.Equals(Microsoft.CodeAnalysis.ISymbol other, Microsoft.CodeAnalysis.SymbolEqualityComparer equalityComparer) -> bool` * [ ] `Microsoft.CodeAnalysis.ITypeParameterSymbol.ConstraintNullableAnnotations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.ITypeParameterSymbol.HasNotNullConstraint.get -> bool` * [ ] `Microsoft.CodeAnalysis.ITypeParameterSymbol.ReferenceTypeConstraintNullableAnnotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.ToDisplayParts(Microsoft.CodeAnalysis.NullableFlowState topLevelNullability, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.ToDisplayString(Microsoft.CodeAnalysis.NullableFlowState topLevelNullability, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.ToMinimalDisplayParts(Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.NullableFlowState topLevelNullability, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.ToMinimalDisplayString(Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.NullableFlowState topLevelNullability, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `Microsoft.CodeAnalysis.NullabilityInfo` * [ ] `Microsoft.CodeAnalysis.NullabilityInfo.Annotation.get -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.NullabilityInfo.Equals(Microsoft.CodeAnalysis.NullabilityInfo other) -> bool` * [ ] `Microsoft.CodeAnalysis.NullabilityInfo.FlowState.get -> Microsoft.CodeAnalysis.NullableFlowState` * [ ] `Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.NullableAnnotation.Annotated = 2 -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.NullableAnnotation.None = 0 -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.NullableAnnotation.NotAnnotated = 1 -> Microsoft.CodeAnalysis.NullableAnnotation` * [ ] `Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.AnnotationsContextInherited = 8 -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.AnnotationsEnabled = 2 -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.ContextInherited = Microsoft.CodeAnalysis.NullableContext.WarningsContextInherited | Microsoft.CodeAnalysis.NullableContext.AnnotationsContextInherited -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.Disabled = 0 -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.Enabled = Microsoft.CodeAnalysis.NullableContext.WarningsEnabled | Microsoft.CodeAnalysis.NullableContext.AnnotationsEnabled -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.WarningsContextInherited = 4 -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContext.WarningsEnabled = 1 -> Microsoft.CodeAnalysis.NullableContext` * [ ] `Microsoft.CodeAnalysis.NullableContextExtensions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptions.Annotations = 2 -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptions.Disable = 0 -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptions.Enable = Microsoft.CodeAnalysis.NullableContextOptions.Warnings | Microsoft.CodeAnalysis.NullableContextOptions.Annotations -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptions.Warnings = 1 -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `Microsoft.CodeAnalysis.NullableContextOptionsExtensions` * [ ] `Microsoft.CodeAnalysis.NullableFlowState` * [ ] `Microsoft.CodeAnalysis.NullableFlowState.MaybeNull = 2 -> Microsoft.CodeAnalysis.NullableFlowState` * [ ] `Microsoft.CodeAnalysis.NullableFlowState.None = 0 -> Microsoft.CodeAnalysis.NullableFlowState` * [ ] `Microsoft.CodeAnalysis.NullableFlowState.NotNull = 1 -> Microsoft.CodeAnalysis.NullableFlowState` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.Equals(Microsoft.CodeAnalysis.SuppressionDescriptor other) -> bool` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.Id.get -> string` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.Justification.get -> Microsoft.CodeAnalysis.LocalizableString` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.SuppressedDiagnosticId.get -> string` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.SuppressionDescriptor(string id, string suppressedDiagnosticId, Microsoft.CodeAnalysis.LocalizableString justification) -> void` * [ ] `Microsoft.CodeAnalysis.SuppressionDescriptor.SuppressionDescriptor(string id, string suppressedDiagnosticId, string justification) -> void` * [ ] `Microsoft.CodeAnalysis.OperationKind.PropertySubpattern = 107 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertySubpatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertySubpatternOperation.Member.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IPropertySubpatternOperation.Pattern.get -> Microsoft.CodeAnalysis.Operations.IPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation.DeclaredSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation.DeconstructSymbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation.DeconstructionSubpatterns.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation.MatchedType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRecursivePatternOperation.PropertySubpatterns.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.SymbolEqualityComparer` * [ ] `Microsoft.CodeAnalysis.SymbolEqualityComparer.Equals(Microsoft.CodeAnalysis.ISymbol x, Microsoft.CodeAnalysis.ISymbol y) -> bool` * [ ] `Microsoft.CodeAnalysis.SymbolEqualityComparer.GetHashCode(Microsoft.CodeAnalysis.ISymbol obj) -> int` * [ ] `Microsoft.CodeAnalysis.TypeInfo.ConvertedNullability.get -> Microsoft.CodeAnalysis.NullabilityInfo` * [ ] `Microsoft.CodeAnalysis.TypeInfo.Nullability.get -> Microsoft.CodeAnalysis.NullabilityInfo` * [ ] `abstract Microsoft.CodeAnalysis.Compilation.ClassifyCommonConversion(Microsoft.CodeAnalysis.ITypeSymbol source, Microsoft.CodeAnalysis.ITypeSymbol destination) -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `abstract Microsoft.CodeAnalysis.Compilation.ContainsSymbolsWithName(string name, Microsoft.CodeAnalysis.SymbolFilter filter = Microsoft.CodeAnalysis.SymbolFilter.TypeAndMember, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> bool` * [ ] `abstract Microsoft.CodeAnalysis.Compilation.GetSymbolsWithName(string name, Microsoft.CodeAnalysis.SymbolFilter filter = Microsoft.CodeAnalysis.SymbolFilter.TypeAndMember, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IEnumerable` * [ ] `abstract Microsoft.CodeAnalysis.CompilationOptions.NullableContextOptions.get -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions.TryGetValue(string key, out string value) -> bool` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider.GetOptions(Microsoft.CodeAnalysis.AdditionalText textFile) -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider.GetOptions(Microsoft.CodeAnalysis.SyntaxTree tree) -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor.ReportSuppressions(Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext context) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor.SupportedSuppressions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterCodeBlockAction(System.Action action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterCodeBlockStartAction(System.Action> action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterOperationAction(System.Action action, System.Collections.Immutable.ImmutableArray operationKinds) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterOperationBlockAction(System.Action action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterOperationBlockStartAction(System.Action action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterSymbolEndAction(System.Action action) -> void` * [ ] `abstract Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterSyntaxNodeAction(System.Action action, System.Collections.Immutable.ImmutableArray syntaxKinds) -> void` * [ ] `abstract Microsoft.CodeAnalysis.SemanticModel.GetNullableContext(int position) -> Microsoft.CodeAnalysis.NullableContext` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.CountPropertyName = "Count" -> string` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.DisposeAsyncMethodName = "DisposeAsync" -> string` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.DisposeMethodName = "Dispose" -> string` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.GetAsyncEnumeratorMethodName = "GetAsyncEnumerator" -> string` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.LengthPropertyName = "Length" -> string` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.MoveNextAsyncMethodName = "MoveNextAsync" -> string` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.GlobalSection.get -> Microsoft.CodeAnalysis.AnalyzerConfig.Section` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.IsRoot.get -> bool` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.NamedSections.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.NormalizedDirectory.get -> string` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.PathToFile.get -> string` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.Section` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.Section.Name.get -> string` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.Section.Properties.get -> System.Collections.Immutable.ImmutableDictionary` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.Section.Section(string name, System.Collections.Immutable.ImmutableDictionary properties) -> void` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.SectionNameMatcher` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfig.SectionNameMatcher.IsMatch(string s) -> bool` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult.AnalyzerOptions.get -> System.Collections.Immutable.ImmutableDictionary` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult.Diagnostics.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult.TreeOptions.get -> System.Collections.Immutable.ImmutableDictionary` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigSet` * [ ] `Microsoft.CodeAnalysis.AnalyzerConfigSet.GetOptionsForSourcePath(string sourcePath) -> Microsoft.CodeAnalysis.AnalyzerConfigOptionsResult` * [ ] `Microsoft.CodeAnalysis.CommandLineArguments.AnalyzerConfigPaths.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions.AnalyzerConfigOptions() -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider.AnalyzerConfigOptionsProvider() -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions.AnalyzerConfigOptionsProvider.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider` * [ ] `Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions.AnalyzerOptions(System.Collections.Immutable.ImmutableArray additionalFiles, Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider optionsProvider) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.GetControlFlowGraph() -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.GetControlFlowGraph(Microsoft.CodeAnalysis.IOperation operationBlock) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.GetControlFlowGraph(Microsoft.CodeAnalysis.IOperation operationBlock) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.CancellationToken.get -> System.Threading.CancellationToken` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterOperationAction(System.Action action, params Microsoft.CodeAnalysis.OperationKind[] operationKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.RegisterSyntaxNodeAction(System.Action action, params TLanguageKindEnum[] syntaxKinds) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.Symbol.get -> Microsoft.CodeAnalysis.ISymbol` * [ ] `Microsoft.CodeAnalysis.Diagnostics.SymbolStartAnalysisContext.SymbolStartAnalysisContext(Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.Compilation compilation, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Threading.CancellationToken cancellationToken) -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.Concurrent.get -> bool` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.Concurrent.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SymbolEndActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SymbolEndActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SymbolStartActionsCount.get -> int` * [ ] `Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.SymbolStartActionsCount.set -> void` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.BranchValue.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.ConditionalSuccessor.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.ConditionKind.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.EnclosingRegion.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.FallThroughSuccessor.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.IsReachable.get -> bool` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.Kind.get -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.Operations.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.Ordinal.get -> int` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock.Predecessors.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind.Block = 2 -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind.Entry = 0 -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind.Exit = 1 -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlockKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.CaptureId` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.CaptureId.Equals(Microsoft.CodeAnalysis.FlowAnalysis.CaptureId other) -> bool` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.Destination.get -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.EnteringRegions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.FinallyRegions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.IsConditionalSuccessor.get -> bool` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.LeavingRegions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.Semantics.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranch.Source.get -> Microsoft.CodeAnalysis.FlowAnalysis.BasicBlock` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.Error = 7 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.None = 0 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.ProgramTermination = 4 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.Regular = 1 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.Rethrow = 6 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.Return = 2 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.StructuredExceptionHandling = 3 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics.Throw = 5 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowBranchSemantics` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind.None = 0 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind.WhenFalse = 1 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind.WhenTrue = 2 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowConditionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Blocks.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.GetAnonymousFunctionControlFlowGraph(Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation anonymousFunction, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.GetLocalFunctionControlFlowGraph(Microsoft.CodeAnalysis.IMethodSymbol localFunction, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.LocalFunctions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.OriginalOperation.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Parent.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Root.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraphExtensions` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.CaptureIds.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.EnclosingRegion.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.ExceptionType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.FirstBlockOrdinal.get -> int` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.Kind.get -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.LastBlockOrdinal.get -> int` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.LocalFunctions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegion.NestedRegions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.Catch = 4 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.ErroneousBody = 10 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.Filter = 3 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.FilterAndHandler = 5 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.Finally = 7 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.LocalLifetime = 1 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.Root = 0 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.StaticLocalInitializer = 9 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.Try = 2 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.TryAndCatch = 6 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind.TryAndFinally = 8 -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowRegionKind` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.ICaughtExceptionOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation.Symbol.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureOperation.Id.get -> Microsoft.CodeAnalysis.FlowAnalysis.CaptureId` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation.Id.get -> Microsoft.CodeAnalysis.FlowAnalysis.CaptureId` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IIsNullOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IIsNullOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IStaticLocalInitializationSemaphoreOperation` * [ ] `Microsoft.CodeAnalysis.FlowAnalysis.IStaticLocalInitializationSemaphoreOperation.Local.get -> Microsoft.CodeAnalysis.ILocalSymbol` * [ ] `Microsoft.CodeAnalysis.IFieldSymbol.IsFixedSizeBuffer.get -> bool` * [ ] `Microsoft.CodeAnalysis.ILocalSymbol.IsFixed.get -> bool` * [ ] `Microsoft.CodeAnalysis.IMethodSymbol.IsReadOnly.get -> bool` * [ ] `Microsoft.CodeAnalysis.IOperation.SemanticModel.get -> Microsoft.CodeAnalysis.SemanticModel` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.IsReadOnly.get -> bool` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.IsRefLikeType.get -> bool` * [ ] `Microsoft.CodeAnalysis.ITypeSymbol.IsUnmanagedType.get -> bool` * [ ] `Microsoft.CodeAnalysis.OperationKind.Binary = 32 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CaughtException = 94 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.CoalesceAssignment = 97 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ConstructorBody = 89 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.DiscardPattern = 104 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.FlowAnonymousFunction = 96 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.FlowCapture = 91 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.FlowCaptureReference = 92 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.IsNull = 93 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.MethodBody = 88 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Range = 99 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.RecursivePattern = 103 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ReDim = 101 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.ReDimClause = 102 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.StaticLocalInitializationSemaphore = 95 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.SwitchExpression = 105 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.SwitchExpressionArm = 106 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.TupleBinary = 87 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.OperationKind.Unary = 31 -> Microsoft.CodeAnalysis.OperationKind` * [ ] `Microsoft.CodeAnalysis.Operations.CommonConversion.IsImplicit.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ICaseClauseOperation.Label.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ICoalesceAssignmentOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ICoalesceOperation.ValueConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation.MatchedType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IDeclarationPatternOperation.MatchesNull.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IDiscardPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IEventAssignmentOperation.EventReference.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IForLoopOperation.ConditionLocals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IForToLoopOperation.IsChecked.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IInstanceReferenceOperation.ReferenceKind.get -> Microsoft.CodeAnalysis.Operations.InstanceReferenceKind` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation.ContinueLabel.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ILoopOperation.ExitLabel.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.InstanceReferenceKind` * [ ] `Microsoft.CodeAnalysis.Operations.InstanceReferenceKind.ContainingTypeInstance = 0 -> Microsoft.CodeAnalysis.Operations.InstanceReferenceKind` * [ ] `Microsoft.CodeAnalysis.Operations.InstanceReferenceKind.ImplicitReceiver = 1 -> Microsoft.CodeAnalysis.Operations.InstanceReferenceKind` * [ ] `Microsoft.CodeAnalysis.Operations.InstanceReferenceKind.PatternInput = 2 -> Microsoft.CodeAnalysis.Operations.InstanceReferenceKind` * [ ] `Microsoft.CodeAnalysis.Operations.IPatternOperation.InputType.get -> Microsoft.CodeAnalysis.ITypeSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeOperation.IsLifted.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeOperation.LeftOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeOperation.Method.get -> Microsoft.CodeAnalysis.IMethodSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IRangeOperation.RightOperand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimClauseOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimClauseOperation.DimensionSizes.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimClauseOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimOperation` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimOperation.Clauses.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IReDimOperation.Preserve.get -> bool` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchCaseOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation.Guard.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation.Pattern.get -> Microsoft.CodeAnalysis.Operations.IPatternOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation.Arms.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation.Value.get -> Microsoft.CodeAnalysis.IOperation` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchOperation.ExitLabel.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.ISwitchOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.ITryOperation.ExitLabel.get -> Microsoft.CodeAnalysis.ILabelSymbol` * [ ] `Microsoft.CodeAnalysis.Operations.IUsingOperation.Locals.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.IVariableDeclarationOperation.IgnoredDimensions.get -> System.Collections.Immutable.ImmutableArray` * [ ] `Microsoft.CodeAnalysis.Operations.UnaryOperatorKind.Hat = 7 -> Microsoft.CodeAnalysis.Operations.UnaryOperatorKind` * [ ] `Microsoft.CodeAnalysis.SpecialType.Count = 44 -> Microsoft.CodeAnalysis.SpecialType` * [ ] `Microsoft.CodeAnalysis.SpecialType.System_Runtime_CompilerServices_RuntimeFeature = 44 -> Microsoft.CodeAnalysis.SpecialType` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral = 128 -> Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier = 64 -> Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayPartKind.ConstantName = 30 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayPartKind.EnumMemberName = 28 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind` * [ ] `Microsoft.CodeAnalysis.SymbolDisplayPartKind.ExtensionMethodName = 29 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind` * [ ] `const Microsoft.CodeAnalysis.WellKnownMemberNames.SliceMethodName = "Slice" -> string` * [ ] `override Microsoft.CodeAnalysis.FlowAnalysis.CaptureId.Equals(object obj) -> bool` * [ ] `override Microsoft.CodeAnalysis.FlowAnalysis.CaptureId.GetHashCode() -> int` * [ ] `override Microsoft.CodeAnalysis.SuppressionDescriptor.Equals(object obj) -> bool` * [ ] `override Microsoft.CodeAnalysis.SuppressionDescriptor.GetHashCode() -> int` * [ ] `override sealed Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext context) -> void` * [ ] `override sealed Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.Parse(Microsoft.CodeAnalysis.Text.SourceText text, string pathToFile) -> Microsoft.CodeAnalysis.AnalyzerConfig` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.Parse(string text, string pathToFile) -> Microsoft.CodeAnalysis.AnalyzerConfig` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.ReservedKeys.get -> System.Collections.Immutable.ImmutableHashSet` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.ReservedValues.get -> System.Collections.Immutable.ImmutableHashSet` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.Section.NameComparer.get -> System.StringComparison` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.Section.PropertiesKeyComparer.get -> System.StringComparer` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfig.TryCreateSectionNameMatcher(string sectionName) -> Microsoft.CodeAnalysis.AnalyzerConfig.SectionNameMatcher?` * [ ] `static Microsoft.CodeAnalysis.AnalyzerConfigSet.Create(TList analyzerConfigs) -> Microsoft.CodeAnalysis.AnalyzerConfigSet` * [ ] `static Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions.KeyComparer.get -> System.StringComparer` * [ ] `override Microsoft.CodeAnalysis.NullabilityInfo.Equals(object other) -> bool` * [ ] `override Microsoft.CodeAnalysis.NullabilityInfo.GetHashCode() -> int` * [ ] `static Microsoft.CodeAnalysis.Diagnostics.Suppression.Create(Microsoft.CodeAnalysis.SuppressionDescriptor descriptor, Microsoft.CodeAnalysis.Diagnostic suppressedDiagnostic) -> Microsoft.CodeAnalysis.Diagnostics.Suppression` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IBlockOperation body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IConstructorBodyOperation constructorBody, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IFieldInitializerOperation initializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IMethodBodyOperation methodBody, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IParameterInitializerOperation initializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.Operations.IPropertyInitializerOperation initializer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph.Create(Microsoft.CodeAnalysis.SyntaxNode node, Microsoft.CodeAnalysis.SemanticModel semanticModel, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraphExtensions.GetAnonymousFunctionControlFlowGraphInScope(this Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph controlFlowGraph, Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation anonymousFunction, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraphExtensions.GetLocalFunctionControlFlowGraphInScope(this Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph controlFlowGraph, Microsoft.CodeAnalysis.IMethodSymbol localFunction, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.FlowAnalysis.ControlFlowGraph` * [ ] `static Microsoft.CodeAnalysis.NullableContextExtensions.AnnotationsEnabled(this Microsoft.CodeAnalysis.NullableContext context) -> bool` * [ ] `static Microsoft.CodeAnalysis.NullableContextExtensions.AnnotationsInherited(this Microsoft.CodeAnalysis.NullableContext context) -> bool` * [ ] `static Microsoft.CodeAnalysis.NullableContextExtensions.WarningsEnabled(this Microsoft.CodeAnalysis.NullableContext context) -> bool` * [ ] `static Microsoft.CodeAnalysis.NullableContextExtensions.WarningsInherited(this Microsoft.CodeAnalysis.NullableContext context) -> bool` * [ ] `static Microsoft.CodeAnalysis.NullableContextOptionsExtensions.AnnotationsEnabled(this Microsoft.CodeAnalysis.NullableContextOptions context) -> bool` * [ ] `static Microsoft.CodeAnalysis.NullableContextOptionsExtensions.WarningsEnabled(this Microsoft.CodeAnalysis.NullableContextOptions context) -> bool` * [ ] `static Microsoft.CodeAnalysis.Operations.OperationExtensions.GetCorrespondingOperation(this Microsoft.CodeAnalysis.Operations.IBranchOperation operation) -> Microsoft.CodeAnalysis.IOperation` * [ ] `static readonly Microsoft.CodeAnalysis.SymbolEqualityComparer.Default -> Microsoft.CodeAnalysis.SymbolEqualityComparer` * [ ] `static readonly Microsoft.CodeAnalysis.SymbolEqualityComparer.IncludeNullability -> Microsoft.CodeAnalysis.SymbolEqualityComparer` * [ ] `static readonly Microsoft.CodeAnalysis.SyntaxTree.EmptyDiagnosticOptions -> System.Collections.Immutable.ImmutableDictionary` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterSymbolStartAction(System.Action action, Microsoft.CodeAnalysis.SymbolKind symbolKind) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterSymbolStartAction(System.Action action, Microsoft.CodeAnalysis.SymbolKind symbolKind) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCaughtException(Microsoft.CodeAnalysis.FlowAnalysis.ICaughtExceptionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCoalesceAssignment(Microsoft.CodeAnalysis.Operations.ICoalesceAssignmentOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDiscardPattern(Microsoft.CodeAnalysis.Operations.IDiscardPatternOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowAnonymousFunction(Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCapture(Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCaptureReference(Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsNull(Microsoft.CodeAnalysis.FlowAnalysis.IIsNullOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRangeOperation(Microsoft.CodeAnalysis.Operations.IRangeOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReDim(Microsoft.CodeAnalysis.Operations.IReDimOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReDimClause(Microsoft.CodeAnalysis.Operations.IReDimClauseOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitStaticLocalInitializationSemaphore(Microsoft.CodeAnalysis.FlowAnalysis.IStaticLocalInitializationSemaphoreOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchExpression(Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchExpressionArm(Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation operation) -> void` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCaughtException(Microsoft.CodeAnalysis.FlowAnalysis.ICaughtExceptionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCoalesceAssignment(Microsoft.CodeAnalysis.Operations.ICoalesceAssignmentOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitDiscardPattern(Microsoft.CodeAnalysis.Operations.IDiscardPatternOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowAnonymousFunction(Microsoft.CodeAnalysis.FlowAnalysis.IFlowAnonymousFunctionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCapture(Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitFlowCaptureReference(Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitIsNull(Microsoft.CodeAnalysis.FlowAnalysis.IIsNullOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRangeOperation(Microsoft.CodeAnalysis.Operations.IRangeOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReDim(Microsoft.CodeAnalysis.Operations.IReDimOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitReDimClause(Microsoft.CodeAnalysis.Operations.IReDimClauseOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitStaticLocalInitializationSemaphore(Microsoft.CodeAnalysis.FlowAnalysis.IStaticLocalInitializationSemaphoreOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchExpression(Microsoft.CodeAnalysis.Operations.ISwitchExpressionOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSwitchExpressionArm(Microsoft.CodeAnalysis.Operations.ISwitchExpressionArmOperation operation, TArgument argument) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxTree.DiagnosticOptions.get -> System.Collections.Immutable.ImmutableDictionary` * [ ] `virtual Microsoft.CodeAnalysis.SyntaxTree.WithDiagnosticOptions(System.Collections.Immutable.ImmutableDictionary options) -> Microsoft.CodeAnalysis.SyntaxTree` See [Microsoft.CodeAnalysis.CSharp release/dev16.3@c955f3c99b5698c906e0700ef691b5b1571c8136](https://raw.githubusercontent.com/dotnet/roslyn/c955f3c99b5698c906e0700ef691b5b1571c8136/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt) * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode root, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Text.Encoding encoding = null) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(string text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Text.Encoding encoding = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(this string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool` * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.ParseOptions options = null, string path = "", System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `*REMOVED*static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(string text, Microsoft.CodeAnalysis.ParseOptions options = null, string path = "", System.Text.Encoding encoding = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.DelegateDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.DelegateDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.IncompleteMemberSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.IncompleteMemberSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind outputKind, bool reportSuppressedDiagnostics = false, string moduleName = null, string mainTypeName = null, string scriptClassName = null, System.Collections.Generic.IEnumerable usings = null, Microsoft.CodeAnalysis.OptimizationLevel optimizationLevel = Microsoft.CodeAnalysis.OptimizationLevel.Debug, bool checkOverflow = false, bool allowUnsafe = false, string cryptoKeyContainer = null, string cryptoKeyFile = null, System.Collections.Immutable.ImmutableArray cryptoPublicKey = default(System.Collections.Immutable.ImmutableArray), bool? delaySign = null, Microsoft.CodeAnalysis.Platform platform = Microsoft.CodeAnalysis.Platform.AnyCpu, Microsoft.CodeAnalysis.ReportDiagnostic generalDiagnosticOption = Microsoft.CodeAnalysis.ReportDiagnostic.Default, int warningLevel = 4, System.Collections.Generic.IEnumerable> specificDiagnosticOptions = null, bool concurrentBuild = true, bool deterministic = false, Microsoft.CodeAnalysis.XmlReferenceResolver xmlReferenceResolver = null, Microsoft.CodeAnalysis.SourceReferenceResolver sourceReferenceResolver = null, Microsoft.CodeAnalysis.MetadataReferenceResolver metadataReferenceResolver = null, Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer = null, Microsoft.CodeAnalysis.StrongNameProvider strongNameProvider = null, bool publicSign = false, Microsoft.CodeAnalysis.MetadataImportOptions metadataImportOptions = Microsoft.CodeAnalysis.MetadataImportOptions.Public, Microsoft.CodeAnalysis.NullableContextOptions nullableContextOptions = Microsoft.CodeAnalysis.NullableContextOptions.Disable) -> void` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.WithNullableContextOptions(Microsoft.CodeAnalysis.NullableContextOptions options) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax.SemicolonToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken eventKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.AccessorListSyntax accessorList, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken eventKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.TargetToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken hashToken, Microsoft.CodeAnalysis.SyntaxToken nullableKeyword, Microsoft.CodeAnalysis.SyntaxToken settingToken, Microsoft.CodeAnalysis.SyntaxToken targetToken, Microsoft.CodeAnalysis.SyntaxToken endOfDirectiveToken, bool isActive) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithTargetToken(Microsoft.CodeAnalysis.SyntaxToken targetToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax.IsNotNull.get -> bool` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.AnnotationsKeyword = 8489 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.WarningsKeyword = 8488 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.AwaitKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Conversion.IsSwitchExpression.get -> bool` * [ ] `Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind outputKind, bool reportSuppressedDiagnostics, string moduleName, string mainTypeName, string scriptClassName, System.Collections.Generic.IEnumerable usings, Microsoft.CodeAnalysis.OptimizationLevel optimizationLevel, bool checkOverflow, bool allowUnsafe, string cryptoKeyContainer, string cryptoKeyFile, System.Collections.Immutable.ImmutableArray cryptoPublicKey, bool? delaySign, Microsoft.CodeAnalysis.Platform platform, Microsoft.CodeAnalysis.ReportDiagnostic generalDiagnosticOption, int warningLevel, System.Collections.Generic.IEnumerable> specificDiagnosticOptions, bool concurrentBuild, bool deterministic, Microsoft.CodeAnalysis.XmlReferenceResolver xmlReferenceResolver, Microsoft.CodeAnalysis.SourceReferenceResolver sourceReferenceResolver, Microsoft.CodeAnalysis.MetadataReferenceResolver metadataReferenceResolver, Microsoft.CodeAnalysis.AssemblyIdentityComparer assemblyIdentityComparer, Microsoft.CodeAnalysis.StrongNameProvider strongNameProvider, bool publicSign, Microsoft.CodeAnalysis.MetadataImportOptions metadataImportOptions) -> void` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8 = 800 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.LatestMajor = 2147483645 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [x] `Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview = 2147483646 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.WithAsyncKeyword(Microsoft.CodeAnalysis.SyntaxToken asyncKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax.WithBody(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode body) -> Microsoft.CodeAnalysis.CSharp.Syntax.AnonymousFunctionExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseArgumentListSyntax.AddArguments(params Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseArgumentListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseArgumentListSyntax.WithArguments(Microsoft.CodeAnalysis.SeparatedSyntaxList arguments) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseArgumentListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseCrefParameterListSyntax.AddParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseCrefParameterListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseCrefParameterListSyntax.WithParameters(Microsoft.CodeAnalysis.SeparatedSyntaxList parameters) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseCrefParameterListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.AddDeclarationVariables(params Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclaratorSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.WithDeclaration(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AddBodyStatements(params Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AddParameterListParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithBody(Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax body) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithExpressionBody(Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithParameterList(Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseParameterListSyntax.AddParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseParameterListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseParameterListSyntax.WithParameters(Microsoft.CodeAnalysis.SeparatedSyntaxList parameters) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseParameterListSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.AddAccessorListAccessors(params Microsoft.CodeAnalysis.CSharp.Syntax.AccessorDeclarationSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.WithAccessorList(Microsoft.CodeAnalysis.CSharp.Syntax.AccessorListSyntax accessorList) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.WithExplicitInterfaceSpecifier(Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.AddBaseListTypes(params Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithBaseList(Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax baseList) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithCloseBraceToken(Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithOpenBraceToken(Microsoft.CodeAnalysis.SyntaxToken openBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BranchingDirectiveTriviaSyntax.WithEndOfDirectiveToken(Microsoft.CodeAnalysis.SyntaxToken endOfDirectiveToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BranchingDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.BranchingDirectiveTriviaSyntax.WithHashToken(Microsoft.CodeAnalysis.SyntaxToken hashToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BranchingDirectiveTriviaSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax.QuestionToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword, Microsoft.CodeAnalysis.SyntaxToken questionToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax.WithQuestionToken(Microsoft.CodeAnalysis.SyntaxToken questionToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithAwaitKeyword(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithForEachKeyword(Microsoft.CodeAnalysis.SyntaxToken forEachKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithInKeyword(Microsoft.CodeAnalysis.SyntaxToken inKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax.WithStatement(Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.ConditionalDirectiveTriviaSyntax.WithCondition(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax condition) -> Microsoft.CodeAnalysis.CSharp.Syntax.ConditionalDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.DirectiveTriviaSyntax.WithEndOfDirectiveToken(Microsoft.CodeAnalysis.SyntaxToken endOfDirectiveToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.DirectiveTriviaSyntax.WithHashToken(Microsoft.CodeAnalysis.SyntaxToken hashToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DirectiveTriviaSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.UnderscoreToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.WithUnderscoreToken(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.WithAwaitKeyword(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.WithAwaitKeyword(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.WithArrowToken(Microsoft.CodeAnalysis.SyntaxToken arrowToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.WithAsyncKeyword(Microsoft.CodeAnalysis.SyntaxToken asyncKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax.WithBody(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode body) -> Microsoft.CodeAnalysis.CSharp.Syntax.LambdaExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax.AwaitKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax.UsingKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax.WithAwaitKeyword(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax.WithUsingKeyword(Microsoft.CodeAnalysis.SyntaxToken usingKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.NullableKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.SettingToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithEndOfDirectiveToken(Microsoft.CodeAnalysis.SyntaxToken endOfDirectiveToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithHashToken(Microsoft.CodeAnalysis.SyntaxToken hashToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithIsActive(bool isActive) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithNullableKeyword(Microsoft.CodeAnalysis.SyntaxToken nullableKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.WithSettingToken(Microsoft.CodeAnalysis.SyntaxToken settingToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.AddSubpatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.CloseParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.OpenParenToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.Subpatterns.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.WithCloseParenToken(Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.WithOpenParenToken(Microsoft.CodeAnalysis.SyntaxToken openParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.WithSubpatterns(Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.AddSubpatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.CloseBraceToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.OpenBraceToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.Subpatterns.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.WithCloseBraceToken(Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.WithOpenBraceToken(Microsoft.CodeAnalysis.SyntaxToken openBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.WithSubpatterns(Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.LeftOperand.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.OperatorToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.RightOperand.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax leftOperand, Microsoft.CodeAnalysis.SyntaxToken operatorToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax rightOperand) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.WithLeftOperand(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax leftOperand) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.WithOperatorToken(Microsoft.CodeAnalysis.SyntaxToken operatorToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.WithRightOperand(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax rightOperand) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.AddPositionalPatternClauseSubpatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.AddPropertyPatternClauseSubpatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.Designation.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.PositionalPatternClause.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.PropertyPatternClause.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax positionalPatternClause, Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax propertyPatternClause, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.WithDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.WithPositionalPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax positionalPatternClause) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.WithPropertyPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax propertyPatternClause) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.SimpleNameSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleNameSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.NameColon.get -> Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.Pattern.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.WithNameColon(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.WithPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.EqualsGreaterThanToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.Pattern.get -> Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken equalsGreaterThanToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.WhenClause.get -> Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.WithEqualsGreaterThanToken(Microsoft.CodeAnalysis.SyntaxToken equalsGreaterThanToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.WithPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.WithWhenClause(Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.AddArms(params Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.Arms.get -> Microsoft.CodeAnalysis.SeparatedSyntaxList` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.CloseBraceToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.GoverningExpression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.OpenBraceToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.SwitchKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax governingExpression, Microsoft.CodeAnalysis.SyntaxToken switchKeyword, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SeparatedSyntaxList arms, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.WithArms(Microsoft.CodeAnalysis.SeparatedSyntaxList arms) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.WithCloseBraceToken(Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.WithGoverningExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax governingExpression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.WithOpenBraceToken(Microsoft.CodeAnalysis.SyntaxToken openBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.WithSwitchKeyword(Microsoft.CodeAnalysis.SyntaxToken switchKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchLabelSyntax.WithColonToken(Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchLabelSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.SwitchLabelSyntax.WithKeyword(Microsoft.CodeAnalysis.SyntaxToken keyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchLabelSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddBaseListTypes(params Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseTypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddConstraintClauses(params Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddMembers(params Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.AddTypeParameterListParameters(params Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithBaseList(Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax baseList) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithCloseBraceToken(Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithConstraintClauses(Microsoft.CodeAnalysis.SyntaxList constraintClauses) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithKeyword(Microsoft.CodeAnalysis.SyntaxToken keyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithMembers(Microsoft.CodeAnalysis.SyntaxList members) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithOpenBraceToken(Microsoft.CodeAnalysis.SyntaxToken openBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithSemicolonToken(Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax.WithTypeParameterList(Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList) -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax.AwaitKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax.WithAwaitKeyword(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.Designation.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax` * [x] ~~`Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken varKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax`~~ * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.VarKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.WithDesignation(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.WithVarKeyword(Microsoft.CodeAnalysis.SyntaxToken varKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax.WithEndQuoteToken(Microsoft.CodeAnalysis.SyntaxToken endQuoteToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax.WithEqualsToken(Microsoft.CodeAnalysis.SyntaxToken equalsToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax.WithName(Microsoft.CodeAnalysis.CSharp.Syntax.XmlNameSyntax name) -> Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax.WithStartQuoteToken(Microsoft.CodeAnalysis.SyntaxToken startQuoteToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.XmlAttributeSyntax` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.CoalesceAssignmentExpression = 8725 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DiscardPattern = 9024 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.DotDotToken = 8222 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.EnableKeyword = 8487 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.IndexExpression = 8741 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.NullableDirectiveTrivia = 9055 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.NullableKeyword = 8486 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.PositionalPatternClause = 9023 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.PropertyPatternClause = 9021 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.QuestionQuestionEqualsToken = 8284 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.RangeExpression = 8658 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.RecursivePattern = 9020 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.Subpattern = 9022 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.SuppressNullableWarningExpression = 9054 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.SwitchExpression = 9025 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.SwitchExpressionArm = 9026 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.VarKeyword = 8490 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [x] `Microsoft.CodeAnalysis.CSharp.SyntaxKind.VarPattern = 9027 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpCompilation.ClassifyCommonConversion(Microsoft.CodeAnalysis.ITypeSymbol source, Microsoft.CodeAnalysis.ITypeSymbol destination) -> Microsoft.CodeAnalysis.Operations.CommonConversion` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpCompilation.ContainsSymbolsWithName(string name, Microsoft.CodeAnalysis.SymbolFilter filter = Microsoft.CodeAnalysis.SymbolFilter.TypeAndMember, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> bool` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSymbolsWithName(string name, Microsoft.CodeAnalysis.SymbolFilter filter = Microsoft.CodeAnalysis.SymbolFilter.TypeAndMember, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Generic.IEnumerable` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions.NullableContextOptions.get -> Microsoft.CodeAnalysis.NullableContextOptions` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitNullableDirectiveTrivia(Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitPositionalPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitPropertyPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitRangeExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitRecursivePattern(Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitSubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitSwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitSwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitVarPattern(Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax.AwaitKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [x] `override Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax.AwaitKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.EndOfDirectiveToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.HashToken.get -> Microsoft.CodeAnalysis.SyntaxToken` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax.IsActive.get -> bool` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode root, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Text.Encoding encoding = null, System.Collections.Immutable.ImmutableDictionary diagnosticOptions = null) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.Create(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode root, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options, string path, System.Text.Encoding encoding) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Collections.Immutable.ImmutableDictionary diagnosticOptions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options, string path, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(string text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options, string path, System.Text.Encoding encoding, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(string text, Microsoft.CodeAnalysis.CSharp.CSharpParseOptions options = null, string path = "", System.Text.Encoding encoding = null, System.Collections.Immutable.ImmutableDictionary diagnosticOptions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayParts(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableAnnotation nullableAnnotation, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayParts(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableFlowState nullableFlowState, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableAnnotation nullableAnnotation, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToDisplayString(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableFlowState nullableFlowState, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToMinimalDisplayParts(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableAnnotation nullableAnnotation, Microsoft.CodeAnalysis.SemanticModel semanticModel, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToMinimalDisplayParts(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableFlowState nullableFlowState, Microsoft.CodeAnalysis.SemanticModel semanticModel, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> System.Collections.Immutable.ImmutableArray` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToMinimalDisplayString(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableAnnotation nullableAnnotation, Microsoft.CodeAnalysis.SemanticModel semanticModel, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.SymbolDisplay.ToMinimalDisplayString(Microsoft.CodeAnalysis.ITypeSymbol symbol, Microsoft.CodeAnalysis.NullableFlowState nullableFlowState, Microsoft.CodeAnalysis.SemanticModel semanticModel, int position, Microsoft.CodeAnalysis.SymbolDisplayFormat format = null) -> string` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ClassOrStructConstraint(Microsoft.CodeAnalysis.CSharp.SyntaxKind kind, Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword, Microsoft.CodeAnalysis.SyntaxToken questionToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ClassOrStructConstraintSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DiscardPattern() -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DiscardPattern(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.EventDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken eventKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.AccessorListSyntax accessorList, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.EventDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken eventKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.EventDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ForEachStatement(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ForEachVariableStatement(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken forEachKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax variable, Microsoft.CodeAnalysis.SyntaxToken inKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.LocalDeclarationStatement(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.LocalDeclarationStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.NullableDirectiveTrivia(Microsoft.CodeAnalysis.SyntaxToken hashToken, Microsoft.CodeAnalysis.SyntaxToken nullableKeyword, Microsoft.CodeAnalysis.SyntaxToken settingToken, Microsoft.CodeAnalysis.SyntaxToken targetToken, Microsoft.CodeAnalysis.SyntaxToken endOfDirectiveToken, bool isActive) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.NullableDirectiveTrivia(Microsoft.CodeAnalysis.SyntaxToken settingToken, Microsoft.CodeAnalysis.SyntaxToken targetToken, bool isActive) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.NullableDirectiveTrivia(Microsoft.CodeAnalysis.SyntaxToken settingToken, bool isActive) -> Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseMemberDeclaration(string text, int offset = 0, Microsoft.CodeAnalysis.ParseOptions options = null, bool consumeFullText = true) -> Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.ParseOptions options = null, string path = "", System.Collections.Immutable.ImmutableDictionary diagnosticOptions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(Microsoft.CodeAnalysis.Text.SourceText text, Microsoft.CodeAnalysis.ParseOptions options, string path, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(string text, Microsoft.CodeAnalysis.ParseOptions options = null, string path = "", System.Text.Encoding encoding = null, System.Collections.Immutable.ImmutableDictionary diagnosticOptions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.SyntaxTree` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParseSyntaxTree(string text, Microsoft.CodeAnalysis.ParseOptions options, string path, System.Text.Encoding encoding, System.Threading.CancellationToken cancellationToken) -> Microsoft.CodeAnalysis.SyntaxTree` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PositionalPatternClause(Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PositionalPatternClause(Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns, Microsoft.CodeAnalysis.SyntaxToken closeParenToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PropertyPatternClause(Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [x] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PropertyPatternClause(Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SeparatedSyntaxList subpatterns, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RangeExpression() -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RangeExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax leftOperand, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax rightOperand) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RangeExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax leftOperand, Microsoft.CodeAnalysis.SyntaxToken operatorToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax rightOperand) -> Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecursivePattern() -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecursivePattern(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax positionalPatternClause, Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax propertyPatternClause, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Subpattern(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Subpattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax governingExpression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax governingExpression, Microsoft.CodeAnalysis.SeparatedSyntaxList arms) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax governingExpression, Microsoft.CodeAnalysis.SyntaxToken switchKeyword, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SeparatedSyntaxList arms, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken equalsGreaterThanToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.UsingStatement(Microsoft.CodeAnalysis.SyntaxToken awaitKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.VarPattern(Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.VarPattern(Microsoft.CodeAnalysis.SyntaxToken varKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitNullableDirectiveTrivia(Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPositionalPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPropertyPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRangeExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRecursivePattern(Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitVarPattern(Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax node) -> void` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitNullableDirectiveTrivia(Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPositionalPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPropertyPatternClause(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRangeExpression(Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRecursivePattern(Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSwitchExpression(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSwitchExpressionArm(Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax node) -> TResult` * [ ] `virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitVarPattern(Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax node) -> TResult` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.EqualsValueClauseSyntax equalsValue) -> Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.AddAttributeLists(params Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.AddModifiers(params Microsoft.CodeAnalysis.SyntaxToken[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken namespaceKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SyntaxList externs, Microsoft.CodeAnalysis.SyntaxList usings, Microsoft.CodeAnalysis.SyntaxList members, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.WithModifiers(Microsoft.CodeAnalysis.SyntaxTokenList modifiers) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DelegateDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.DelegateDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `abstract Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.IncompleteMemberSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.IncompleteMemberSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseFieldDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseMethodDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override abstract Microsoft.CodeAnalysis.CSharp.Syntax.BasePropertyDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.EnumMemberDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.EqualsValueClauseSyntax equalsValue) -> Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.GlobalStatement(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.StatementSyntax statement) -> Microsoft.CodeAnalysis.CSharp.Syntax.GlobalStatementSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.NamespaceDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxList externs, Microsoft.CodeAnalysis.SyntaxList usings, Microsoft.CodeAnalysis.SyntaxList members) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.NamespaceDeclaration(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken namespaceKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken openBraceToken, Microsoft.CodeAnalysis.SyntaxList externs, Microsoft.CodeAnalysis.SyntaxList usings, Microsoft.CodeAnalysis.SyntaxList members, Microsoft.CodeAnalysis.SyntaxToken closeBraceToken, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList` * [ ] `override Microsoft.CodeAnalysis.CSharp.Syntax.EnumMemberDeclarationSyntax.Modifiers.get -> Microsoft.CodeAnalysis.SyntaxTokenList` ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/IFieldSymbolExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; public static class IFieldSymbolExtensions { private static readonly Func CorrespondingTupleFieldAccessor; static IFieldSymbolExtensions() { CorrespondingTupleFieldAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IFieldSymbol), nameof(CorrespondingTupleField)); } public static IFieldSymbol CorrespondingTupleField(this IFieldSymbol symbol) { return CorrespondingTupleFieldAccessor(symbol); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/INamedTypeSymbolExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace StyleCop.Analyzers.Lightup; public static class INamedTypeSymbolExtensions { private static readonly Func TupleUnderlyingTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(INamedTypeSymbol), "TupleUnderlyingType"); private static readonly Func> TupleElementsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(typeof(INamedTypeSymbol), "TupleElements"); private static readonly Func IsSerializableAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(INamedTypeSymbol), "IsSerializable"); private static readonly Func IsExtensionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(INamedTypeSymbol), "IsExtension"); extension(INamedTypeSymbol symbol) { public INamedTypeSymbol TupleUnderlyingType => TupleUnderlyingTypeAccessor(symbol); public ImmutableArray TupleElements => TupleElementsAccessor(symbol); public bool IsSerializable => IsSerializableAccessor(symbol); public bool IsExtension => IsExtensionAccessor(symbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/ISyntaxWrapper`1.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using Microsoft.CodeAnalysis; /// /// Represents a light-up wrapper for a type derived from a known back syntax kind . /// /// The base syntax kind which is exposed in the referenced API. public interface ISyntaxWrapper where T : SyntaxNode { /// /// Gets the wrapped syntax node. /// /// /// The wrapped syntax node. /// T SyntaxNode { get; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/ITypeParameterSymbolExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; public static class ITypeParameterSymbolExtensions { private static readonly Func HasUnmanagedTypeConstraintAccessor; static ITypeParameterSymbolExtensions() { HasUnmanagedTypeConstraintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(ITypeParameterSymbol), nameof(HasUnmanagedTypeConstraint)); } public static bool HasUnmanagedTypeConstraint(this ITypeParameterSymbol symbol) { return HasUnmanagedTypeConstraintAccessor(symbol); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/ITypeSymbolExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; public static partial class ITypeSymbolExtensions { private static readonly Func IsTupleTypeAccessor; static ITypeSymbolExtensions() { IsTupleTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(ITypeSymbol), nameof(IsTupleType)); } public static bool IsTupleType(this ITypeSymbol symbol) { return IsTupleTypeAccessor(symbol); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/LICENSE ================================================ MIT License Copyright (c) Tunnel Vision Laboratories, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/LightupHelpers.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Roslyn.Utilities; public static class LightupHelpers { private static readonly ConcurrentDictionary> SupportedObjectWrappers = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SupportedSyntaxWrappers = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> SupportedOperationWrappers = new ConcurrentDictionary>(); public static bool SupportsCSharp7 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7)); public static bool SupportsCSharp71 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_1)); public static bool SupportsCSharp72 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_2)); public static bool SupportsCSharp73 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp7_3)); public static bool SupportsCSharp8 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp8)); public static bool SupportsCSharp9 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp9)); public static bool SupportsCSharp10 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp10)); public static bool SupportsCSharp11 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp11)); public static bool SupportsCSharp12 { get; } = Enum.GetNames(typeof(LanguageVersion)).Contains(nameof(LanguageVersionEx.CSharp12)); public static bool SupportsIOperation => SupportsCSharp73; [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapObject(object obj, Type underlyingType) { if (obj == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedObject = SupportedObjectWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class if (!wrappedObject.TryGetValue(obj.GetType(), out var canCast)) { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(obj.GetType().GetTypeInfo()); wrappedObject.TryAdd(obj.GetType(), canCast); } return canCast; } [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapNode(SyntaxNode node, Type underlyingType) { if (node == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedSyntax = SupportedSyntaxWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class if (!wrappedSyntax.TryGetValue(node.Kind(), out var canCast)) { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(node.GetType().GetTypeInfo()); wrappedSyntax.TryAdd(node.Kind(), canCast); } return canCast; } [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8106", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] // Sonar internal static bool CanWrapOperation(IOperation operation, Type underlyingType) { if (operation == null) { // The wrappers support a null instance return true; } if (underlyingType == null) { // The current runtime doesn't define the target type of the conversion, so no instance of it can exist return false; } ConcurrentDictionary wrappedSyntax = SupportedOperationWrappers.GetOrAdd(underlyingType, static _ => new ConcurrentDictionary()); // Avoid creating a delegate and capture class // Sonar: https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.operationkind Loop && CaseClause are further differentiated by LoopKind & CaseKind, but are not castable between different Kinds which can result in InvalidCast Exceptions if (!wrappedSyntax.TryGetValue(operation.Kind, out var canCast) || operation.Kind is OperationKindEx.Loop or OperationKindEx.CaseClause) // Sonar { canCast = underlyingType.GetTypeInfo().IsAssignableFrom(operation.GetType().GetTypeInfo()); wrappedSyntax.TryAdd(operation.Kind, canCast); } return canCast; } internal static Func CreateOperationPropertyAccessor(Type type, string propertyName) { TProperty FallbackAccessor(TOperation syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TOperation).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } var operationParameter = Expression.Parameter(typeof(TOperation), "operation"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TOperation).GetTypeInfo()) ? (Expression)operationParameter : Expression.Convert(operationParameter, type); if (property.PropertyType.FullName == "Microsoft.CodeAnalysis.FlowAnalysis.CaptureId") // Sonar - begin { var constructor = typeof(CaptureId).GetConstructors().Single(); Expression> expression = Expression.Lambda>( Expression.New(constructor, Expression.Convert(Expression.Call(instance, property.GetMethod), typeof(object))), operationParameter); return expression.Compile(); } else if (typeof(TProperty).IsEnum && property.PropertyType.IsEnum) { Expression> expression = Expression.Lambda>( Expression.Convert(Expression.Call(instance, property.GetMethod), typeof(TProperty)), operationParameter); return expression.Compile(); } else if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } else { Expression> expression = Expression.Lambda>( Expression.Call(instance, property.GetMethod), operationParameter); return expression.Compile(); } // Sonar - end } internal static Func> CreateOperationListPropertyAccessor(Type type, string propertyName) { ImmutableArray FallbackAccessor(TOperation syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return ImmutableArray.Empty; } if (type == null) { return FallbackAccessor; } if (!typeof(TOperation).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(ImmutableArray<>)) { throw new InvalidOperationException(); } var propertyOperationType = property.PropertyType.GenericTypeArguments[0]; if (!typeof(IOperation).GetTypeInfo().IsAssignableFrom(propertyOperationType.GetTypeInfo())) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TOperation), "syntax"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TOperation).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression propertyAccess = Expression.Call(instance, property.GetMethod); var unboundCastUpMethod = typeof(ImmutableArray).GetTypeInfo().GetDeclaredMethod(nameof(ImmutableArray.CastUp)); var boundCastUpMethod = unboundCastUpMethod.MakeGenericMethod(propertyOperationType); Expression>> expression = Expression.Lambda>>( Expression.Call(boundCastUpMethod, propertyAccess), syntaxParameter); return expression.Compile(); } internal static Func CreateStaticPropertyAccessor(Type type, string propertyName) { static TProperty FallbackAccessor() { return default; } if (type == null) { return FallbackAccessor; } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } Expression> expression = Expression.Lambda>( Expression.Call(null, property.GetMethod)); return expression.Compile(); } public static Func CreateSyntaxPropertyAccessor(Type type, string propertyName) { TProperty FallbackAccessor(TSyntax syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); // Sonar - begin Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression body = Expression.Call(instance, property.GetMethod); if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { body = Expression.Convert(body, typeof(TProperty)); } return Expression.Lambda>(body, syntaxParameter).Compile(); // Sonar - end } internal static Func CreateSyntaxPropertyAccessor(Type type, Type argumentType, string accessorMethodName) { static TProperty FallbackAccessor(TSyntax syntax, TArg argument) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return default; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TArg).GetTypeInfo().IsAssignableFrom(argumentType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(accessorMethodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 1) { continue; } if (Equals(argumentType, parameters[0].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo())) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var argParameter = Expression.Parameter(typeof(TArg), "arg"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression argument = argumentType.GetTypeInfo().IsAssignableFrom(typeof(TArg).GetTypeInfo()) ? (Expression)argParameter : Expression.Convert(argParameter, argumentType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, argument), syntaxParameter, argParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type keyType, string methodName) { static bool FallbackAccessor(TSyntax syntax, TKey key, out TValue value) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TKey).GetTypeInfo().IsAssignableFrom(keyType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 2) { continue; } if (Equals(keyType, parameters[0].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[1].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var keyParameter = Expression.Parameter(typeof(TKey), "key"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression key = keyType.GetTypeInfo().IsAssignableFrom(typeof(TKey).GetTypeInfo()) ? (Expression)keyParameter : Expression.Convert(keyParameter, keyType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, key, valueParameter), syntaxParameter, keyParameter, valueParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type firstType, Type secondType, string methodName) // Sonar { static bool FallbackAccessor(TSender sender, TFirst first, TSecond second, out TValue value) { if (sender == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSender).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TFirst).GetTypeInfo().IsAssignableFrom(firstType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TSecond).GetTypeInfo().IsAssignableFrom(secondType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 4) { continue; } if (Equals(firstType, parameters[0].ParameterType) && Equals(secondType, parameters[1].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[2].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var senderParameter = Expression.Parameter(typeof(TSender), "sender"); var firstParameter = Expression.Parameter(typeof(TFirst), "first"); var secondParameter = Expression.Parameter(typeof(TSecond), "second"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSender).GetTypeInfo()) ? (Expression)senderParameter : Expression.Convert(senderParameter, type); Expression first = firstType.GetTypeInfo().IsAssignableFrom(typeof(TFirst).GetTypeInfo()) ? (Expression)firstParameter : Expression.Convert(firstParameter, firstType); Expression second = secondType.GetTypeInfo().IsAssignableFrom(typeof(TSecond).GetTypeInfo()) ? (Expression)secondParameter : Expression.Convert(secondParameter, secondType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, first, second, valueParameter), senderParameter, firstParameter, secondParameter, valueParameter); return expression.Compile(); } internal static TryGetValueAccessor CreateTryGetValueAccessor(Type type, Type firstType, Type secondType, Type thirdType, string methodName) // Sonar { static bool FallbackAccessor(TSender sender, TFirst first, TSecond second, TThird third, out TValue value) { if (sender == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } value = default; return false; } if (type == null) { return FallbackAccessor; } if (!typeof(TSender).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TFirst).GetTypeInfo().IsAssignableFrom(firstType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TSecond).GetTypeInfo().IsAssignableFrom(secondType.GetTypeInfo())) { throw new InvalidOperationException(); } if (!typeof(TThird).GetTypeInfo().IsAssignableFrom(thirdType.GetTypeInfo())) { throw new InvalidOperationException(); } var methods = type.GetTypeInfo().GetDeclaredMethods(methodName); MethodInfo method = null; foreach (var candidate in methods) { var parameters = candidate.GetParameters(); if (parameters.Length != 4) { continue; } if (Equals(firstType, parameters[0].ParameterType) && Equals(secondType, parameters[1].ParameterType) && Equals(thirdType, parameters[2].ParameterType) && Equals(typeof(TValue).MakeByRefType(), parameters[3].ParameterType)) { method = candidate; break; } } if (method == null) { return FallbackAccessor; } if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException(); } var senderParameter = Expression.Parameter(typeof(TSender), "sender"); var firstParameter = Expression.Parameter(typeof(TFirst), "first"); var secondParameter = Expression.Parameter(typeof(TSecond), "second"); var thirdParameter = Expression.Parameter(typeof(TThird), "third"); var valueParameter = Expression.Parameter(typeof(TValue).MakeByRefType(), "value"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSender).GetTypeInfo()) ? (Expression)senderParameter : Expression.Convert(senderParameter, type); Expression first = firstType.GetTypeInfo().IsAssignableFrom(typeof(TFirst).GetTypeInfo()) ? (Expression)firstParameter : Expression.Convert(firstParameter, firstType); Expression second = secondType.GetTypeInfo().IsAssignableFrom(typeof(TSecond).GetTypeInfo()) ? (Expression)secondParameter : Expression.Convert(secondParameter, secondType); Expression third = thirdType.GetTypeInfo().IsAssignableFrom(typeof(TThird).GetTypeInfo()) ? (Expression)thirdParameter : Expression.Convert(thirdParameter, thirdType); Expression> expression = Expression.Lambda>( Expression.Call(instance, method, first, second, third, valueParameter), senderParameter, firstParameter, secondParameter, thirdParameter, valueParameter); return expression.Compile(); } internal static Func> CreateSeparatedSyntaxListPropertyAccessor(Type type, string propertyName) { SeparatedSyntaxListWrapper FallbackAccessor(TSyntax syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return SeparatedSyntaxListWrapper.UnsupportedEmpty; } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>)) { throw new InvalidOperationException(); } var propertySyntaxType = property.PropertyType.GenericTypeArguments[0]; if (!ValidatePropertyType(typeof(TProperty), propertySyntaxType)) { throw new InvalidOperationException(); } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression propertyAccess = Expression.Call(instance, property.GetMethod); var unboundWrapperType = typeof(SeparatedSyntaxListWrapper<>.AutoWrapSeparatedSyntaxList<>); var boundWrapperType = unboundWrapperType.MakeGenericType(typeof(TProperty), propertySyntaxType); var constructorInfo = boundWrapperType.GetTypeInfo().DeclaredConstructors.Single(constructor => constructor.GetParameters().Length == 1); Expression>> expression = Expression.Lambda>>( Expression.New(constructorInfo, propertyAccess), syntaxParameter); return expression.Compile(); } internal static Func CreateSyntaxWithPropertyAccessor(Type type, string propertyName) { TSyntax FallbackAccessor(TSyntax syntax, TProperty newValue) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } if (Equals(newValue, default(TProperty))) { return syntax; } throw new NotSupportedException(); } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (!typeof(TProperty).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo())) { throw new InvalidOperationException(); } var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName) .SingleOrDefault(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType)); if (methodInfo is null) { return FallbackAccessor; } var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var valueParameter = Expression.Parameter(typeof(TProperty), methodInfo.GetParameters()[0].Name); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); Expression value = property.PropertyType.GetTypeInfo().IsAssignableFrom(typeof(TProperty).GetTypeInfo()) ? (Expression)valueParameter : Expression.Convert(valueParameter, property.PropertyType); Expression> expression = Expression.Lambda>( Expression.Call(instance, methodInfo, value), syntaxParameter, valueParameter); return expression.Compile(); } internal static Func, TSyntax> CreateSeparatedSyntaxListWithPropertyAccessor(Type type, string propertyName) { TSyntax FallbackAccessor(TSyntax syntax, SeparatedSyntaxListWrapper newValue) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } if (newValue is null) { return syntax; } throw new NotSupportedException(); } if (type == null) { return FallbackAccessor; } if (!typeof(TSyntax).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { throw new InvalidOperationException(); } var property = type.GetTypeInfo().GetDeclaredProperty(propertyName); if (property == null) { return FallbackAccessor; } if (property.PropertyType.GetGenericTypeDefinition() != typeof(SeparatedSyntaxList<>)) { throw new InvalidOperationException(); } var propertySyntaxType = property.PropertyType.GenericTypeArguments[0]; if (!ValidatePropertyType(typeof(TProperty), propertySyntaxType)) { throw new InvalidOperationException(); } var methodInfo = type.GetTypeInfo().GetDeclaredMethods("With" + propertyName) .Single(m => !m.IsStatic && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Equals(property.PropertyType)); var syntaxParameter = Expression.Parameter(typeof(TSyntax), "syntax"); var valueParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), methodInfo.GetParameters()[0].Name); Expression instance = type.GetTypeInfo().IsAssignableFrom(typeof(TSyntax).GetTypeInfo()) ? (Expression)syntaxParameter : Expression.Convert(syntaxParameter, type); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression value = Expression.Convert( Expression.Call(valueParameter, underlyingListProperty.GetMethod), property.PropertyType); Expression, TSyntax>> expression = Expression.Lambda, TSyntax>>( Expression.Call(instance, methodInfo, value), syntaxParameter, valueParameter); return expression.Compile(); } private static bool ValidatePropertyType(Type returnType, Type actualType) { var requiredType = SyntaxWrapperHelper.GetWrappedType(returnType) ?? returnType; return requiredType == actualType; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/OperationInterfaces.xml ================================================  Represents an invalid operation with one or more child operations. Current usage: C# invalid expression or invalid statement VB invalid expression or invalid statement Represents a block containing a sequence of operations and local declarations. Current usage: C# "{ ... }" block statement VB implicit block statement for method bodies and other block scoped statements Operations contained within the block. Local declarations contained within the block. Represents a variable declaration statement. Current Usage: C# local declaration statement C# fixed statement C# using statement C# using declaration VB Dim statement VB Using statement Variable declaration in the statement. In C#, this will always be a single declaration, with all variables in . Represents a switch operation with a value to be switched upon and switch cases. Current usage: C# switch statement VB Select Case statement Locals declared within the switch operation with scope spanning across all . Value to be switched upon. Cases of the switch. Exit label for the switch statement. Represents a loop operation. Current usage: C# 'while', 'for', 'foreach' and 'do' loop statements VB 'While', 'ForTo', 'ForEach', 'Do While' and 'Do Until' loop statements Kind of the loop. Body of the loop. Declared locals. Loop continue label. Loop exit/break label. Represents a for each loop. Current usage: C# 'foreach' loop statement VB 'For Each' loop statement Refers to the operation for declaring a new local variable or reference an existing variable or an expression. Collection value over which the loop iterates. Optional list of comma separated next variables at loop bottom in VB. This list is always empty for C#. Whether this for each loop is asynchronous. Always false for VB. Represents a for loop. Current usage: C# 'for' loop statement List of operations to execute before entry to the loop. For C#, this comes from the first clause of the for statement. Locals declared within the loop Condition and are in scope throughout the , and . They are considered to be declared per iteration. Condition of the loop. For C#, this comes from the second clause of the for statement. List of operations to execute at the bottom of the loop. For C#, this comes from the third clause of the for statement. Represents a for to loop with loop control variable and initial, limit and step values for the control variable. Current usage: VB 'For ... To ... Step' loop statement Refers to the operation for declaring a new local variable or reference an existing variable or an expression. Operation for setting the initial value of the loop control variable. This comes from the expression between the 'For' and 'To' keywords. Operation for the limit value of the loop control variable. This comes from the expression after the 'To' keyword. Operation for the step value of the loop control variable. This comes from the expression after the 'Step' keyword, or inferred by the compiler if 'Step' clause is omitted. if arithmetic operations behind this loop are 'checked'. Optional list of comma separated next variables at loop bottom. Represents a while or do while loop. Current usage: C# 'while' and 'do while' loop statements VB 'While', 'Do While' and 'Do Until' loop statements Condition of the loop. This can only be null in error scenarios. True if the is evaluated at start of each loop iteration. False if it is evaluated at the end of each loop iteration. True if the loop has 'Until' loop semantics and the loop is executed while is false. Additional conditional supplied for loop in error cases, which is ignored by the compiler. For example, for VB 'Do While' or 'Do Until' loop with syntax errors where both the top and bottom conditions are provided. The top condition is preferred and exposed as and the bottom condition is ignored and exposed by this property. This property should be null for all non-error cases. Represents an operation with a label. Current usage: C# labeled statement VB label statement Label that can be the target of branches. Operation that has been labeled. In VB, this is always null. Represents a branch operation. Current usage: C# goto, break, or continue statement VB GoTo, Exit ***, or Continue *** statement Label that is the target of the branch. Kind of the branch. Represents an empty or no-op operation. Current usage: C# empty statement Represents a return from the method with an optional return value. Current usage: C# return statement and yield statement VB Return statement Value to be returned. Represents a of operations that are executed while holding a lock onto the . Current usage: C# lock statement VB SyncLock statement Operation producing a value to be locked. Body of the lock, to be executed while holding the lock. Represents a try operation for exception handling code with a body, catch clauses and a finally handler. Current usage: C# try statement VB Try statement Body of the try, over which the handlers are active. Catch clauses of the try. Finally handler of the try. Exit label for the try. This will always be null for C#. Represents a of operations that are executed while using disposable . Current usage: C# using statement VB Using statement Declaration introduced or resource held by the using. Body of the using, over which the resources of the using are maintained. Locals declared within the with scope spanning across this entire . Whether this using is asynchronous. Always false for VB. Information about the method that will be invoked to dispose the when pattern based disposal is used. Represents an operation that drops the resulting value and the type of the underlying wrapped . Current usage: C# expression statement VB expression statement Underlying operation with a value and type. Represents a local function defined within a method. Current usage: C# local function statement Local function symbol. Body of the local function. This can be null in error scenarios, or when the method is an extern method. An extra body for the local function, if both a block body and expression body are specified in source. This is only ever non-null in error situations. Represents an operation to stop or suspend execution of code. Current usage: VB Stop statement Represents an operation that stops the execution of code abruptly. Current usage: VB End Statement Represents an operation for raising an event. Current usage: VB raise event statement Reference to the event to be raised. Arguments of the invocation, excluding the instance argument. Arguments are in evaluation order. If the invocation is in its expanded form, then params/ParamArray arguments would be collected into arrays. Default values are supplied for optional arguments missing in source. Represents a textual literal numeric, string, etc. Current usage: C# literal expression VB literal expression Represents a type conversion. Current usage: C# conversion expression VB conversion expression Value to be converted. Operator method used by the operation, null if the operation does not use an operator method. Type parameter which runtime type will be used to resolve virtual invocation of the , if any. Null if is resolved statically, or is null. Gets the underlying common conversion information. If you need conversion information that is language specific, use either or . False if the conversion will fail with a at runtime if the cast fails. This is true for C#'s as operator and for VB's TryCast operator. True if the conversion can fail at runtime with an overflow exception. This corresponds to C# checked and unchecked blocks. Represents an invocation of a method. Current usage: C# method invocation expression C# collection element initializer. For example, in the following collection initializer: new C() { 1, 2, 3 }, we will have 3 nodes, each of which will be a call to the corresponding Add method with either 1, 2, 3 as the argument VB method invocation expression VB collection element initializer. Similar to the C# example, New C() From {1, 2, 3} will have 3 nodes with 1, 2, and 3 as their arguments, respectively Method to be invoked. Type parameter which runtime type will be used to resolve virtual invocation of the . Null if is resolved statically, or is an instance method. 'This' or 'Me' instance to be supplied to the method, or null if the method is static. True if the invocation uses a virtual mechanism, and false otherwise. Arguments of the invocation, excluding the instance argument. Arguments are in evaluation order. If the invocation is in its expanded form, then params/ParamArray arguments would be collected into arrays. Default values are supplied for optional arguments missing in source. Represents a reference to an array element. Current usage: C# array element reference expression VB array element reference expression Array to be indexed. Indices that specify an individual element. Represents a reference to a declared local variable. Current usage: C# local reference expression VB local reference expression Referenced local variable. True if this reference is also the declaration site of this variable. This is true in out variable declarations and in deconstruction operations where a new variable is being declared. Represents a reference to a parameter. Current usage: C# parameter reference expression VB parameter reference expression Referenced parameter. Represents a reference to a member of a class, struct, or interface. Current usage: C# member reference expression VB member reference expression Instance of the type. Null if the reference is to a static/shared member. Referenced member. Type parameter which runtime type will be used to resolve virtual invocation of the . Null if is resolved statically, or is an instance member. Represents a reference to a field. Current usage: C# field reference expression VB field reference expression Referenced field. If the field reference is also where the field was declared. This is only ever true in CSharp scripts, where a top-level statement creates a new variable in a reference, such as an out variable declaration or a deconstruction declaration. Represents a reference to a method other than as the target of an invocation. Current usage: C# method reference expression VB method reference expression Referenced method. Indicates whether the reference uses virtual semantics. Represents a reference to a property. Current usage: C# property reference expression VB property reference expression Referenced property. Arguments of the indexer property reference, excluding the instance argument. Arguments are in evaluation order. If the invocation is in its expanded form, then params/ParamArray arguments would be collected into arrays. Default values are supplied for optional arguments missing in source. Represents a reference to an event. Current usage: C# event reference expression VB event reference expression Referenced event. Represents an operation with one operand and a unary operator. Current usage: C# unary operation expression VB unary operation expression Kind of unary operation. Operand. if this is a 'lifted' unary operator. When there is an operator that is defined to work on a value type, 'lifted' operators are created to work on the versions of those value types. if overflow checking is performed for the arithmetic operation. Operator method used by the operation, null if the operation does not use an operator method. Type parameter which runtime type will be used to resolve virtual invocation of the , if any. Null if is resolved statically, or is null. Represents an operation with two operands and a binary operator that produces a result with a non-null type. Current usage: C# binary operator expression VB binary operator expression Kind of binary operation. Left operand. Right operand. if this is a 'lifted' binary operator. When there is an operator that is defined to work on a value type, 'lifted' operators are created to work on the versions of those value types. if this is a 'checked' binary operator. if the comparison is text based for string or object comparison in VB. Operator method used by the operation, null if the operation does not use an operator method. Type parameter which runtime type will be used to resolve virtual invocation of the or corresponding true/false operator, if any. Null if operators are resolved statically, or are not used. True/False operator method used for short circuiting. https://github.com/dotnet/roslyn/issues/27598 tracks exposing this information through public API Represents a conditional operation with: to be tested operation to be executed when is true and operation to be executed when the is false Current usage: C# ternary expression a ? b : c and if statement VB ternary expression If(a, b, c) and If Else statement Condition to be tested. Operation to be executed if the is true. Operation to be executed if the is false. if the result is by-reference. This occurs in C# for ternaries whose branches use . Represents a coalesce operation with two operands: , which is the first operand that is unconditionally evaluated and is the result of the operation if non null , which is the second operand that is conditionally evaluated and is the result of the operation if is null Current usage: C# null-coalescing expression Value ?? WhenNull VB binary conditional expression If(Value, WhenNull) Operation to be unconditionally evaluated. Operation to be conditionally evaluated if evaluates to null/Nothing. Conversion associated with when it is not null/Nothing. Identity if result type of the operation is the same as type of . Otherwise, if type of is nullable, then conversion is applied to an unwrapped , otherwise to the itself. Represents an anonymous function operation. Current usage: C# lambda expression VB anonymous delegate expression Symbol of the anonymous function. Body of the anonymous function. Represents creation of an object instance. Current usage: C# new expression VB New expression Constructor to be invoked on the created instance. Object or collection initializer, if any. Arguments of the object creation, excluding the instance argument. Arguments are in evaluation order. If the invocation is in its expanded form, then params/ParamArray arguments would be collected into arrays. Default values are supplied for optional arguments missing in source. Represents a creation of a type parameter object, i.e. new T(), where T is a type parameter with new constraint. Current usage: C# type parameter object creation expression VB type parameter object creation expression Object or collection initializer, if any. Represents the creation of an array instance. Current usage: C# array creation expression VB array creation expression Sizes of the dimensions of the created array instance. Values of elements of the created array instance. Represents an implicit/explicit reference to an instance. Current usage: C# this or base expression VB Me, MyClass, or MyBase expression C# object or collection or 'with' expression initializers VB With statements, object or collection initializers The kind of reference that is being made. Represents an operation that tests if a value is of a specific type. Current usage: C# "is" operator expression VB "TypeOf" and "TypeOf IsNot" expression Value to test. Type for which to test. Flag indicating if this is an "is not" type expression. True for VB "TypeOf ... IsNot ..." expression. False, otherwise. Represents an await operation. Current usage: C# await expression VB await expression Awaited operation. Represents a base interface for assignments. Current usage: C# simple, compound and deconstruction assignment expressions VB simple and compound assignment expressions Target of the assignment. Value to be assigned to the target of the assignment. Represents a simple assignment operation. Current usage: C# simple assignment expression VB simple assignment expression Is this a ref assignment Represents a compound assignment that mutates the target with the result of a binary operation. Current usage: C# compound assignment expression VB compound assignment expression Conversion applied to before the operation occurs. Conversion applied to the result of the binary operation, before it is assigned back to . Kind of binary operation. if this assignment contains a 'lifted' binary operation. if overflow checking is performed for the arithmetic operation. Operator method used by the operation, null if the operation does not use an operator method. Type parameter which runtime type will be used to resolve virtual invocation of the , if any. Null if is resolved statically, or is null. Represents a parenthesized operation. Current usage: VB parenthesized expression Operand enclosed in parentheses. Represents a binding of an event. Current usage: C# event assignment expression VB Add/Remove handler statement Reference to the event being bound. Handler supplied for the event. True for adding a binding, false for removing one. Represents a conditionally accessed operation. Note that is used to refer to the value of within . Current usage: C# conditional access expression (? or ?. operator) VB conditional access expression (? or ?. operator) Operation that will be evaluated and accessed if non null. Operation to be evaluated if is non null. Represents the value of a conditionally-accessed operation within . For a conditional access operation of the form someExpr?.Member, this operation is used as the InstanceReceiver for the right operation Member. See https://github.com/dotnet/roslyn/issues/21279#issuecomment-323153041 for more details. Current usage: C# conditional access instance expression VB conditional access instance expression Represents an interpolated string. Current usage: (1) C# interpolated string expression. (2) VB interpolated string expression. Constituent parts of interpolated string, each of which is an . Represents a creation of anonymous object. Current usage: C# new { ... } expression VB New With { ... } expression Property initializers. Each initializer is an , with an as the target whose Instance is an with kind. Represents an initialization for an object or collection creation. Current usage: C# object or collection initializer expression. For example, object initializer { X = x } within object creation new Class() { X = x } and collection initializer { x, y, 3 } within collection creation new MyList() { x, y, 3 } VB object or collection initializer expression Object member or collection initializers. Represents an initialization of member within an object initializer with a nested object or collection initializer. Current usage: C# nested member initializer expression. For example, given an object creation with initializer new Class() { X = x, Y = { x, y, 3 }, Z = { X = z } }, member initializers for Y and Z, i.e. Y = { x, y, 3 }, and Z = { X = z } are nested member initializers represented by this operation VB object or collection initializer expression Initialized member reference or an invalid operation for error cases. Member initializer. "ICollectionElementInitializerOperation has been replaced with " + nameof(IInvocationOperation) + " and " + nameof(IDynamicInvocationOperation) Obsolete interface that used to represent a collection element initializer. It has been replaced by and , as appropriate. Current usage: None. This API has been obsoleted in favor of and . Represents an operation that gets a string value for the name. Current usage: C# nameof expression VB NameOf expression Argument to the name of operation. Represents a tuple with one or more elements. Current usage: C# tuple expression VB tuple expression Tuple elements. Natural type of the tuple, or null if tuple doesn't have a natural type. Natural type can be different from depending on the conversion context, in which the tuple is used. Represents an object creation with a dynamically bound constructor. Current usage: C# new expression with dynamic argument(s) VB late bound New expression Object or collection initializer, if any. Dynamically bound arguments, excluding the instance argument. Represents a reference to a member of a class, struct, or module that is dynamically bound. Current usage: C# dynamic member reference expression VB late bound member reference expression Instance receiver, if it exists. Referenced member. Type arguments. The containing type of the referenced member, if different from type of the . Represents a invocation that is dynamically bound. Current usage: C# dynamic invocation expression C# dynamic collection element initializer. For example, in the following collection initializer: new C() { do1, do2, do3 } where the doX objects are of type dynamic, we'll have 3 with do1, do2, and do3 as their arguments VB late bound invocation expression VB dynamic collection element initializer. Similar to the C# example, New C() From {do1, do2, do3} will generate 3 nodes with do1, do2, and do3 as their arguments, respectively Dynamically or late bound operation. Dynamically bound arguments, excluding the instance argument. Represents an indexer access that is dynamically bound. Current usage: C# dynamic indexer access expression Dynamically indexed operation. Dynamically bound arguments, excluding the instance argument. Represents an unrolled/lowered query operation. For example, for a C# query expression "from x in set where x.Name != null select x.Name", the Operation tree has the following shape: ITranslatedQueryExpression IInvocationExpression ('Select' invocation for "select x.Name") IInvocationExpression ('Where' invocation for "where x.Name != null") IInvocationExpression ('From' invocation for "from x in set") Current usage: C# query expression VB query expression Underlying unrolled operation. Represents a delegate creation. This is created whenever a new delegate is created. Current usage: C# delegate creation expression VB delegate creation expression The lambda or method binding that this delegate is created from. Represents a default value operation. Current usage: C# default value expression Represents an operation that gets for the given . Current usage: C# typeof expression VB GetType expression Type operand. Represents an operation to compute the size of a given type. Current usage: C# sizeof expression Type operand. Represents an operation that creates a pointer value by taking the address of a reference. Current usage: C# address of expression Addressed reference. Represents an operation that tests if a value matches a specific pattern. Current usage: C# is pattern expression. For example, x is int i Underlying operation to test. Pattern. Represents an or operation. Note that this operation is different from an as it mutates the , while unary operator expression does not mutate it's operand. Current usage: C# increment expression or decrement expression if this is a postfix expression. if this is a prefix expression. if this is a 'lifted' increment operator. When there is an operator that is defined to work on a value type, 'lifted' operators are created to work on the versions of those value types. if overflow checking is performed for the arithmetic operation. Target of the assignment. Operator method used by the operation, null if the operation does not use an operator method. Type parameter which runtime type will be used to resolve virtual invocation of the , if any. Null if is resolved statically, or is null. Represents an operation to throw an exception. Current usage: C# throw expression C# throw statement VB Throw statement Instance of an exception being thrown. Represents a assignment with a deconstruction. Current usage: C# deconstruction assignment expression Represents a declaration expression operation. Unlike a regular variable declaration and , this operation represents an "expression" declaring a variable. Current usage: C# deconstruction assignment expression. For example: var (x, y) is a deconstruction declaration expression with variables x and y (var x, var y) is a tuple expression with two declaration expressions M(out var x); is an invocation expression with an out var x declaration expression Underlying expression. Represents an argument value that has been omitted in an invocation. Current usage: VB omitted argument in an invocation expression Represents an initializer for a field, property, parameter or a local variable declaration. Current usage: C# field, property, parameter or local variable initializer VB field(s), property, parameter or local variable initializer Local declared in and scoped to the . Underlying initializer value. Represents an initialization of a field. Current usage: C# field initializer with equals value clause VB field(s) initializer with equals value clause or AsNew clause. Multiple fields can be initialized with AsNew clause in VB Initialized fields. There can be multiple fields for Visual Basic fields declared with AsNew clause. Represents an initialization of a local variable. Current usage: C# local variable initializer with equals value clause VB local variable initializer with equals value clause or AsNew clause Represents an initialization of a property. Current usage: C# property initializer with equals value clause VB property initializer with equals value clause or AsNew clause. Multiple properties can be initialized with 'WithEvents' declaration with AsNew clause in VB Initialized properties. There can be multiple properties for Visual Basic 'WithEvents' declaration with AsNew clause. Represents an initialization of a parameter at the point of declaration. Current usage: C# parameter initializer with equals value clause VB parameter initializer with equals value clause Initialized parameter. Represents the initialization of an array instance. Current usage: C# array initializer VB array initializer Values to initialize array elements. Represents a single variable declarator and initializer. Current Usage: C# variable declarator C# catch variable declaration VB single variable declaration VB catch variable declaration In VB, the initializer for this node is only ever used for explicit array bounds initializers. This node corresponds to the VariableDeclaratorSyntax in C# and the ModifiedIdentifierSyntax in VB. Symbol declared by this variable declaration Optional initializer of the variable. If this variable is in an , the initializer may be located in the parent operation. Call to check in all locations. It is only possible to have initializers in both locations in VB invalid code scenarios. Additional arguments supplied to the declarator in error cases, ignored by the compiler. This only used for the C# case of DeclaredArgumentSyntax nodes on a VariableDeclaratorSyntax. Represents a declarator that declares multiple individual variables. Current Usage: C# VariableDeclaration C# fixed declarations VB Dim statement declaration groups VB Using statement variable declarations The initializer of this node is applied to all individual declarations in . There cannot be initializers in both locations except in invalid code scenarios. In C#, this node will never have an initializer. This corresponds to the VariableDeclarationSyntax in C#, and the VariableDeclaratorSyntax in Visual Basic. Individual variable declarations declared by this multiple declaration. All will have at least 1 , even if the declaration group only declares 1 variable. Optional initializer of the variable. In C#, this will always be null. Array dimensions supplied to an array declaration in error cases, ignored by the compiler. This is only used for the C# case of RankSpecifierSyntax nodes on an ArrayTypeSyntax. Represents an argument to a method invocation. Current usage: C# argument to an invocation expression, object creation expression, etc. VB argument to an invocation expression, object creation expression, etc. Kind of argument. Parameter the argument matches. This can be null for __arglist parameters. Value supplied for the argument. Information of the conversion applied to the argument value passing it into the target method. Applicable only to VB Reference arguments. Information of the conversion applied to the argument value after the invocation. Applicable only to VB Reference arguments. Represents a catch clause. Current usage: C# catch clause VB Catch clause Optional source for exception. This could be any of the following operation: 1. Declaration for the local catch variable bound to the caught exception (C# and VB) OR 2. Null, indicating no declaration or expression (C# and VB) 3. Reference to an existing local or parameter (VB) OR 4. Other expression for error scenarios (VB) Type of the exception handled by the catch clause. Locals declared by the and/or clause. Filter operation to be executed to determine whether to handle the exception. Body of the exception handler. Represents a switch case section with one or more case clauses to match and one or more operations to execute within the section. Current usage: C# switch section for one or more case clause and set of statements to execute VB case block with a case statement for one or more case clause and set of statements to execute Clauses of the case. One or more operations to execute within the switch section. Locals declared within the switch case section scoped to the section. Optional combined logical condition that accounts for all . An instance of with kind is used to refer to the in context of this expression. It is not part of list and likely contains duplicate nodes for nodes exposed by , like , etc. Never set for C# at the moment. Represents a case clause. Current usage: C# case clause VB Case clause Kind of the clause. Label associated with the case clause, if any. Represents a default case clause. Current usage: C# default clause VB Case Else clause Represents a case clause with a pattern and an optional guard operation. Current usage: (1) C# pattern case clause. Label associated with the case clause. Pattern associated with case clause. Guard associated with the pattern case clause. Represents a case clause with range of values for comparison. Current usage: VB range case clause of the form Case x To y Minimum value of the case range. Maximum value of the case range. Represents a case clause with custom relational operator for comparison. Current usage: VB relational case clause of the form Case Is op x Case value. Relational operator used to compare the switch value with the case value. Represents a case clause with a single value for comparison. Current usage: C# case clause of the form case x VB case clause of the form Case x Case value. Represents a constituent part of an interpolated string. Current usage: C# interpolated string content VB interpolated string content Represents a constituent string literal part of an interpolated string operation. Current usage: C# interpolated string text VB interpolated string text Text content. Represents a constituent interpolation part of an interpolated string operation. Current usage: C# interpolation part VB interpolation part Expression of the interpolation. Optional alignment of the interpolation. Optional format string of the interpolation. Represents a pattern matching operation. Current usage: C# pattern The input type to the pattern-matching operation. The narrowed type of the pattern-matching operation. Represents a pattern with a constant value. Current usage: C# constant pattern Constant value of the pattern operation. Represents a pattern that declares a symbol. Current usage: C# declaration pattern The type explicitly specified, or null if it was inferred (e.g. using in C#). True if the pattern is of a form that accepts null. For example, in C# the pattern `var x` will match a null input, while the pattern `string x` will not. Symbol declared by the pattern, if any. Represents a comparison of two operands that returns a bool type. Current usage: C# tuple binary operator expression Kind of binary operation. Left operand. Right operand. Represents a method body operation. Current usage: C# method body Method body corresponding to BaseMethodDeclarationSyntax.Body or AccessorDeclarationSyntax.Body Method body corresponding to BaseMethodDeclarationSyntax.ExpressionBody or AccessorDeclarationSyntax.ExpressionBody Represents a method body operation. Current usage: C# method body for non-constructor Represents a constructor method body operation. Current usage: C# method body for constructor declaration Local declarations contained within the . Constructor initializer, if any. Represents a discard operation. Current usage: C# discard expressions The symbol of the discard operation. Represents that an intermediate result is being captured. This node is produced only as part of a . An id used to match references to the same intermediate result. Value to be captured. Represents a point of use of an intermediate result captured earlier. The fact of capturing the result is represented by . This node is produced only as part of a . An id used to match references to the same intermediate result. True if this reference to the capture initializes the capture. Used when the capture is being initialized by being passed as an parameter. Represents result of checking whether the is null. For reference types this checks if the is a null reference, for nullable types this checks if the doesn’t have a value. The node is produced as part of a flow graph during rewrite of and nodes. Value to check. Represents a exception instance passed by an execution environment to an exception filter or handler. This node is produced only as part of a . Represents the check during initialization of a VB static local that is initialized on the first call of the function, and never again. If the semaphore operation returns true, the static local has not yet been initialized, and the initializer will be run. If it returns false, then the local has already been initialized, and the static local initializer region will be skipped. This node is produced only as part of a . The static local variable that is possibly initialized. Represents an anonymous function operation in context of a . Current usage: C# lambda expression VB anonymous delegate expression A for the body of the anonymous function is available from the enclosing . Symbol of the anonymous function. Represents a coalesce assignment operation with a target and a conditionally-evaluated value: is evaluated for null. If it is null, is evaluated and assigned to target is conditionally evaluated if is null, and the result is assigned into The result of the entire expression is , which is only evaluated once. Current usage: C# null-coalescing assignment operation Target ??= Value Represents a range operation. Current usage: C# range expressions Left operand. Right operand. if this is a 'lifted' range operation. When there is an operator that is defined to work on a value type, 'lifted' operators are created to work on the versions of those value types. Factory method used to create this Range value. Can be null if appropriate symbol was not found. Represents the ReDim operation to re-allocate storage space for array variables. Current usage: VB ReDim statement Individual clauses of the ReDim operation. Modifier used to preserve the data in the existing array when you change the size of only the last dimension. Represents an individual clause of an to re-allocate storage space for a single array variable. Current usage: VB ReDim clause Operand whose storage space needs to be re-allocated. Sizes of the dimensions of the created array instance. Represents a C# recursive pattern. The type accepted for the recursive pattern. The symbol, if any, used for the fetching values for subpatterns. This is either a Deconstruct method, the type System.Runtime.CompilerServices.ITuple, or null (for example, in error cases or when matching a tuple type). This contains the patterns contained within a deconstruction or positional subpattern. This contains the (symbol, property) pairs within a property subpattern. Symbol declared by the pattern. Represents a discard pattern. Current usage: C# discard pattern Represents a switch expression. Current usage: C# switch expression Value to be switched upon. Arms of the switch expression. True if the switch expressions arms cover every possible input value. Represents one arm of a switch expression. The pattern to match. Guard (when clause expression) associated with the switch arm, if any. Result value of the enclosing switch expression when this arm matches. Locals declared within the switch arm (e.g. pattern locals and locals declared in the guard) scoped to the arm. Represents an element of a property subpattern, which identifies a member to be matched and the pattern to match it against. The member being matched in a property subpattern. This can be a in non-error cases, or an in error cases. The pattern to which the member is matched in a property subpattern. Represents a standalone VB query Aggregate operation with more than one item in Into clause. Represents a C# fixed statement. Locals declared. Variables to be fixed. Body of the fixed, over which the variables are fixed. Represents a creation of an instance of a NoPia interface, i.e. new I(), where I is an embedded NoPia interface. Current usage: C# NoPia interface instance creation expression VB NoPia interface instance creation expression Object or collection initializer, if any. Represents a general placeholder when no more specific kind of placeholder is available. A placeholder is an expression whose meaning is inferred from context. Represents a reference through a pointer. Current usage: C# pointer indirection reference expression Pointer to be dereferenced. Represents a of operations that are executed with implicit reference to the for member references. Current usage: VB With statement Body of the with. Value to whose members leading-dot-qualified references within the with body bind. Represents using variable declaration, with scope spanning across the parent . Current Usage: C# using declaration C# asynchronous using declaration The variables declared by this using declaration. True if this is an asynchronous using declaration. Information about the method that will be invoked to dispose the declared instances when pattern based disposal is used. Represents a negated pattern. Current usage: C# negated pattern The negated pattern. Represents a binary ("and" or "or") pattern. Current usage: C# "and" and "or" patterns Kind of binary pattern; either or . The pattern on the left. The pattern on the right. Represents a pattern comparing the input with a given type. Current usage: C# type pattern The type explicitly specified, or null if it was inferred (e.g. using in C#). Represents a pattern comparing the input with a constant value using a relational operator. Current usage: C# relational pattern The kind of the relational operator. Constant value of the pattern operation. Represents cloning of an object instance. Current usage: C# with expression Operand to be cloned. Clone method to be invoked on the value. This can be null in error scenarios. With collection initializer. Represents an interpolated string converted to a custom interpolated string handler type. The construction of the interpolated string handler instance. This can be an for valid code, and or for invalid code. True if the last parameter of is an out parameter that will be checked before executing the code in . False otherwise. True if the AppendLiteral or AppendFormatted calls in nested return . When that is true, each part will be conditional on the return of the part before it, only being executed when the Append call returns true. False otherwise. when this is true and is true, then the first part in nested is conditionally run. If this is true and is false, then the first part is unconditionally run.
Just because this is true or false does not guarantee that all Append calls actually do return boolean values, as there could be dynamic calls or errors. It only governs what the compiler was expecting, based on the first calls it did see.
The interpolated string expression or addition operation that makes up the content of this string. This is either an or an operation.
Represents an addition of multiple interpolated string literals being converted to an interpolated string handler type. The interpolated string expression or addition operation on the left side of the operator. This is either an or an operation. The interpolated string expression or addition operation on the right side of the operator. This is either an or an operation. Represents a call to either AppendLiteral or AppendFormatted as part of an interpolated string handler conversion. If this interpolated string is subject to an interpolated string handler conversion, the construction of the interpolated string handler instance. This can be an or for valid code, and for invalid code. Represents an argument from the method call, indexer access, or constructor invocation that is creating the containing The index of the argument of the method call, indexer, or object creation containing the interpolated string handler conversion this placeholder is referencing. -1 if is anything other than . The component this placeholder represents. Represents an invocation of a function pointer. Invoked pointer. Arguments of the invocation. Arguments are in evaluation order. Represents a C# list pattern. The Length or Count property that is used to fetch the length value. Returns null if no such property is found. The indexer that is used to fetch elements. Returns null for an array input. Returns subpatterns contained within the list pattern. Symbol declared by the pattern, if any. Represents a C# slice pattern. The range indexer or the Slice method used to fetch the slice value. The pattern that the slice value is matched with, if any. Represents a reference to an implicit System.Index or System.Range indexer over a non-array type. Current usage: C# implicit System.Index or System.Range indexer reference expression Instance of the type to be indexed. System.Index or System.Range value. The Length or Count property that might be used to fetch the length value. Symbol for the underlying indexer or a slice method that is used to implement the implicit indexer. Represents a UTF-8 encoded byte representation of a string. Current usage: C# UTF-8 string literal expression The underlying string value. Represents the application of an attribute. Current usage: C# attribute application VB attribute application The operation representing the attribute. This can be a in non-error cases, or an in error cases. Represents an element reference or a slice operation over an inline array type. Current usage: C# inline array access Instance of the inline array type to be accessed. System.Int32, System.Index or System.Range value. Represents a collection expression. Current usage: C# collection expression Method used to construct the collection. If the collection type is an array, span, array interface, or type parameter, the method is null; if the collection type has a [CollectionBuilder] attribute, the method is the builder method; otherwise, the method is the collection type constructor. Collection expression elements. If the element is an expression, the entry is the expression, with a conversion to the target element type if necessary; otherwise, the entry is an ISpreadOperation. Represents a collection expression spread element. Current usage: C# spread element Collection being spread. Type of the elements in the collection. Conversion from the type of the collection element to the target element type of the containing collection expression.
================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/README.md ================================================ # Roslyn Shim Layer ## Purpose Enables the new Roslyn API usage while maintaining compatibility with old versions. ## Structure The project is divided into the following parts: - At the root level is the `Lightup` layer introduced by the stylecop analyzers. The code was copied from https://github.com/DotNetAnalyzers/StyleCopAnalyzers/tree/master/StyleCop.Analyzers/StyleCop.Analyzers/Lightup excepting `Syntax.xml` and `OperationInterfaces.xml`. - [Syntax.xml](https://github.com/dotnet/roslyn/blob/main/src/Compilers/CSharp/Portable/Syntax/Syntax.xml) and [OperationInterfaces.xml](https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml) are copied from the Roslyn repository (ideally, from the latest release version branch). They are used to generate the `Lightup` layer. We copy them from Roslyn and not from StyleCopAnalyzers to ensure that we have the most recent version to be able to support the latest features. - All the additions made by SonarSource are in the `Sonar` folder. ## Conventions - Keep our changes and all logic in a dedicated directory `Sonar`, using partial classes, extension methods, and external handlers. - Keep the namespace. Have different license headers. - Inject ourselves to the StyleCop code with minimal changes that are annotated with `// Sonar` comment everywhere. - When importing `Syntax.xml` and `OperationInterfaces.xml`, it's important to maintain the original file content, including the whitespaces. To ensure this, download the file directly into the project directory. If you opt to copy and paste the content, you risk losing the whitespaces, which can make the diff more challenging to read and future updates more difficult to implement. ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/SeparatedSyntaxListWrapper`1.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; public abstract class SeparatedSyntaxListWrapper : IEquatable>, IReadOnlyList { private static readonly SyntaxWrapper SyntaxWrapper = SyntaxWrapper.Default; public static SeparatedSyntaxListWrapper UnsupportedEmpty { get; } = new UnsupportedSyntaxList(); public abstract int Count { get; } public abstract TextSpan FullSpan { get; } public abstract int SeparatorCount { get; } public abstract TextSpan Span { get; } [EditorBrowsable(EditorBrowsableState.Never)] public abstract object UnderlyingList { get; } public abstract TNode this[int index] { get; } public static bool operator ==(SeparatedSyntaxListWrapper left, SeparatedSyntaxListWrapper right) { // Currently unused _ = left; _ = right; throw new NotImplementedException(); } public static bool operator !=(SeparatedSyntaxListWrapper left, SeparatedSyntaxListWrapper right) { // Currently unused _ = left; _ = right; throw new NotImplementedException(); } // Summary: // Creates a new list with the specified node added to the end. // // Parameters: // node: // The node to add. public SeparatedSyntaxListWrapper Add(TNode node) => this.Insert(this.Count, node); // Summary: // Creates a new list with the specified nodes added to the end. // // Parameters: // nodes: // The nodes to add. public SeparatedSyntaxListWrapper AddRange(IEnumerable nodes) => this.InsertRange(this.Count, nodes); public abstract bool Any(); public abstract bool Contains(TNode node); public bool Equals(SeparatedSyntaxListWrapper other) { throw new NotImplementedException(); } public override bool Equals(object obj) { throw new NotImplementedException(); } public abstract TNode First(); public abstract TNode FirstOrDefault(); public Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } public override abstract int GetHashCode(); public abstract SyntaxToken GetSeparator(int index); public abstract IEnumerable GetSeparators(); public abstract SyntaxNodeOrTokenList GetWithSeparators(); public abstract int IndexOf(Func predicate); public abstract int IndexOf(TNode node); public abstract SeparatedSyntaxListWrapper Insert(int index, TNode node); public abstract SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes); public abstract TNode Last(); public abstract int LastIndexOf(Func predicate); public abstract int LastIndexOf(TNode node); public abstract TNode LastOrDefault(); public abstract SeparatedSyntaxListWrapper Remove(TNode node); public abstract SeparatedSyntaxListWrapper RemoveAt(int index); public abstract SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode); public abstract SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes); public abstract SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator); public abstract string ToFullString(); public override abstract string ToString(); public struct Enumerator : IEnumerator { private readonly SeparatedSyntaxListWrapper wrapper; private int index; private TNode current; public Enumerator(SeparatedSyntaxListWrapper wrapper) { this.wrapper = wrapper; this.index = -1; this.current = default; } public TNode Current => this.current; object IEnumerator.Current => this.Current; public override bool Equals(object obj) { Enumerator? otherOpt = obj as Enumerator?; if (!otherOpt.HasValue) { return false; } Enumerator other = otherOpt.GetValueOrDefault(); return other.wrapper == this.wrapper && other.index == this.index; } public override int GetHashCode() { if (this.wrapper == null) { return 0; } return this.wrapper.GetHashCode() ^ this.index; } public void Dispose() { } public bool MoveNext() { if (this.index < -1) { return false; } if (this.index == this.wrapper.Count - 1) { this.index = int.MinValue; return false; } this.index++; this.current = this.wrapper[this.index]; return true; } public void Reset() { this.index = -1; this.current = default; } } internal sealed class AutoWrapSeparatedSyntaxList : SeparatedSyntaxListWrapper where TSyntax : SyntaxNode { private readonly SeparatedSyntaxList syntaxList; public AutoWrapSeparatedSyntaxList() : this(default) { } public AutoWrapSeparatedSyntaxList(SeparatedSyntaxList syntaxList) { this.syntaxList = syntaxList; } public override int Count => this.syntaxList.Count; public override TextSpan FullSpan => this.syntaxList.FullSpan; public override int SeparatorCount => this.syntaxList.SeparatorCount; public override TextSpan Span => this.syntaxList.Span; public override object UnderlyingList => this.syntaxList; public override TNode this[int index] => SyntaxWrapper.Wrap(this.syntaxList[index]); public override bool Any() => this.syntaxList.Any(); public override bool Contains(TNode node) => this.syntaxList.Contains(SyntaxWrapper.Unwrap(node)); public override TNode First() => SyntaxWrapper.Wrap(this.syntaxList.First()); public override TNode FirstOrDefault() => SyntaxWrapper.Wrap(this.syntaxList.FirstOrDefault()); public override int GetHashCode() => this.syntaxList.GetHashCode(); public override SyntaxToken GetSeparator(int index) => this.syntaxList.GetSeparator(index); public override IEnumerable GetSeparators() => this.syntaxList.GetSeparators(); public override SyntaxNodeOrTokenList GetWithSeparators() => this.syntaxList.GetWithSeparators(); public override int IndexOf(TNode node) => this.syntaxList.IndexOf((TSyntax)SyntaxWrapper.Unwrap(node)); public override int IndexOf(Func predicate) => this.syntaxList.IndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override SeparatedSyntaxListWrapper Insert(int index, TNode node) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Insert(index, (TSyntax)SyntaxWrapper.Unwrap(node))); public override SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes) => new AutoWrapSeparatedSyntaxList(this.syntaxList.InsertRange(index, nodes.Select(node => (TSyntax)SyntaxWrapper.Unwrap(node)))); public override TNode Last() => SyntaxWrapper.Wrap(this.syntaxList.Last()); public override int LastIndexOf(TNode node) => this.syntaxList.LastIndexOf((TSyntax)SyntaxWrapper.Unwrap(node)); public override int LastIndexOf(Func predicate) => this.syntaxList.LastIndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override TNode LastOrDefault() => SyntaxWrapper.Wrap(this.syntaxList.LastOrDefault()); public override SeparatedSyntaxListWrapper Remove(TNode node) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Remove((TSyntax)SyntaxWrapper.Unwrap(node))); public override SeparatedSyntaxListWrapper RemoveAt(int index) => new AutoWrapSeparatedSyntaxList(this.syntaxList.RemoveAt(index)); public override SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode) => new AutoWrapSeparatedSyntaxList(this.syntaxList.Replace((TSyntax)SyntaxWrapper.Unwrap(nodeInList), (TSyntax)SyntaxWrapper.Unwrap(newNode))); public override SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes) => new AutoWrapSeparatedSyntaxList(this.syntaxList.ReplaceRange((TSyntax)SyntaxWrapper.Unwrap(nodeInList), newNodes.Select(node => (TSyntax)SyntaxWrapper.Unwrap(node)))); public override SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator) => new AutoWrapSeparatedSyntaxList(this.syntaxList.ReplaceSeparator(separatorToken, newSeparator)); public override string ToFullString() => this.syntaxList.ToFullString(); public override string ToString() => this.syntaxList.ToString(); } private sealed class UnsupportedSyntaxList : SeparatedSyntaxListWrapper { private static readonly SeparatedSyntaxList SyntaxList = default; public UnsupportedSyntaxList() { } public override int Count => 0; public override TextSpan FullSpan => SyntaxList.FullSpan; public override int SeparatorCount => 0; public override TextSpan Span => SyntaxList.Span; public override object UnderlyingList => null; public override TNode this[int index] => SyntaxWrapper.Wrap(SyntaxList[index]); public override bool Any() => false; public override bool Contains(TNode node) => false; public override TNode First() => SyntaxWrapper.Wrap(SyntaxList.First()); public override TNode FirstOrDefault() => SyntaxWrapper.Wrap(default); public override int GetHashCode() => SyntaxList.GetHashCode(); public override SyntaxToken GetSeparator(int index) => SyntaxList.GetSeparator(index); public override IEnumerable GetSeparators() => SyntaxList.GetSeparators(); public override SyntaxNodeOrTokenList GetWithSeparators() => SyntaxList.GetWithSeparators(); public override int IndexOf(TNode node) => SyntaxList.IndexOf(SyntaxWrapper.Unwrap(node)); public override int IndexOf(Func predicate) => SyntaxList.IndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override SeparatedSyntaxListWrapper Insert(int index, TNode node) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper InsertRange(int index, IEnumerable nodes) { throw new NotSupportedException(); } public override TNode Last() => SyntaxWrapper.Wrap(SyntaxList.Last()); public override int LastIndexOf(TNode node) => SyntaxList.LastIndexOf(SyntaxWrapper.Unwrap(node)); public override int LastIndexOf(Func predicate) => SyntaxList.LastIndexOf(node => predicate(SyntaxWrapper.Wrap(node))); public override TNode LastOrDefault() => SyntaxWrapper.Wrap(default); public override SeparatedSyntaxListWrapper Remove(TNode node) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper RemoveAt(int index) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper Replace(TNode nodeInList, TNode newNode) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper ReplaceRange(TNode nodeInList, IEnumerable newNodes) { throw new NotSupportedException(); } public override SeparatedSyntaxListWrapper ReplaceSeparator(SyntaxToken separatorToken, SyntaxToken newSeparator) { throw new NotSupportedException(); } public override string ToFullString() => SyntaxList.ToFullString(); public override string ToString() => SyntaxList.ToString(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/CaptureId.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public readonly struct CaptureId : IEquatable { private readonly object instance; // Underlaying struct holds only internal int Value as the identificator. public CaptureId(object instance) => this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); public override bool Equals(object obj) => obj is CaptureId capture && Equals(capture); public bool Equals(CaptureId other) => instance.Equals(other.instance); public override int GetHashCode() => instance.GetHashCode(); public string Serialize() => "#Capture-" + GetHashCode(); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/CompilationOptionsWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public readonly struct CompilationOptionsWrapper { internal const string WrappedTypeName = "Microsoft.CodeAnalysis.CompilationOptions"; private static readonly Type WrappedType; private static readonly Func SyntaxTreeOptionsProviderAccessor; private readonly CompilationOptions node; public SyntaxTreeOptionsProviderWrapper SyntaxTreeOptionsProvider => node is null || WrappedType is null ? default : SyntaxTreeOptionsProviderWrapper.FromObject(SyntaxTreeOptionsProviderAccessor(node)); static CompilationOptionsWrapper() { WrappedType = WrapperHelper.GetWrappedType(typeof(CompilationOptionsWrapper)); SyntaxTreeOptionsProviderAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, nameof(SyntaxTreeOptionsProvider)); } private CompilationOptionsWrapper(CompilationOptions node) => this.node = node; public static CompilationOptionsWrapper FromObject(CompilationOptions node) => node is null ? default : new(node); public static bool IsInstance(object obj) => obj is not null && LightupHelpers.CanWrapObject(obj, WrappedType); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/IEventSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public static class IEventSymbolExtensions { private static readonly Func PartialDefinitionPartAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IEventSymbol), "PartialDefinitionPart"); extension(IEventSymbol symbol) { public IEventSymbol PartialDefinitionPart => PartialDefinitionPartAccessor(symbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/IMethodSymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public static class IMethodSymbolExtensions { private static readonly Func IsPartialDefinitionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IMethodSymbol), "IsPartialDefinition"); private static readonly Func AssociatedExtensionImplementationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IMethodSymbol), "AssociatedExtensionImplementation"); extension(IMethodSymbol symbol) { public IMethodSymbol AssociatedExtensionImplementation => AssociatedExtensionImplementationAccessor(symbol); public bool IsPartialDefinition => IsPartialDefinitionAccessor(symbol); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/INamedTypeSymbolExtensions.Sonar.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using static System.Linq.Expressions.Expression; namespace StyleCop.Analyzers.Lightup; public static class INamedTypeSymbolExtensionsSonar { private static readonly Func> TypeArgumentNullableAnnotationsAccessor; static INamedTypeSymbolExtensionsSonar() { TypeArgumentNullableAnnotationsAccessor = CreateTypeArgumentNullableAnnotationsAccessor(); } public static ImmutableArray TypeArgumentNullableAnnotations(this INamedTypeSymbol symbol) => TypeArgumentNullableAnnotationsAccessor(symbol); private static Func> CreateTypeArgumentNullableAnnotationsAccessor() { // INamedTypeSymbol.TypeArgumentNullableAnnotations is ImmutableArray // The generated code is symbol => ImmutableArray.CreateRange(symbol.TypeArgumentNullableAnnotations, x => (Sonar.NullableAnnotationType)x); // Callers may rely on the fact that symbol.TypeArgumentNullableAnnotations is supposed to have the same length as symbol.TypeArguments var fallback = static (INamedTypeSymbol x) => Enumerable.Repeat(NullableAnnotation.None, x.TypeArguments.Length).ToImmutableArray(); if (OriginalNullableAnnotationType() is not { } originalNullableAnnotationType) { return fallback; } if (typeof(ImmutableArray).GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(x => x.Name == nameof(ImmutableArray.CreateRange) // https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable.immutablearray.createrange && x.GetParameters() is { Length: 2 } parameters // CreateRange(ImmutableArray items, Func selector) && parameters[0].Name == "items" // see also https://stackoverflow.com/a/4036187 && parameters[1].Name == "selector" && x.GetGenericArguments() is { Length: 2 } typeArguments && typeArguments[0].Name == "TSource" && typeArguments[1].Name == "TResult") is not { } createRange) { return fallback; } var sonarNullableAnnotationType = typeof(NullableAnnotation); var createRangeT = createRange.MakeGenericMethod(originalNullableAnnotationType, sonarNullableAnnotationType); var delegateType = typeof(Func<,>).MakeGenericType(originalNullableAnnotationType, sonarNullableAnnotationType); var originalNullableAnnotationParameter = Parameter(originalNullableAnnotationType, "x"); var conversion = Lambda(delegateType, Convert(originalNullableAnnotationParameter, sonarNullableAnnotationType), originalNullableAnnotationParameter); // (originalNullableAnnotationType x) => (sonarNullableAnnotationType)x; var symbolParameter = Parameter(typeof(INamedTypeSymbol), "symbol"); return Lambda>>( Call(createRangeT, Property(symbolParameter, nameof(TypeArgumentNullableAnnotations)), conversion), // ImmutableArray.CreateRange(symbol.TypeArgumentNullableAnnotations, conversion) symbolParameter).Compile(); } private static Type OriginalNullableAnnotationType() { try { return Type.GetType("Microsoft.CodeAnalysis.NullableAnnotation, Microsoft.CodeAnalysis"); } catch { return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/IOperationWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public interface IOperationWrapper { IOperation? WrappedOperation { get; } ////IOperationWrapper Parent { get; } ////OperationKind Kind { get; } ////SyntaxNode Syntax { get; } ITypeSymbol? Type { get; } ////Optional ConstantValue { get; } ////IEnumerable Children { get; } ////string Language { get; } ////bool IsImplicit { get; } ////SemanticModel SemanticModel { get; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/IOperationWrapperSonar.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; // This is a temporary substitute for IOperationWrapper in case StyleCop will accept PR https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3381 public readonly struct IOperationWrapperSonar { private static readonly Func ParentAccessor; private static readonly Func> ChildrenAccessor; private static readonly Func LanguageAccessor; private static readonly Func IsImplicitAccessor; private static readonly Func SemanticModelAccessor; public IOperation Instance { get; } public IOperation Parent => ParentAccessor(Instance); public IEnumerable Children => ChildrenAccessor(Instance); public string Language => LanguageAccessor(Instance); public bool IsImplicit => IsImplicitAccessor(Instance); public SemanticModel SemanticModel => SemanticModelAccessor(Instance); public IOperationWrapperSonar(IOperation instance) => Instance = instance ?? throw new ArgumentNullException(nameof(instance)); static IOperationWrapperSonar() { var type = typeof(IOperation); ParentAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(type, nameof(Parent)); ChildrenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(type, nameof(Children)); LanguageAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(type, nameof(Language)); IsImplicitAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(type, nameof(IsImplicit)); SemanticModelAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(type, nameof(SemanticModel)); } public override int GetHashCode() => Instance.GetHashCode(); public override bool Equals(object obj) => obj is IOperationWrapperSonar wrapper && wrapper.Instance.Equals(Instance); public override string ToString() => Instance.ToString(); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/IPropertySymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public static class IPropertySymbolExtensions { private static readonly Func IsRequiredAccessor; private static readonly Func PartialDefinitionPartAccessor; private static readonly Func PartialImplementationPartAccessor; private static readonly Func IsPartialDefinitionAccessor; static IPropertySymbolExtensions() { IsRequiredAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IPropertySymbol), nameof(IsRequired)); IsPartialDefinitionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IPropertySymbol), nameof(IsPartialDefinition)); PartialDefinitionPartAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IPropertySymbol), nameof(PartialDefinitionPart)); PartialImplementationPartAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(IPropertySymbol), nameof(PartialImplementationPart)); } public static bool IsRequired(this IPropertySymbol propertySymbol) => IsRequiredAccessor(propertySymbol); public static bool IsPartialDefinition(this IPropertySymbol propertySymbol) => IsPartialDefinitionAccessor(propertySymbol); public static IPropertySymbol PartialDefinitionPart(this IPropertySymbol propertySymbol) => PartialDefinitionPartAccessor(propertySymbol); public static IPropertySymbol PartialImplementationPart(this IPropertySymbol propertySymbol) => PartialImplementationPartAccessor(propertySymbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/ISymbolNullableExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public static class ISymbolNullableExtensions { private static readonly Func TypeSymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func ArrayTypeSymbolElementAccessor = CreateSymbolNullableAnnotationAccessor("ElementNullableAnnotation"); private static readonly Func ParameterSymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func LocalSymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func PropertySymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func FieldSymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func EventSymbolAccessor = CreateSymbolNullableAnnotationAccessor(); private static readonly Func TypeParameterSymbolReferenceTypeConstraintAccessor = CreateSymbolNullableAnnotationAccessor("ReferenceTypeConstraintNullableAnnotation"); // ConstraintNullableAnnotations is not yet supported private static readonly Func MethodSymbolReceiverAccessor = CreateSymbolNullableAnnotationAccessor("ReceiverNullableAnnotation"); private static readonly Func MethodSymbolReturnAccessor = CreateSymbolNullableAnnotationAccessor("ReturnNullableAnnotation"); // TypeArgumentNullableAnnotations is not yet supported /// /// Nullable annotation associated with the type, or < see cref="NullableAnnotation.None" /> if there are none. /// public static NullableAnnotation NullableAnnotation(this ITypeSymbol type) => TypeSymbolAccessor(type); /// /// Gets the top-level nullability of the parameter. /// public static NullableAnnotation NullableAnnotation(this IParameterSymbol parameter) => ParameterSymbolAccessor(parameter); /// /// Gets the top-level nullability of this local variable. /// public static NullableAnnotation NullableAnnotation(this ILocalSymbol local) => LocalSymbolAccessor(local); /// /// Gets the top-level nullability of this property. /// public static NullableAnnotation NullableAnnotation(this IPropertySymbol property) => PropertySymbolAccessor(property); /// /// Gets the top-level nullability of this field. /// public static NullableAnnotation NullableAnnotation(this IFieldSymbol field) => FieldSymbolAccessor(field); /// /// The top-level nullability of the event. /// public static NullableAnnotation NullableAnnotation(this IEventSymbol eventSymbol) => EventSymbolAccessor(eventSymbol); /// /// Gets the top-level nullability of the elements stored in the array. /// public static NullableAnnotation ElementNullableAnnotation(this IArrayTypeSymbol arrayType) => ArrayTypeSymbolElementAccessor(arrayType); /// /// If is , returns the top-level nullability of the /// class constraint that was specified for the type parameter. If there was no class constraint, this returns . /// public static NullableAnnotation ReferenceTypeConstraintNullableAnnotation(this ITypeParameterSymbol eventSymbol) => TypeParameterSymbolReferenceTypeConstraintAccessor(eventSymbol); /// /// If this method can be applied to an object, returns the top-level nullability of the object it is applied to. /// public static NullableAnnotation ReceiverNullableAnnotation(this IMethodSymbol method) => MethodSymbolReceiverAccessor(method); /// /// Gets the top-level nullability of the return type of the method. /// public static NullableAnnotation ReturnNullableAnnotation(this IMethodSymbol method) => MethodSymbolReturnAccessor(method); private static Func CreateSymbolNullableAnnotationAccessor(string propertyName = nameof(NullableAnnotation)) where T : ISymbol => LightupHelpers.CreateSyntaxPropertyAccessor(typeof(T), propertyName); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/ITypeSymbolExtension.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public static partial class ITypeSymbolExtensions { private static readonly Func IsRefLikeTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(ITypeSymbol), nameof(IsRefLikeType)); public static bool IsRefLikeType(this ITypeSymbol symbol) => IsRefLikeTypeAccessor(symbol); } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/NullabilityInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public readonly record struct NullabilityInfo { /// /// The nullable annotation of the expression represented by the syntax node. This represents /// the nullability of expressions that can be assigned to this expression, if this expression /// can be used as an lvalue. /// public NullableAnnotation Annotation { get; } /// /// The nullable flow state of the expression represented by the syntax node. This represents /// the compiler's understanding of whether this expression can currently contain null, if /// this expression can be used as an rvalue. /// public NullableFlowState FlowState { get; } public NullabilityInfo(NullableAnnotation annotation, NullableFlowState flowState) { Annotation = annotation; FlowState = flowState; } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/SyntaxTreeOptionsProviderWrapper.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace StyleCop.Analyzers.Lightup; public readonly struct SyntaxTreeOptionsProviderWrapper { internal const string WrappedTypeName = "Microsoft.CodeAnalysis.SyntaxTreeOptionsProvider"; private static readonly Type WrappedType; private static readonly TryGetValueAccessor TryGetDiagnosticValueAccessor; private static readonly TryGetValueAccessor TryGetGlobalDiagnosticValueAccessor; public object Instance { get; } static SyntaxTreeOptionsProviderWrapper() { WrappedType = WrapperHelper.GetWrappedType(typeof(SyntaxTreeOptionsProviderWrapper)); TryGetDiagnosticValueAccessor = LightupHelpers.CreateTryGetValueAccessor(WrappedType, typeof(SyntaxTree),typeof(string), typeof(CancellationToken), nameof(TryGetDiagnosticValue)); TryGetGlobalDiagnosticValueAccessor = LightupHelpers.CreateTryGetValueAccessor(WrappedType, typeof(string), typeof(CancellationToken), nameof(TryGetGlobalDiagnosticValue)); } private SyntaxTreeOptionsProviderWrapper(object instance) => Instance = instance; public static SyntaxTreeOptionsProviderWrapper FromObject(object instance) { if (instance is null) { return default; } else if (IsInstance(instance)) { return new SyntaxTreeOptionsProviderWrapper(instance); } else { throw new InvalidCastException($"Cannot cast '{instance.GetType().FullName}' to '{WrappedTypeName}'"); } } public static bool IsInstance(object obj) => obj is not null && LightupHelpers.CanWrapObject(obj, WrappedType); public bool TryGetDiagnosticValue(SyntaxTree tree, string diagnosticId, CancellationToken cancel, out ReportDiagnostic severity) { if (WrappedType is null) { severity = ReportDiagnostic.Default; return false; } else { return TryGetDiagnosticValueAccessor(Instance, tree, diagnosticId, cancel, out severity); } } public bool TryGetGlobalDiagnosticValue(string diagnosticId, CancellationToken cancel, out ReportDiagnostic severity) { if (WrappedType is null) { severity = ReportDiagnostic.Default; return false; } else { return TryGetGlobalDiagnosticValueAccessor(Instance, diagnosticId, cancel, out severity); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Sonar/TypeInfoExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static System.Linq.Expressions.Expression; namespace StyleCop.Analyzers.Lightup; public static class TypeInfoExtensions { private static readonly Func ConvertedNullabilityAccessor = CreateNullabilityAccessor(nameof(ConvertedNullability)); private static readonly Func NullabilityAccessor = CreateNullabilityAccessor(nameof(Nullability)); public static NullabilityInfo ConvertedNullability(this TypeInfo typeInfo) => ConvertedNullabilityAccessor(typeInfo); public static NullabilityInfo Nullability(this TypeInfo typeInfo) => NullabilityAccessor(typeInfo); private static Func CreateNullabilityAccessor(string propertyName) { var property = typeof(TypeInfo).GetProperty(propertyName); if (property is null) { return static _ => default; } Type nullableAnnotationType = typeof(NullableAnnotation), nullableFlowStateType = typeof(NullableFlowState); var typeInfoParameter = Parameter(typeof(TypeInfo), "typeInfo"); var intermediateResult = Variable(property.PropertyType); // local variable which holds the Roslyn NullabilityInfo // intermediateResult = typeInfo.{propertyName}; // return new Lightup.NullabilityInfo((Lightup.NullableAnnotation)intermediateResult.Annotation, (Lightup.NullableFlowState)intermediateResult.FlowState); var body = Block(variables: new[] { intermediateResult }, Assign(intermediateResult, Property(typeInfoParameter, propertyName)), New(typeof(NullabilityInfo).GetConstructor(new[] { nullableAnnotationType, nullableFlowStateType }), Convert(Property(intermediateResult, "Annotation"), nullableAnnotationType), // enum to enum conversion Convert(Property(intermediateResult, "FlowState"), nullableFlowStateType))); var expression = Lambda>(body, typeInfoParameter); return expression.Compile(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/SonarAnalyzer.ShimLayer.Lightup.csproj ================================================  netstandard2.0 NU1701 NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/StatementSyntaxExtensions.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; public static class StatementSyntaxExtensions { private static readonly Func> AttributeListsAccessor; private static readonly Func, StatementSyntax> WithAttributeListsAccessor; static StatementSyntaxExtensions() { AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(typeof(StatementSyntax), nameof(AttributeLists)); WithAttributeListsAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor>(typeof(StatementSyntax), nameof(AttributeLists)); } public static SyntaxList AttributeLists(this StatementSyntax syntax) { return AttributeListsAccessor(syntax); } public static StatementSyntax WithAttributeLists(this StatementSyntax syntax, SyntaxList attributeLists) { return WithAttributeListsAccessor(syntax, attributeLists); } public static StatementSyntax AddAttributeLists(this StatementSyntax syntax, params AttributeListSyntax[] items) { return syntax.WithAttributeLists(syntax.AttributeLists().AddRange(items)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/Syntax.xml ================================================ Provides the base class from which the classes that represent name syntax nodes are derived. This is an abstract class. SyntaxToken representing the identifier of the simple name. Provides the base class from which the classes that represent simple name syntax nodes are derived. This is an abstract class. SyntaxToken representing the keyword for the kind of the identifier name. Class which represents the syntax node for identifier name. Creates an IdentifierNameSyntax node. NameSyntax node representing the name on the left side of the dot token of the qualified name. SyntaxToken representing the dot. SimpleNameSyntax node representing the name on the right side of the dot token of the qualified name. Class which represents the syntax node for qualified name. Creates a QualifiedNameSyntax node. SyntaxToken representing the name of the identifier of the generic name. TypeArgumentListSyntax node representing the list of type arguments of the generic name. Class which represents the syntax node for generic name. Creates a GenericNameSyntax node. SyntaxToken representing less than. SeparatedSyntaxList of TypeSyntax node representing the type arguments. SyntaxToken representing greater than. Class which represents the syntax node for type argument list. Creates a TypeArgumentListSyntax node. IdentifierNameSyntax node representing the name of the alias SyntaxToken representing colon colon. SimpleNameSyntax node representing the name that is being alias qualified. Class which represents the syntax node for alias qualified name. Creates an AliasQualifiedNameSyntax node. Provides the base class from which the classes that represent type syntax nodes are derived. This is an abstract class. SyntaxToken which represents the keyword corresponding to the predefined type. Class which represents the syntax node for predefined types. Creates a PredefinedTypeSyntax node. TypeSyntax node representing the type of the element of the array. SyntaxList of ArrayRankSpecifierSyntax nodes representing the list of rank specifiers for the array. Class which represents the syntax node for the array type. Creates an ArrayTypeSyntax node. TypeSyntax node that represents the element type of the pointer. SyntaxToken representing the asterisk. Class which represents the syntax node for pointer type. Creates a PointerTypeSyntax node. SyntaxToken representing the delegate keyword. SyntaxToken representing the asterisk. Node representing the optional calling convention. List of the parameter types and return type of the function pointer. Function pointer parameter list syntax. SyntaxToken representing the less than token. SeparatedSyntaxList of ParameterSyntaxes representing the list of parameters and return type. SyntaxToken representing the greater than token. Function pointer calling convention syntax. SyntaxToken representing whether the calling convention is managed or unmanaged. Optional list of identifiers that will contribute to an unmanaged calling convention. Function pointer calling convention syntax. SyntaxToken representing open bracket. SeparatedSyntaxList of calling convention identifiers. SyntaxToken representing close bracket. Individual function pointer unmanaged calling convention. SyntaxToken representing the calling convention identifier. TypeSyntax node representing the type of the element. SyntaxToken representing the question mark. Class which represents the syntax node for a nullable type. Creates a NullableTypeSyntax node. SyntaxToken representing the open parenthesis. SyntaxToken representing the close parenthesis. Class which represents the syntax node for tuple type. Creates a TupleTypeSyntax node. Tuple type element. Gets the type of the tuple element. Gets the name of the tuple element. SyntaxToken representing the omitted type argument. Class which represents a placeholder in the type argument list of an unbound generic type. Creates an OmittedTypeArgumentSyntax node. The ref modifier of a method's return value or a local. Gets the optional "readonly" keyword. The 'scoped' modifier of a local. Provides the base class from which the classes that represent expression syntax nodes are derived. This is an abstract class. SyntaxToken representing the open parenthesis. ExpressionSyntax node representing the expression enclosed within the parenthesis. SyntaxToken representing the close parenthesis. Class which represents the syntax node for parenthesized expression. Creates a ParenthesizedExpressionSyntax node. SyntaxToken representing the open parenthesis. SeparatedSyntaxList of ArgumentSyntax representing the list of arguments. SyntaxToken representing the close parenthesis. Class which represents the syntax node for tuple expression. Creates a TupleExpressionSyntax node. " SyntaxToken representing the kind of the operator of the prefix unary expression. ExpressionSyntax representing the operand of the prefix unary expression. Class which represents the syntax node for prefix unary expression. Creates a PrefixUnaryExpressionSyntax node. SyntaxToken representing the kind "await" keyword. ExpressionSyntax representing the operand of the "await" operator. Class which represents the syntax node for an "await" expression. Creates an AwaitExpressionSyntax node. ExpressionSyntax representing the operand of the postfix unary expression. SyntaxToken representing the kind of the operator of the postfix unary expression. Class which represents the syntax node for postfix unary expression. Creates a PostfixUnaryExpressionSyntax node. ExpressionSyntax node representing the object that the member belongs to. SyntaxToken representing the kind of the operator in the member access expression. SimpleNameSyntax node representing the member being accessed. Class which represents the syntax node for member access expression. Creates a MemberAccessExpressionSyntax node. ExpressionSyntax node representing the object conditionally accessed. SyntaxToken representing the question mark. ExpressionSyntax node representing the access expression to be executed when the object is not null. Class which represents the syntax node for conditional access expression. Creates a ConditionalAccessExpressionSyntax node. SyntaxToken representing dot. SimpleNameSyntax node representing the member being bound to. Class which represents the syntax node for member binding expression. Creates a MemberBindingExpressionSyntax node. BracketedArgumentListSyntax node representing the list of arguments of the element binding expression. Class which represents the syntax node for element binding expression. Creates an ElementBindingExpressionSyntax node. ExpressionSyntax node representing the expression on the left of the range operator. SyntaxToken representing the operator of the range expression. ExpressionSyntax node representing the expression on the right of the range operator. Class which represents the syntax node for a range expression. Creates an RangeExpressionSyntax node. BracketedArgumentListSyntax node representing the list of arguments of the implicit element access expression. Class which represents the syntax node for implicit element access expression. Creates an ImplicitElementAccessSyntax node. ExpressionSyntax node representing the expression on the left of the binary operator. SyntaxToken representing the operator of the binary expression. ExpressionSyntax node representing the expression on the right of the binary operator. Class which represents an expression that has a binary operator. Creates a BinaryExpressionSyntax node. ExpressionSyntax node representing the expression on the left of the assignment operator. SyntaxToken representing the operator of the assignment expression. ExpressionSyntax node representing the expression on the right of the assignment operator. Class which represents an expression that has an assignment operator. Creates an AssignmentExpressionSyntax node. ExpressionSyntax node representing the condition of the conditional expression. SyntaxToken representing the question mark. ExpressionSyntax node representing the expression to be executed when the condition is true. SyntaxToken representing the colon. ExpressionSyntax node representing the expression to be executed when the condition is false. Class which represents the syntax node for conditional expression. Creates a ConditionalExpressionSyntax node. Provides the base class from which the classes that represent instance expression syntax nodes are derived. This is an abstract class. SyntaxToken representing the this keyword. Class which represents the syntax node for a this expression. Creates a ThisExpressionSyntax node. SyntaxToken representing the base keyword. Class which represents the syntax node for a base expression. Creates a BaseExpressionSyntax node. SyntaxToken representing the keyword corresponding to the kind of the literal expression. Class which represents the syntax node for a literal expression. Creates a LiteralExpressionSyntax node. SyntaxToken representing the MakeRefKeyword. SyntaxToken representing open parenthesis. Argument of the primary function. SyntaxToken representing close parenthesis. Class which represents the syntax node for MakeRef expression. Creates a MakeRefExpressionSyntax node. SyntaxToken representing the RefTypeKeyword. SyntaxToken representing open parenthesis. Argument of the primary function. SyntaxToken representing close parenthesis. Class which represents the syntax node for RefType expression. Creates a RefTypeExpressionSyntax node. SyntaxToken representing the RefValueKeyword. SyntaxToken representing open parenthesis. Typed reference expression. Comma separating the arguments. The type of the value. SyntaxToken representing close parenthesis. Class which represents the syntax node for RefValue expression. Creates a RefValueExpressionSyntax node. SyntaxToken representing the checked or unchecked keyword. SyntaxToken representing open parenthesis. Argument of the primary function. SyntaxToken representing close parenthesis. Class which represents the syntax node for Checked or Unchecked expression. Creates a CheckedExpressionSyntax node. SyntaxToken representing the DefaultKeyword. SyntaxToken representing open parenthesis. Argument of the primary function. SyntaxToken representing close parenthesis. Class which represents the syntax node for Default expression. Creates a DefaultExpressionSyntax node. SyntaxToken representing the TypeOfKeyword. SyntaxToken representing open parenthesis. The expression to return type of. SyntaxToken representing close parenthesis. Class which represents the syntax node for TypeOf expression. Creates a TypeOfExpressionSyntax node. SyntaxToken representing the SizeOfKeyword. SyntaxToken representing open parenthesis. Argument of the primary function. SyntaxToken representing close parenthesis. Class which represents the syntax node for SizeOf expression. Creates a SizeOfExpressionSyntax node. ExpressionSyntax node representing the expression part of the invocation. ArgumentListSyntax node representing the list of arguments of the invocation expression. Class which represents the syntax node for invocation expression. Creates an InvocationExpressionSyntax node. ExpressionSyntax node representing the expression which is accessing the element. BracketedArgumentListSyntax node representing the list of arguments of the element access expression. Class which represents the syntax node for element access expression. Creates an ElementAccessExpressionSyntax node. SeparatedSyntaxList of ArgumentSyntax nodes representing the list of arguments. Provides the base class from which the classes that represent argument list syntax nodes are derived. This is an abstract class. SyntaxToken representing open parenthesis. SeparatedSyntaxList of ArgumentSyntax representing the list of arguments. SyntaxToken representing close parenthesis. Class which represents the syntax node for the list of arguments. Creates an ArgumentListSyntax node. SyntaxToken representing open bracket. SeparatedSyntaxList of ArgumentSyntax representing the list of arguments. SyntaxToken representing close bracket. Class which represents the syntax node for bracketed argument list. Creates a BracketedArgumentListSyntax node. NameColonSyntax node representing the optional name arguments. SyntaxToken representing the optional ref or out keyword. ExpressionSyntax node representing the argument. Class which represents the syntax node for argument. Creates an ArgumentSyntax node. IdentifierNameSyntax representing the identifier name. SyntaxToken representing colon. Class which represents the syntax node for name colon syntax. Creates a NameColonSyntax node. Declaration representing the variable declared in an out parameter or deconstruction. Class which represents the syntax node for the variable declaration in an out var declaration or a deconstruction declaration. Creates a DeclarationExpression node. SyntaxToken representing the open parenthesis. TypeSyntax node representing the type to which the expression is being cast. SyntaxToken representing the close parenthesis. ExpressionSyntax node representing the expression that is being casted. Class which represents the syntax node for cast expression. Creates a CastExpressionSyntax node. Provides the base class from which the classes that represent anonymous function expressions are derived. BlockSyntax node representing the body of the anonymous function. Only one of Block or ExpressionBody will be non-null. ExpressionSyntax node representing the body of the anonymous function. Only one of Block or ExpressionBody will be non-null. SyntaxToken representing the delegate keyword. List of parameters of the anonymous method expression, or null if there no parameters are specified. BlockSyntax node representing the body of the anonymous function. This will never be null. Inherited from AnonymousFunctionExpressionSyntax, but not used for AnonymousMethodExpressionSyntax. This will always be null. Class which represents the syntax node for anonymous method expression. Creates an AnonymousMethodExpressionSyntax node. Provides the base class from which the classes that represent lambda expressions are derived. SyntaxToken representing equals greater than. ParameterSyntax node representing the parameter of the lambda expression. SyntaxToken representing equals greater than. BlockSyntax node representing the body of the lambda. Only one of Block or ExpressionBody will be non-null. ExpressionSyntax node representing the body of the lambda. Only one of Block or ExpressionBody will be non-null. Class which represents the syntax node for a simple lambda expression. Creates a SimpleLambdaExpressionSyntax node. ParameterListSyntax node representing the list of parameters for the lambda expression. SyntaxToken representing equals greater than. BlockSyntax node representing the body of the lambda. Only one of Block or ExpressionBody will be non-null. ExpressionSyntax node representing the body of the lambda. Only one of Block or ExpressionBody will be non-null. Class which represents the syntax node for parenthesized lambda expression. Creates a ParenthesizedLambdaExpressionSyntax node. SyntaxToken representing the open brace. SeparatedSyntaxList of ExpressionSyntax representing the list of expressions in the initializer expression. SyntaxToken representing the close brace. Class which represents the syntax node for initializer expression. Creates an InitializerExpressionSyntax node. SyntaxToken representing the new keyword. ArgumentListSyntax representing the list of arguments passed as part of the object creation expression. InitializerExpressionSyntax representing the initializer expression for the object being created. SyntaxToken representing the new keyword. ArgumentListSyntax representing the list of arguments passed as part of the object creation expression. InitializerExpressionSyntax representing the initializer expression for the object being created. Class which represents the syntax node for implicit object creation expression. Creates an ImplicitObjectCreationExpressionSyntax node. SyntaxToken representing the new keyword. TypeSyntax representing the type of the object being created. ArgumentListSyntax representing the list of arguments passed as part of the object creation expression. InitializerExpressionSyntax representing the initializer expression for the object being created. Class which represents the syntax node for object creation expression. Creates an ObjectCreationExpressionSyntax node. InitializerExpressionSyntax representing the initializer expression for the with expression. NameEqualsSyntax representing the optional name of the member being initialized. ExpressionSyntax representing the value the member is initialized with. Creates an AnonymousObjectMemberDeclaratorSyntax node. SyntaxToken representing the new keyword. SyntaxToken representing the open brace. SeparatedSyntaxList of AnonymousObjectMemberDeclaratorSyntax representing the list of object member initializers. SyntaxToken representing the close brace. Class which represents the syntax node for anonymous object creation expression. Creates an AnonymousObjectCreationExpressionSyntax node. SyntaxToken representing the new keyword. ArrayTypeSyntax node representing the type of the array. InitializerExpressionSyntax node representing the initializer of the array creation expression. Class which represents the syntax node for array creation expression. Creates an ArrayCreationExpressionSyntax node. SyntaxToken representing the new keyword. SyntaxToken representing the open bracket. SyntaxList of SyntaxToken representing the commas in the implicit array creation expression. SyntaxToken representing the close bracket. InitializerExpressionSyntax representing the initializer expression of the implicit array creation expression. Class which represents the syntax node for implicit array creation expression. Creates an ImplicitArrayCreationExpressionSyntax node. SyntaxToken representing the stackalloc keyword. TypeSyntax node representing the type of the stackalloc array. InitializerExpressionSyntax node representing the initializer of the stackalloc array creation expression. Class which represents the syntax node for stackalloc array creation expression. Creates a StackAllocArrayCreationExpressionSyntax node. SyntaxToken representing the stackalloc keyword. SyntaxToken representing the open bracket. SyntaxToken representing the close bracket. InitializerExpressionSyntax representing the initializer expression of the implicit stackalloc array creation expression. Class which represents the syntax node for implicit stackalloc array creation expression. Creates an ImplicitStackAllocArrayCreationExpressionSyntax node. SeparatedSyntaxList of CollectionElementSyntax representing the list of elements in the collection expression. Gets the identifier. Gets the identifier. Gets the identifier. Gets the identifier. Gets the identifier. SyntaxToken representing the omitted array size expression. Class which represents a placeholder in an array size list. Creates an OmittedArraySizeExpressionSyntax node. The first part of an interpolated string, $" or $@" or $""" List of parts of the interpolated string, each one is either a literal part or an interpolation. The closing quote of the interpolated string. ExpressionSyntax node representing the expression on the left of the "is" operator. PatternSyntax node representing the pattern on the right of the "is" operator. Class which represents a simple pattern-matching expression using the "is" keyword. Creates an IsPatternExpressionSyntax node. ExpressionSyntax node representing the constant expression. SyntaxToken representing the operator of the relational pattern. The type for the type pattern. The text contents of a part of the interpolated string. This could be a single { or multiple in a row (in the case of an interpolation in a raw interpolated string). This could be a single } or multiple in a row (in the case of an interpolation in a raw interpolated string). The text contents of the format specifier for an interpolation. Always empty on a global statement. Always empty on a global statement. Represents the base class for all statements syntax classes. Gets the identifier. Gets the optional semicolon token. Gets the modifier list. Gets the identifier. Gets the identifier. Gets a SyntaxToken that represents the colon following the statement's label. Represents a labeled statement syntax. Creates a LabeledStatementSyntax node Gets a SyntaxToken that represents the goto keyword. Gets a SyntaxToken that represents the case or default keywords if any exists. Gets a constant expression for a goto case statement. Gets a SyntaxToken that represents the semi-colon at the end of the statement. Represents a goto statement syntax Creates a GotoStatementSyntax node. Gets the identifier. The variable(s) of the loop. In correct code this is a tuple literal, declaration expression with a tuple designator, or a discard syntax in the form of a simple identifier. In broken code it could be something else. Gets a SyntaxToken that represents the if keyword. Gets a SyntaxToken that represents the open parenthesis before the if statement's condition expression. Gets an ExpressionSyntax that represents the condition of the if statement. Gets a SyntaxToken that represents the close parenthesis after the if statement's condition expression. Gets a StatementSyntax the represents the statement to be executed when the condition is true. Gets an ElseClauseSyntax that represents the statement to be executed when the condition is false if such statement exists. Represents an if statement syntax. Creates an IfStatementSyntax node Gets a syntax token Represents an else statement syntax. Creates a ElseClauseSyntax node Gets a SyntaxToken that represents the switch keyword. Gets a SyntaxToken that represents the open parenthesis preceding the switch governing expression. Gets an ExpressionSyntax representing the expression of the switch statement. Gets a SyntaxToken that represents the close parenthesis following the switch governing expression. Gets a SyntaxToken that represents the open braces preceding the switch sections. Gets a SyntaxList of SwitchSectionSyntax's that represents the switch sections of the switch statement. Gets a SyntaxToken that represents the open braces following the switch sections. Represents a switch statement syntax. Creates a SwitchStatementSyntax node. Gets a SyntaxList of SwitchLabelSyntax's the represents the possible labels that control can transfer to within the section. Gets a SyntaxList of StatementSyntax's the represents the statements to be executed when control transfer to a label the belongs to the section. Represents a switch section syntax of a switch statement. Creates a SwitchSectionSyntax node. Gets a SyntaxToken that represents a case or default keyword that belongs to a switch label. Gets a SyntaxToken that represents the colon that terminates the switch label. Represents a switch label within a switch statement. Gets the case keyword token. Gets a PatternSyntax that represents the pattern that gets matched for the case label. Represents a case label within a switch statement. Creates a CaseMatchLabelSyntax node. Gets the case keyword token. Gets an ExpressionSyntax that represents the constant expression that gets matched for the case label. Represents a case label within a switch statement. Creates a CaseSwitchLabelSyntax node. Gets the default keyword token. Represents a default label within a switch statement. Creates a DefaultSwitchLabelSyntax node. Gets the attribute declaration list. SyntaxToken representing the extern keyword. SyntaxToken representing the alias keyword. Gets the identifier. SyntaxToken representing the semicolon token. Represents an ExternAlias directive syntax, e.g. "extern alias MyAlias;" with specifying "/r:MyAlias=SomeAssembly.dll " on the compiler command line. Creates an ExternAliasDirectiveSyntax node Member declaration syntax. Gets the attribute declaration list. Gets the modifier list. Gets the optional semicolon token. Class representing one or more attributes applied to a language construct. Gets the open bracket token. Gets the optional construct targeted by the attribute. Gets the attribute declaration list. Gets the close bracket token. Class representing what language construct an attribute targets. Gets the identifier. Gets the colon token. Attribute syntax. Gets the name. Attribute argument list syntax. Gets the open paren token. Gets the arguments syntax list. Gets the close paren token. Attribute argument syntax. Gets the expression. Class representing an identifier name followed by an equals token. Gets the identifier name. Type parameter list syntax. Gets the < token. Gets the parameter list. Gets the > token. Type parameter syntax. Gets the attribute declaration list. Gets the identifier. Base class for type declaration syntax. Gets the identifier. Gets the base type list. Gets the open brace token. Gets the close brace token. Gets the optional semicolon token. Base class for type declaration syntax (class, struct, interface, record). Gets the type keyword token ("class", "struct", "interface", "record"). Gets the type constraint list. Gets the member declarations. Class type declaration syntax. Gets the class keyword token. Struct type declaration syntax. Gets the struct keyword token. Interface type declaration syntax. Gets the interface keyword token. Enum type declaration syntax. Gets the enum keyword token. Gets the members declaration list. Gets the optional semicolon token. Delegate declaration syntax. Gets the "delegate" keyword. Gets the return type. Gets the identifier. Gets the parameter list. Gets the constraint clause list. Gets the semicolon token. Gets the identifier. Base list syntax. Gets the colon token. Gets the base type references. Provides the base class from which the classes that represent base type syntax nodes are derived. This is an abstract class. Type parameter constraint clause. Gets the identifier. Gets the colon token. Gets the constraints list. Base type for type parameter constraint syntax. Constructor constraint syntax. Gets the "new" keyword. Gets the open paren keyword. Gets the close paren keyword. Class or struct constraint syntax. Gets the constraint keyword ("class" or "struct"). SyntaxToken representing the question mark. Type constraint syntax. Gets the type syntax. Default constraint syntax. Gets the "default" keyword. The allows type parameter constraint clause. Gets the constraints list. Base type for allow constraint syntax. Ref struct constraint syntax. Gets the "ref" keyword. Gets the "struct" keyword. Base type for method declaration syntax. Gets the parameter list. Gets the optional semicolon token. Method declaration syntax. Gets the return type syntax. Gets the identifier. Gets the constraint clause list. Gets the optional semicolon token. Operator declaration syntax. Gets the return type. Gets the "operator" keyword. Gets the "checked" keyword. Gets the operator token. Gets the optional semicolon token. Conversion operator declaration syntax. Gets the "implicit" or "explicit" token. Gets the "operator" token. Gets the "checked" keyword. Gets the type. Gets the optional semicolon token. Constructor declaration syntax. Gets the identifier. Gets the optional semicolon token. Constructor initializer syntax. Gets the colon token. Gets the "this" or "base" keyword. Destructor declaration syntax. Gets the tilde token. Gets the identifier. Gets the optional semicolon token. Base type for property declaration syntax. Gets the type syntax. Gets the optional explicit interface specifier. Gets the identifier. The syntax for the expression body of an expression-bodied member. Gets the identifier. Gets the parameter list. Gets the attribute declaration list. Gets the modifier list. Gets the keyword token, or identifier if an erroneous accessor declaration. Gets the optional body block which may be empty, but it is null if there are no braces. Gets the optional expression body. Gets the optional semicolon token. Base type for parameter list syntax. Gets the parameter list. Parameter list syntax. Gets the open paren token. Gets the close paren token. Parameter list syntax with surrounding brackets. Gets the open bracket token. Gets the close bracket token. Base parameter syntax. Gets the attribute declaration list. Gets the modifier list. Parameter syntax. Gets the attribute declaration list. Gets the modifier list. Gets the identifier. Parameter syntax. Gets the attribute declaration list. Gets the modifier list. A symbol referenced by a cref attribute (e.g. in a <see> or <seealso> documentation comment tag). For example, the M in <see cref="M" />. A symbol reference that definitely refers to a type. For example, "int", "A::B", "A.B", "A<T>", but not "M()" (has parameter list) or "this" (indexer). NOTE: TypeCrefSyntax, QualifiedCrefSyntax, and MemberCrefSyntax overlap. The syntax in a TypeCrefSyntax will always be bound as type, so it's safer to use QualifiedCrefSyntax or MemberCrefSyntax if the symbol might be a non-type member. A symbol reference to a type or non-type member that is qualified by an enclosing type or namespace. For example, cref="System.String.ToString()". NOTE: TypeCrefSyntax, QualifiedCrefSyntax, and MemberCrefSyntax overlap. The syntax in a TypeCrefSyntax will always be bound as type, so it's safer to use QualifiedCrefSyntax or MemberCrefSyntax if the symbol might be a non-type member. The unqualified part of a CrefSyntax. For example, "ToString()" in "object.ToString()". NOTE: TypeCrefSyntax, QualifiedCrefSyntax, and MemberCrefSyntax overlap. The syntax in a TypeCrefSyntax will always be bound as type, so it's safer to use QualifiedCrefSyntax or MemberCrefSyntax if the symbol might be a non-type member. A MemberCrefSyntax specified by a name (an identifier, predefined type keyword, or an alias-qualified name, with an optional type parameter list) and an optional parameter list. For example, "M", "M<T>" or "M(int)". Also, "A::B()" or "string()". A MemberCrefSyntax specified by a this keyword and an optional parameter list. For example, "this" or "this[int]". A MemberCrefSyntax specified by an operator keyword, an operator symbol and an optional parameter list. For example, "operator +" or "operator -[int]". NOTE: the operator must be overloadable. Gets the operator token. A MemberCrefSyntax specified by an implicit or explicit keyword, an operator keyword, a destination type, and an optional parameter list. For example, "implicit operator int" or "explicit operator MyType(int)". A list of cref parameters with surrounding punctuation. Unlike regular parameters, cref parameters do not have names. Gets the parameter list. A parenthesized list of cref parameters. Gets the open paren token. Gets the close paren token. A bracketed list of cref parameters. Gets the open bracket token. Gets the close bracket token. An element of a BaseCrefParameterListSyntax. Unlike a regular parameter, a cref parameter has only an optional ref, in, out keyword, an optional readonly keyword, and a type - there is no name and there are no attributes or other modifiers. ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/SyntaxFactoryEx.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; public static class SyntaxFactoryEx { private static readonly Func, CSharpSyntaxNode> PositionalPatternClauseAccessor1; private static readonly Func, SyntaxToken, CSharpSyntaxNode> PositionalPatternClauseAccessor2; private static readonly Func, CSharpSyntaxNode> PropertyPatternClauseAccessor1; private static readonly Func, SyntaxToken, CSharpSyntaxNode> PropertyPatternClauseAccessor2; private static readonly Func TupleElementAccessor1; private static readonly Func TupleElementAccessor2; private static readonly Func, ExpressionSyntax> TupleExpressionAccessor1; private static readonly Func, SyntaxToken, ExpressionSyntax> TupleExpressionAccessor2; private static readonly Func, TypeSyntax> TupleTypeAccessor1; private static readonly Func, SyntaxToken, TypeSyntax> TupleTypeAccessor2; static SyntaxFactoryEx() { var positionalPatternClauseMethods = typeof(SyntaxFactory).GetTypeInfo().GetDeclaredMethods(nameof(PositionalPatternClause)); var positionalPatternClauseMethod = positionalPatternClauseMethods.FirstOrDefault(method => method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(SubpatternSyntaxWrapper)))); if (positionalPatternClauseMethod is object) { var subpatternsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "subpatterns"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, CSharpSyntaxNode>> expression = Expression.Lambda, CSharpSyntaxNode>>( Expression.Call( positionalPatternClauseMethod, Expression.Convert( Expression.Call(subpatternsParameter, underlyingListProperty.GetMethod), positionalPatternClauseMethod.GetParameters()[0].ParameterType)), subpatternsParameter); PositionalPatternClauseAccessor1 = expression.Compile(); } else { PositionalPatternClauseAccessor1 = ThrowNotSupportedOnFallback, CSharpSyntaxNode>(nameof(SyntaxFactory), nameof(PositionalPatternClause)); } positionalPatternClauseMethod = positionalPatternClauseMethods.FirstOrDefault(method => method.GetParameters().Length == 3 && method.GetParameters()[0].ParameterType == typeof(SyntaxToken) && method.GetParameters()[1].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(SubpatternSyntaxWrapper))) && method.GetParameters()[2].ParameterType == typeof(SyntaxToken)); if (positionalPatternClauseMethod is object) { var openParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "openParenToken"); var subpatternsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "subpatterns"); var closeParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "closeParenToken"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, SyntaxToken, CSharpSyntaxNode>> expression = Expression.Lambda, SyntaxToken, CSharpSyntaxNode>>( Expression.Call( positionalPatternClauseMethod, openParenTokenParameter, Expression.Convert( Expression.Call(subpatternsParameter, underlyingListProperty.GetMethod), positionalPatternClauseMethod.GetParameters()[1].ParameterType), closeParenTokenParameter), openParenTokenParameter, subpatternsParameter, closeParenTokenParameter); PositionalPatternClauseAccessor2 = expression.Compile(); } else { PositionalPatternClauseAccessor2 = ThrowNotSupportedOnFallback, SyntaxToken, TypeSyntax>(nameof(SyntaxFactory), nameof(PositionalPatternClause)); } var propertyPatternClauseMethods = typeof(SyntaxFactory).GetTypeInfo().GetDeclaredMethods(nameof(PropertyPatternClause)); var propertyPatternClauseMethod = propertyPatternClauseMethods.FirstOrDefault(method => method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(SubpatternSyntaxWrapper)))); if (propertyPatternClauseMethod is object) { var subpatternsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "subpatterns"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, CSharpSyntaxNode>> expression = Expression.Lambda, CSharpSyntaxNode>>( Expression.Call( propertyPatternClauseMethod, Expression.Convert( Expression.Call(subpatternsParameter, underlyingListProperty.GetMethod), propertyPatternClauseMethod.GetParameters()[0].ParameterType)), subpatternsParameter); PropertyPatternClauseAccessor1 = expression.Compile(); } else { PropertyPatternClauseAccessor1 = ThrowNotSupportedOnFallback, CSharpSyntaxNode>(nameof(SyntaxFactory), nameof(PropertyPatternClause)); } propertyPatternClauseMethod = propertyPatternClauseMethods.FirstOrDefault(method => method.GetParameters().Length == 3 && method.GetParameters()[0].ParameterType == typeof(SyntaxToken) && method.GetParameters()[1].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(SubpatternSyntaxWrapper))) && method.GetParameters()[2].ParameterType == typeof(SyntaxToken)); if (propertyPatternClauseMethod is object) { var openBraceTokenParameter = Expression.Parameter(typeof(SyntaxToken), "openBraceToken"); var subpatternsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "subpatterns"); var closeBraceTokenParameter = Expression.Parameter(typeof(SyntaxToken), "closeBraceToken"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, SyntaxToken, CSharpSyntaxNode>> expression = Expression.Lambda, SyntaxToken, CSharpSyntaxNode>>( Expression.Call( propertyPatternClauseMethod, openBraceTokenParameter, Expression.Convert( Expression.Call(subpatternsParameter, underlyingListProperty.GetMethod), propertyPatternClauseMethod.GetParameters()[1].ParameterType), closeBraceTokenParameter), openBraceTokenParameter, subpatternsParameter, closeBraceTokenParameter); PropertyPatternClauseAccessor2 = expression.Compile(); } else { PropertyPatternClauseAccessor2 = ThrowNotSupportedOnFallback, SyntaxToken, TypeSyntax>(nameof(SyntaxFactory), nameof(PropertyPatternClause)); } var tupleElementMethods = typeof(SyntaxFactory).GetTypeInfo().GetDeclaredMethods(nameof(TupleElement)); var tupleElementMethod = tupleElementMethods.FirstOrDefault(method => method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(TypeSyntax)); if (tupleElementMethod is object) { var typeParameter = Expression.Parameter(typeof(TypeSyntax), "type"); Expression> expression = Expression.Lambda>( Expression.Call(tupleElementMethod, typeParameter), typeParameter); TupleElementAccessor1 = expression.Compile(); } else { TupleElementAccessor1 = ThrowNotSupportedOnFallback(nameof(SyntaxFactory), nameof(TupleElement)); } tupleElementMethod = tupleElementMethods.FirstOrDefault(method => method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(TypeSyntax) && method.GetParameters()[1].ParameterType == typeof(SyntaxToken)); if (tupleElementMethod is object) { var typeParameter = Expression.Parameter(typeof(TypeSyntax), "type"); var identifierParameter = Expression.Parameter(typeof(SyntaxToken), "identifier"); Expression> expression = Expression.Lambda>( Expression.Call(tupleElementMethod, typeParameter, identifierParameter), typeParameter, identifierParameter); TupleElementAccessor2 = expression.Compile(); } else { TupleElementAccessor2 = ThrowNotSupportedOnFallback(nameof(SyntaxFactory), nameof(TupleElement)); } var tupleExpressionMethods = typeof(SyntaxFactory).GetTypeInfo().GetDeclaredMethods(nameof(TupleExpression)); var tupleExpressionMethod = tupleExpressionMethods.FirstOrDefault(method => method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(SeparatedSyntaxList)); if (tupleExpressionMethod is object) { var argumentsParameter = Expression.Parameter(typeof(SeparatedSyntaxList), "arguments"); Expression, ExpressionSyntax>> expression = Expression.Lambda, ExpressionSyntax>>( Expression.Call(tupleExpressionMethod, argumentsParameter), argumentsParameter); TupleExpressionAccessor1 = expression.Compile(); } else { TupleExpressionAccessor1 = ThrowNotSupportedOnFallback, ExpressionSyntax>(nameof(SyntaxFactory), nameof(TupleExpression)); } tupleExpressionMethod = tupleExpressionMethods.FirstOrDefault(method => method.GetParameters().Length == 3 && method.GetParameters()[0].ParameterType == typeof(SyntaxToken) && method.GetParameters()[1].ParameterType == typeof(SeparatedSyntaxList) && method.GetParameters()[2].ParameterType == typeof(SyntaxToken)); if (tupleExpressionMethod is object) { var openParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "openParenToken"); var argumentsParameter = Expression.Parameter(typeof(SeparatedSyntaxList), "arguments"); var closeParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "closeParenToken"); Expression, SyntaxToken, ExpressionSyntax>> expression = Expression.Lambda, SyntaxToken, ExpressionSyntax>>( Expression.Call(tupleExpressionMethod, openParenTokenParameter, argumentsParameter, closeParenTokenParameter), openParenTokenParameter, argumentsParameter, closeParenTokenParameter); TupleExpressionAccessor2 = expression.Compile(); } else { TupleExpressionAccessor2 = ThrowNotSupportedOnFallback, SyntaxToken, ExpressionSyntax>(nameof(SyntaxFactory), nameof(TupleExpression)); } var tupleTypeMethods = typeof(SyntaxFactory).GetTypeInfo().GetDeclaredMethods(nameof(TupleType)); var tupleTypeMethod = tupleTypeMethods.FirstOrDefault(method => method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(TupleElementSyntaxWrapper)))); if (tupleTypeMethod is object) { var elementsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "elements"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, TypeSyntax>> expression = Expression.Lambda, TypeSyntax>>( Expression.Call( tupleTypeMethod, Expression.Convert( Expression.Call(elementsParameter, underlyingListProperty.GetMethod), tupleTypeMethod.GetParameters()[0].ParameterType)), elementsParameter); TupleTypeAccessor1 = expression.Compile(); } else { TupleTypeAccessor1 = ThrowNotSupportedOnFallback, TypeSyntax>(nameof(SyntaxFactory), nameof(TupleType)); } tupleTypeMethod = tupleTypeMethods.FirstOrDefault(method => method.GetParameters().Length == 3 && method.GetParameters()[0].ParameterType == typeof(SyntaxToken) && method.GetParameters()[1].ParameterType == typeof(SeparatedSyntaxList<>).MakeGenericType(SyntaxWrapperHelper.GetWrappedType(typeof(TupleElementSyntaxWrapper))) && method.GetParameters()[2].ParameterType == typeof(SyntaxToken)); if (tupleTypeMethod is object) { var openParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "openParenToken"); var elementsParameter = Expression.Parameter(typeof(SeparatedSyntaxListWrapper), "elements"); var closeParenTokenParameter = Expression.Parameter(typeof(SyntaxToken), "closeParenToken"); var underlyingListProperty = typeof(SeparatedSyntaxListWrapper).GetTypeInfo().GetDeclaredProperty(nameof(SeparatedSyntaxListWrapper.UnderlyingList)); Expression, SyntaxToken, TypeSyntax>> expression = Expression.Lambda, SyntaxToken, TypeSyntax>>( Expression.Call( tupleTypeMethod, openParenTokenParameter, Expression.Convert( Expression.Call(elementsParameter, underlyingListProperty.GetMethod), tupleTypeMethod.GetParameters()[1].ParameterType), closeParenTokenParameter), openParenTokenParameter, elementsParameter, closeParenTokenParameter); TupleTypeAccessor2 = expression.Compile(); } else { TupleTypeAccessor2 = ThrowNotSupportedOnFallback, SyntaxToken, TypeSyntax>(nameof(SyntaxFactory), nameof(TupleType)); } } public static PositionalPatternClauseSyntaxWrapper PositionalPatternClause(SeparatedSyntaxListWrapper subpatterns = default) { return (PositionalPatternClauseSyntaxWrapper)PositionalPatternClauseAccessor1(subpatterns); } public static PositionalPatternClauseSyntaxWrapper PositionalPatternClause(SyntaxToken openParenToken, SeparatedSyntaxListWrapper subpatterns, SyntaxToken closeParenToken) { return (PositionalPatternClauseSyntaxWrapper)PositionalPatternClauseAccessor2(openParenToken, subpatterns, closeParenToken); } public static PropertyPatternClauseSyntaxWrapper PropertyPatternClause(SeparatedSyntaxListWrapper subpatterns = default) { return (PropertyPatternClauseSyntaxWrapper)PropertyPatternClauseAccessor1(subpatterns); } public static PropertyPatternClauseSyntaxWrapper PropertyPatternClause(SyntaxToken openBraceToken, SeparatedSyntaxListWrapper subpatterns, SyntaxToken closeBraceToken) { return (PropertyPatternClauseSyntaxWrapper)PropertyPatternClauseAccessor2(openBraceToken, subpatterns, closeBraceToken); } public static TupleElementSyntaxWrapper TupleElement(TypeSyntax type) { return (TupleElementSyntaxWrapper)TupleElementAccessor1(type); } public static TupleElementSyntaxWrapper TupleElement(TypeSyntax type, SyntaxToken identifier) { return (TupleElementSyntaxWrapper)TupleElementAccessor2(type, identifier); } public static TupleExpressionSyntaxWrapper TupleExpression(SeparatedSyntaxList arguments = default) { return (TupleExpressionSyntaxWrapper)TupleExpressionAccessor1(arguments); } public static TupleExpressionSyntaxWrapper TupleExpression(SyntaxToken openParenToken, SeparatedSyntaxList arguments, SyntaxToken closeParenToken) { return (TupleExpressionSyntaxWrapper)TupleExpressionAccessor2(openParenToken, arguments, closeParenToken); } public static TupleTypeSyntaxWrapper TupleType(SeparatedSyntaxListWrapper elements = default) { return (TupleTypeSyntaxWrapper)TupleTypeAccessor1(elements); } public static TupleTypeSyntaxWrapper TupleType(SyntaxToken openParenToken, SeparatedSyntaxListWrapper elements, SyntaxToken closeParenToken) { return (TupleTypeSyntaxWrapper)TupleTypeAccessor2(openParenToken, elements, closeParenToken); } private static Func ThrowNotSupportedOnFallback(string typeName, string methodName) { return _ => throw new NotSupportedException($"{typeName}.{methodName} is not supported in this version"); } private static Func ThrowNotSupportedOnFallback(string typeName, string methodName) { return (_, __) => throw new NotSupportedException($"{typeName}.{methodName} is not supported in this version"); } private static Func ThrowNotSupportedOnFallback(string typeName, string methodName) { return (arg1, arg2, arg3) => throw new NotSupportedException($"{typeName}.{methodName} is not supported in this version"); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/SyntaxFactsEx.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; public static class SyntaxFactsEx { private static readonly Func TryGetInferredMemberNameAccessor; private static readonly Func IsReservedTupleElementNameAccessor; static SyntaxFactsEx() { string FallbackAccessor(SyntaxNode syntax) { if (syntax == null) { // Unlike an extension method which would throw ArgumentNullException here, the light-up // behavior needs to match behavior of the underlying property. throw new NullReferenceException(); } return null; } var tryGetInferredMemberNameMethod = typeof(SyntaxFacts).GetTypeInfo().GetDeclaredMethod(nameof(TryGetInferredMemberName)); if (tryGetInferredMemberNameMethod is object) { var syntaxParameter = Expression.Parameter(typeof(SyntaxNode), "syntax"); Expression> expression = Expression.Lambda>( Expression.Call(tryGetInferredMemberNameMethod, syntaxParameter), syntaxParameter); TryGetInferredMemberNameAccessor = expression.Compile(); } else { TryGetInferredMemberNameAccessor = FallbackAccessor; } var isReservedTupleElementNameMethod = typeof(SyntaxFacts).GetTypeInfo().GetDeclaredMethod(nameof(IsReservedTupleElementName)); if (isReservedTupleElementNameMethod is object) { var elementNameParameter = Expression.Parameter(typeof(string), "elementName"); Expression> expression = Expression.Lambda>( Expression.Call(isReservedTupleElementNameMethod, elementNameParameter), elementNameParameter); IsReservedTupleElementNameAccessor = expression.Compile(); } else { IsReservedTupleElementNameAccessor = _ => false; } } public static string TryGetInferredMemberName(this SyntaxNode syntax) { return TryGetInferredMemberNameAccessor(syntax); } public static bool IsReservedTupleElementName(string elementName) { return IsReservedTupleElementNameAccessor(elementName); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/SyntaxWrapper`1.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.CodeAnalysis; public abstract class SyntaxWrapper { public static SyntaxWrapper Default { get; } = FindDefaultSyntaxWrapper(); public abstract TNode Wrap(SyntaxNode node); public abstract SyntaxNode Unwrap(TNode node); private static SyntaxWrapper FindDefaultSyntaxWrapper() { if (typeof(SyntaxNode).GetTypeInfo().IsAssignableFrom(typeof(TNode).GetTypeInfo())) { return new DirectCastSyntaxWrapper(); } return new ConversionSyntaxWrapper(); } private sealed class DirectCastSyntaxWrapper : SyntaxWrapper { public override SyntaxNode Unwrap(TNode node) { return (SyntaxNode)(object)node; } public override TNode Wrap(SyntaxNode node) { return (TNode)(object)node; } } private sealed class ConversionSyntaxWrapper : SyntaxWrapper { private readonly Func unwrapAccessor; private readonly Func wrapAccessor; public ConversionSyntaxWrapper() { this.unwrapAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(typeof(TNode), nameof(ISyntaxWrapper.SyntaxNode)); var explicitOperator = typeof(TNode).GetTypeInfo().GetDeclaredMethods("op_Explicit") .Single(m => m.ReturnType == typeof(TNode) && m.GetParameters()[0].ParameterType == typeof(SyntaxNode)); var syntaxParameter = Expression.Parameter(typeof(SyntaxNode), "syntax"); Expression> wrapAccessorExpression = Expression.Lambda>( Expression.Call(explicitOperator, syntaxParameter), syntaxParameter); this.wrapAccessor = wrapAccessorExpression.Compile(); } public override SyntaxNode Unwrap(TNode node) { return this.unwrapAccessor(node); } public override TNode Wrap(SyntaxNode node) { return this.wrapAccessor(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/TryGetValueAccessor`3.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { internal delegate bool TryGetValueAccessor(T instance, TKey key, out TValue value); internal delegate bool TryGetValueAccessor(T instance, TFirst first, TSecond second, out TValue value); // Sonar internal delegate bool TryGetValueAccessor(T instance, TFirst first, TSecond second, TThird third, out TValue value); // Sonar } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/WrapperHelper.cs ================================================ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #nullable disable namespace StyleCop.Analyzers.Lightup { using System; using System.Collections.Immutable; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; public static class WrapperHelper { private static readonly ImmutableDictionary WrappedTypes; static WrapperHelper() { var codeAnalysisAssembly = typeof(SyntaxNode).GetTypeInfo().Assembly; var builder = ImmutableDictionary.CreateBuilder(); builder.Add(typeof(AnalyzerConfigOptionsProviderWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsProviderWrapper.WrappedTypeName)); builder.Add(typeof(AnalyzerConfigOptionsWrapper), codeAnalysisAssembly.GetType(AnalyzerConfigOptionsWrapper.WrappedTypeName)); builder.Add(typeof(CompilationOptionsWrapper), codeAnalysisAssembly.GetType(CompilationOptionsWrapper.WrappedTypeName)); // Sonar builder.Add(typeof(SyntaxTreeOptionsProviderWrapper), codeAnalysisAssembly.GetType(SyntaxTreeOptionsProviderWrapper.WrappedTypeName)); // Sonar WrappedTypes = builder.ToImmutable(); } /// /// Gets the type that is wrapped by the given wrapper. /// /// Type of the wrapper for which the wrapped type should be retrieved. /// The wrapped type, or null if there is no info. internal static Type GetWrappedType(Type wrapperType) { if (WrappedTypes.TryGetValue(wrapperType, out Type wrappedType)) { return wrappedType; } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.ShimLayer.Lightup/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.Composition": { "type": "Direct", "requested": "[1.0.27, )", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.SourceGenerators/RuleCatalogGenerator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; namespace SonarAnalyzer.SourceGenerators; [Generator] [ExcludeFromCodeCoverage] public class RuleCatalogGenerator : IIncrementalGenerator { private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(200); private static readonly Regex HtmlTagRegex = new("<[^>]*>", RegexOptions.None, RegexTimeout); private static readonly Regex MultipleWhitespaceRegex = new(@"\s{2,}", RegexOptions.None, RegexTimeout); public void Initialize(IncrementalGeneratorInitializationContext context) { // IIncrementalGenerator transformers should return types with value equality or use a custom comparer via WithComparer to avoid performance issues. // This implementation doesn't do it because it would make the implementation much more complex and performance is not a concern for this generator. var jsonFiles = RspecFiles(context, "json"); var htmlFiles = RspecFiles(context, "html"); var qualityProfile = RspecFiles(context, "qualityprofile").Collect().Select((x, ct) => CancelableExecute(() => ParseSonarWay(x.Single().GetText().ToString()), ct)); var filePair = jsonFiles.Combine(htmlFiles.Collect()) .Select((x, ct) => CancelableExecute(() => (Json: x.Left, Html: x.Right.Single(html => html.Path == Path.ChangeExtension(x.Left.Path, ".html"))), ct)); var ruleDescriptorArguments = filePair.Combine(qualityProfile).Select((x, ct) => CancelableExecute(() => RuleDescriptorArguments(x.Left.Json, x.Left.Html, x.Right), ct)).Collect(); var rootNamespace = context.AnalyzerConfigOptionsProvider .Select((x, ct) => CancelableExecute(() => x.GlobalOptions.TryGetValue("build_property.rootnamespace", out var rootNamespace) ? rootNamespace : null, ct)); var source = ruleDescriptorArguments.Combine(rootNamespace).Select((x, ct) => CancelableExecute(() => GenerateSource(x.Right, x.Left), ct)); context.RegisterSourceOutput(source, (context, source) => context.AddSource("RuleCatalog.g.cs", source)); } private static T CancelableExecute(Func func, CancellationToken ct) { if (ct.IsCancellationRequested) { return default; } return func(); } private static IncrementalValuesProvider RspecFiles(IncrementalGeneratorInitializationContext context, string fileType) => context.AdditionalTextsProvider .Combine(context.AnalyzerConfigOptionsProvider) .Where(x => x.Right.GetOptions(x.Left).TryGetValue("build_metadata.AdditionalFiles.RspecFile", out var value) && value == fileType) .Select((x, _) => x.Left); private static string[] RuleDescriptorArguments(AdditionalText jsonFile, AdditionalText htmlFile, ImmutableHashSet sonarWay) { var json = JObject.Parse(jsonFile.GetText().ToString()); var id = Path.GetFileNameWithoutExtension(jsonFile.Path); var html = htmlFile.GetText().ToString(); return [ Encode(id), Encode(json.Value("title")), Encode(json.Value("type")), Encode(json.Value("defaultSeverity")), Encode(json.Value("status")), $"SourceScope.{json.Value("scope")}", sonarWay.Contains(id).ToString().ToLower(), Encode(FirstParagraphText(id, html)) ]; } private static string GenerateSource(string namespacePrefix, ImmutableArray rulesArguments) { var sb = new StringBuilder(); sb.AppendLine($$""" // namespace {{namespacePrefix}}.Rspec; public static class RuleCatalog { public static Dictionary Rules { get; } = new() { """); foreach (var arguments in rulesArguments) { sb.AppendLine($@" {{ {arguments[0]}, new({string.Join(", ", arguments)}) }},"); } sb.AppendLine(""" }; } """); return sb.ToString(); } private static string Encode(string value) => $@"@""{value.Replace(@"""", @"""""")}"""; private static string FirstParagraphText(string id, string html) { var startIndex = html.IndexOf("

"); if (startIndex >= 0) { startIndex += 3; // end of

var endIndex = html.IndexOf("

", startIndex); var text = html.Substring(startIndex, endIndex - startIndex); text = HtmlTagRegex.Replace(text, string.Empty); text = text.Replace("\n", " ").Replace("\r", " "); text = MultipleWhitespaceRegex.Replace(text, " "); return WebUtility.HtmlDecode(text); } else { throw new NotSupportedException($"Description of rule {id} does not contain any HTML

paragraphs

."); } } private static ImmutableHashSet ParseSonarWay(string json) => JObject.Parse(json)["ruleKeys"].Values().ToImmutableHashSet(); } ================================================ FILE: analyzers/src/SonarAnalyzer.SourceGenerators/SonarAnalyzer.SourceGenerators.csproj ================================================  netstandard2.0 Internal.SonarAnalyzer.SourceGenerators $(GetTargetPathDependsOn);GetDependencyTargetPaths ================================================ FILE: analyzers/src/SonarAnalyzer.SourceGenerators/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", "requested": "[5.0.0, )", "resolved": "5.0.0", "contentHash": "5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "Microsoft.CodeAnalysis.Common": "[5.0.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "Newtonsoft.Json": { "type": "Direct", "requested": "[13.0.4, )", "resolved": "13.0.4", "contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==" }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "3.11.0", "contentHash": "v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "3.11.0", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Memory": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==", "dependencies": { "System.Buffers": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "t+SoieZsRuEyiw/J+qXUbolyO219tKQQI0+2/YI+Qv7YdGValA6WiuokrNKqjrTNsy5ABWU11bdKOzUdheteXg==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", "dependencies": { "System.Collections.Immutable": "9.0.0", "System.Memory": "4.5.5" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.1.0", "contentHash": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg==" }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.1.0" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Metrics/VisualBasicCognitiveComplexityMetric.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; namespace SonarAnalyzer.VisualBasic.Metrics; public static class VisualBasicCognitiveComplexityMetric { public static CognitiveComplexity GetComplexity(SyntaxNode node) { var walker = new CognitiveWalker(); walker.SafeVisit(node); return new CognitiveComplexity(walker.State.Complexity, walker.State.IncrementLocations.ToImmutableArray()); } private class CognitiveWalker : SafeVisualBasicSyntaxWalker { public CognitiveComplexityWalkerState State { get; } = new(); public override void VisitBinaryConditionalExpression(BinaryConditionalExpressionSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.IfKeyword); State.VisitWithNesting(node, base.VisitBinaryConditionalExpression); } public override void VisitBinaryExpression(BinaryExpressionSyntax node) { var nodeKind = node.Kind(); if (!State.LogicalOperationsToIgnore.Contains(node) && (nodeKind == SyntaxKind.AndExpression || nodeKind == SyntaxKind.AndAlsoExpression || nodeKind == SyntaxKind.OrExpression || nodeKind == SyntaxKind.OrElseExpression)) { var left = node.Left.RemoveParentheses(); if (!left.IsKind(nodeKind)) { State.IncreaseComplexityByOne(node.OperatorToken); } var right = node.Right.RemoveParentheses(); if (right.IsKind(nodeKind)) { State.LogicalOperationsToIgnore.Add(right); } } base.VisitBinaryExpression(node); } public override void VisitCatchBlock(CatchBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.CatchStatement.CatchKeyword); State.VisitWithNesting(node, base.VisitCatchBlock); } public override void VisitDoLoopBlock(DoLoopBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.DoStatement.DoKeyword); State.VisitWithNesting(node, base.VisitDoLoopBlock); } public override void VisitElseIfStatement(ElseIfStatementSyntax node) { State.IncreaseComplexityByOne(node.ElseIfKeyword); base.VisitElseIfStatement(node); } public override void VisitElseStatement(ElseStatementSyntax node) { State.IncreaseComplexityByOne(node.ElseKeyword); base.VisitElseStatement(node); } public override void VisitForEachBlock(ForEachBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.ForEachStatement.ForKeyword); State.VisitWithNesting(node, base.VisitForEachBlock); } public override void VisitForBlock(ForBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.ForStatement.ForKeyword); State.VisitWithNesting(node, base.VisitForBlock); } public override void VisitGoToStatement(GoToStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.GoToKeyword); base.VisitGoToStatement(node); } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (node.Expression == null || node.ArgumentList == null || State.CurrentMethod == null || node.ArgumentList.Arguments.Count != State.CurrentMethod.ParameterList?.Parameters.Count) { return; } State.HasDirectRecursiveCall = string.Equals(GetIdentifierName(node.Expression), State.CurrentMethod.Identifier.ValueText, StringComparison.Ordinal); base.VisitInvocationExpression(node); static string GetIdentifierName(ExpressionSyntax expression) { if (expression.IsKind(SyntaxKind.IdentifierName)) { return (expression as IdentifierNameSyntax).Identifier.ValueText; } else if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression)) { return (expression as MemberAccessExpressionSyntax).Name.Identifier.ValueText; } else { return string.Empty; } } } public override void VisitMethodBlock(MethodBlockSyntax node) { State.CurrentMethod = node.SubOrFunctionStatement; base.VisitMethodBlock(node); if (State.HasDirectRecursiveCall) { State.HasDirectRecursiveCall = false; State.IncreaseComplexity(node.SubOrFunctionStatement.Identifier, 1, "+1 (recursion)"); } } public override void VisitMultiLineIfBlock(MultiLineIfBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.IfStatement.IfKeyword); State.VisitWithNesting(node, base.VisitMultiLineIfBlock); } public override void VisitMultiLineLambdaExpression(MultiLineLambdaExpressionSyntax node) => State.VisitWithNesting(node, base.VisitMultiLineLambdaExpression); public override void VisitSelectBlock(SelectBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.SelectStatement.SelectKeyword); State.VisitWithNesting(node, base.VisitSelectBlock); } public override void VisitSingleLineIfStatement(SingleLineIfStatementSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.IfKeyword); State.VisitWithNesting(node, base.VisitSingleLineIfStatement); } public override void VisitSingleLineLambdaExpression(SingleLineLambdaExpressionSyntax node) => State.VisitWithNesting(node, base.VisitSingleLineLambdaExpression); public override void VisitTernaryConditionalExpression(TernaryConditionalExpressionSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.IfKeyword); State.VisitWithNesting(node, base.VisitTernaryConditionalExpression); } public override void VisitWhileBlock(WhileBlockSyntax node) { State.IncreaseComplexityByNestingPlusOne(node.WhileStatement.WhileKeyword); State.VisitWithNesting(node, base.VisitWhileBlock); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Metrics/VisualBasicExecutableLinesMetric.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Metrics; public static class VisualBasicExecutableLinesMetric { public static ImmutableArray GetLineNumbers(SyntaxTree syntaxTree, SemanticModel semanticModel) { var walker = new ExecutableLinesWalker(semanticModel); walker.SafeVisit(syntaxTree.GetRoot()); return walker.ExecutableLines.ToImmutableArray(); } private class ExecutableLinesWalker : SafeVisualBasicSyntaxWalker { private readonly HashSet executableLineNumbers = new(); private readonly SemanticModel semanticModel; public ExecutableLinesWalker(SemanticModel semanticModel) { this.semanticModel = semanticModel; } public ICollection ExecutableLines => this.executableLineNumbers; public override void DefaultVisit(SyntaxNode node) { if (FindExecutableLines(node)) { base.DefaultVisit(node); } } private static bool HasExcludedAttribute(AttributeSyntax attribute) { var attributeName = attribute?.Name?.ToString() ?? string.Empty; // Check the attribute name without the attribute suffix OR the full name of the attribute var excludeCoverageName = KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute.TypeName; return attributeName.EndsWith(excludeCoverageName.Substring(0, excludeCoverageName.Length - 9), StringComparison.Ordinal) || attributeName.EndsWith(excludeCoverageName, StringComparison.Ordinal); } private bool FindExecutableLines(SyntaxNode node) { switch (node.Kind()) { // The following C# constructs have no equivalent in VB.NET: // - checked // - unchecked // - fixed // - unsafe case SyntaxKind.AttributeList: return false; case SyntaxKind.SyncLockStatement: case SyntaxKind.UsingStatement: case SyntaxKind.EmptyStatement: case SyntaxKind.ExpressionStatement: case SyntaxKind.SimpleAssignmentStatement: case SyntaxKind.DoUntilStatement: case SyntaxKind.DoWhileStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.ForStatement: case SyntaxKind.WhileStatement: case SyntaxKind.IfStatement: case SyntaxKind.ElseIfStatement: case SyntaxKind.LabelStatement: case SyntaxKind.SelectStatement: case SyntaxKind.ConditionalAccessExpression: case SyntaxKind.BinaryConditionalExpression: case SyntaxKind.TernaryConditionalExpression: case SyntaxKind.GoToStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.ReturnStatement: case SyntaxKind.ExitDoStatement: case SyntaxKind.ExitForStatement: case SyntaxKind.ExitFunctionStatement: case SyntaxKind.ExitOperatorStatement: case SyntaxKind.ExitPropertyStatement: case SyntaxKind.ExitSelectStatement: case SyntaxKind.ExitSubStatement: case SyntaxKind.ExitTryStatement: case SyntaxKind.ExitWhileStatement: case SyntaxKind.ContinueDoStatement: case SyntaxKind.ContinueForStatement: case SyntaxKind.YieldStatement: case SyntaxKind.SimpleMemberAccessExpression: case SyntaxKind.InvocationExpression: case SyntaxKind.SingleLineSubLambdaExpression: case SyntaxKind.SingleLineFunctionLambdaExpression: case SyntaxKind.MultiLineSubLambdaExpression: case SyntaxKind.MultiLineFunctionLambdaExpression: this.executableLineNumbers.Add(node.GetLocation().LineNumberToReport()); return true; case SyntaxKind.StructureStatement: case SyntaxKind.ClassStatement: case SyntaxKind.ModuleStatement: return !HasExcludedCodeAttribute((TypeStatementSyntax)node, tss => tss.AttributeLists, canBePartial: true); case SyntaxKind.FunctionStatement: case SyntaxKind.SubStatement: case SyntaxKind.SubNewStatement: return !HasExcludedCodeAttribute((MethodBaseSyntax)node, mbs => mbs.AttributeLists, canBePartial: true); case SyntaxKind.PropertyStatement: case SyntaxKind.EventStatement: return !HasExcludedCodeAttribute((MethodBaseSyntax)node, mbs => mbs.AttributeLists, canBePartial: false); case SyntaxKind.AddHandlerAccessorStatement: case SyntaxKind.RemoveHandlerAccessorStatement: case SyntaxKind.SetAccessorStatement: case SyntaxKind.GetAccessorStatement: return !HasExcludedCodeAttribute((AccessorStatementSyntax)node, ass => ass.AttributeLists, canBePartial: false); default: return true; } } private bool HasExcludedCodeAttribute(T node, Func> getAttributeLists, bool canBePartial = false) where T : SyntaxNode { var hasExcludeFromCodeCoverageAttribute = getAttributeLists(node) .SelectMany(attributeList => attributeList.Attributes) .Any(HasExcludedAttribute); if (!canBePartial) { return hasExcludeFromCodeCoverageAttribute; } var nodeSymbol = this.semanticModel.GetDeclaredSymbol(node); switch (nodeSymbol?.Kind) { case SymbolKind.Method: case SymbolKind.NamedType: return hasExcludeFromCodeCoverageAttribute || nodeSymbol.HasAttribute(KnownType.System_Diagnostics_CodeAnalysis_ExcludeFromCodeCoverageAttribute); default: return hasExcludeFromCodeCoverageAttribute; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Metrics/VisualBasicMetrics.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; namespace SonarAnalyzer.VisualBasic.Metrics; public sealed class VisualBasicMetrics : MetricsBase { private static readonly ISet FunctionKinds = new HashSet { SyntaxKind.SubNewStatement, SyntaxKind.SubStatement, SyntaxKind.FunctionStatement, SyntaxKind.OperatorStatement, SyntaxKind.GetAccessorStatement, SyntaxKind.SetAccessorStatement, SyntaxKind.RaiseEventAccessorStatement, SyntaxKind.AddHandlerAccessorStatement, SyntaxKind.RemoveHandlerAccessorStatement, SyntaxKind.DeclareSubStatement, SyntaxKind.DeclareFunctionStatement }; private static readonly ISet MethodBlocks = new HashSet { SyntaxKind.ConstructorBlock, SyntaxKind.FunctionBlock, SyntaxKind.SubBlock, SyntaxKind.OperatorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.RaiseEventAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock }; private readonly Lazy> lazyExecutableLines; public override ImmutableArray ExecutableLines => lazyExecutableLines.Value; public VisualBasicMetrics(SyntaxTree tree, SemanticModel semanticModel) : base(tree) { var root = tree.GetRoot(); if (root.Language != LanguageNames.VisualBasic) { throw new ArgumentException(InitializationErrorTextPattern, nameof(tree)); } lazyExecutableLines = new Lazy>(() => VisualBasicExecutableLinesMetric.GetLineNumbers(tree, semanticModel)); } protected override int ComputeCognitiveComplexity(SyntaxNode node) => VisualBasicCognitiveComplexityMetric.GetComplexity(node).Complexity; public override int ComputeCyclomaticComplexity(SyntaxNode node) => node.DescendantNodesAndSelf().Count(n => IsComplexityIncreasingKind(n) || IsFunction(n)); protected override bool IsClass(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.ClassBlock: case SyntaxKind.StructureBlock: case SyntaxKind.InterfaceBlock: case SyntaxKind.ModuleBlock: return true; default: return false; } } protected override bool IsCommentTrivia(SyntaxTrivia trivia) => trivia.IsComment(); protected override bool IsEndOfFile(SyntaxToken token) => token.IsKind(SyntaxKind.EndOfFileToken); protected override bool IsFunction(SyntaxNode node) { if (!FunctionKinds.Contains(node.Kind()) || !MethodBlocks.Contains(node.Parent.Kind()) || node.Parent.Parent.IsKind(SyntaxKind.InterfaceBlock)) { return false; } if (node is MethodBaseSyntax method && method.Modifiers.Any(SyntaxKind.MustOverrideKeyword)) { return false; } return true; } protected override bool IsNoneToken(SyntaxToken token) => token.IsKind(SyntaxKind.None); protected override bool IsStatement(SyntaxNode node) => node is ExecutableStatementSyntax; private static bool IsComplexityIncreasingKind(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.IfStatement: case SyntaxKind.SingleLineIfStatement: case SyntaxKind.TernaryConditionalExpression: case SyntaxKind.CaseStatement: case SyntaxKind.WhileStatement: case SyntaxKind.DoWhileStatement: case SyntaxKind.DoUntilStatement: case SyntaxKind.SimpleDoStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.ThrowStatement: case SyntaxKind.TryStatement: case SyntaxKind.ErrorStatement: case SyntaxKind.ResumeStatement: case SyntaxKind.ResumeNextStatement: case SyntaxKind.ResumeLabelStatement: case SyntaxKind.OnErrorGoToLabelStatement: case SyntaxKind.OnErrorGoToMinusOneStatement: case SyntaxKind.OnErrorGoToZeroStatement: case SyntaxKind.OnErrorResumeNextStatement: case SyntaxKind.GoToStatement: case SyntaxKind.ExitDoStatement: case SyntaxKind.ExitForStatement: case SyntaxKind.ExitFunctionStatement: case SyntaxKind.ExitOperatorStatement: case SyntaxKind.ExitPropertyStatement: case SyntaxKind.ExitSelectStatement: case SyntaxKind.ExitSubStatement: case SyntaxKind.ExitTryStatement: case SyntaxKind.ExitWhileStatement: case SyntaxKind.ContinueDoStatement: case SyntaxKind.ContinueForStatement: case SyntaxKind.ContinueWhileStatement: case SyntaxKind.StopStatement: case SyntaxKind.AndAlsoExpression: case SyntaxKind.OrElseExpression: case SyntaxKind.EndStatement: return true; default: return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.TestFramework.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AllBranchesShouldNotHaveSameImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class AllBranchesShouldNotHaveSameImplementation : AllBranchesShouldNotHaveSameImplementationBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( new SelectCaseStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.SelectBlock); context.RegisterNodeAction( new TernaryStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.TernaryConditionalExpression); context.RegisterNodeAction( new IfStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.ElseBlock); context.RegisterNodeAction( new SingleLineIfStatementAnalyzer().GetAnalysisAction(rule), SyntaxKind.SingleLineElseClause); } private class IfStatementAnalyzer : IfStatementAnalyzerBase { protected override bool IsLastElseInChain(ElseBlockSyntax elseSyntax) => true; protected override IEnumerable GetStatements(ElseBlockSyntax elseSyntax) => elseSyntax.Statements; protected override IEnumerable> GetIfBlocksStatements(ElseBlockSyntax elseSyntax, out MultiLineIfBlockSyntax topLevelIf) { topLevelIf = (MultiLineIfBlockSyntax)elseSyntax.Parent; return topLevelIf.ElseIfBlocks .Select(elseif => elseif.Statements.Cast()) .Concat(new[] { topLevelIf.Statements.Cast() }); } protected override Location GetLocation(MultiLineIfBlockSyntax topLevelIf) => topLevelIf.IfStatement.IfKeyword.GetLocation(); } private class TernaryStatementAnalyzer : TernaryStatementAnalyzerBase { protected override SyntaxNode GetWhenFalse(TernaryConditionalExpressionSyntax ternaryStatement) => ternaryStatement.WhenFalse.RemoveParentheses(); protected override SyntaxNode GetWhenTrue(TernaryConditionalExpressionSyntax ternaryStatement) => ternaryStatement.WhenTrue.RemoveParentheses(); protected override Location GetLocation(TernaryConditionalExpressionSyntax ternaryStatement) => ternaryStatement.IfKeyword.GetLocation(); } private class SingleLineIfStatementAnalyzer : IfStatementAnalyzerBase { protected override IEnumerable> GetIfBlocksStatements(SingleLineElseClauseSyntax elseSyntax, out SingleLineIfStatementSyntax topLevelIf) { topLevelIf = (SingleLineIfStatementSyntax)elseSyntax.Parent; return new[] { topLevelIf.Statements.Cast() }; } protected override IEnumerable GetStatements(SingleLineElseClauseSyntax elseSyntax) => elseSyntax.Statements; protected override bool IsLastElseInChain(SingleLineElseClauseSyntax elseSyntax) => true; protected override Location GetLocation(SingleLineIfStatementSyntax topLevelIf) => topLevelIf.IfKeyword.GetLocation(); } private class SelectCaseStatementAnalyzer : SwitchStatementAnalyzerBase { protected override bool AreEquivalent(CaseBlockSyntax section1, CaseBlockSyntax section2) => SyntaxFactory.AreEquivalent(section1.Statements, section2.Statements); protected override IEnumerable GetSections(SelectBlockSyntax switchStatement) => switchStatement.CaseBlocks; protected override bool HasDefaultLabel(SelectBlockSyntax switchStatement) => switchStatement.CaseBlocks.Any(section => section.IsKind(SyntaxKind.CaseElseBlock)); protected override Location GetLocation(SelectBlockSyntax switchStatement) => switchStatement.SelectStatement.SelectKeyword.GetLocation(); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AlwaysSetDateTimeKind.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class AlwaysSetDateTimeKind : AlwaysSetDateTimeKindBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind ObjectCreationExpression => SyntaxKind.ObjectCreationExpression; protected override string[] ValidNames { get; } = new[] { nameof(DateTime), "Date" }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayCreationLongSyntax.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ArrayCreationLongSyntax : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2355"; private const string MessageFormat = "Use an array literal here instead."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var arrayCreation = (ArrayCreationExpressionSyntax)c.Node; if (arrayCreation.Initializer == null || HasSizeSpecifier(arrayCreation) || c.Model.GetTypeInfo(arrayCreation).Type is not IArrayTypeSymbol arrayType || arrayType.ElementType is null or IErrorTypeSymbol) { return; } if (arrayCreation.Initializer.Initializers.Any()) { if (AtLeastOneExactTypeMatch(c.Model, arrayCreation, arrayType) && AllTypesAreConvertible(c.Model, arrayCreation, arrayType)) { c.ReportIssue(Rule, arrayCreation); } } else { if (arrayType.ElementType.Is(KnownType.System_Object)) { c.ReportIssue(Rule, arrayCreation); } } }, SyntaxKind.ArrayCreationExpression); private static bool HasSizeSpecifier(ArrayCreationExpressionSyntax arrayCreation) => arrayCreation.ArrayBounds != null && arrayCreation.ArrayBounds.Arguments.Any(); private static bool AllTypesAreConvertible(SemanticModel semanticModel, ArrayCreationExpressionSyntax arrayCreation, IArrayTypeSymbol arrayType) => arrayCreation.Initializer.Initializers.All(x => semanticModel.ClassifyConversion(x, arrayType.ElementType) is var conversion && conversion.Exists && (conversion.IsIdentity || conversion.IsWidening)); private static bool AtLeastOneExactTypeMatch(SemanticModel semanticModel, ArrayCreationExpressionSyntax arrayCreation, IArrayTypeSymbol arrayType) => arrayCreation.Initializer.Initializers.Any(x => arrayType.ElementType.Equals(semanticModel.GetTypeInfo(x).Type)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayCreationLongSyntaxCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class ArrayCreationLongSyntaxCodeFix : SonarCodeFix { internal const string Title = "Use an array literal"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ArrayCreationLongSyntax.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root.FindNode(diagnosticSpan); ArrayCreationExpressionSyntax arrayCreation; switch (node.Kind()) { case SyntaxKind.SimpleArgument: arrayCreation = ((SimpleArgumentSyntax)node).Expression as ArrayCreationExpressionSyntax; break; case SyntaxKind.ArrayCreationExpression: arrayCreation = (ArrayCreationExpressionSyntax)node; break; default: arrayCreation = null; break; } if (arrayCreation != null) { context.RegisterCodeFix( Title, c => { var newRoot = root.ReplaceNode( arrayCreation, arrayCreation.Initializer); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayDesignatorOnVariable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ArrayDesignatorOnVariable : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1197"; private const string MessageFormat = "Move the array designator from the variable to the type."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var declarator = (VariableDeclaratorSyntax)c.Node; if (declarator.AsClause is AsNewClauseSyntax) { return; } foreach (var name in declarator.Names.Where(n => n.ArrayRankSpecifiers.Any())) { c.ReportIssue(rule, name); } }, SyntaxKind.VariableDeclarator); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayDesignatorOnVariableCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class ArrayDesignatorOnVariableCodeFix : SonarCodeFix { internal const string Title = "Move the array designator to the type"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ArrayDesignatorOnVariable.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var name = root.FindNode(diagnosticSpan) as ModifiedIdentifierSyntax; if (!(name?.Parent is VariableDeclaratorSyntax variableDeclarator) || variableDeclarator.Names.Count != 1) { return Task.CompletedTask; } if (!(variableDeclarator.AsClause is SimpleAsClauseSyntax simpleAsClause)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => { var type = simpleAsClause.Type.WithoutTrivia(); var rankSpecifiers = name.ArrayRankSpecifiers.Select(rank => rank.WithoutTrivia()); var newType = !(type is ArrayTypeSyntax typeAsArrayType) ? SyntaxFactory.ArrayType( type, SyntaxFactory.List(rankSpecifiers)) : typeAsArrayType.AddRankSpecifiers(rankSpecifiers.ToArray()); newType = newType.WithTriviaFrom(simpleAsClause.Type); var newVariableDeclarator = variableDeclarator .WithNames(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.ModifiedIdentifier(name.Identifier, name.ArrayBounds).WithTriviaFrom(name) })) .WithAsClause(simpleAsClause.WithType(newType)); var newRoot = root.ReplaceNode( variableDeclarator, newVariableDeclarator); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayInitializationMultipleStatements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ArrayInitializationMultipleStatements : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2429"; private const string MessageFormat = "Refactor this code to use the '... = {}' syntax."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var declaration = (LocalDeclarationStatementSyntax)c.Node; if (declaration.Declarators.Count != 1) { return; } var declarator = declaration.Declarators.First(); if (declarator.Names.Count != 1) { return; } var name = declarator.Names.First(); if (name?.ArrayBounds == null || name.ArrayBounds.Arguments.Count != 1) { return; } var bound = GetConstantArgumentValue(name.ArrayBounds.Arguments.First(), c.Model); if (!bound.HasValue) { return; } if (!(c.Model.GetDeclaredSymbol(name) is ILocalSymbol variableSymbol) || !(variableSymbol.Type is IArrayTypeSymbol)) { return; } var statements = GetFollowingStatements(declaration); var indexes = GetAssignedIndexes(statements, variableSymbol, c.Model).ToHashSet(); var upperBound = Math.Max(bound.Value, 0); if (Enumerable.Range(0, upperBound + 1).Any(index => !indexes.Contains(index))) { return; } c.ReportIssue(rule, declaration); }, SyntaxKind.LocalDeclarationStatement); } private static IEnumerable GetAssignedIndexes(IEnumerable statements, ILocalSymbol symbol, SemanticModel semanticModel) { foreach (var statement in statements) { if (!(statement is AssignmentStatementSyntax assignment)) { yield break; } var invocation = assignment.Left as InvocationExpressionSyntax; if (invocation?.ArgumentList == null || invocation.ArgumentList.Arguments.Count != 1) { yield break; } var assignedSymbol = semanticModel.GetSymbolInfo(invocation.Expression).Symbol; if (!symbol.Equals(assignedSymbol)) { yield break; } var argument = invocation.ArgumentList.Arguments.First(); var index = GetConstantArgumentValue(argument, semanticModel); if (!index.HasValue) { yield break; } yield return index.Value; } } private static IEnumerable GetFollowingStatements(SyntaxNode node) { var siblings = node.Parent.ChildNodes().ToList(); var index = siblings.IndexOf(node); if (index < 0) { yield break; } for (var i = index + 1; i < siblings.Count; i++) { if (siblings[i] is StatementSyntax statement) { yield return statement; } else { yield break; } } } private static int? GetConstantArgumentValue(ArgumentSyntax argument, SemanticModel semanticModel) { var bound = semanticModel.GetConstantValue(argument.GetExpression()); if (!bound.HasValue) { return null; } var boundValue = bound.Value as int?; return boundValue; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ArrayPassedAsParams.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ArrayPassedAsParams : ArrayPassedAsParamsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] ExpressionKinds { get; } = [ SyntaxKind.ObjectCreationExpression, SyntaxKind.InvocationExpression ]; protected override ArgumentSyntax LastArgumentIfArrayCreation(SyntaxNode expression) => ArgumentList(expression) is { Arguments: { Count: > 0 } arguments } && arguments.Last() is var lastArgument && IsArrayCreation(lastArgument.GetExpression()) ? lastArgument : null; protected override ITypeSymbol ArrayElementType(ArgumentSyntax argument, SemanticModel model) => argument.GetExpression() switch { ArrayCreationExpressionSyntax arrayCreation => model.GetTypeInfo(arrayCreation.Type).Type, CollectionInitializerSyntax collectionInitializer => (model.GetTypeInfo(collectionInitializer).Type as IArrayTypeSymbol)?.ElementType, _ => null }; private static ArgumentListSyntax ArgumentList(SyntaxNode expression) => expression switch { ObjectCreationExpressionSyntax { } creation => creation.ArgumentList, InvocationExpressionSyntax { } invocation => invocation.ArgumentList, _ => null }; private static bool IsArrayCreation(ExpressionSyntax expression) => expression switch { ArrayCreationExpressionSyntax { Initializer.Initializers.Count: > 0 } => true, ArrayCreationExpressionSyntax { ArrayBounds: null } => true, CollectionInitializerSyntax => true, _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BackslashShouldBeAvoidedInAspNetRoutes : BackslashShouldBeAvoidedInAspNetRoutesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds => [SyntaxKind.SimpleArgument]; protected override bool IsNamedAttributeArgument(SyntaxNode node) => false; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AspNet/RouteTemplateShouldNotStartWithSlash.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RouteTemplateShouldNotStartWithSlash : RouteTemplateShouldNotStartWithSlashBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AvoidDateTimeNowForBenchmarking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class AvoidDateTimeNowForBenchmarking : AvoidDateTimeNowForBenchmarkingBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool ContainsDateTimeArgument(InvocationExpressionSyntax invocation, SemanticModel model) => invocation.ArgumentList.Arguments[0].GetExpression().IsKnownType(KnownType.System_DateTime, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/AvoidUnsealedAttributes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class AvoidUnsealedAttributes : AvoidUnsealedAttributesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/BeginInvokePairedWithEndInvoke.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BeginInvokePairedWithEndInvoke : BeginInvokePairedWithEndInvokeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind InvocationExpressionKind { get; } = SyntaxKind.InvocationExpression; protected override string CallbackParameterName { get; } = "DelegateCallback"; protected override ISet ParentDeclarationKinds { get; } = new HashSet { // Methods SyntaxKind.ConstructorBlock, SyntaxKind.FunctionBlock, SyntaxKind.SubBlock, SyntaxKind.OperatorBlock, // Properties SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.RaiseEventAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock, SyntaxKind.SetAccessorBlock, // Lambdas SyntaxKind.MultiLineFunctionLambdaExpression, SyntaxKind.MultiLineSubLambdaExpression, SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression }.ToImmutableHashSet(); protected override void VisitInvocation(EndInvokeContext context) => new InvocationWalker(context).SafeVisit(context.Root); protected override bool IsInvalidCallback(SyntaxNode callbackArg, SemanticModel semanticModel) => FindCallback(callbackArg, semanticModel) is { } callback && callback.EnsureCorrectSemanticModelOrDefault(semanticModel) is { } callbackModel && (Language.Syntax.IsNullLiteral(callback) || !IsParentDeclarationWithEndInvoke(callback, callbackModel)); /// /// This method is looking for the callback code which can be: /// - in a identifier initializer (like a lambda) /// - passed directly as a lambda argument /// - passed as a new delegate instantiation (and the code can be inside the method declaration) /// - a mix of the above. /// private static SyntaxNode FindCallback(SyntaxNode callbackArg, SemanticModel model) { var callback = callbackArg.RemoveParentheses(); if (callback is IdentifierNameSyntax identifier) { callback = LookupIdentifierInitializer(identifier, model); } if (callback is ObjectCreationExpressionSyntax objectCreation) { callback = objectCreation.ArgumentList.Arguments.Count == 1 ? objectCreation.ArgumentList.Arguments.Single().GetExpression() : null; } if (callback is UnaryExpressionSyntax addressOf && callback.IsKind(SyntaxKind.AddressOfExpression) && callback.EnsureCorrectSemanticModelOrDefault(model) is { } addressOfModel && addressOfModel.GetSymbolInfo(addressOf.Operand).Symbol is IMethodSymbol methodSymbol) { callback = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().Parent; } return callback; } private static SyntaxNode LookupIdentifierInitializer(IdentifierNameSyntax identifier, SemanticModel semantic) => semantic.GetSymbolInfo(identifier).Symbol?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is ModifiedIdentifierSyntax modifiedIdentifier && modifiedIdentifier.Parent is VariableDeclaratorSyntax variableDeclarator ? variableDeclarator.Initializer?.Value.RemoveParentheses() ?? (variableDeclarator.AsClause as AsNewClauseSyntax)?.NewExpression : null; private class InvocationWalker : SafeVisualBasicSyntaxWalker { private readonly EndInvokeContext context; public InvocationWalker(EndInvokeContext context) => this.context = context; public override void Visit(SyntaxNode node) { if (context.Visit(node)) { base.Visit(node); } } public override void VisitInvocationExpression(InvocationExpressionSyntax node) { if (context.VisitInvocationExpression(node)) { base.VisitInvocationExpression(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/BinaryOperationWithIdenticalExpressions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BinaryOperationWithIdenticalExpressions : BinaryOperationWithIdenticalExpressionsBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, "{0}"); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); private static readonly SyntaxKind[] SyntaxKindsToCheckBinary = { SyntaxKind.SubtractExpression, SyntaxKind.DivideExpression, SyntaxKind.ModuloExpression, SyntaxKind.IntegerDivideExpression, SyntaxKind.OrElseExpression, SyntaxKind.AndAlsoExpression, SyntaxKind.OrExpression, SyntaxKind.AndExpression, SyntaxKind.ExclusiveOrExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression }; private static readonly SyntaxKind[] SyntaxKindsToCheckAssignment = { SyntaxKind.SubtractAssignmentStatement, SyntaxKind.DivideAssignmentStatement, SyntaxKind.IntegerDivideAssignmentStatement }; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var expression = (BinaryExpressionSyntax)c.Node; ReportIfExpressionsMatch(c, expression.Left, expression.Right, expression.OperatorToken); }, SyntaxKindsToCheckBinary); context.RegisterNodeAction( c => { var expression = (AssignmentStatementSyntax)c.Node; ReportIfExpressionsMatch(c, expression.Left, expression.Right, expression.OperatorToken); }, SyntaxKindsToCheckAssignment); } private static void ReportIfExpressionsMatch(SonarSyntaxNodeReportingContext context, ExpressionSyntax left, ExpressionSyntax right, SyntaxToken operatorToken) { if (VisualBasicEquivalenceChecker.AreEquivalent(left.RemoveParentheses(), right.RemoveParentheses())) { var message = string.Format(OperatorMessageFormat, operatorToken); context.ReportIssue(Rule, context.Node, message); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/BooleanCheckInverted.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BooleanCheckInverted : BooleanCheckInvertedBase { private static readonly ISet UnsafeInversionOperators = new HashSet { SyntaxKind.GreaterThanToken, SyntaxKind.GreaterThanEqualsToken, SyntaxKind.LessThanToken, SyntaxKind.LessThanEqualsToken, }; private static readonly Dictionary OppositeTokens = new() { { SyntaxKind.GreaterThanToken, "<=" }, { SyntaxKind.GreaterThanEqualsToken, "<" }, { SyntaxKind.LessThanToken, ">=" }, { SyntaxKind.LessThanEqualsToken, ">" }, { SyntaxKind.EqualsToken, "<>" }, { SyntaxKind.LessThanGreaterThanToken, "=" }, }; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( AnalysisAction(Rule), SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression); protected override bool IsUnsafeInversionOperation(BinaryExpressionSyntax expression, SemanticModel model) => expression.OperatorToken.IsAnyKind(UnsafeInversionOperators) && (IsNullable(expression.Left, model) || IsNullable(expression.Right, model) || IsConditionalAccessExpression(expression.Left) || IsConditionalAccessExpression(expression.Right) || IsFloatingPoint(expression.Left, model) || IsFloatingPoint(expression.Right, model)); protected override SyntaxNode LogicalNotNode(BinaryExpressionSyntax expression) => expression.SelfOrTopParenthesizedExpression.Parent is UnaryExpressionSyntax unaryExpression && unaryExpression.OperatorToken.IsKind(SyntaxKind.NotKeyword) ? unaryExpression : null; protected override string SuggestedReplacement(BinaryExpressionSyntax expression) => OppositeTokens[expression.OperatorToken.Kind()]; private static bool IsConditionalAccessExpression(ExpressionSyntax expression) => expression.RemoveParentheses().IsKind(SyntaxKind.ConditionalAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/BooleanLiteralUnnecessary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BooleanLiteralUnnecessary : BooleanLiteralUnnecessaryBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxToken? GetOperatorToken(SyntaxNode node) => ((BinaryExpressionSyntax)node).OperatorToken; protected override bool IsTrue(SyntaxNode syntaxNode) => syntaxNode.IsTrue(); protected override bool IsFalse(SyntaxNode syntaxNode) => syntaxNode.IsFalse(); protected override SyntaxNode GetLeftNode(SyntaxNode node) => ((BinaryExpressionSyntax)node).Left; protected override SyntaxNode GetRightNode(SyntaxNode node) => ((BinaryExpressionSyntax)node).Right; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(CheckLogicalNot, SyntaxKind.NotExpression); context.RegisterNodeAction(CheckAndExpression, SyntaxKind.AndAlsoExpression); context.RegisterNodeAction(CheckAndExpression, SyntaxKind.AndExpression); context.RegisterNodeAction(CheckOrExpression, SyntaxKind.OrElseExpression); context.RegisterNodeAction(CheckOrExpression, SyntaxKind.OrExpression); context.RegisterNodeAction(CheckEquals, SyntaxKind.EqualsExpression); context.RegisterNodeAction(CheckNotEquals, SyntaxKind.NotEqualsExpression); context.RegisterNodeAction(CheckConditional, SyntaxKind.TernaryConditionalExpression); base.Initialize(context); } private void CheckLogicalNot(SonarSyntaxNodeReportingContext context) { var logicalNot = (UnaryExpressionSyntax)context.Node; var logicalNotOperand = logicalNot.Operand.RemoveParentheses(); if (IsTrue(logicalNotOperand) || IsFalse(logicalNotOperand)) { context.ReportIssue(Rule, logicalNot.Operand); } } private void CheckConditional(SonarSyntaxNodeReportingContext context) { var conditional = (TernaryConditionalExpressionSyntax)context.Node; var whenTrue = conditional.WhenTrue; var whenFalse = conditional.WhenFalse; var typeLeft = context.Model.GetTypeInfo(whenTrue).Type; var typeRight = context.Model.GetTypeInfo(whenFalse).Type; if (typeLeft.IsNullableBoolean() || typeRight.IsNullableBoolean() || typeLeft == null || typeRight == null) { return; } CheckTernaryExpressionBranches(context, whenTrue, whenFalse); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/BypassingAccessibility.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class BypassingAccessibility : BypassingAccessibilityBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public BypassingAccessibility() : base(AnalyzerConfiguration.AlwaysEnabled) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CatchRethrow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CatchRethrow : CatchRethrowBase { private static readonly SyntaxList ThrowBlock = SyntaxFactory.List([SyntaxFactory.ThrowStatement()]); protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool ContainsOnlyThrow(CatchBlockSyntax currentCatch) => VisualBasicEquivalenceChecker.AreEquivalent(currentCatch.Statements, ThrowBlock); protected override CatchBlockSyntax[] AllCatches(SyntaxNode node) => ((TryBlockSyntax)node).CatchBlocks.ToArray(); protected override SyntaxNode DeclarationType(CatchBlockSyntax catchClause) => catchClause.CatchStatement?.AsClause?.Type; protected override bool HasFilter(CatchBlockSyntax catchClause) => catchClause.CatchStatement?.WhenClause is not null; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(RaiseOnInvalidCatch, SyntaxKind.TryBlock); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CertificateValidationCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CertificateValidationCheck : CertificateValidationCheckBase< SyntaxKind, ArgumentSyntax, ExpressionSyntax, IdentifierNameSyntax, AssignmentStatementSyntax, InvocationExpressionSyntax, ParameterSyntax, ModifiedIdentifierSyntax, LambdaExpressionSyntax, MemberAccessExpressionSyntax> { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override HashSet MethodDeclarationKinds { get; } = [SyntaxKind.FunctionBlock, SyntaxKind.SubBlock]; protected override HashSet TypeDeclarationKinds { get; } = VisualBasicFacade.Instance.SyntaxKind.TypeDeclaration.ToHashSet(); internal override MethodParameterLookupBase CreateParameterLookup(SyntaxNode argumentListNode, IMethodSymbol method) => argumentListNode switch { InvocationExpressionSyntax invocation => new VisualBasicMethodParameterLookup(invocation.ArgumentList, method), ObjectCreationExpressionSyntax ctor => new VisualBasicMethodParameterLookup(ctor.ArgumentList, method), _ => throw new ArgumentException("Unexpected type.", nameof(argumentListNode)) // This should be throw only by bad usage of this method, not by input dependency }; protected override void Initialize(SonarAnalysisContext context) { // C# += equivalent: // AddHandler situation does not exist in VB.NET. Delegate is pointer to one function only and can not have multiple handlers. It's not an event. // Only assignment and object creation are valid cases for VB.NET // Handling of = syntax context.RegisterNodeAction(CheckAssignmentSyntax, SyntaxKind.SimpleAssignmentStatement); // Handling of constructor parameter syntax (SslStream) context.RegisterNodeAction(CheckConstructorParameterSyntax, SyntaxKind.ObjectCreationExpression); } protected override Location ExpressionLocation(SyntaxNode expression) => // For Lambda expression extract location of the parentheses only to separate them from secondary location of "true" ((expression is LambdaExpressionSyntax lambda) ? lambda.SubOrFunctionHeader.ParameterList : expression).GetLocation(); protected override void SplitAssignment(AssignmentStatementSyntax assignment, out IdentifierNameSyntax leftIdentifier, out ExpressionSyntax right) { leftIdentifier = assignment.Left.DescendantNodesAndSelf().OfType().LastOrDefault(); right = assignment.Right; } protected override IEqualityComparer CreateNodeEqualityComparer() => new VisualBasicSyntaxNodeEqualityComparer(); protected override SyntaxNode FindRootTypeDeclaration(SyntaxNode node) { if (node.FirstAncestorOrSelf() is { } module) { return module; // Modules can't be nested. If there's one, it's the Root } return base.FindRootTypeDeclaration(node); } protected override ExpressionSyntax[] FindReturnAndThrowExpressions(InspectionContext c, SyntaxNode block) { // Return value set by assignment to function variable/value var assignments = block.DescendantNodes() .OfType() .Where(x => c.Context.Model.GetSymbolInfo(x.Left).Symbol is ILocalSymbol { IsFunctionValue: true }); // And normal Return statements and throws return block.DescendantNodes().OfType().Select(x => x.Expression) // Throw statements #2825. x.Expression can be NULL for standalone Throw and we need that one as well. .Concat(block.DescendantNodes().OfType().Select(x => x.Expression)) .Concat(assignments.Select(x => x.Right)) .ToArray(); } protected override bool IsTrueLiteral(ExpressionSyntax expression) => expression?.RemoveParentheses().Kind() == SyntaxKind.TrueLiteralExpression; protected override ExpressionSyntax VariableInitializer(ModifiedIdentifierSyntax variable) => variable.FirstAncestorOrSelf()?.Initializer?.Value; protected override ImmutableArray LambdaLocations(InspectionContext c, LambdaExpressionSyntax lambda) => lambda switch { SingleLineLambdaExpressionSyntax single => single.Body is ExpressionSyntax expr && IsTrueLiteral(expr) // LiteralExpressionSyntax or ParenthesizedExpressionSyntax like (((true))) ? new[] { single.Body.GetLocation() }.ToImmutableArray() : ImmutableArray.Empty, MultiLineLambdaExpressionSyntax multi => BlockLocations(c, multi), _ => ImmutableArray.Empty, }; protected override SyntaxNode LocalVariableScope(ModifiedIdentifierSyntax variable) => variable.FirstAncestorOrSelf()?.Parent; protected override SyntaxNode ExtractArgumentExpressionNode(SyntaxNode expression) { expression = expression.RemoveParentheses(); return expression is UnaryExpressionSyntax unary && unary.Kind() == SyntaxKind.AddressOfExpression ? unary.Operand // Parentheses can not wrap AddressOf operand : expression; } protected override SyntaxNode SyntaxFromReference(SyntaxReference reference) { var syntax = reference.GetSyntax(); return syntax is MethodStatementSyntax ? syntax.Parent : syntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CheckFileLicense.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CheckFileLicense : CheckFileLicenseBase { internal const string HeaderFormatDefaultValue = @"' ' Copyright (c) - ' ' Please configure this header in your SonarCloud/SonarQube quality profile. ' You can also set it in SonarLint.xml additional file for SonarLint or standalone NuGet analyzer. "; protected override ILanguageFacade Language => VisualBasicFacade.Instance; [RuleParameter(HeaderFormatRuleParameterKey, PropertyType.Text, "Expected copyright and license header.", HeaderFormatDefaultValue)] public override string HeaderFormat { get; set; } = HeaderFormatDefaultValue; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassNamedException.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ClassNamedException : ClassNamedExceptionBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassNotInstantiatable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ClassNotInstantiatable : ClassNotInstantiatableBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IEnumerable CollectRemovableDeclarations(INamedTypeSymbol namedType, Compilation compilation, string messageArg) { var typeDeclarations = new VisualBasicRemovableDeclarationCollector(namedType, compilation).TypeDeclarations; return typeDeclarations.Select(x => new ConstructorContext(x, Diagnostic.Create(Rule, x.Node.BlockStatement.Identifier.GetLocation(), "class", messageArg))); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ClassShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ClassShouldNotBeEmpty : ClassShouldNotBeEmptyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsEmptyAndNotPartial(SyntaxNode node) => node is ClassBlockSyntax { Members.Count: 0 } classSyntax && !classSyntax.ClassStatement.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)); protected override TypeBlockSyntax GetIfHasDeclaredBaseClassOrInterface(SyntaxNode node) => node is ClassBlockSyntax { Inherits.Count: > 0 } or ClassBlockSyntax { Implements.Count: > 0 } ? (ClassBlockSyntax)node : null; protected override bool HasInterfaceOrGenericBaseClass(TypeBlockSyntax declaration) => declaration.Implements.Any() || declaration.Inherits.Any(x => x.Types.Any(t => t is GenericNameSyntax)); protected override bool HasAnyAttribute(SyntaxNode node) => node is ClassBlockSyntax { ClassStatement.AttributeLists.Count: > 0 }; protected override bool HasConditionalCompilationDirectives(SyntaxNode node) => node.DescendantNodes(descendIntoTrivia: true).Any(x => x.IsAnyKind( SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseIfDirectiveTrivia, SyntaxKind.ElseDirectiveTrivia, SyntaxKind.EndIfDirectiveTrivia)); protected override string DeclarationTypeKeyword(SyntaxNode node) => ((TypeBlockSyntax)node).BlockStatement.DeclarationKeyword.ValueText.ToLower(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CognitiveComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Metrics; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CognitiveComplexity : CognitiveComplexityBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => CheckComplexity(c, m => m, m => m.SubOrFunctionStatement.Identifier.GetLocation(), VisualBasicCognitiveComplexityMetric.GetComplexity, "method", Threshold), SyntaxKind.SubBlock, SyntaxKind.FunctionBlock); context.RegisterNodeAction( c => CheckComplexity(c, co => co, co => co.BlockStatement.DeclarationKeyword.GetLocation(), VisualBasicCognitiveComplexityMetric.GetComplexity, "constructor", Threshold), SyntaxKind.ConstructorBlock); context.RegisterNodeAction( c => CheckComplexity(c, o => o, o => o.BlockStatement.DeclarationKeyword.GetLocation(), VisualBasicCognitiveComplexityMetric.GetComplexity, "operator", Threshold), SyntaxKind.OperatorBlock); context.RegisterNodeAction( c => CheckComplexity(c, a => a, a => a.AccessorStatement.DeclarationKeyword.GetLocation(), VisualBasicCognitiveComplexityMetric.GetComplexity, "accessor", PropertyThreshold), SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock); context.RegisterNodeAction( c => CheckComplexity(c, f => f, f => f.Declarators[0].Names[0].Identifier.GetLocation(), VisualBasicCognitiveComplexityMetric.GetComplexity, "field", Threshold), SyntaxKind.FieldDeclaration); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CollectionEmptinessChecking.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CollectionEmptinessChecking : CollectionEmptinessCheckingBase { protected override string MessageFormat => "Use '.Any()' to test whether this 'IEnumerable(Of {0})' is empty or not."; protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CommentKeyword.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CommentKeyword : CommentKeywordBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsComment(SyntaxTrivia trivia) => trivia.IsComment(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CommentLineEnd.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CommentLineEnd : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S139"; private const string MessageFormat = "Move this trailing comment on the previous empty line."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private const string DefaultPattern = @"^'\s*\S+\s*$"; [RuleParameter("legalCommentPattern", PropertyType.String, "Pattern for text of trailing comments that are allowed.", DefaultPattern)] public string LegalCommentPattern { get; set; } = DefaultPattern; protected override void Initialize(SonarAnalysisContext context) { context.RegisterTreeAction( c => { foreach (var token in c.Tree.GetRoot().DescendantTokens()) { CheckTokenComments(c, token); } }); } private void CheckTokenComments(SonarSyntaxTreeReportingContext context, SyntaxToken token) { var tokenLine = token.GetLocation().StartLine(); var comments = token.TrailingTrivia .Where(tr => tr.IsKind(SyntaxKind.CommentTrivia)); foreach (var comment in comments) { var location = comment.GetLocation(); if (location.StartLine() == tokenLine && !SafeRegex.IsMatch(comment.ToString(), LegalCommentPattern)) { context.ReportIssue(rule, location); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/CommentsShouldNotBeEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CommentsShouldNotBeEmpty : CommentsShouldNotBeEmptyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsValidTriviaType(SyntaxTrivia trivia) => trivia.IsComment(); protected override string GetCommentText(SyntaxTrivia trivia) => trivia.Kind() switch { SyntaxKind.CommentTrivia => GetText(trivia), SyntaxKind.DocumentationCommentTrivia => GetDocumentationText(trivia), }; // ' private static string GetText(SyntaxTrivia trivia) => trivia.ToString().Trim().Substring(1); // ''' private static string GetDocumentationText(SyntaxTrivia trivia) { var stringBuilder = new StringBuilder(); foreach (var line in trivia.ToFullString().Split(Constants.LineTerminators, StringSplitOptions.None)) { var trimmedLine = line.TrimStart(null); trimmedLine = trimmedLine.StartsWith("'''") ? trimmedLine.Substring(3).Trim() : trimmedLine.TrimEnd(null); stringBuilder.Append(trimmedLine); } return stringBuilder.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ConditionalStructureSameCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ConditionalStructureSameCondition : ConditionalStructureSameConditionBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var ifBlock = (MultiLineIfBlockSyntax)c.Node; var conditions = new[] { ifBlock.IfStatement?.Condition } .Concat(ifBlock.ElseIfBlocks.Select(x => x.ElseIfStatement?.Condition)) .WhereNotNull() .Select(x => x.RemoveParentheses()) .ToArray(); for (var i = 1; i < conditions.Length; i++) { CheckConditionAt(c, conditions, i); } }, SyntaxKind.MultiLineIfBlock); private void CheckConditionAt(SonarSyntaxNodeReportingContext context, ExpressionSyntax[] conditions, int currentIndex) { for (var i = 0; i < currentIndex; i++) { if (VisualBasicEquivalenceChecker.AreEquivalent(conditions[currentIndex], conditions[i])) { context.ReportIssue(rule, conditions[currentIndex], conditions[i].LineNumberToReport().ToString()); return; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ConditionalStructureSameImplementation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ConditionalStructureSameImplementation : ConditionalStructureSameImplementationBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); private static readonly ISet IgnoredStatementsInSwitch = new HashSet { SyntaxKind.ReturnStatement, SyntaxKind.ThrowStatement }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var ifStatement = (SingleLineIfStatementSyntax)c.Node; if (ifStatement.ElseClause is not null && VisualBasicEquivalenceChecker.AreEquivalent(ifStatement.ElseClause.Statements, ifStatement.Statements)) { ReportIssue(c, ifStatement.ElseClause.Statements, ifStatement.Statements, "branch"); } }, SyntaxKind.SingleLineIfStatement); context.RegisterNodeAction( c => { var ifBlock = (MultiLineIfBlockSyntax)c.Node; var statements = new[] { ifBlock.Statements } .Concat(ifBlock.ElseIfBlocks.Select(elseIf => elseIf.Statements)) .Concat(new[] { ifBlock.ElseBlock?.Statements ?? new SyntaxList() }) .Where(l => l.Any()) .ToList(); for (var i = 1; i < statements.Count; i++) { CheckStatementsAt(c, statements, i, ifBlock.ElseBlock is not null, "branch"); } }, SyntaxKind.MultiLineIfBlock); context.RegisterNodeAction( c => { var select = (SelectBlockSyntax)c.Node; var statements = select.CaseBlocks.Select(b => b.Statements).ToList(); var hasCaseElse = select.CaseBlocks.Any(b => b.IsKind(SyntaxKind.CaseElseBlock)); for (var i = 1; i < statements.Count; i++) { CheckStatementsAt(c, statements, i, hasCaseElse, "case"); } }, SyntaxKind.SelectBlock); } private static void CheckStatementsAt(SonarSyntaxNodeReportingContext context, List> statements, int currentIndex, bool hasElse, string constructType) { var currentBlockStatements = statements[currentIndex]; var numberOfStatements = currentBlockStatements.Count(IsApprovedStatement); if (!hasElse && numberOfStatements == 1) { if (statements.Count > 1 && statements.TrueForAll(x => VisualBasicEquivalenceChecker.AreEquivalent(currentBlockStatements, x))) { ReportIssue(context, currentBlockStatements, statements[0], constructType); } } else if (numberOfStatements > 1) { for (var j = 0; j < currentIndex; j++) { if (VisualBasicEquivalenceChecker.AreEquivalent(currentBlockStatements, statements[j])) { ReportIssue(context, currentBlockStatements, statements[j], constructType); return; } } } } private static void ReportIssue(SonarSyntaxNodeReportingContext context, SyntaxList statementsToReport, SyntaxList locationProvider, string constructType) { var firstStatement = statementsToReport.FirstOrDefault(); if (firstStatement is null) { return; } var lastStatement = statementsToReport.Last(); context.ReportIssue(Rule, firstStatement.CreateLocation(lastStatement), locationProvider.First().LineNumberToReport().ToString(), constructType); } private static bool IsApprovedStatement(StatementSyntax statement) => !statement.IsAnyKind(IgnoredStatementsInSwitch); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ConstructorArgumentValueShouldExist.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ConstructorArgumentValueShouldExist : ConstructorArgumentValueShouldExistBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode GetFirstAttributeArgument(AttributeSyntax attributeSyntax) => attributeSyntax.ArgumentList?.Arguments.FirstOrDefault(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DangerousGetHandleShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DangerousGetHandleShouldNotBeCalled : DangerousGetHandleShouldNotBeCalledBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DateAndTimeShouldNotBeUsedasTypeForPrimaryKey.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.VisualBasic.SyntaxKind; namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey : DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IEnumerable TypeNodesOfTemporalKeyProperties(SonarSyntaxNodeReportingContext context) { var classDeclaration = (ClassBlockSyntax)context.Node; var className = classDeclaration.ClassStatement.Identifier.ValueText; return PropertyDeclarationsInClass(classDeclaration) .Where(x => IsCandidateProperty(x) && IsTemporalType(x.AsClause.Type().GetName()) && IsKeyProperty(x, className)) .Select(x => x.AsClause.Type()); } protected override bool IsTemporalType(string propertyTypeName) => propertyTypeName.Equals("Date", Language.NameComparison) || base.IsTemporalType(propertyTypeName); private static IEnumerable PropertyDeclarationsInClass(ClassBlockSyntax classDeclaration) => classDeclaration.Members .OfType() .Concat(classDeclaration.Members.OfType().Select(x => x.PropertyStatement)); private static bool IsCandidateProperty(PropertyStatementSyntax property) => (property.Modifiers.Any(x => x.IsKind(PublicKeyword)) || property.Modifiers.All(x => !(x.Kind() is PrivateKeyword or ProtectedKeyword or FriendKeyword))) && property.Modifiers.All(x => !(x.Kind() is SharedKeyword or ReadOnlyKeyword or WriteOnlyKeyword)); private bool IsKeyProperty(PropertyStatementSyntax property, string className) { var propertyName = property.Identifier.ValueText; return IsKeyPropertyBasedOnName(propertyName, className) || HasKeyAttribute(property); } private bool HasKeyAttribute(PropertyStatementSyntax property) => property.AttributeLists .SelectMany(x => x.Attributes) .Any(x => MatchesAttributeName(x.GetName(), KeyAttributeTypeNames)); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DateTimeFormatShouldNotBeHardcoded.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DateTimeFormatShouldNotBeHardcoded : DateTimeFormatShouldNotBeHardcodedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override Location HardCodedArgumentLocation(InvocationExpressionSyntax invocation) { var simpleArgument = (SimpleArgumentSyntax)invocation.ArgumentList.Arguments[0]; return simpleArgument.Expression.GetLocation(); } protected override bool HasInvalidFirstArgument(InvocationExpressionSyntax invocation, SemanticModel semanticModel) => invocation.ArgumentList is { } && invocation.ArgumentList.Arguments.Any() && invocation.ArgumentList.Arguments[0] is SimpleArgumentSyntax simpleArgument && simpleArgument.Expression.FindConstantValue(semanticModel) is string { Length: > 1 }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode AttributeTarget(SimpleArgumentSyntax attribute) => attribute.GetAncestor()?.Parent; protected override ImmutableArray ResolvableIdentifiers(SyntaxNode expression) => expression is IdentifierNameSyntax identifierName ? ImmutableArray.Create(identifierName) : ImmutableArray.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DeclareTypesInNamespaces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DeclareTypesInNamespaces : DeclareTypesInNamespacesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassStatement, SyntaxKind.StructureStatement, SyntaxKind.EnumStatement, SyntaxKind.InterfaceStatement }; protected override SyntaxToken GetTypeIdentifier(SyntaxNode declaration) { switch (declaration.Kind()) { case SyntaxKind.EnumStatement: return ((EnumStatementSyntax)declaration).Identifier; case SyntaxKind.ClassStatement: case SyntaxKind.InterfaceStatement: case SyntaxKind.StructureStatement: return ((TypeStatementSyntax)declaration).Identifier; default: return default; } } protected override bool IsException(SyntaxNode node) => false; protected override bool IsInnerTypeOrWithinNamespace(SyntaxNode declaration, SemanticModel semanticModel) { switch (declaration.Parent.Parent.Kind()) { case SyntaxKind.ClassBlock: case SyntaxKind.InterfaceBlock: case SyntaxKind.StructureBlock: case SyntaxKind.NamespaceBlock: return true; default: // If declaration is an outer type that is not within a namespace block, // make sure there is no Root Namespace set in the project var typeSymbol = (INamedTypeSymbol)semanticModel.GetDeclaredSymbol(declaration); return typeSymbol == null || !typeSymbol.ContainingNamespace.IsGlobalNamespace; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotCheckZeroSizeCollection.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotCheckZeroSizeCollection : DoNotCheckZeroSizeCollectionBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string IEnumerableTString => "IEnumerable(Of T)"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotHardcodeCredentials.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotHardcodeCredentials : DoNotHardcodeCredentialsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void InitializeActions(SonarParametrizedAnalysisContext context) => context.RegisterCompilationStartAction( c => { c.RegisterNodeAction( new VariableDeclarationBannedWordsFinder(this).AnalysisAction(), SyntaxKind.VariableDeclarator); c.RegisterNodeAction( new AssignmentExpressionBannedWordsFinder(this).AnalysisAction(), SyntaxKind.SimpleAssignmentStatement); c.RegisterNodeAction( new StringLiteralBannedWordsFinder(this).AnalysisAction(), SyntaxKind.StringLiteralExpression); c.RegisterNodeAction( new AddExpressionBannedWordsFinder(this).AnalysisAction(), SyntaxKind.ConcatenateExpression, SyntaxKind.AddExpression); c.RegisterNodeAction( new InterpolatedStringBannedWordsFinder(this).AnalysisAction(), SyntaxKind.InterpolatedStringExpression); c.RegisterNodeAction( new InvocationBannedWordsFinder(this).AnalysisAction(), SyntaxKind.InvocationExpression); }); protected override bool IsSecureStringAppendCharFromConstant(SyntaxNode argumentNode, SemanticModel model) => argumentNode is ArgumentSyntax argument && argument.GetExpression() is { } argumentExpression && argumentExpression switch { InvocationExpressionSyntax { Expression: { } accessed } => accessed.FindConstantValue(model) is string, LiteralExpressionSyntax { RawKind: (int)SyntaxKind.CharacterLiteralExpression } => true, IdentifierNameSyntax identifier when model.GetSymbolInfo(identifier) is { Symbol: ILocalSymbol { } local } && local.DeclaringSyntaxReferences.Length == 1 && local.DeclaringSyntaxReferences[0].GetSyntax() is ForEachStatementSyntax { Expression: { } forEachExpression } && forEachExpression.FindConstantValue(model) is string => true, _ => false, }; private sealed class VariableDeclarationBannedWordsFinder : CredentialWordsFinderBase { public VariableDeclarationBannedWordsFinder(DoNotHardcodeCredentialsBase analyzer) : base(analyzer) { } protected override string AssignedValue(VariableDeclaratorSyntax syntaxNode, SemanticModel model) => syntaxNode.Initializer.Value.StringValue(model); protected override string VariableName(VariableDeclaratorSyntax syntaxNode) => syntaxNode.Names[0].Identifier.ValueText; // We already tested the count in IsAssignedWithStringLiteral protected override bool ShouldHandle(VariableDeclaratorSyntax syntaxNode, SemanticModel model) => syntaxNode.Names.Count == 1 && syntaxNode.Initializer?.Value is LiteralExpressionSyntax literalExpression && literalExpression.IsKind(SyntaxKind.StringLiteralExpression) && syntaxNode.Names[0].IsDeclarationKnownType(KnownType.System_String, model); } private sealed class AssignmentExpressionBannedWordsFinder : CredentialWordsFinderBase { public AssignmentExpressionBannedWordsFinder(DoNotHardcodeCredentialsBase analyzer) : base(analyzer) { } protected override string AssignedValue(AssignmentStatementSyntax syntaxNode, SemanticModel model) => syntaxNode.Right.StringValue(model); protected override string VariableName(AssignmentStatementSyntax syntaxNode) => (syntaxNode.Left as IdentifierNameSyntax)?.Identifier.ValueText; protected override bool ShouldHandle(AssignmentStatementSyntax syntaxNode, SemanticModel model) => syntaxNode.IsKind(SyntaxKind.SimpleAssignmentStatement) && syntaxNode.Left.IsKnownType(KnownType.System_String, model) && syntaxNode.Right.IsKind(SyntaxKind.StringLiteralExpression); } /// /// This finder checks all string literal in the code, except VariableDeclarator, SimpleAssignmentExpression and String.Format invocation. /// These two have their own finders with precise logic and variable name checking. /// This class inspects all other standalone string literals for values considered as hardcoded passwords (in connection strings) /// based on same rules as in VariableDeclarationBannedWordsFinder and AssignmentExpressionBannedWordsFinder. /// private sealed class StringLiteralBannedWordsFinder : CredentialWordsFinderBase { public StringLiteralBannedWordsFinder(DoNotHardcodeCredentialsBase analyzer) : base(analyzer) { } protected override string AssignedValue(LiteralExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.StringValue(model); // We don't have a variable for cases that this finder should handle. Cases with variable name are // handled by VariableDeclarationBannedWordsFinder and AssignmentExpressionBannedWordsFinder // Returning null is safe here, it will not be considered as a value. protected override string VariableName(LiteralExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(LiteralExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.IsKind(SyntaxKind.StringLiteralExpression) && ShouldHandle(syntaxNode.GetTopMostContainingMethod(), syntaxNode, model); // We don't want to handle VariableDeclarator and SimpleAssignmentExpression, // they are implemented by other finders with better and more precise logic. private static bool ShouldHandle(SyntaxNode method, SyntaxNode current, SemanticModel model) { while (current is not null && current != method) { switch (current.Kind()) { case SyntaxKind.VariableDeclarator: case SyntaxKind.SimpleAssignmentStatement: return false; // Direct return from nested syntaxes that must be handled by this finder // before search reaches top level VariableDeclarator or SimpleAssignmentExpression. case SyntaxKind.InvocationExpression: case SyntaxKind.AddExpression: // String concatenation is not supported by other finders case SyntaxKind.ConcatenateExpression: return true; // Handle all arguments except those inside string.Format. InvocationBannedWordsFinder takes care of them. case SyntaxKind.SimpleArgument: return !(current.Parent.Parent is InvocationExpressionSyntax invocation && invocation.IsMethodInvocation(KnownType.System_String, "Format", model)); default: current = current.Parent; break; } } // We want to handle all other literals (property initializers, return statement and return values from lambdas, arrow functions, ...) return true; } } private sealed class AddExpressionBannedWordsFinder : CredentialWordsFinderBase { public AddExpressionBannedWordsFinder(DoNotHardcodeCredentialsBase analyzer) : base(analyzer) { } protected override string AssignedValue(BinaryExpressionSyntax syntaxNode, SemanticModel model) { var left = syntaxNode.Left is BinaryExpressionSyntax precedingConcat && precedingConcat.Kind() is SyntaxKind.ConcatenateExpression or SyntaxKind.AddExpression ? precedingConcat.Right : syntaxNode.Left; return left.FindStringConstant(model) is { } leftString && syntaxNode.Right.FindStringConstant(model) is { } rightString ? leftString + rightString : null; } protected override string VariableName(BinaryExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(BinaryExpressionSyntax syntaxNode, SemanticModel model) => true; } private sealed class InterpolatedStringBannedWordsFinder : CredentialWordsFinderBase { public InterpolatedStringBannedWordsFinder(DoNotHardcodeCredentialsBase analyzer) : base(analyzer) { } protected override string AssignedValue(InterpolatedStringExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.Contents.JoinStr(null, x => x switch { InterpolationSyntax interpolation => interpolation.Expression.FindStringConstant(model), InterpolatedStringTextSyntax text => text.TextToken.ToString(), _ => null } ?? KeywordSeparator.ToString()); // Unknown elements resolved to separator to terminate the keyword-value sequence protected override string VariableName(InterpolatedStringExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(InterpolatedStringExpressionSyntax syntaxNode, SemanticModel model) => true; } private sealed class InvocationBannedWordsFinder : CredentialWordsFinderBase { public InvocationBannedWordsFinder(DoNotHardcodeCredentials analyzer) : base(analyzer) { } protected override string AssignedValue(InvocationExpressionSyntax syntaxNode, SemanticModel model) { var allArgs = syntaxNode.ArgumentList.Arguments.Select(x => x.GetExpression().FindStringConstant(model) ?? KeywordSeparator.ToString()); try { return string.Format(allArgs.First(), allArgs.Skip(1).ToArray()); } catch (FormatException) { return null; } } protected override string VariableName(InvocationExpressionSyntax syntaxNode) => null; protected override bool ShouldHandle(InvocationExpressionSyntax syntaxNode, SemanticModel model) => syntaxNode.IsMethodInvocation(KnownType.System_String, "Format", model) && model.GetSymbolInfo(syntaxNode).Symbol is IMethodSymbol symbol && symbol.Parameters.First().Type.Is(KnownType.System_String); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotHardcodeSecrets.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotHardcodeSecrets : DoNotHardcodeSecretsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void RegisterNodeActions(SonarCompilationStartAnalysisContext context) { context.RegisterNodeAction( ReportIssues, SyntaxKind.AddAssignmentStatement, SyntaxKind.ConcatenateAssignmentStatement, SyntaxKind.SimpleAssignmentStatement, SyntaxKind.VariableDeclarator, SyntaxKind.PropertyStatement, SyntaxKind.EqualsExpression); context.RegisterNodeAction(c => { var invocationExpression = (InvocationExpressionSyntax)c.Node; if (invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Name.Identifier.ValueText == EqualsName && invocationExpression.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && memberAccessExpression.IsMemberAccessOnKnownType(EqualsName, KnownType.System_String, c.Model)) { ReportIssuesForEquals(c, memberAccessExpression, IdentifierAndValue(memberAccessExpression.Expression, firstArgument)); } }, SyntaxKind.InvocationExpression); } protected override SyntaxNode IdentifierRoot(SyntaxNode node) => node switch { AssignmentStatementSyntax assignment => assignment.Left, BinaryExpressionSyntax { Left: IdentifierNameSyntax left } => left, BinaryExpressionSyntax { Right: IdentifierNameSyntax right } => right, _ => node }; protected override SyntaxNode RightHandSide(SyntaxNode node) => node switch { AssignmentStatementSyntax assignment => assignment.Right, VariableDeclaratorSyntax variable => variable.Initializer?.Value, PropertyStatementSyntax property => property.Initializer?.Value, BinaryExpressionSyntax { Left: IdentifierNameSyntax } binary => binary.Right, BinaryExpressionSyntax { Right: IdentifierNameSyntax } binary => binary.Left, _ => null }; private static IdentifierValuePair IdentifierAndValue(ExpressionSyntax expression, ArgumentSyntax argument) => expression switch { MemberAccessExpressionSyntax or IdentifierNameSyntax or InvocationExpressionSyntax => new(expression.GetIdentifier(), argument.GetExpression()), LiteralExpressionSyntax literal => new(argument.GetExpression().GetIdentifier(), literal), _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotInstantiateSharedClasses.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotInstantiateSharedClasses : DoNotInstantiateSharedClassesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var creationSyntax = (ObjectCreationExpressionSyntax)c.Node; var createdType = c.Model.GetTypeInfo(creationSyntax).Type; if (createdType != null && createdType.GetAttributes(KnownType.System_ComponentModel_Composition_PartCreationPolicyAttribute).Any(IsShared)) { c.ReportIssue(rule, creationSyntax); } }, SyntaxKind.ObjectCreationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotLockOnSharedResource.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotLockOnSharedResource : DoNotLockOnSharedResourceBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var lockStatement = (SyncLockStatementSyntax)c.Node; if (IsLockOnThis(lockStatement.Expression) || IsLockOnStringLiteral(lockStatement.Expression) || IsLockOnForbiddenKnownType(lockStatement.Expression, c.Model)) { c.ReportIssue(rule, lockStatement.Expression); } }, SyntaxKind.SyncLockStatement); } private static bool IsLockOnThis(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.MeExpression); private static bool IsLockOnStringLiteral(ExpressionSyntax expression) => expression.IsKind(SyntaxKind.StringLiteralExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotLockWeakIdentityObjects.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotLockWeakIdentityObjects : DoNotLockWeakIdentityObjectsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind SyntaxKind { get; } = SyntaxKind.SyncLockStatement; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotNestTernaryOperators.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotNestTernaryOperators : DoNotNestTernaryOperatorsBase { private const string MessageFormat = "Extract this nested If operator into independent If...Then...Else statements."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { if (c.Node.Ancestors() .TakeWhile(x => !x.IsAnyKind( SyntaxKind.MultiLineFunctionLambdaExpression, SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.MultiLineSubLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression)) .OfType() .Any()) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.TernaryConditionalExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotOverwriteCollectionElements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotOverwriteCollectionElements : DoNotOverwriteCollectionElementsBase { private static readonly HashSet IdentifierOrLiteral = [ SyntaxKind.IdentifierName, SyntaxKind.StringLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.CharacterLiteralExpression, SyntaxKind.NothingLiteralExpression, SyntaxKind.TrueLiteralExpression, SyntaxKind.FalseLiteralExpression ]; private static readonly HashSet CollectionModifyingMethods = new(StringComparer.InvariantCultureIgnoreCase) // VB is case-insensitive { "Item", "Add", }; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( AnalysisAction, SyntaxKind.ExpressionStatement, SyntaxKind.SimpleAssignmentStatement); protected override SyntaxNode GetCollectionIdentifier(StatementSyntax statement) { var invocation = GetInvocation(statement); return invocation is not null ? GetInvokedMethodContainer(invocation).RemoveParentheses() : null; } protected override SyntaxNode GetIndexOrKey(StatementSyntax statement) { var invocation = GetInvocation(statement); return invocation is not null ? GetFirstArgumentExpression(invocation).RemoveParentheses() : null; } protected override bool IsIdentifierOrLiteral(SyntaxNode node) => node.IsAnyKind(IdentifierOrLiteral); // In Visual Basic all collection/dictionary item sets are made through invocations private static InvocationExpressionSyntax GetInvocation(StatementSyntax statement) { switch (statement.Kind()) { case SyntaxKind.SimpleAssignmentStatement: var assignmentStatement = (AssignmentStatementSyntax)statement; return assignmentStatement.Left as InvocationExpressionSyntax; case SyntaxKind.ExpressionStatement: var expression = ((ExpressionStatementSyntax)statement).Expression; return expression.IsKind(SyntaxKind.ConditionalAccessExpression) ? ((ConditionalAccessExpressionSyntax)expression).WhenNotNull as InvocationExpressionSyntax : expression as InvocationExpressionSyntax; default: return null; } } private static SyntaxNode GetInvokedMethodContainer(InvocationExpressionSyntax invocation) { // Supported syntax structures: // dictionary(key) = value // dictionary.Item(key) = value // dictionary.Add(key, value) // list(index) = value // list.Item(index) = value var expression = invocation.Expression.RemoveParentheses(); switch (expression.Kind()) { case SyntaxKind.SimpleMemberAccessExpression: var memberAccess = (MemberAccessExpressionSyntax)expression; if (!CollectionModifyingMethods.Contains(memberAccess.Name.ToString())) { // Possibly an indexer syntax return memberAccess; } if (memberAccess.Expression is null) // Possibly in a ConditionalAccess { var conditionalAccess = memberAccess.Parent.Parent as ConditionalAccessExpressionSyntax; return conditionalAccess?.Expression; } if (memberAccess.Name.ToString().Equals("Add", StringComparison.OrdinalIgnoreCase) && invocation.ArgumentList?.Arguments.Count == 1) { return null; // #2674 Do not raise on ICollection.Add(item) } return memberAccess.Expression; // Return the collection identifier containing the method case SyntaxKind.IdentifierName: return expression; default: return null; } } private static ExpressionSyntax GetFirstArgumentExpression(InvocationExpressionSyntax invocation) => invocation.ArgumentList?.Arguments.ElementAtOrDefault(0)?.GetExpression().RemoveParentheses(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotThrowFromDestructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotThrowFromDestructors : DoNotThrowFromDestructorsBase { private const string MessageFormat = "Remove this 'Throw' statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { if (IsFinalizer(c.Node.FirstAncestorOrSelf())) { c.ReportIssue(rule, c.Node); } }, SyntaxKind.ThrowStatement); } private bool IsFinalizer(MethodBlockSyntax methodBlockSyntax) { if (methodBlockSyntax == null) { return false; } var subOrFunctionDeclaration = methodBlockSyntax.SubOrFunctionStatement; var noParam = subOrFunctionDeclaration.ParameterList == null || subOrFunctionDeclaration.ParameterList.Parameters.Count == 0; var noTypeParam = subOrFunctionDeclaration.TypeParameterList == null || subOrFunctionDeclaration.TypeParameterList.Parameters.Count == 0; var isSub = subOrFunctionDeclaration.SubOrFunctionKeyword.IsKind(SyntaxKind.SubKeyword); var isProtected = subOrFunctionDeclaration.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.ProtectedKeyword)); return noParam && noTypeParam && isSub && isProtected && subOrFunctionDeclaration.Identifier.ValueText.Equals("Finalize", StringComparison.InvariantCultureIgnoreCase); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotUseByVal.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotUseByVal : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3860"; private const string MessageFormat = "Remove this redundant 'ByVal' modifier."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var parameter = (ParameterSyntax)c.Node; foreach (var byVal in parameter.Modifiers.Where(IsByVal)) { c.ReportIssue(rule, byVal); } }, SyntaxKind.Parameter); } public static bool IsByVal(SyntaxToken modifier) { return modifier.IsKind(SyntaxKind.ByValKeyword); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotUseByValCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class DoNotUseByValCodeFix : SonarCodeFix { internal const string Title = "Remove the 'ByVal' modifier."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DoNotUseByVal.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (root.FindNode(diagnosticSpan) is ParameterSyntax oldNode) { context.RegisterCodeFix( Title, c => { var modifiers = default(SyntaxTokenList) .AddRange(oldNode.Modifiers.Where(m => !DoNotUseByVal.IsByVal(m))); var newNode = oldNode.WithModifiers(modifiers); var newRoot = root.ReplaceNode(oldNode, newNode); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotUseDateTimeNow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotUseDateTimeNow : DoNotUseDateTimeNowBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsInsideNameOf(SyntaxNode node) => node.Ancestors() .OfType() .Any(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotUseIIf.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DoNotUseIIf : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S3866"; private const string MessageFormat = "Use the 'If' operator here instead of 'IIf'."; private const string IIfOperatorName = "IIf"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var invocationExpression = (InvocationExpressionSyntax)c.Node; var invokedMethod = c.Model.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol; if (IsIIf(invokedMethod)) { c.ReportIssue(Rule, invocationExpression); } }, SyntaxKind.InvocationExpression); private static bool IsIIf(IMethodSymbol method) => method != null && method.Name == IIfOperatorName && method.ContainingType.Is(KnownType.Microsoft_VisualBasic_Interaction); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/DoNotUseIIfCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class DoNotUseIIfCodeFix : SonarCodeFix { internal const string IfOperatorName = "If"; internal const string Title = "Use 'If' operator."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DoNotUseIIf.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (root.FindNode(diagnosticSpan) is InvocationExpressionSyntax iifInvocation) { context.RegisterCodeFix( Title, c => { var ifInvocation = SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName(IfOperatorName), iifInvocation.ArgumentList); var newRoot = root.ReplaceNode(iifInvocation, ifInvocation); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EmptyMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EmptyMethod : EmptyMethodBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override HashSet SyntaxKinds { get; } = [ SyntaxKind.FunctionBlock, SyntaxKind.SubBlock ]; protected override void CheckMethod(SonarSyntaxNodeReportingContext context) { var methodBlock = (MethodBlockSyntax)context.Node; if (methodBlock.Statements.Count == 0 && !ContainsComments(methodBlock.EndSubOrFunctionStatement.GetLeadingTrivia()) && !ShouldMethodBeExcluded(context, methodBlock.SubOrFunctionStatement)) { context.ReportIssue(Rule, methodBlock.SubOrFunctionStatement.Identifier); } } private static bool ContainsComments(IEnumerable trivias) => trivias.Any(s => s.IsKind(SyntaxKind.CommentTrivia)); private static bool ShouldMethodBeExcluded(SonarSyntaxNodeReportingContext context, MethodStatementSyntax methodStatement) { if (methodStatement.Modifiers.Any(SyntaxKind.MustOverrideKeyword) || methodStatement.Modifiers.Any(SyntaxKind.OverridableKeyword) || IsDllImport(context.Model, methodStatement)) { return true; } else if (context.Model.GetDeclaredSymbol(methodStatement) is { IsOverride: true } methodSymbol) { return methodSymbol.OverriddenMethod is { IsAbstract: true } || context.IsTestProject(); } else { return false; } } private static bool IsDllImport(SemanticModel model, MethodStatementSyntax methodStatement) => methodStatement.AttributeLists.SelectMany(x => x.Attributes).Any(x => x.IsKnownType(KnownType.System_Runtime_InteropServices_DllImportAttribute, model)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EmptyNestedBlock.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EmptyNestedBlock : EmptyNestedBlockBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = new[] { SyntaxKind.SimpleDoLoopBlock, SyntaxKind.DoLoopUntilBlock, SyntaxKind.DoLoopWhileBlock, SyntaxKind.DoUntilLoopBlock, SyntaxKind.DoWhileLoopBlock, SyntaxKind.ForBlock, SyntaxKind.ForEachBlock, // The Else and ElseIf blocks are inside the MultiLineIfBlock SyntaxKind.MultiLineIfBlock, SyntaxKind.SelectBlock, // The CatchBlock and FinallyBlock are inside the TryBlock SyntaxKind.TryBlock, SyntaxKind.UsingBlock, SyntaxKind.WhileBlock, SyntaxKind.WithBlock }; /** * Verify that the given block has no statements and no comments inside * (notable exception: the Select block) * * Note: Roslyn maps the comments which are inside a block as trivia of the following block * e.g. in the below snippet, the comment will be part of the Finally block * * Try * ' my comment * Finally * End Try */ protected override IEnumerable EmptyBlocks(SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.SimpleDoLoopBlock: case SyntaxKind.DoLoopUntilBlock: case SyntaxKind.DoLoopWhileBlock: case SyntaxKind.DoUntilLoopBlock: case SyntaxKind.DoWhileLoopBlock: return VisitDoLoopBlock((DoLoopBlockSyntax)node); case SyntaxKind.ForBlock: return VisitForBlock((ForBlockSyntax)node); case SyntaxKind.ForEachBlock: return VisitForEachBlock((ForEachBlockSyntax)node); case SyntaxKind.MultiLineIfBlock: return VisitMultiLineIfBlock((MultiLineIfBlockSyntax)node); case SyntaxKind.SelectBlock: return VisitSelectBlock((SelectBlockSyntax)node); case SyntaxKind.TryBlock: return VisitTryBlock((TryBlockSyntax)node); case SyntaxKind.UsingBlock: return VisitUsingBlock((UsingBlockSyntax)node); case SyntaxKind.WhileBlock: return VisitWhileBlock((WhileBlockSyntax)node); case SyntaxKind.WithBlock: return VisitWithBlock((WithBlockSyntax)node); default: // we do not throw an exception as the language can evolve over time return Enumerable.Empty(); } } private static IEnumerable VisitDoLoopBlock(DoLoopBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.LoopStatement)) { yield return node.DoStatement; } } private static IEnumerable VisitForBlock(ForBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.NextStatement)) { yield return node.ForStatement; } } private static IEnumerable VisitForEachBlock(ForEachBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.NextStatement)) { yield return node.ForEachStatement; } } private static IEnumerable VisitMultiLineIfBlock(MultiLineIfBlockSyntax node) { var result = new List(); if (node.ElseBlock == null) { if (node.ElseIfBlocks.Any()) { result.AddRange(VerifyIfAndMostElseIfBlocks(node)); result.AddRange(VerifyElseIfBlock(node.ElseIfBlocks[node.ElseIfBlocks.Count - 1], node.EndIfStatement)); } else { result.AddRange(VerifyIfBlock(node, node.EndIfStatement)); } } else { if (node.ElseIfBlocks.Any()) { result.AddRange(VerifyIfAndMostElseIfBlocks(node)); result.AddRange(VerifyElseIfBlock(node.ElseIfBlocks[node.ElseIfBlocks.Count - 1], node.ElseBlock)); result.AddRange(VerifyElseBlock(node.ElseBlock, node.EndIfStatement)); } else { result.AddRange(VerifyIfBlock(node, node.ElseBlock)); result.AddRange(VerifyElseBlock(node.ElseBlock, node.EndIfStatement)); } } return result; } private static IEnumerable VisitSelectBlock(SelectBlockSyntax node) { if (!node.CaseBlocks.Any()) { yield return node.SelectStatement; } } private static IEnumerable VisitTryBlock(TryBlockSyntax node) { var result = new List(); if (node.CatchBlocks.Any() && node.FinallyBlock != null) { result.AddRange(VerifyTryAndMostCatches(node)); result.AddRange(VerifyCatchBlock(node.CatchBlocks[node.CatchBlocks.Count - 1], node.FinallyBlock)); result.AddRange(VerifyFinallyBlock(node.FinallyBlock, node.EndTryStatement)); } else if (node.FinallyBlock != null) { result.AddRange(VerifyTryBlock(node, node.FinallyBlock)); result.AddRange(VerifyFinallyBlock(node.FinallyBlock, node.EndTryStatement)); } else if (node.CatchBlocks.Any()) { result.AddRange(VerifyTryAndMostCatches(node)); result.AddRange(VerifyCatchBlock(node.CatchBlocks[node.CatchBlocks.Count - 1], node.EndTryStatement)); } else { throw new InvalidOperationException("Try block must be followed by at least one catch or one finally block"); } return result; } private static IEnumerable VisitUsingBlock(UsingBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.EndUsingStatement)) { yield return node.UsingStatement; } } private static IEnumerable VisitWhileBlock(WhileBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.EndWhileStatement)) { yield return node.WhileStatement; } } private static IEnumerable VisitWithBlock(WithBlockSyntax node) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node.EndWithStatement)) { yield return node.WithStatement; } } private static IEnumerable VerifyIfAndMostElseIfBlocks(MultiLineIfBlockSyntax ifBlock) { var result = new List(); result.AddRange(VerifyIfBlock(ifBlock, ifBlock.ElseIfBlocks[0])); // verify all ElseIf except the last one for (int i = 0; i < ifBlock.ElseIfBlocks.Count - 1; i++) { result.AddRange(VerifyElseIfBlock(ifBlock.ElseIfBlocks[i], ifBlock.ElseIfBlocks[i + 1])); } return result; } private static IEnumerable VerifyIfBlock(MultiLineIfBlockSyntax ifBlock, SyntaxNode node) { if (!ifBlock.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node)) { yield return ifBlock.IfStatement; } } private static IEnumerable VerifyElseIfBlock(ElseIfBlockSyntax elseIfBlock, SyntaxNode node) { if (!elseIfBlock.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node)) { yield return elseIfBlock.ElseIfStatement; } } private static IEnumerable VerifyElseBlock(ElseBlockSyntax elseBlock, SyntaxNode node) { if (!elseBlock.Statements.Any() && NoCommentsOrConditionalCompilationBefore(node)) { yield return elseBlock.ElseStatement; } } private static IEnumerable VerifyTryAndMostCatches(TryBlockSyntax node) { var result = new List(); result.AddRange(VerifyTryBlock(node, node.CatchBlocks[0])); // verify all catches except the last one for (int i = 0; i < node.CatchBlocks.Count - 1; i++) { result.AddRange(VerifyCatchBlock(node.CatchBlocks[i], node.CatchBlocks[i + 1])); } return result; } private static IEnumerable VerifyTryBlock(TryBlockSyntax node, SyntaxNode nextBlock) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(nextBlock)) { yield return node.TryStatement; } } private static IEnumerable VerifyCatchBlock(CatchBlockSyntax node, SyntaxNode nextBlock) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(nextBlock)) { yield return node.CatchStatement; } } private static IEnumerable VerifyFinallyBlock(FinallyBlockSyntax node, SyntaxNode nextBlock) { if (!node.Statements.Any() && NoCommentsOrConditionalCompilationBefore(nextBlock)) { yield return node.FinallyStatement; } } private static bool NoCommentsOrConditionalCompilationBefore(SyntaxNode node) => !node.GetLeadingTrivia().Any(t => t.IsComment() || t.IsKind(SyntaxKind.DisabledTextTrivia)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EncryptionAlgorithmsShouldBeSecure.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EncryptionAlgorithmsShouldBeSecure : EncryptionAlgorithmsShouldBeSecureBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public EncryptionAlgorithmsShouldBeSecure() : base(AnalyzerConfiguration.AlwaysEnabled) { } protected override TrackerBase.Condition IsInsideObjectInitializer() => context => context.Node.FirstAncestorOrSelf() != null; protected override TrackerBase.Condition HasPkcs1PaddingArgument() => (context) => { var argumentList = ((InvocationExpressionSyntax)context.Node).ArgumentList; var values = argumentList.ArgumentValuesForParameter(context.Model, "padding"); return values.Length == 1 && values[0] is ExpressionSyntax valueSyntax && context.Model.GetSymbolInfo(valueSyntax).Symbol is ISymbol symbol && symbol.Name == "Pkcs1"; }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EndStatementUsage.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EndStatementUsage : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1147"; private const string MessageFormat = "Remove this call to 'End' or ensure it is really required."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => c.ReportIssue(rule, c.Node), SyntaxKind.EndStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EnumNameHasEnumSuffix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EnumNameHasEnumSuffix : EnumNameHasEnumSuffixBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/EventNameContainsBeforeOrAfter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EventNameContainsBeforeOrAfter : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2349"; private const string MessageFormat = "Rename this event to remove the '{0}' {1}."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private const string PrefixLiteral = "prefix"; private const string SuffixLiteral = "suffix"; private const string AfterLiteral = "after"; private const string BeforeLiteral = "before"; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var eventStatement = (EventStatementSyntax)c.Node; var name = eventStatement.Identifier.ValueText; string part; string matched; if (name.StartsWith(BeforeLiteral, System.StringComparison.OrdinalIgnoreCase)) { part = PrefixLiteral; matched = name.Substring(0, BeforeLiteral.Length); } else if (name.StartsWith(AfterLiteral, System.StringComparison.OrdinalIgnoreCase)) { part = PrefixLiteral; matched = name.Substring(0, AfterLiteral.Length); } else if (name.EndsWith(BeforeLiteral, System.StringComparison.OrdinalIgnoreCase)) { part = SuffixLiteral; matched = name.Substring(name.Length - 1 - BeforeLiteral.Length); } else if (name.EndsWith(AfterLiteral, System.StringComparison.OrdinalIgnoreCase)) { part = SuffixLiteral; matched = name.Substring(name.Length - 1 - AfterLiteral.Length); } else { return; } c.ReportIssue(rule, eventStatement.Identifier, matched, part); }, SyntaxKind.EventStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExceptionsShouldBePublic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExceptionsShouldBePublic : ExceptionsShouldBePublicBase { protected override string MessageFormat => "Make this exception 'Public'."; protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExcludeFromCodeCoverageAttributesNeedJustification.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExcludeFromCodeCoverageAttributesNeedJustification : ExcludeFromCodeCoverageAttributesNeedJustificationBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode GetJustificationExpression(SyntaxNode node) => node is AttributeSyntax { ArgumentList.Arguments: { Count: 1 } arguments } && arguments[0] is SimpleArgumentSyntax { Expression: { } } argument && Language.NameComparer.Equals(argument.GetName(), JustificationPropertyName) ? argument.Expression : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExitStatementUsage.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExitStatementUsage : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S3385"; private const string MessageFormat = "Remove this 'Exit' statement."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => c.ReportIssue(Rule, c.Node), SyntaxKind.ExitFunctionStatement, SyntaxKind.ExitPropertyStatement, SyntaxKind.ExitSubStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExpectedExceptionAttributeShouldNotBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Extensions; namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExpectedExceptionAttributeShouldNotBeUsed : ExpectedExceptionAttributeShouldNotBeUsedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode FindExpectedExceptionAttribute(SyntaxNode node) => ((MethodStatementSyntax)node).AttributeLists.SelectMany(x => x.Attributes).FirstOrDefault(x => x.GetName() is "ExpectedException" or "ExpectedExceptionAttribute"); protected override bool HasMultiLineBody(SyntaxNode node) => node.Parent is MethodBlockSyntax { Statements.Count: > 1 }; protected override bool AssertInCatchFinallyBlock(SyntaxNode node) { var walker = new CatchFinallyAssertion(); foreach (var x in node.Parent.DescendantNodes().Where(x => x.Kind() is SyntaxKind.CatchBlock or SyntaxKind.FinallyBlock)) { if (!walker.HasAssertion) { walker.SafeVisit(x); } } return walker.HasAssertion; } private sealed class CatchFinallyAssertion : SafeVisualBasicSyntaxWalker { public bool HasAssertion { get; set; } public override void VisitInvocationExpression(InvocationExpressionSyntax node) => HasAssertion = HasAssertion || node.Expression.ToString().SplitCamelCaseToWords().Intersect(KnownMethods.AssertionMethodParts).Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExpressionComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExpressionComplexity : ExpressionComplexityBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override HashSet TransparentKinds { get; } = [ SyntaxKind.ParenthesizedExpression, SyntaxKind.NotExpression, ]; protected override HashSet ComplexityIncreasingKinds { get; } = [ SyntaxKind.AndExpression, SyntaxKind.AndAlsoExpression, SyntaxKind.OrExpression, SyntaxKind.OrElseExpression, SyntaxKind.ExclusiveOrExpression ]; protected override SyntaxNode[] ExpressionChildren(SyntaxNode node) => node switch { BinaryExpressionSyntax { RawKind: (int)SyntaxKind.AndExpression or (int)SyntaxKind.AndAlsoExpression or (int)SyntaxKind.OrExpression or (int)SyntaxKind.OrElseExpression or (int)SyntaxKind.ExclusiveOrExpression } binary => new[] { binary.Left, binary.Right }, ParenthesizedExpressionSyntax parenthesized => new[] { parenthesized.Expression }, UnaryExpressionSyntax { RawKind: (int)SyntaxKind.NotExpression } unary => new[] { unary.Operand }, _ => Array.Empty(), }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ExtensionMethodShouldNotExtendObject.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExtensionMethodShouldNotExtendObject : ExtensionMethodShouldNotExtendObjectBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsExtensionMethod(MethodStatementSyntax declaration, SemanticModel semanticModel) => declaration.Parent.Parent is ModuleBlockSyntax && declaration.AttributeLists.SelectMany(x => x.Attributes).Any(x => x.IsKnownType(KnownType.System_Runtime_CompilerServices_ExtensionAttribute, semanticModel)); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FieldShadowsParentField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FieldShadowsParentField : FieldShadowsParentFieldBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; if (!fieldDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.ShadowsKeyword))) { foreach (var diagnostics in fieldDeclaration.Declarators.SelectMany(x => x.Names).SelectMany(x => CheckFields(c.Model, x))) { c.ReportIssue(diagnostics); } } }, SyntaxKind.FieldDeclaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FieldShouldNotBePublic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FieldShouldNotBePublic : FieldShouldNotBePublicBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IEnumerable Variables(FieldDeclarationSyntax fieldDeclaration) => fieldDeclaration.Declarators.SelectMany(x => x.Names); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FileLines.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FileLines : FileLinesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override bool IsEndOfFileToken(SyntaxToken token) => token.IsKind(SyntaxKind.EndOfFileToken); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FindInsteadOfFirstOrDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FindInsteadOfFirstOrDefault : FindInsteadOfFirstOrDefaultBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FlagsEnumWithoutInitializer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FlagsEnumWithoutInitializer : FlagsEnumWithoutInitializerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override bool IsInitialized(EnumMemberDeclarationSyntax member) => member.Initializer != null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FlagsEnumZeroMember.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FlagsEnumZeroMember : FlagsEnumZeroMemberBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FunctionComplexity.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Metrics; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FunctionComplexity : FunctionComplexityBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => CheckComplexity(c, m => m.BlockStatement.GetLocation(), "procedure"), SyntaxKind.SubBlock); context.RegisterNodeAction( c => CheckComplexity(c, m => m.BlockStatement.GetLocation(), "function"), SyntaxKind.FunctionBlock); context.RegisterNodeAction( c => CheckComplexity(c, m => m.BlockStatement.GetLocation(), "constructor"), SyntaxKind.ConstructorBlock); context.RegisterNodeAction( c => CheckComplexity(c, m => m.OperatorStatement.GetLocation(), "operator"), SyntaxKind.OperatorBlock); context.RegisterNodeAction( c => CheckComplexity(c, m => m.AccessorStatement.GetLocation(), "accessor"), SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock); } protected override int GetComplexity(SyntaxNode node, SemanticModel semanticModel) => new VisualBasicMetrics(node.SyntaxTree, semanticModel).ComputeCyclomaticComplexity(node); protected sealed override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/FunctionNestingDepth.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FunctionNestingDepth : FunctionNestingDepthBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var walker = new NestingDepthWalker(Maximum, token => c.ReportIssue(rule, token, Maximum.ToString())); walker.SafeVisit(c.Node); }, SyntaxKind.SubBlock, SyntaxKind.FunctionBlock, SyntaxKind.OperatorBlock, SyntaxKind.ConstructorBlock, SyntaxKind.GetAccessorBlock, SyntaxKind.SetAccessorBlock, SyntaxKind.AddHandlerAccessorBlock, SyntaxKind.RemoveHandlerAccessorBlock); private class NestingDepthWalker : SafeVisualBasicSyntaxWalker { private readonly NestingDepthCounter counter; public NestingDepthWalker(int maximumNestingDepth, Action actionMaximumExceeded) => counter = new NestingDepthCounter(maximumNestingDepth, actionMaximumExceeded); public override void VisitMultiLineIfBlock(MultiLineIfBlockSyntax node) => counter.CheckNesting(node.IfStatement.IfKeyword, () => base.VisitMultiLineIfBlock(node)); public override void VisitForBlock(ForBlockSyntax node) => counter.CheckNesting(node.ForStatement.ForKeyword, () => base.VisitForBlock(node)); public override void VisitForEachBlock(ForEachBlockSyntax node) => counter.CheckNesting(node.ForEachStatement.ForKeyword, () => base.VisitForEachBlock(node)); public override void VisitWhileBlock(WhileBlockSyntax node) => counter.CheckNesting(node.WhileStatement.WhileKeyword, () => base.VisitWhileBlock(node)); public override void VisitDoLoopBlock(DoLoopBlockSyntax node) => counter.CheckNesting(node.DoStatement.DoKeyword, () => base.VisitDoLoopBlock(node)); public override void VisitSelectBlock(SelectBlockSyntax node) => counter.CheckNesting(node.SelectStatement.SelectKeyword, () => base.VisitSelectBlock(node)); public override void VisitTryBlock(TryBlockSyntax node) => counter.CheckNesting(node.TryStatement.TryKeyword, () => base.VisitTryBlock(node)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/GenericInheritanceShouldNotBeRecursive.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class GenericInheritanceShouldNotBeRecursive : GenericInheritanceShouldNotBeRecursiveBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassBlock, SyntaxKind.InterfaceBlock, }; protected override SyntaxToken GetKeyword(TypeBlockSyntax declaration) => declaration.BlockStatement.DeclarationKeyword; protected override Location GetLocation(TypeBlockSyntax declaration) => declaration.BlockStatement.Identifier.GetLocation(); protected override INamedTypeSymbol GetNamedTypeSymbol(TypeBlockSyntax declaration, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(declaration); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/GotoStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class GotoStatement : GotoStatementBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] GotoSyntaxKinds => new[] { SyntaxKind.GoToStatement }; protected override string GoToLabel => "GoTo"; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/CommandPath.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CommandPath : CommandPathBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public CommandPath() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ CommandPath(IAnalyzerConfiguration configuration) : base(configuration) { } protected override string FirstArgument(InvocationContext context) => ((InvocationExpressionSyntax)context.Node).ArgumentList.Get(0).FindStringConstant(context.Model); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ConfiguringLoggers.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ConfiguringLoggers : ConfiguringLoggersBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public ConfiguringLoggers() : this(AnalyzerConfiguration.Hotspot) { } // Set internal for testing internal ConfiguringLoggers(IAnalyzerConfiguration configuration) : base(configuration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/CreatingHashAlgorithms.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class CreatingHashAlgorithms : CreatingHashAlgorithmsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public CreatingHashAlgorithms() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ CreatingHashAlgorithms(IAnalyzerConfiguration configuration) : base(configuration) { } protected override bool IsUnsafeAlgorithm(SyntaxNode argumentNode, SemanticModel model) => argumentNode as SimpleArgumentSyntax is { } argument && argument.Expression as MemberAccessExpressionSyntax is { } memberAccess && memberAccess.Name.ToString() is "SHA1" or "MD5" && model.GetSymbolInfo(memberAccess.Expression).Symbol.GetSymbolType().Is(KnownType.System_Security_Cryptography_HashAlgorithmName); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/DeliveringDebugFeaturesInProduction.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DeliveringDebugFeaturesInProduction : DeliveringDebugFeaturesInProductionBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public DeliveringDebugFeaturesInProduction() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ DeliveringDebugFeaturesInProduction(IAnalyzerConfiguration configuration) : base(configuration) { } protected override bool IsDevelopmentCheckInvoked(SyntaxNode node, SemanticModel semanticModel) => node.FirstAncestorOrSelf() is var invocationStatement && invocationStatement.Ancestors().Any(x => IsDevelopmentCheck(x, semanticModel)); protected override bool IsInDevelopmentContext(SyntaxNode node) => node.Ancestors() .OfType() .Any(x => x.ClassStatement.Identifier.Text == StartupDevelopment); private bool IsDevelopmentCheck(SyntaxNode node, SemanticModel semanticModel) => FindCondition(node).RemoveParentheses() is InvocationExpressionSyntax condition && IsValidationMethod(semanticModel, condition, condition.Expression.GetIdentifier()?.ValueText); private static ExpressionSyntax FindCondition(SyntaxNode node) => node switch { MultiLineIfBlockSyntax multiline => multiline.IfStatement.Condition, SingleLineIfStatementSyntax singleLine => singleLine.Condition, _ => null }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/DisablingRequestValidation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class DisablingRequestValidation : DisablingRequestValidationBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public DisablingRequestValidation() : this(AnalyzerConfiguration.Hotspot) { } public DisablingRequestValidation(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ExecutingSqlQueries.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExecutingSqlQueries : ExecutingSqlQueriesBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; public ExecutingSqlQueries() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ ExecutingSqlQueries(IAnalyzerConfiguration configuration) : base(configuration) { } protected override ExpressionSyntax GetArgumentAtIndex(InvocationContext context, int index) => context.Node is InvocationExpressionSyntax invocation ? invocation.ArgumentList.Get(index) : null; protected override ExpressionSyntax GetArgumentAtIndex(ObjectCreationContext context, int index) => context.Node is ObjectCreationExpressionSyntax objectCreation ? objectCreation.ArgumentList.Get(index) : null; protected override ExpressionSyntax GetSetValue(PropertyAccessContext context) => context.Node is MemberAccessExpressionSyntax setter && setter.IsLeftSideOfAssignment ? ((AssignmentStatementSyntax)setter.SelfOrTopParenthesizedExpression.Parent).Right.RemoveParentheses() : null; protected override bool IsTracked(ExpressionSyntax expression, SyntaxBaseContext context) => expression is not null && (IsSensitiveExpression(expression, context.Model) || IsTrackedVariableDeclaration(expression, context)); protected override bool IsSensitiveExpression(ExpressionSyntax expression, SemanticModel semanticModel) => IsConcatenation(expression, semanticModel) || expression.IsKind(SyntaxKind.InterpolatedStringExpression) || (expression is InvocationExpressionSyntax invocation && IsInvocationOfInterest(invocation, semanticModel)); protected override Location SecondaryLocationForExpression(ExpressionSyntax node, string identifierNameToFind, out string identifierNameFound) { identifierNameFound = string.Empty; if (node is null) { return Location.None; } if (node.Parent is EqualsValueSyntax equalsValue && equalsValue.Parent is VariableDeclaratorSyntax declarationSyntax) { var identifier = declarationSyntax.Names.FirstOrDefault(x => x.Identifier.ValueText.Equals(identifierNameToFind, StringComparison.OrdinalIgnoreCase)); if (identifier is null) { return Location.None; } else { identifierNameFound = identifier.Identifier.ValueText; return identifier.GetLocation(); } } if (node.Parent is AssignmentStatementSyntax assignment) { identifierNameFound = assignment.Left.GetName(); return assignment.Left.GetLocation(); } return Location.None; } private static bool IsInvocationOfInterest(InvocationExpressionSyntax invocation, SemanticModel model) => (invocation.IsMethodInvocation(KnownType.System_String, "Format", model) || invocation.IsMethodInvocation(KnownType.System_String, "Concat", model)) && !AllConstants(invocation.ArgumentList.Arguments.ToList(), model); private static bool IsConcatenation(ExpressionSyntax expression, SemanticModel model) => IsConcatenationOperator(expression) && expression is BinaryExpressionSyntax concatenation && !IsConcatenationOfConstants(concatenation, model); private static bool AllConstants(List arguments, SemanticModel model) => arguments.TrueForAll(x => x.GetExpression().HasConstantValue(model)); private static bool IsConcatenationOperator(SyntaxNode node) => node.IsKind(SyntaxKind.ConcatenateExpression) || node.IsKind(SyntaxKind.AddExpression); private static bool IsConcatenationOfConstants(BinaryExpressionSyntax binaryExpression, SemanticModel model) { if ((model.GetTypeInfo(binaryExpression).Type is ITypeSymbol) && binaryExpression.Right.HasConstantValue(model)) { var nestedLeft = binaryExpression.Left; var nestedBinary = nestedLeft as BinaryExpressionSyntax; while (nestedBinary is not null) { if (nestedBinary.Right.HasConstantValue(model) && (IsConcatenationOperator(nestedBinary) || nestedBinary.HasConstantValue(model))) { nestedLeft = nestedBinary.Left; nestedBinary = nestedLeft as BinaryExpressionSyntax; } else { return false; } } return nestedLeft.HasConstantValue(model); } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/ExpandingArchives.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ExpandingArchives : ExpandingArchivesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public ExpandingArchives() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ ExpandingArchives(IAnalyzerConfiguration configuration) : base(configuration) { } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/HardcodedIpAddress.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class HardcodedIpAddress : HardcodedIpAddressBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public HardcodedIpAddress() : this(AnalyzerConfiguration.Hotspot) { } public HardcodedIpAddress(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override bool HasAttributes(SyntaxNode literalExpression) => literalExpression.HasAncestor(SyntaxKind.Attribute); protected override string GetAssignedVariableName(SyntaxNode stringLiteral) => stringLiteral.FirstAncestorOrSelf(IsVariableIdentifier)?.ToString(); private static bool IsVariableIdentifier(SyntaxNode syntaxNode) => syntaxNode is StatementSyntax || syntaxNode is VariableDeclaratorSyntax || syntaxNode is ParameterSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/PubliclyWritableDirectories.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PubliclyWritableDirectories : PubliclyWritableDirectoriesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public PubliclyWritableDirectories() : this(AnalyzerConfiguration.Hotspot) { } internal PubliclyWritableDirectories(IAnalyzerConfiguration configuration) : base(configuration) { } private protected override bool IsGetTempPathAssignment(InvocationExpressionSyntax invocationExpression, KnownType type, string methodName, SemanticModel semanticModel) => invocationExpression.IsMethodInvocation(type, methodName, semanticModel) && invocationExpression.Parent.IsAnyKind(SyntaxKind.EqualsValue, SyntaxKind.SimpleAssignmentStatement, SyntaxKind.ReturnStatement); private protected override bool IsInsecureEnvironmentVariableRetrieval(InvocationExpressionSyntax invocation, KnownType type, string methodName, SemanticModel semanticModel) => invocation.IsMethodInvocation(type, methodName, semanticModel) && invocation.ArgumentList?.Arguments.First() is var firstArgument && InsecureEnvironmentVariables.Any(x => x.Equals(firstArgument.GetExpression().StringValue(semanticModel), StringComparison.OrdinalIgnoreCase)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/RequestsWithExcessiveLength.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RequestsWithExcessiveLength : RequestsWithExcessiveLengthBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public RequestsWithExcessiveLength() : this(SonarAnalyzer.Core.Common.AnalyzerConfiguration.Hotspot) { } public RequestsWithExcessiveLength(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override AttributeSyntax IsInvalidRequestFormLimits(AttributeSyntax attribute, SemanticModel semanticModel) => IsRequestFormLimits(attribute.Name.ToString()) && attribute.ArgumentList?.Arguments.FirstOrDefault(arg => IsMultipartBodyLengthLimit(arg)) is { } firstArgument && semanticModel.GetConstantValue(firstArgument.GetExpression()) is { HasValue: true } constantValue && constantValue.Value is int intValue && intValue > FileUploadSizeLimit && attribute.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_RequestFormLimitsAttribute, semanticModel) ? attribute : null; protected override AttributeSyntax IsInvalidRequestSizeLimit(AttributeSyntax attribute, SemanticModel semanticModel) => IsRequestSizeLimit(attribute.Name.ToString()) && attribute.ArgumentList?.Arguments.FirstOrDefault() is { } firstArgument && semanticModel.GetConstantValue(firstArgument.GetExpression()) is { HasValue: true } constantValue && constantValue.Value is int intValue && intValue > FileUploadSizeLimit && attribute.IsKnownType(KnownType.Microsoft_AspNetCore_Mvc_RequestSizeLimitAttribute, semanticModel) ? attribute : null; protected override SyntaxNode GetMethodLocalFunctionOrClassDeclaration(AttributeSyntax attribute) => attribute.FirstAncestorOrSelf(); protected override string AttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); private bool IsMultipartBodyLengthLimit(ArgumentSyntax argument) => argument is SimpleArgumentSyntax { NameColonEquals: { } nameColonEquals } && nameColonEquals.Name.Identifier.ValueText.Equals(MultipartBodyLengthLimit, Language.NameComparison); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/SpecifyTimeoutOnRegex.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SpecifyTimeoutOnRegex : SpecifyTimeoutOnRegexBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public SpecifyTimeoutOnRegex() : this(AnalyzerConfiguration.Hotspot) { } internal /*for testing*/ SpecifyTimeoutOnRegex(IAnalyzerConfiguration config) : base(config) { } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Hotspots/UsingNonstandardCryptography.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UsingNonstandardCryptography : UsingNonstandardCryptographyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassBlock, SyntaxKind.InterfaceBlock }; public UsingNonstandardCryptography() : this(AnalyzerConfiguration.Hotspot) { } public UsingNonstandardCryptography(IAnalyzerConfiguration analyzerConfiguration) : base(analyzerConfiguration) { } protected override INamedTypeSymbol DeclaredSymbol(TypeBlockSyntax typeDeclarationSyntax, SemanticModel semanticModel) => semanticModel.GetDeclaredSymbol(typeDeclarationSyntax); protected override Location Location(TypeBlockSyntax typeDeclarationSyntax) => typeDeclarationSyntax switch { ClassBlockSyntax c => c.ClassStatement.Identifier.GetLocation(), InterfaceBlockSyntax i => i.InterfaceStatement.Identifier.GetLocation(), _ => null, }; protected override bool DerivesOrImplementsAny(TypeBlockSyntax typeDeclarationSyntax) => typeDeclarationSyntax.Implements.Any() || typeDeclarationSyntax.Inherits.Any(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/IfChainWithoutElse.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class IfChainWithoutElse : IfChainWithoutElseBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind SyntaxKind => SyntaxKind.MultiLineIfBlock; protected override string ElseClause => "Else"; protected override bool IsElseIfWithoutElse(MultiLineIfBlockSyntax ifSyntax) => ifSyntax.ElseIfBlocks.Any() && (ifSyntax.ElseBlock == null || IsEmptyBlock(ifSyntax)); protected override Location IssueLocation(SonarSyntaxNodeReportingContext context, MultiLineIfBlockSyntax ifSyntax) => ifSyntax.ElseIfBlocks.Last().ElseIfStatement.ElseIfKeyword.GetLocation(); private static bool IsEmptyBlock(MultiLineIfBlockSyntax multiLineIfBlock) => !(multiLineIfBlock.ElseBlock.Statements.Count > 0 || multiLineIfBlock.ElseBlock.GetTrailingTrivia().Any(IsCommentOrDisabledText) || multiLineIfBlock.EndIfStatement.GetLeadingTrivia().Any(IsCommentOrDisabledText)); private static bool IsCommentOrDisabledText(SyntaxTrivia trivia) => trivia.IsComment() || trivia.IsKind(SyntaxKind.DisabledTextTrivia); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/IfCollapsible.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class IfCollapsible : IfCollapsibleBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var multilineIfBlock = (MultiLineIfBlockSyntax)c.Node; if (multilineIfBlock.ElseIfBlocks.Count > 0 || multilineIfBlock.ElseBlock is not null) { return; } var parentMultilineIfBlock = multilineIfBlock.Parent as MultiLineIfBlockSyntax; if (parentMultilineIfBlock is null || parentMultilineIfBlock.ElseIfBlocks.Count != 0 || parentMultilineIfBlock.ElseBlock is not null || parentMultilineIfBlock.Statements.Count != 1) { return; } c.ReportIssue(rule, multilineIfBlock.IfStatement.IfKeyword, [parentMultilineIfBlock.IfStatement.IfKeyword.ToSecondaryLocation(SecondaryMessage)]); }, SyntaxKind.MultiLineIfBlock); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ImplementSerializationMethodsCorrectly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ImplementSerializationMethodsCorrectly : ImplementSerializationMethodsCorrectlyBase { private const string ProblemStatic = "non-shared"; private const string ProblemReturnVoidText = "a 'Sub' not a 'Function'"; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string MethodStaticMessage => ProblemStatic; protected override string MethodReturnTypeShouldBeVoidMessage => ProblemReturnVoidText; protected override Location GetIdentifierLocation(IMethodSymbol methodSymbol) => methodSymbol.DeclaringSyntaxReferences.Select(x => x.GetSyntax()) .OfType() .FirstOrDefault() ?.Identifier .GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/IndexOfCheckAgainstZero.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class IndexOfCheckAgainstZero : IndexOfCheckAgainstZeroBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind LessThanExpression => SyntaxKind.LessThanExpression; protected override SyntaxKind GreaterThanExpression => SyntaxKind.GreaterThanExpression; protected override SyntaxNode Left(BinaryExpressionSyntax binaryExpression) => binaryExpression.Left; protected override SyntaxToken OperatorToken(BinaryExpressionSyntax binaryExpression) => binaryExpression.OperatorToken; protected override SyntaxNode Right(BinaryExpressionSyntax binaryExpression) => binaryExpression.Right; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/IndexedPropertyWithMultipleParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class IndexedPropertyWithMultipleParameters : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2352"; private const string MessageFormat = "This indexed property has {0} parameters, use methods instead."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var property = (PropertyStatementSyntax)c.Node; if (property.ParameterList != null && property.ParameterList.Parameters.Count > 1) { c.ReportIssue(rule, property.Identifier, property.ParameterList.Parameters.Count.ToString()); } }, SyntaxKind.PropertyStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/InsecureEncryptionAlgorithm.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class InsecureEncryptionAlgorithm : InsecureEncryptionAlgorithmBase { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override Location Location(SyntaxNode objectCreation) => ((ObjectCreationExpressionSyntax)objectCreation).Type.GetLocation(); protected override ArgumentListSyntax ArgumentList(InvocationExpressionSyntax invocationExpression) => invocationExpression.ArgumentList; protected override SeparatedSyntaxList Arguments(ArgumentListSyntax argumentList) => argumentList.Arguments; protected override bool IsStringLiteralArgument(ArgumentSyntax argument) => argument.GetExpression().IsKind(SyntaxKind.StringLiteralExpression); protected override SyntaxNode Expression(ArgumentSyntax argument) => argument.GetExpression(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/InsecureTemporaryFilesCreation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class InsecureTemporaryFilesCreation : InsecureTemporaryFilesCreationBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/InsteadOfAny.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class InsteadOfAny : InsteadOfAnyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsSimpleEqualityCheck(InvocationExpressionSyntax node, SemanticModel model) => GetArgumentExpression(node, 0) is SingleLineLambdaExpressionSyntax lambda && lambda.SubOrFunctionHeader.ParameterList.Parameters is { Count: 1 } parameters && parameters[0].Identifier.GetName() is var lambdaVariableName && lambda.Body switch { BinaryExpressionSyntax binary when binary.OperatorToken.Kind() is SyntaxKind.EqualsToken or SyntaxKind.IsKeyword => HasValidBinaryOperands(lambdaVariableName, binary.Left, binary.Right, model), InvocationExpressionSyntax invocation => HasValidInvocationOperands(invocation, lambdaVariableName, model), _ => false }; private bool HasValidBinaryOperands(string lambdaVariableName, SyntaxNode first, SyntaxNode second, SemanticModel model) => (AreValidOperands(lambdaVariableName, first, second) && IsNullOrValueTypeOrString(second, model)) || (AreValidOperands(lambdaVariableName, second, first) && IsNullOrValueTypeOrString(first, model)); private static bool IsNullOrValueTypeOrString(SyntaxNode node, SemanticModel model) => node.IsKind(SyntaxKind.NothingLiteralExpression) || IsValueTypeOrString(node, model); protected override bool AreValidOperands(string lambdaVariable, SyntaxNode first, SyntaxNode second) => first is IdentifierNameSyntax && IsNameEqualTo(first, lambdaVariable) && second switch { LiteralExpressionSyntax => true, IdentifierNameSyntax => !IsNameEqualTo(first, second.GetName()), _ => false, }; protected override SyntaxNode GetArgumentExpression(InvocationExpressionSyntax invocation, int index) => invocation.ArgumentList.Arguments[index].GetExpression(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/InvalidCastToInterface.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class InvalidCastToInterface : InvalidCastToInterfaceBase { protected override DiagnosticDescriptor Rule { get; } = DescriptorFactory.Create(DiagnosticId, MessageFormat); protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/JwtSigned.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.VisualBasic.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class JwtSigned : JwtSignedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public JwtSigned() : base(AnalyzerConfiguration.AlwaysEnabled) { } protected override BuilderPatternCondition CreateBuilderPatternCondition() => new VisualBasicBuilderPatternCondition(JwtBuilderConstructorIsSafe, JwtBuilderDescriptors( invocation => invocation.ArgumentList?.Arguments.Count != 1 || !invocation.ArgumentList.Arguments.Single().GetExpression().RemoveParentheses().IsKind(SyntaxKind.FalseLiteralExpression))); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/LineContinuation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class LineContinuation : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2354"; private const string MessageFormat = "Reformat the code to remove this use of the line continuation character."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterTreeAction( c => { var lineContinuations = c.Tree.GetRoot().DescendantTokens() .SelectMany(token => token.TrailingTrivia) .Where(trivia => trivia.IsKind(SyntaxKind.LineContinuationTrivia)); foreach (var lineContinuation in lineContinuations) { c.ReportIssue(rule, lineContinuation.GetLocation()); } }); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/LineLength.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class LineLength : LineLengthBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/LinkedListPropertiesInsteadOfMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class LinkedListPropertiesInsteadOfMethods : LinkedListPropertiesInsteadOfMethodsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsRelevantCallAndType(InvocationExpressionSyntax invocation, SemanticModel model) => invocation.Operands().Right is { } right && IsRelevantType(right, model) && IsCorrectType(invocation, model); private static bool IsCorrectType(InvocationExpressionSyntax invocation, SemanticModel model) => invocation?.ArgumentList?.Arguments is { Count: 1 } args && model.GetTypeInfo(args[0].GetExpression()).Type is { } type && type.DerivesFrom(KnownType.System_Collections_Generic_LinkedList_T); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/LooseFilePermissions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class LooseFilePermissions : LooseFilePermissionsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void VisitInvocations(SonarSyntaxNodeReportingContext context) { var invocation = (InvocationExpressionSyntax)context.Node; if ((IsSetAccessRule(invocation, context.Model) || IsAddAccessRule(invocation, context.Model)) && (ObjectCreation(invocation, context.Model) is { } objectCreation)) { var invocationLocation = invocation.GetLocation(); var secondaryLocation = objectCreation.GetLocation(); context.ReportIssue(rule, invocationLocation, invocationLocation.StartLine() == secondaryLocation.StartLine() ? [] : [secondaryLocation.ToSecondary(MessageFormat)]); } } private static ObjectCreationExpressionSyntax ObjectCreation(InvocationExpressionSyntax invocation, SemanticModel model) { var accessRuleSyntaxNode = VulnerableFileSystemAccessRule(invocation.DescendantNodes()); if (accessRuleSyntaxNode is not null) { return accessRuleSyntaxNode; } var accessRuleSymbol = invocation.GetArgumentSymbolsOfKnownType(KnownType.System_Security_AccessControl_FileSystemAccessRule, model).FirstOrDefault(); return accessRuleSymbol is null or IMethodSymbol ? null : VulnerableFileSystemAccessRule(accessRuleSymbol.LocationNodes(invocation)); ObjectCreationExpressionSyntax VulnerableFileSystemAccessRule(IEnumerable nodes) => nodes.OfType() .FirstOrDefault(x => IsFileSystemAccessRuleForEveryoneWithAllow(x, model)); } private static bool IsFileSystemAccessRuleForEveryoneWithAllow(ObjectCreationExpressionSyntax objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_AccessControl_FileSystemAccessRule, model) && objectCreation.ArgumentList is { } argumentList && IsEveryone(argumentList.Arguments.First().GetExpression(), model) && model.GetConstantValue(argumentList.Arguments.Last().GetExpression()) is {HasValue: true, Value: 0}; private static bool IsEveryone(SyntaxNode node, SemanticModel model) => model.GetConstantValue(node) is {HasValue: true, Value: Everyone} || node.DescendantNodesAndSelf() .OfType() .Any(x => IsNTAccountWithEveryone(x, model) || IsSecurityIdentifierWithEveryone(x, model)); private static bool IsNTAccountWithEveryone(ObjectCreationExpressionSyntax objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_Principal_NTAccount, model) && objectCreation.ArgumentList is { } argumentList && model.GetConstantValue(argumentList.Arguments.Last().GetExpression()) is { HasValue: true, Value: Everyone }; private static bool IsSecurityIdentifierWithEveryone(ObjectCreationExpressionSyntax objectCreation, SemanticModel model) => objectCreation.IsKnownType(KnownType.System_Security_Principal_SecurityIdentifier, model) && objectCreation.ArgumentList is { } argumentList && model.GetConstantValue(argumentList.Arguments.First().GetExpression()) is { HasValue: true, Value: 1 }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MarkAssemblyWithAssemblyVersionAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MarkAssemblyWithAssemblyVersionAttribute : MarkAssemblyWithAssemblyVersionAttributeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MarkAssemblyWithClsCompliantAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MarkAssemblyWithClsCompliantAttribute : MarkAssemblyWithClsCompliantAttributeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MarkAssemblyWithComVisibleAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MarkAssemblyWithComVisibleAttribute : MarkAssemblyWithComVisibleAttributeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MarkWindowsFormsMainWithStaThread.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MarkWindowsFormsMainWithStaThread : MarkWindowsFormsMainWithStaThreadBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.FunctionBlock, SyntaxKind.SubBlock }; protected override Location GetLocation(MethodBlockSyntax method) => method.SubOrFunctionStatement.Identifier.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MethodOverloadsShouldBeGrouped.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MethodOverloadsShouldBeGrouped : MethodOverloadsShouldBeGroupedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassBlock, SyntaxKind.InterfaceBlock, SyntaxKind.StructureBlock }; protected override MemberInfo CreateMemberInfo(SonarSyntaxNodeReportingContext c, StatementSyntax member) { if (member is ConstructorBlockSyntax constructor) { return new MemberInfo(c, member, constructor.SubNewStatement.NewKeyword, IsStaticStatement(constructor.SubNewStatement), false, false); } else if (member is MethodBlockSyntax methodBlock) { return new MemberInfo( c, member, methodBlock.SubOrFunctionStatement.Identifier, IsStaticStatement(methodBlock.SubOrFunctionStatement), IsAbstractStatement(methodBlock.SubOrFunctionStatement), false); } else if (member is MethodStatementSyntax methodStatement) { return new MemberInfo(c, member, methodStatement.Identifier, IsStaticStatement(methodStatement), IsAbstractStatement(methodStatement), false); } return null; } protected override IEnumerable MemberDeclarations(SyntaxNode node) => ((TypeBlockSyntax)node).Members; private static bool IsStaticStatement(MethodBaseSyntax statement) => statement.DescendantTokens().Any(x => x.Kind() == SyntaxKind.SharedKeyword); private static bool IsAbstractStatement(MethodBaseSyntax statement) => statement.DescendantTokens().Any(x => x.Kind() == SyntaxKind.MustOverrideKeyword); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MethodParameterUnused.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MethodParameterUnused : MethodParameterUnusedBase { private const string MessageFormat = "Remove this unused procedure parameter '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var methodBlock = (MethodBlockSyntax)c.Node; // Bail-out if this is not a method we want to report on (only based on syntax checks) if (methodBlock.SubOrFunctionStatement == null || !HasAnyParameter(methodBlock) || IsEmptyMethod(methodBlock) || IsVirtualOrOverride(methodBlock) || IsInterfaceImplementation(methodBlock) || IsWithEventsHandler(methodBlock) || HasAnyAttribute(methodBlock) || OnlyThrowsNotImplementedException(methodBlock, c.Model)) { return; } var unusedParameters = GetUnusedParameters(methodBlock); if (unusedParameters.Count == 0) { return; } // Bail-out if this is not a method we want to report on (only based on symbols checks) var methodSymbol = c.Model.GetDeclaredSymbol(methodBlock); if (methodSymbol == null || methodSymbol.IsMainMethod() || methodSymbol.IsEventHandler() || methodSymbol.GetEffectiveAccessibility() != Accessibility.Private) { return; } foreach (var parameter in unusedParameters) { c.ReportIssue(Rule, parameter, parameter.Identifier.Identifier.ValueText); } }, SyntaxKind.SubBlock, SyntaxKind.FunctionBlock); private static bool HasAnyParameter(MethodBlockBaseSyntax method) => method.BlockStatement.ParameterList != null && method.BlockStatement.ParameterList.Parameters.Any(); private static bool IsEmptyMethod(MethodBlockBaseSyntax method) => method.Statements.Count == 0; private static bool IsVirtualOrOverride(MethodBlockBaseSyntax method) => method.BlockStatement.Modifiers.Any(x => x.Kind() is SyntaxKind.OverridesKeyword or SyntaxKind.OverridableKeyword); private static bool IsInterfaceImplementation(MethodBlockSyntax method) => method.SubOrFunctionStatement.ImplementsClause != null; private static bool IsWithEventsHandler(MethodBlockSyntax method) => method.SubOrFunctionStatement.HandlesClause != null; private static bool HasAnyAttribute(MethodBlockBaseSyntax method) => method.BlockStatement.AttributeLists.Any(); private static bool OnlyThrowsNotImplementedException(MethodBlockBaseSyntax method, SemanticModel semanticModel) => method.Statements.Count == 1 && method.Statements .OfType() .Select(x => x.Expression) .OfType() .Select(x => semanticModel.GetSymbolInfo(x).Symbol) .OfType() .Any(x => x.ContainingType.Is(KnownType.System_NotImplementedException)); private static List GetUnusedParameters(MethodBlockBaseSyntax methodBlock) { var usedIdentifiers = methodBlock.Statements.SelectMany(x => x.DescendantNodes()) .Where(node => node.IsKind(SyntaxKind.IdentifierName) && IsVarOrParameter(node)) .Cast() .Select(x => x.Identifier.ValueText) .WhereNotNull() .ToHashSet(StringComparer.InvariantCultureIgnoreCase); return methodBlock.BlockStatement.ParameterList.Parameters .Where(p => !usedIdentifiers.Contains(p.Identifier.Identifier.ValueText)) .ToList(); static bool IsVarOrParameter(SyntaxNode node) => node.Parent switch { MemberAccessExpressionSyntax memberAccess => memberAccess.Expression == node, ConditionalAccessExpressionSyntax conditionalAccess => conditionalAccess.Expression == node, _ => true }; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MethodsShouldNotHaveIdenticalImplementations.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MethodsShouldNotHaveIdenticalImplementations : MethodsShouldNotHaveIdenticalImplementationsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds => [SyntaxKind.ClassBlock, SyntaxKind.StructureBlock]; protected override IEnumerable GetMethodDeclarations(SyntaxNode node) => ((TypeBlockSyntax)node).Members.OfType(); protected override bool AreDuplicates(SemanticModel model, MethodBlockSyntax firstMethod, MethodBlockSyntax secondMethod) => firstMethod.Statements.Count > 1 && firstMethod.GetIdentifierText() != secondMethod.GetIdentifierText() && HaveSameParameters(firstMethod.GetParameters(), secondMethod.GetParameters()) && HaveSameTypeParameters(model, firstMethod.SubOrFunctionStatement?.TypeParameterList?.Parameters, secondMethod.SubOrFunctionStatement?.TypeParameterList?.Parameters) && AreTheSameType(model, firstMethod.SubOrFunctionStatement.AsClause?.Type, secondMethod.SubOrFunctionStatement.AsClause?.Type) && VisualBasicEquivalenceChecker.AreEquivalent(firstMethod.Statements, secondMethod.Statements); protected override SyntaxToken GetMethodIdentifier(MethodBlockSyntax method) => method.SubOrFunctionStatement.Identifier; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MethodsShouldNotHaveTooManyLines.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MethodsShouldNotHaveTooManyLines : MethodsShouldNotHaveTooManyLinesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = new[] { SyntaxKind.ConstructorBlock, SyntaxKind.SubBlock, SyntaxKind.FunctionBlock }; protected override string MethodKeyword { get; } = "procedures"; protected override IEnumerable GetMethodTokens(MethodBlockBaseSyntax baseMethodDeclaration) => baseMethodDeclaration.Statements.SelectMany(s => s.DescendantTokens()); protected override SyntaxToken? GetMethodIdentifierToken(MethodBlockBaseSyntax baseMethodDeclaration) => baseMethodDeclaration.GetIdentifierOrDefault(); protected override string GetMethodKindAndName(SyntaxToken identifierToken) { var declaration = identifierToken.Parent; if (declaration.IsKind(SyntaxKind.SubNewStatement)) { return $"constructor"; } var identifierName = identifierToken.ValueText; if (string.IsNullOrEmpty(identifierName)) { return "procedure"; } if (declaration.IsKind(SyntaxKind.FunctionStatement)) { return $"function '{identifierName}'"; } if (declaration.IsKind(SyntaxKind.SubStatement)) { return identifierName == "Finalize" ? "finalizer" : $"method '{identifierName}'"; } return "procedure"; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MultipleVariableDeclaration.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MultipleVariableDeclaration : MultipleVariableDeclarationBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/MultipleVariableDeclarationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; namespace SonarAnalyzer.VisualBasic.Rules; [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class MultipleVariableDeclarationCodeFix : MultipleVariableDeclarationCodeFixBase { protected override SyntaxNode CalculateNewRoot(SyntaxNode root, SyntaxNode node) => node is ModifiedIdentifierSyntax { Parent: VariableDeclaratorSyntax declarator } ? root.ReplaceNode(declarator.Parent, CreateNewNodes(declarator)) : root; private static IEnumerable CreateNewNodes(VariableDeclaratorSyntax declarator) => declarator.Parent switch { FieldDeclarationSyntax fieldDeclaration => CreateNewNodes(fieldDeclaration), LocalDeclarationStatementSyntax localDeclaration => CreateNewNodes(localDeclaration), _ => new[] { declarator.Parent } }; private static IEnumerable CreateNewNodes(FieldDeclarationSyntax declaration) => declaration.Declarators.SelectMany(x => GetConvertedDeclarators(x).Select(declarator => CreateFieldDeclarationSyntax(declaration, declarator))); private static IEnumerable CreateNewNodes(LocalDeclarationStatementSyntax declaration) => declaration.Declarators.SelectMany(x => GetConvertedDeclarators(x).Select(declarator => CreateLocalDeclarationStatementSyntax(declaration, declarator))); private static FieldDeclarationSyntax CreateFieldDeclarationSyntax(FieldDeclarationSyntax declaration, VariableDeclaratorSyntax declarator) => SyntaxFactory.FieldDeclaration(declaration.AttributeLists, declaration.Modifiers, SyntaxFactory.SeparatedList(new[] { declarator })); private static LocalDeclarationStatementSyntax CreateLocalDeclarationStatementSyntax(LocalDeclarationStatementSyntax declaration, VariableDeclaratorSyntax declarator) => SyntaxFactory.LocalDeclarationStatement(declaration.Modifiers, SyntaxFactory.SeparatedList(new[] { declarator })); private static IEnumerable GetConvertedDeclarators(VariableDeclaratorSyntax declarator) { var declarators = declarator.Names.Select(x => SyntaxFactory.VariableDeclarator(SyntaxFactory.SeparatedList(new[] { x }), declarator.AsClause, null)).ToList(); if (declarator.Initializer != null) { var last = declarators.Last(); last = last.WithInitializer(declarator.Initializer); declarators[declarators.Count - 1] = last; } return declarators.Select(x => x.WithTrailingTrivia(SyntaxFactory.EndOfLineTrivia(Environment.NewLine)).WithAdditionalAnnotations(Formatter.Annotation)); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/NameOfShouldBeUsed.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class NameOfShouldBeUsed : NameOfShouldBeUsedBase { private static readonly HashSet StringTokenTypes = new HashSet { SyntaxKind.InterpolatedStringTextToken, SyntaxKind.StringLiteralToken }; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string NameOf => "NameOf"; protected override MethodBlockBaseSyntax MethodSyntax(SyntaxNode node) => node.AncestorsAndSelf().OfType().FirstOrDefault(); protected override bool IsStringLiteral(SyntaxToken t) => t.IsAnyKind(StringTokenTypes); protected override IEnumerable GetParameterNames(MethodBlockBaseSyntax method) { var paramGroups = method?.BlockStatement.ParameterList?.Parameters.GroupBy(x => x.Identifier.Identifier.ValueText); return paramGroups == null || paramGroups.Any(x => x.Count() != 1) ? Enumerable.Empty() : paramGroups.Select(x => x.First().Identifier.Identifier.ValueText); } protected override bool LeastLanguageVersionMatches(SonarSyntaxNodeReportingContext context) => context.Compilation.IsAtLeastLanguageVersion(LanguageVersion.VisualBasic14); protected override bool IsArgumentExceptionCallingNameOf(SyntaxNode node, IEnumerable arguments) => ((ThrowStatementSyntax)node).Expression is ObjectCreationExpressionSyntax objectCreation && ArgumentExceptionNameOfPosition(objectCreation.Type.ToString()) is var idx && objectCreation.ArgumentList?.Arguments is { } creationArguments && creationArguments.Count >= idx + 1 && creationArguments[idx].GetExpression() is NameOfExpressionSyntax nameOfExpression && arguments.Contains(nameOfExpression.Argument.ToString()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/ClassName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ClassName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S101"; private const string MessageFormat = "Rename this class to match the regular expression: '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the class names against.", NamingPatterns.PascalCasingPattern)] public string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (ClassStatementSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(declaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, declaration.Identifier, Pattern); } }, SyntaxKind.ClassStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/EnumNameShouldFollowRegex.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EnumNameShouldFollowRegex : EnumNameShouldFollowRegexBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/EnumerationValueName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EnumerationValueName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2343"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the enumeration value names against.", NamingPatterns.PascalCasingPattern)] public string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction( c => { var enumMemberDeclaration = (EnumMemberDeclarationSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(enumMemberDeclaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, enumMemberDeclaration.Identifier, enumMemberDeclaration.Identifier.ValueText, Pattern); } }, SyntaxKind.EnumMemberDeclaration); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/EventHandlerName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EventHandlerName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2347"; private const string MessageFormat = "Rename event handler '{0}' to match the regular expression: '{1}'."; private const string DefaultPattern = "^(([a-z][a-z0-9]*)?" + NamingPatterns.PascalCasingInternalPattern + "_)?" + NamingPatterns.PascalCasingInternalPattern + "$"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the even handler names against.", DefaultPattern)] public string Pattern { get; set; } = DefaultPattern; internal static bool IsEventHandler(MethodStatementSyntax declaration, SemanticModel model) { if (declaration.HandlesClause is not null) { return true; } var symbol = model.GetDeclaredSymbol(declaration); return symbol is not null && symbol.IsEventHandler(); } protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var methodDeclaration = (MethodStatementSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(methodDeclaration.Identifier.ValueText, Pattern) && IsEventHandler(methodDeclaration, c.Model)) { c.ReportIssue(Rule, methodDeclaration.Identifier, methodDeclaration.Identifier.ValueText, Pattern); } }, SyntaxKind.SubStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/EventName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class EventName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2348"; private const string MessageFormat = "Rename this event to match the regular expression: '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the event names against.", NamingPatterns.PascalCasingPattern)] public string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var eventDeclaration = (EventStatementSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(eventDeclaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, eventDeclaration.Identifier, Pattern); } }, SyntaxKind.EventStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/FieldNameChecker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { public abstract class FieldNameChecker : ParametrizedDiagnosticAnalyzer { public virtual string Pattern { get; set; } protected abstract bool IsCandidateSymbol(IFieldSymbol symbol); protected sealed override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var fieldDeclaration = (FieldDeclarationSyntax)c.Node; foreach (var name in fieldDeclaration.Declarators.SelectMany(v => v.Names).WhereNotNull()) { if (c.Model.GetDeclaredSymbol(name) is IFieldSymbol symbol && IsCandidateSymbol(symbol) && !NamingPatterns.IsRegexMatch(symbol.Name, Pattern)) { c.ReportIssue(SupportedDiagnostics[0], name, symbol.Name, Pattern); } } }, SyntaxKind.FieldDeclaration); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/FunctionName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FunctionName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S1542"; private const string MessageFormat = "Rename {0} '{1}' to match the regular expression: '{2}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the function names against.", NamingPatterns.PascalCasingPattern)] public string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var methodDeclaration = (MethodStatementSyntax)c.Node; if (ShouldBeChecked(methodDeclaration, c.ContainingSymbol) && !NamingPatterns.IsRegexMatch(methodDeclaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, methodDeclaration.Identifier, "function", methodDeclaration.Identifier.ValueText, Pattern); } }, SyntaxKind.FunctionStatement); context.RegisterNodeAction( c => { var methodDeclaration = (MethodStatementSyntax)c.Node; if (ShouldBeChecked(methodDeclaration, c.ContainingSymbol) && !NamingPatterns.IsRegexMatch(methodDeclaration.Identifier.ValueText, Pattern) && !EventHandlerName.IsEventHandler(methodDeclaration, c.Model)) { c.ReportIssue(Rule, methodDeclaration.Identifier, "procedure", methodDeclaration.Identifier.ValueText, Pattern); } }, SyntaxKind.SubStatement); static bool ShouldBeChecked(MethodStatementSyntax methodStatement, ISymbol declaredSymbol) => !declaredSymbol.IsOverride && !IsExternImport(declaredSymbol) && !ImplementsSingleMethodWithoutOverride(methodStatement, declaredSymbol); static bool IsExternImport(ISymbol methodSymbol) => methodSymbol.IsExtern && methodSymbol.IsStatic && methodSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_DllImportAttribute); static bool ImplementsSingleMethodWithoutOverride(MethodStatementSyntax methodStatement, ISymbol methodSymbol) => methodStatement.ImplementsClause is { } implementsClause && implementsClause.InterfaceMembers.Count == 1 && methodSymbol.InterfaceMembers().FirstOrDefault() is { } interfaceMember && string.Equals(interfaceMember.Name, methodStatement.Identifier.ValueText, StringComparison.Ordinal); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/InterfaceName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class InterfaceName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S114"; private const string MessageFormat = "Rename this interface to match the regular expression: '{0}'."; private const string DefaultPattern = "^I" + NamingPatterns.PascalCasingInternalPattern + "$"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the interface names against.", DefaultPattern)] public string Pattern { get; set; } = DefaultPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (InterfaceStatementSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(declaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, declaration.Identifier, Pattern); } }, SyntaxKind.InterfaceStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/LocalVariableName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class LocalVariableName : ParametrizedDiagnosticAnalyzer { internal const string DiagnosticId = "S117"; private const string MessageFormat = "Rename this local variable to match the regular expression: '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the local variable names against.", NamingPatterns.CamelCasingPattern)] public string Pattern { get; set; } = NamingPatterns.CamelCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction(ProcessVariableDeclarator, SyntaxKind.VariableDeclarator); context.RegisterNodeAction(c => ProcessLoop(c, (ForStatementSyntax)c.Node, f => f.ControlVariable, s => s.IsFor()), SyntaxKind.ForStatement); context.RegisterNodeAction(c => ProcessLoop(c, (ForEachStatementSyntax)c.Node, f => f.ControlVariable, s => s.IsForEach()), SyntaxKind.ForEachStatement); } private void ProcessLoop(SonarSyntaxNodeReportingContext context, T loop, Func controlVariable, Func isDeclaredInLoop) { if (controlVariable(loop) is IdentifierNameSyntax identifier && context.Model.GetSymbolInfo(identifier).Symbol is ILocalSymbol symbol && isDeclaredInLoop(symbol) && !NamingPatterns.IsRegexMatch(symbol.Name, Pattern)) { context.ReportIssue(Rule, identifier, Pattern); } } private void ProcessVariableDeclarator(SonarSyntaxNodeReportingContext context) { var declarator = (VariableDeclaratorSyntax)context.Node; if (declarator.Parent is FieldDeclarationSyntax) { return; } foreach (var name in declarator.Names.Where(x => x is not null && !NamingPatterns.IsRegexMatch(x.Identifier.ValueText, Pattern))) { if (context.Model.GetDeclaredSymbol(name) is ILocalSymbol symbol && !symbol.IsConst) { context.ReportIssue(Rule, name.Identifier, Pattern); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/NamespaceName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class NamespaceName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2304"; private const string MessageFormat = "Rename this namespace to match the regular expression: '{0}'."; private const string DefaultPattern = "^" + NamingPatterns.PascalCasingInternalPattern + @"(\." + NamingPatterns.PascalCasingInternalPattern + ")*$"; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the namespace names against.", DefaultPattern)] public string Pattern { get; set; } = DefaultPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var declaration = (NamespaceStatementSyntax)c.Node; var declarationName = declaration.Name?.ToString(); if (declarationName is not null && !NamingPatterns.IsRegexMatch(declarationName, Pattern)) { c.ReportIssue(Rule, declaration.Name, Pattern); } }, SyntaxKind.NamespaceStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/ParameterName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ParameterName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S1654"; private const string MessageFormat = "Rename this parameter to match the regular expression: '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the parameter names against.", NamingPatterns.CamelCasingPattern)] public string Pattern { get; set; } = NamingPatterns.CamelCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var parameter = (ParameterSyntax)c.Node; if (parameter.Identifier is not null && !HasPredefinedName(parameter) && !NamingPatterns.IsRegexMatch(parameter.Identifier.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, parameter.Identifier.Identifier, Pattern); } }, SyntaxKind.Parameter); private static bool HasPredefinedName(SyntaxNode node) { while (node is not null) { if (node is MethodStatementSyntax method) { return method.Modifiers.Any(SyntaxKind.OverridesKeyword) || method.ImplementsClause is not null || method.HandlesClause is not null; } else if (node is PropertyStatementSyntax property) { return property.Modifiers.Any(SyntaxKind.OverridesKeyword) || property.ImplementsClause is not null; } else { node = node.Parent; } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PrivateConstantFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PrivateConstantFieldName : FieldNameChecker { private const string DiagnosticId = "S2362"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the private constant names against.", NamingPatterns.CamelCasingPatternWithOptionalPrefixes)] public override string Pattern { get; set; } = NamingPatterns.CamelCasingPatternWithOptionalPrefixes; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Private && symbol.IsConst; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PrivateFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PrivateFieldName : FieldNameChecker { private const string DiagnosticId = "S2364"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the private field names against.", NamingPatterns.CamelCasingPatternWithOptionalPrefixes)] public override string Pattern { get; set; } = NamingPatterns.CamelCasingPatternWithOptionalPrefixes; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Private && !symbol.IsConst && !(symbol.IsShared() && symbol.IsReadOnly); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PrivateSharedReadonlyFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PrivateSharedReadonlyFieldName : FieldNameChecker { private const string DiagnosticId = "S2363"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the 'Private Shared ReadOnly' field names against.", NamingPatterns.CamelCasingPatternWithOptionalPrefixes)] public override string Pattern { get; set; } = NamingPatterns.CamelCasingPatternWithOptionalPrefixes; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility == Accessibility.Private && symbol.IsShared() && symbol.IsReadOnly; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PropertyName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PropertyName : ParametrizedDiagnosticAnalyzer { private const string DiagnosticId = "S2366"; private const string MessageFormat = "Rename this property to match the regular expression: '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the property names against.", NamingPatterns.PascalCasingPattern)] public string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var propertyDeclaration = (PropertyStatementSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(propertyDeclaration.Identifier.ValueText, Pattern)) { c.ReportIssue(Rule, propertyDeclaration.Identifier, Pattern); } }, SyntaxKind.PropertyStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PublicConstantFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PublicConstantFieldName : FieldNameChecker { private const string DiagnosticId = "S2367"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the non-private constant names against.", NamingPatterns.PascalCasingPattern)] public override string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility != Accessibility.Private && symbol.IsConst; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PublicFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PublicFieldName : FieldNameChecker { private const string DiagnosticId = "S2369"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the non-private field names against.", NamingPatterns.PascalCasingPattern)] public override string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility != Accessibility.Private && !symbol.IsConst && !(symbol.IsShared() && symbol.IsReadOnly); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/PublicSharedReadonlyFieldName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PublicSharedReadonlyFieldName : FieldNameChecker { private const string DiagnosticId = "S2370"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); [RuleParameter("format", PropertyType.String, "Regular expression used to check the non-private 'Shared ReadOnly' field names against.", NamingPatterns.PascalCasingPattern)] public override string Pattern { get; set; } = NamingPatterns.PascalCasingPattern; protected override bool IsCandidateSymbol(IFieldSymbol symbol) => symbol.DeclaredAccessibility != Accessibility.Private && symbol.IsShared() && symbol.IsReadOnly; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Naming/TypeParameterName.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TypeParameterName : ParametrizedDiagnosticAnalyzer { private const string S119DiagnosticId = "S119"; [Obsolete("This rule is superseded by S119.")] private const string S2373DiagnosticId = "S2373"; private const string MessageFormat = "Rename '{0}' to match the regular expression: '{1}'."; private const string DefaultFormat = "^T(" + NamingPatterns.PascalCasingInternalPattern + ")?"; internal static readonly DiagnosticDescriptor S119 = DescriptorFactory.Create(S119DiagnosticId, MessageFormat, isEnabledByDefault: false); internal static readonly DiagnosticDescriptor S2373 = DescriptorFactory.Create(S2373DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(S119, S2373); [RuleParameter("format", PropertyType.String, "Regular expression used to check the generic type parameter names against.", DefaultFormat)] public string Pattern { get; set; } = DefaultFormat; protected override void Initialize(SonarParametrizedAnalysisContext context) => context.RegisterNodeAction(c => { var typeParameter = (TypeParameterSyntax)c.Node; if (!NamingPatterns.IsRegexMatch(typeParameter.Identifier.ValueText, Pattern)) { var location = typeParameter.Identifier.GetLocation(); c.ReportIssue(S119, location, typeParameter.Identifier.ValueText, Pattern); c.ReportIssue(S2373, location, typeParameter.Identifier.ValueText, Pattern); } }, SyntaxKind.TypeParameter); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/NegatedIsExpression.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class NegatedIsExpression : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2358"; private const string MessageFormat = "Replace this use of 'Not...Is...' with 'IsNot'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var unary = (UnaryExpressionSyntax)c.Node; if (unary.Operand.IsKind(SyntaxKind.IsExpression)) { c.ReportIssue(rule, unary); } }, SyntaxKind.NotExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/NegatedIsExpressionCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class NegatedIsExpressionCodeFix : SonarCodeFix { internal const string Title = "Replace 'Not...Is...' with 'IsNot'."; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(NegatedIsExpression.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var unary = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) as UnaryExpressionSyntax; if (!(unary?.Operand is BinaryExpressionSyntax isExpression) || !isExpression.IsKind(SyntaxKind.IsExpression)) { return Task.CompletedTask; } context.RegisterCodeFix( Title, c => ChangeToIsNotAsync(context.Document, unary, isExpression, c), context.Diagnostics); return Task.CompletedTask; } private static async Task ChangeToIsNotAsync(Document document, UnaryExpressionSyntax unary, BinaryExpressionSyntax isExpression, CancellationToken cancel) { var root = await document.GetSyntaxRootAsync(cancel).ConfigureAwait(false); var newRoot = root.ReplaceNode( unary, SyntaxFactory.BinaryExpression( SyntaxKind.IsNotExpression, isExpression.Left, SyntaxFactory.Token(SyntaxKind.IsNotKeyword).WithTriviaFrom(isExpression.OperatorToken), isExpression.Right)); return document.WithSyntaxRoot(newRoot); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/NoExceptionsInFinally.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class NoExceptionsInFinally : NoExceptionsInFinallyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var walker = new ThrowInFinallyWalker(c, Rule); foreach (var statement in ((FinallyBlockSyntax)c.Node).Statements) { walker.SafeVisit(statement); } }, SyntaxKind.FinallyBlock); private class ThrowInFinallyWalker : SafeVisualBasicSyntaxWalker { private readonly SonarSyntaxNodeReportingContext context; private readonly DiagnosticDescriptor rule; public ThrowInFinallyWalker(SonarSyntaxNodeReportingContext context, DiagnosticDescriptor rule) { this.context = context; this.rule = rule; } public override void VisitThrowStatement(ThrowStatementSyntax node) => context.ReportIssue(rule, node); public override void VisitFinallyBlock(FinallyBlockSyntax node) { // Do not call base to force the walker to stop. Another walker will take care of this finally clause. } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/NonAsyncTaskShouldNotReturnNull.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class NonAsyncTaskShouldNotReturnNull : NonAsyncTaskShouldNotReturnNullBase { private const string MessageFormat = "Do not return null from this method, instead return " + "'Task.FromResult(Of T)(Nothing)', 'Task.CompletedTask' or 'Task.Delay(0)'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var nullLiteral = (LiteralExpressionSyntax)c.Node; if (!IsParentReturnOrReturnTernary(nullLiteral)) { return; } var enclosingMember = GetEnclosingMember(nullLiteral); if (enclosingMember != null && !enclosingMember.IsKind(SyntaxKind.VariableDeclarator) && IsInvalidEnclosingSymbolContext(enclosingMember, c.Model)) { c.ReportIssue(rule, nullLiteral); } }, SyntaxKind.NothingLiteralExpression); } private static bool IsParentReturnOrReturnTernary(SyntaxNode node) { var parent = node.GetFirstNonParenthesizedParent(); if (parent.IsKind(SyntaxKind.ReturnStatement)) { return true; } else if (parent.IsKind(SyntaxKind.TernaryConditionalExpression)) { var grandParent = parent.GetFirstNonParenthesizedParent(); return grandParent.IsKind(SyntaxKind.ReturnStatement); } else { return false; } } private static SyntaxNode GetEnclosingMember(LiteralExpressionSyntax literal) { foreach (var ancestor in literal.Ancestors()) { switch (ancestor.Kind()) { case SyntaxKind.MultiLineFunctionLambdaExpression: case SyntaxKind.SingleLineFunctionLambdaExpression: return null; case SyntaxKind.VariableDeclarator: case SyntaxKind.PropertyBlock: case SyntaxKind.FunctionBlock: return ancestor; default: // do nothing break; } } return null; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ObsoleteAttributes.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ObsoleteAttributes : ObsoleteAttributesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode GetExplanationExpression(SyntaxNode node) => node is AttributeSyntax { ArgumentList.Arguments: { Count: >= 1 } arguments } && arguments[0] is SimpleArgumentSyntax { Expression: { } } argument ? argument.Expression : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/OnErrorStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class OnErrorStatement : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2359"; private const string MessageFormat = "Remove this use of 'OnError'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var node = (OnErrorGoToStatementSyntax)c.Node; c.ReportIssue(rule, node.OnKeyword.CreateLocation(node.ErrorKeyword)); }, SyntaxKind.OnErrorGoToLabelStatement, SyntaxKind.OnErrorGoToZeroStatement, SyntaxKind.OnErrorGoToMinusOneStatement); context.RegisterNodeAction( c => { var node = (OnErrorResumeNextStatementSyntax)c.Node; c.ReportIssue(rule, node.OnKeyword.CreateLocation(node.ErrorKeyword)); }, SyntaxKind.OnErrorResumeNextStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/OptionExplicitOn.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class OptionExplicitOn : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6146"; private const string MessageFormat = "{0}"; private const string StatementMessage = "Change this to 'Option Explicit On'."; private const string AssemblyMessageFormat = "Configure 'Option Explicit On' for assembly '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var statement = (OptionStatementSyntax)c.Node; if (statement.NameKeyword.IsKind(SyntaxKind.ExplicitKeyword) && statement.ValueKeyword.IsKind(SyntaxKind.OffKeyword)) { c.ReportIssue(Rule, statement, StatementMessage); } }, SyntaxKind.OptionStatement); context.RegisterCompilationStartAction(cStart => cStart.RegisterCompilationEndAction(c => { if (!c.Compilation.VB().Options.OptionExplicit) { c.ReportIssue(Rule, (Location)null, string.Format(AssemblyMessageFormat, c.Compilation.AssemblyName)); } })); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/OptionStrictOn.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class OptionStrictOn : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6145"; private const string MessageFormat = "{0}"; private const string StatementMessage = "Change this to 'Option Strict On'."; private const string AssemblyMessageFormat = "Configure 'Option Strict On' for assembly '{0}'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction(c => { var statement = (OptionStatementSyntax)c.Node; if (statement.NameKeyword.IsKind(SyntaxKind.StrictKeyword) && !statement.ValueKeyword.IsKind(SyntaxKind.OnKeyword)) { c.ReportIssue(Rule, statement, StatementMessage); } }, SyntaxKind.OptionStatement); context.RegisterCompilationStartAction(cStart => cStart.RegisterCompilationEndAction(c => { if (c.Compilation.VB().Options.OptionStrict != OptionStrict.On) { c.ReportIssue(Rule, (Location)null, string.Format(AssemblyMessageFormat, c.Compilation.AssemblyName)); } })); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/OptionalParameter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class OptionalParameter : OptionalParameterBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray kindsOfInterest = ImmutableArray.Create( SyntaxKind.SubStatement, SyntaxKind.SubNewStatement, SyntaxKind.PropertyStatement, SyntaxKind.FunctionStatement); public override ImmutableArray SyntaxKindsOfInterest => kindsOfInterest; protected override Location GetReportLocation(ParameterSyntax parameter) => parameter.Modifiers.First(m => m.IsKind(SyntaxKind.OptionalKeyword)).GetLocation(); protected override IEnumerable GetParameters(MethodBaseSyntax method) => method.ParameterList?.Parameters ?? Enumerable.Empty(); protected override bool IsOptional(ParameterSyntax parameter) => parameter.Modifiers.Any(SyntaxKind.OptionalKeyword); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/OptionalParameterNotPassedToBaseCall.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class OptionalParameterNotPassedToBaseCall : OptionalParameterNotPassedToBaseCallBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override DiagnosticDescriptor Rule => rule; protected override int GetArgumentCount(InvocationExpressionSyntax invocation) => invocation.ArgumentList == null ? 0 : invocation.ArgumentList.Arguments.Count; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var invocation = (InvocationExpressionSyntax)c.Node; if (!invocation.IsOnBase) { return; } ReportOptionalParameterNotPassedToBase(c, invocation); }, SyntaxKind.InvocationExpression); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ParameterAssignedTo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ParameterAssignedTo : ParameterAssignedToBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsAssignmentToCatchVariable(ISymbol symbol, SyntaxNode node) => // This could mimic the C# variant too, but that doesn't work https://github.com/dotnet/roslyn/issues/6209 symbol is ILocalSymbol localSymbol && localSymbol.Locations.FirstOrDefault() is { } location && node.SyntaxTree.GetRoot().FindNode(location.SourceSpan, getInnermostNodeForTie: true) is IdentifierNameSyntax declarationName && declarationName.Parent is CatchStatementSyntax catchStatement && catchStatement.IdentifierName == declarationName; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ParameterNameMatchesOriginal.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ParameterNameMatchesOriginal : ParameterNameMatchesOriginalBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = new[] { SyntaxKind.SubBlock, SyntaxKind.FunctionBlock }; protected override IEnumerable ParameterIdentifiers(MethodBlockBaseSyntax method) => method.BlockStatement.ParameterList.Parameters.Select(x => x.Identifier.Identifier); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ParametersCorrectOrder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ParametersCorrectOrder : ParametersCorrectOrderBase { protected override SyntaxKind[] InvocationKinds => new[] { SyntaxKind.ObjectCreationExpression, SyntaxKind.InvocationExpression }; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override Location PrimaryLocation(SyntaxNode node) => node switch { InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name: { } name } } => name.GetLocation(), // A.B.C() -> C InvocationExpressionSyntax { Expression: { } expression } => expression.GetLocation(), // A() -> A ObjectCreationExpressionSyntax { Type: QualifiedNameSyntax { Right: { } right } } => right.GetLocation(), // New A.B.C() -> C ObjectCreationExpressionSyntax { Type: { } type } => type.GetLocation(), // New A() -> A _ => base.PrimaryLocation(node), }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PartCreationPolicyShouldBeUsedWithExportAttribute.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PartCreationPolicyShouldBeUsedWithExportAttribute : PartCreationPolicyShouldBeUsedWithExportAttributeBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override ClassStatementSyntax GetTypeDeclaration(AttributeSyntax attribute) => attribute.FirstAncestorOrSelf(); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(AnalyzeNode, SyntaxKind.Attribute); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PreferGuidEmpty.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PreferGuidEmpty : PreferGuidEmptyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PropertiesAccessCorrectField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PropertiesAccessCorrectField : PropertiesAccessCorrectFieldBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IEnumerable FindFieldAssignments(IPropertySymbol property, Compilation compilation) { if (property.SetMethod.GetFirstSyntaxRef() is not AccessorStatementSyntax setter) { return []; } // we only keep information for the first location of the symbol var assignments = new Dictionary(); FillAssignments(setter, true); // If there're no candidate variables, we'll try to inspect one local method invocation with value as argument if (assignments.Count == 0 && SingleInvocation(setter) is { } expression && FindInvokedMethod(compilation, property.ContainingType, expression) is MethodBaseSyntax invokedMethod) { FillAssignments(invokedMethod, false); } return assignments.Values; void FillAssignments(SyntaxNode root, bool useFieldLocation) { // The ".Parent" is to go from the accessor statement to the accessor block foreach (var node in root.Parent.DescendantNodes()) { FieldData? foundField = null; if (node is AssignmentStatementSyntax { RawKind: (int)SyntaxKind.SimpleAssignmentStatement or (int)SyntaxKind.ConcatenateAssignmentStatement } assignment) { foundField = assignment.Left.DescendantNodesAndSelf().OfType() .Select(x => ExtractFieldFromExpression(AccessorKind.Setter, x, compilation, useFieldLocation)) .FirstOrDefault(x => x is not null); } else if (node is ArgumentSyntax argument) { foundField = ExtractFieldFromRefArgument(argument, compilation, useFieldLocation); } if (foundField.HasValue && !assignments.ContainsKey(foundField.Value.Field)) { assignments.Add(foundField.Value.Field, foundField.Value); } } } } protected override IEnumerable FindFieldReads(IPropertySymbol property, Compilation compilation) { // We don't handle properties with multiple returns that return different fields if (property.GetMethod.GetFirstSyntaxRef() is not AccessorStatementSyntax getter) { return []; } var reads = new Dictionary(); FillReads(getter, true); // If there're no candidate variables, we'll try inspect one return of local method invocation if (reads.Count == 0 && SingleReturn(getter) is InvocationExpressionSyntax returnExpression && FindInvokedMethod(compilation, property.ContainingType, returnExpression) is MethodBaseSyntax invokedMethod) { FillReads(invokedMethod, false); } return reads.Values; void FillReads(SyntaxNode root, bool useFieldLocation) { var notAssigned = root.Parent.DescendantNodes().OfType().Where(x => !IsLeftSideOfAssignment(x)); // The ".Parent" is to go from the accessor statement to the accessor block foreach (var expression in notAssigned) { var readField = ExtractFieldFromExpression(AccessorKind.Getter, expression, compilation, useFieldLocation); // we only keep information for the first location of the symbol if (readField.HasValue && !reads.ContainsKey(readField.Value.Field)) { reads.Add(readField.Value.Field, readField.Value); } } } } protected override bool ShouldIgnoreAccessor(IMethodSymbol accessorMethod, Compilation compilation) { if (accessorMethod.GetFirstSyntaxRef() is not AccessorStatementSyntax accessor || accessor.Parent.ContainsGetOrSetOnDependencyProperty(compilation) || AccessesSelfBaseProperty(accessorMethod, accessor.Parent, compilation)) { return true; } // Special case: ignore the accessor if the only statement/expression is a throw. return accessor.DescendantNodes(x => x is StatementSyntax).Take(2).Count() == 1 && accessor.DescendantNodes(x => x is ThrowStatementSyntax).Take(2).Count() == 1; } protected override bool ImplementsExplicitGetterOrSetter(IPropertySymbol property) => HasExplicitAccessor(property.SetMethod) || HasExplicitAccessor(property.GetMethod); private static ExpressionSyntax SingleReturn(StatementSyntax body) { var returns = body.Parent.DescendantNodes().OfType().ToArray(); return returns.Length == 1 ? returns.Single().Expression : null; } private static ExpressionSyntax SingleInvocation(StatementSyntax body) { var expressions = body.Parent.DescendantNodes().OfType().Select(x => x.Expression).ToArray(); if (expressions.Length == 1) { var expr = expressions.Single(); if (expr is IdentifierNameSyntax or MemberAccessExpressionSyntax { Expression: MeExpressionSyntax }) { return expr; } } return null; } private static FieldData? ExtractFieldFromRefArgument(ArgumentSyntax argument, Compilation compilation, bool useFieldLocation) { var model = compilation.GetSemanticModel(argument.SyntaxTree); if (model is not null && argument.Parent is ArgumentListSyntax argList) { var argumentIndex = argList.Arguments.IndexOf(argument); if (model.GetSymbolInfo(argList.Parent).Symbol is IMethodSymbol methodSymbol && argumentIndex < methodSymbol.Parameters.Length && methodSymbol.Parameters[argumentIndex]?.RefKind != RefKind.None) { return ExtractFieldFromExpression(AccessorKind.Setter, argument.GetExpression(), compilation, useFieldLocation); } } return null; } private static FieldData? ExtractFieldFromExpression(AccessorKind accessorKind, ExpressionSyntax expression, Compilation compilation, bool useFieldLocation) { var model = compilation.GetSemanticModel(expression.SyntaxTree); if (model is null) { return null; } var strippedExpression = expression.RemoveParentheses(); // Check for direct field access: "Foo" if (strippedExpression is IdentifierNameSyntax && IsFieldOrWithEvents(out var directSymbol)) { return new FieldData(accessorKind, directSymbol, strippedExpression, useFieldLocation); } // Check for "Me.Foo" else if (strippedExpression is MemberAccessExpressionSyntax { Expression: MeExpressionSyntax } member && model.GetSymbolInfo(strippedExpression).Symbol is IFieldSymbol field) { return new FieldData(accessorKind, field, member.Name, useFieldLocation); } return null; bool IsFieldOrWithEvents(out IFieldSymbol fieldSymbol) { var symbol = model.GetSymbolInfo(strippedExpression).Symbol; if (symbol is IFieldSymbol strippedExpressionSymbol) { fieldSymbol = strippedExpressionSymbol; return true; } else if (symbol is IPropertySymbol { IsWithEvents: true } property) { fieldSymbol = property.ContainingType.GetMembers("_" + property.Name).OfType().SingleOrDefault(); return fieldSymbol is not null; } else { fieldSymbol = null; return false; } } } private static bool IsLeftSideOfAssignment(ExpressionSyntax expression) { var strippedExpression = expression.RemoveParentheses(); return strippedExpression.IsLeftSideOfAssignment || (strippedExpression.Parent is ExpressionSyntax parent && parent.IsLeftSideOfAssignment); // for Me.field } private static bool HasExplicitAccessor(ISymbol symbol) => symbol.GetFirstSyntaxRef() is AccessorStatementSyntax accessor && accessor.Parent.DescendantNodes().Any(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PropertyGetterWithThrow.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PropertyGetterWithThrow : PropertyGetterWithThrowBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override SyntaxKind ThrowSyntaxKind => SyntaxKind.ThrowStatement; protected override bool IsGetter(AccessorBlockSyntax propertyGetter) => propertyGetter.IsKind(SyntaxKind.GetAccessorBlock); protected override bool IsIndexer(AccessorBlockSyntax propertyGetter) { if (!(propertyGetter.Parent is PropertyBlockSyntax propertyBlock)) { return false; } return propertyBlock.PropertyStatement.ParameterList != null && propertyBlock.PropertyStatement.ParameterList.Parameters.Any(); } protected override SyntaxNode GetThrowExpression(SyntaxNode syntaxNode) => ((ThrowStatementSyntax)syntaxNode).Expression; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PropertyWithArrayType.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PropertyWithArrayType : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2365"; private const string MessageFormat = "Refactor '{0}' into a method, properties should not be based on arrays."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var property = (PropertyStatementSyntax)c.Node; if (property.ImplementsClause is null && !property.Modifiers.Any(x => x.IsKind(SyntaxKind.OverridesKeyword)) && c.Model.GetDeclaredSymbol(property) is IPropertySymbol symbol && !symbol.IsAutoProperty() && symbol.Type is IArrayTypeSymbol) { c.ReportIssue(Rule, property.Identifier, symbol.Name); } }, SyntaxKind.PropertyStatement); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PropertyWriteOnly.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PropertyWriteOnly : PropertyWriteOnlyBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind SyntaxKind => SyntaxKind.PropertyStatement; protected override bool IsWriteOnlyProperty(PropertyStatementSyntax prop) => prop.Modifiers.Any(SyntaxKind.WriteOnlyKeyword); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ProvideDeserializationMethodsForOptionalFields.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ProvideDeserializationMethodsForOptionalFields : ProvideDeserializationMethodsForOptionalFieldsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override Location GetNamedTypeIdentifierLocation(SyntaxNode node) => ((TypeStatementSyntax)node).Identifier.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PublicConstantField.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PublicConstantField : PublicConstantFieldBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); public override SyntaxKind FieldDeclarationKind => SyntaxKind.FieldDeclaration; public override string MessageArgument => "'Shared Read-Only'"; protected override Location GetReportLocation(ModifiedIdentifierSyntax node) => node.GetLocation(); protected override IEnumerable GetVariables(FieldDeclarationSyntax node) => node.Declarators.SelectMany(d => d.Names); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PublicMethodWithMultidimensionalArray.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PublicMethodWithMultidimensionalArray : PublicMethodWithMultidimensionalArrayBase { private static readonly ImmutableArray KindsOfInterest = ImmutableArray.Create(SyntaxKind.SubStatement, SyntaxKind.FunctionStatement, SyntaxKind.ConstructorBlock); protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override ImmutableArray SyntaxKindsOfInterest => KindsOfInterest; protected override Location GetIssueLocation(SyntaxNode node) => node is ConstructorBlockSyntax x ? x.SubNewStatement.NewKeyword.GetLocation() : Language.Syntax.NodeIdentifier(node)?.GetLocation(); protected override string GetType(SyntaxNode node) => node is MethodStatementSyntax ? "method" : "constructor"; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/PureAttributeOnVoidMethod.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class PureAttributeOnVoidMethod : PureAttributeOnVoidMethodBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/RedundantExitSelect.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RedundantExitSelect : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2951"; private const string MessageFormat = "Remove this redundant use of 'Exit Select'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var exit = (ExitStatementSyntax)c.Node; if (!(exit.Parent is CaseBlockSyntax caseBlock)) { return; } if (caseBlock.Statements.Last() == exit) { c.ReportIssue(rule, exit); } }, SyntaxKind.ExitSelectStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/RedundantNullCheck.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RedundantNullCheck : RedundantNullCheckBase { private const string MessageFormat = "Remove this unnecessary null check; 'TypeOf ... Is' returns false for nulls."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( CheckAndExpression, SyntaxKind.AndExpression, SyntaxKind.AndAlsoExpression); context.RegisterNodeAction( CheckOrExpression, SyntaxKind.OrExpression, SyntaxKind.OrElseExpression); } protected override SyntaxNode GetLeftNode(BinaryExpressionSyntax binaryExpression) => binaryExpression.Left.RemoveParentheses(); protected override SyntaxNode GetRightNode(BinaryExpressionSyntax binaryExpression) => binaryExpression.Right.RemoveParentheses(); protected override SyntaxNode GetNullCheckVariable(SyntaxNode node) { if (RemoveParentheses(node) is BinaryExpressionSyntax binaryExpression && binaryExpression.IsKind(SyntaxKind.IsExpression) && GetRightNode(binaryExpression).IsKind(SyntaxKind.NothingLiteralExpression)) { return GetLeftNode(binaryExpression); } return null; } protected override SyntaxNode GetNonNullCheckVariable(SyntaxNode node) { var innerExpression = RemoveParentheses(node); if (innerExpression is BinaryExpressionSyntax binaryExpression && binaryExpression.IsKind(SyntaxKind.IsNotExpression) && GetRightNode(binaryExpression).IsKind(SyntaxKind.NothingLiteralExpression)) { return GetLeftNode(binaryExpression); } else if (innerExpression is UnaryExpressionSyntax prefixUnary && prefixUnary.IsKind(SyntaxKind.NotExpression)) { return GetNullCheckVariable(prefixUnary.Operand); } return null; } protected override SyntaxNode GetIsOperatorCheckVariable(SyntaxNode node) { if (RemoveParentheses(node) is TypeOfExpressionSyntax typeOfExpression && typeOfExpression.OperatorToken.IsKind(SyntaxKind.IsKeyword)) { return typeOfExpression.Expression.RemoveParentheses(); } return null; } protected override SyntaxNode GetInvertedIsOperatorCheckVariable(SyntaxNode node) { var innerExpression = RemoveParentheses(node); if (innerExpression is UnaryExpressionSyntax prefixUnary && prefixUnary.IsKind(SyntaxKind.NotExpression)) { return GetIsOperatorCheckVariable(prefixUnary.Operand); } else if (innerExpression is TypeOfExpressionSyntax typeOfExpression && typeOfExpression.OperatorToken.IsKind(SyntaxKind.IsNotKeyword)) { return typeOfExpression.Expression.RemoveParentheses(); } return null; } protected override bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) => VisualBasicEquivalenceChecker.AreEquivalent(node1, node2); private SyntaxNode RemoveParentheses(SyntaxNode syntaxNode) => syntaxNode.RemoveParentheses(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/RedundantParentheses.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RedundantParentheses : RedundantParenthesesBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxKind ParenthesizedExpressionSyntaxKind { get; } = SyntaxKind.ParenthesizedExpression; protected override SyntaxNode GetExpression(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.Expression; protected override SyntaxToken GetOpenParenToken(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.OpenParenToken; protected override SyntaxToken GetCloseParenToken(ParenthesizedExpressionSyntax parenthesizedExpression) => parenthesizedExpression.CloseParenToken; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/RegularExpressions/RegexMustHaveValidSyntax.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class RegexMustHaveValidSyntax : RegexMustHaveValidSyntaxBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ReversedOperators.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ReversedOperators : ReversedOperatorsBase { private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( GetAnalysisAction(Rule), // As opposed to C#, the VB operators for negation ('Not') and inequality ('<>') leave no room for confusion SyntaxKind.UnaryMinusExpression, SyntaxKind.UnaryPlusExpression); protected override SyntaxToken GetOperatorToken(UnaryExpressionSyntax e) => e.OperatorToken; protected override bool IsEqualsToken(SyntaxToken token) => token.IsKind(SyntaxKind.EqualsToken); protected override bool IsMinusToken(SyntaxToken token) => token.IsKind(SyntaxKind.MinusToken); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SecurityPInvokeMethodShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SecurityPInvokeMethodShouldNotBeCalled : SecurityPInvokeMethodShouldNotBeCalledBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsImportFromInteropDll(IMethodSymbol symbol, SemanticModel semanticModel) => base.IsImportFromInteropDll(symbol, semanticModel) || (symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is DeclareStatementSyntax declaration && IsInterop(declaration.LibraryName?.StringValue(semanticModel))); protected override string GetMethodName(ISymbol symbol, SemanticModel semanticModel) => symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is DeclareStatementSyntax declaration && declaration.AliasName != null ? declaration.AliasName.StringValue(semanticModel) : symbol.Name; protected override IMethodSymbol MethodSymbolForInvalidInvocation(SyntaxNode syntaxNode, SemanticModel semanticModel) => semanticModel.GetSymbolInfo(syntaxNode).Symbol is IMethodSymbol methodSymbol && GetMethodName(methodSymbol, semanticModel) is var methodName && InvalidMethods.Any(x => methodName.Equals(x, StringComparison.OrdinalIgnoreCase)) ? methodSymbol : null; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SelfAssignment.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SelfAssignment : SelfAssignmentBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var expression = (AssignmentStatementSyntax) c.Node; if (VisualBasicEquivalenceChecker.AreEquivalent(expression.Left, expression.Right)) { c.ReportIssue(Rule, c.Node); } }, SyntaxKind.SimpleAssignmentStatement); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SetPropertiesInsteadOfMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SetPropertiesInsteadOfMethods : SetPropertiesInsteadOfMethodsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool HasCorrectArgumentCount(InvocationExpressionSyntax invocation) => invocation.HasExactlyNArguments(1); // In VB you need to be explicit: Enumerable.Min(set), because set.Min() resolves to the property. protected override bool TryGetOperands(InvocationExpressionSyntax invocation, out SyntaxNode typeNode, out SyntaxNode methodNode) { typeNode = invocation.ArgumentList.Arguments[0].GetExpression(); methodNode = invocation; return true; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ShiftDynamicNotInteger.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ShiftDynamicNotInteger : ShiftDynamicNotIntegerBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool ShouldRaise(SemanticModel model, SyntaxNode left, SyntaxNode right) => IsObject(left, model) || !IsConvertibleToInt(right, model); protected override bool CanBeConvertedTo(SyntaxNode expression, ITypeSymbol type, SemanticModel model) => expression.IsKind(SyntaxKind.NothingLiteralExpression) // x >> Nothing will not throw, so ignore || model.GetTypeInfo(expression).Type.IsAny(KnownType.IntegralNumbers); private static bool IsObject(SyntaxNode expression, SemanticModel model) => model.GetTypeInfo(expression).Type is { } type && type.Is(KnownType.System_Object); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ShouldImplementExportedInterfaces.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ShouldImplementExportedInterfaces : ShouldImplementExportedInterfacesBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SeparatedSyntaxList? GetAttributeArguments(AttributeSyntax attributeSyntax) => attributeSyntax.ArgumentList?.Arguments; protected override SyntaxNode GetAttributeName(AttributeSyntax attributeSyntax) => attributeSyntax.Name; protected override bool IsClassOrRecordSyntax(SyntaxNode syntaxNode) => syntaxNode.IsKind(SyntaxKind.ClassStatement); protected override SyntaxNode GetTypeOfOrGetTypeExpression(SyntaxNode expressionSyntax) => (expressionSyntax as GetTypeExpressionSyntax)?.Type; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SimpleDoLoop.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SimpleDoLoop : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2340"; private const string MessageFormat = "Use a structured loop instead."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var doBlock = (DoLoopBlockSyntax)c.Node; c.ReportIssue(rule, doBlock.DoStatement.DoKeyword); }, SyntaxKind.SimpleDoLoopBlock); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SingleStatementPerLine.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SingleStatementPerLine : SingleStatementPerLineBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override bool StatementShouldBeExcluded(StatementSyntax statement) => StatementIsBlock(statement) || StatementIsSingleInLambda(statement); private static bool StatementIsSingleInLambda(StatementSyntax st) => !st.DescendantNodes().OfType().Any() && st.Parent is MultiLineLambdaExpressionSyntax multiline && multiline.Statements.Count == 1; private static bool StatementIsBlock(StatementSyntax st) => st is NamespaceBlockSyntax or TypeBlockSyntax or EnumBlockSyntax or MethodBlockBaseSyntax or PropertyBlockSyntax or EventBlockSyntax or DoLoopBlockSyntax or WhileBlockSyntax or ForOrForEachBlockSyntax or MultiLineIfBlockSyntax or ElseStatementSyntax or SyncLockBlockSyntax or TryBlockSyntax or UsingBlockSyntax or WithBlockSyntax or MethodBaseSyntax or InheritsOrImplementsStatementSyntax or SelectBlockSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/StringConcatenationInLoop.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class StringConcatenationInLoop : StringConcatenationInLoopBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] CompoundAssignmentKinds { get; } = [SyntaxKind.AddAssignmentStatement, SyntaxKind.ConcatenateAssignmentStatement]; protected override ISet ExpressionConcatenationKinds { get; } = new HashSet { SyntaxKind.AddExpression, SyntaxKind.ConcatenateExpression }; protected override ISet LoopKinds { get; } = new HashSet { SyntaxKind.WhileBlock, SyntaxKind.SimpleDoLoopBlock, SyntaxKind.ForBlock, SyntaxKind.ForEachBlock, SyntaxKind.DoUntilLoopBlock, SyntaxKind.DoWhileLoopBlock, SyntaxKind.DoLoopUntilBlock, SyntaxKind.DoLoopWhileBlock }; protected override SyntaxNode LeftMostExpression(SyntaxNode expression) => expression is InvocationExpressionSyntax ? null : ((ExpressionSyntax)expression).LeftMostInMemberAccess; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/StringConcatenationWithPlus.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class StringConcatenationWithPlus : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S1645"; private const string MessageFormat = "Switch this use of the '+' operator to the '&'."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction( c => { var binary = (BinaryExpressionSyntax)c.Node; var leftType = c.Model.GetTypeInfo(binary.Left).Type; if (leftType.Is(KnownType.System_String) // If op_Addition exist, there's areason for it => don't raise. We don't care about type of op_Addition arguments, they match because it compiles. || (leftType.GetMembers("op_Addition").IsEmpty && c.Model.GetTypeInfo(binary.Right).Type.Is(KnownType.System_String))) { c.ReportIssue(Rule, binary.OperatorToken); } }, SyntaxKind.AddExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/StringConcatenationWithPlusCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class StringConcatenationWithPlusCodeFix : SonarCodeFix { internal const string Title = "Change to '&'"; public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringConcatenationWithPlus.DiagnosticId); protected override Task RegisterCodeFixesAsync(SyntaxNode root, SonarCodeFixContext context) { var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; if (root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is BinaryExpressionSyntax binary) { context.RegisterCodeFix( Title, c => { var newRoot = CalculateNewRoot(root, binary); return Task.FromResult(context.Document.WithSyntaxRoot(newRoot)); }, context.Diagnostics); } return Task.CompletedTask; } private static SyntaxNode CalculateNewRoot(SyntaxNode root, BinaryExpressionSyntax currentAsBinary) { return root.ReplaceNode(currentAsBinary, SyntaxFactory.ConcatenateExpression( currentAsBinary.Left, SyntaxFactory.Token(SyntaxKind.AmpersandToken).WithTriviaFrom(currentAsBinary.OperatorToken), currentAsBinary.Right)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/StringLiteralShouldNotBeDuplicated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class StringLiteralShouldNotBeDuplicated : StringLiteralShouldNotBeDuplicatedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] SyntaxKinds { get; } = { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock }; protected override bool IsMatchingMethodParameterName(LiteralExpressionSyntax literalExpression) => literalExpression.FirstAncestorOrSelf() ?.BlockStatement?.ParameterList ?.Parameters .Any(p => p.Identifier.Identifier.ValueText.Equals(literalExpression.Token.ValueText, StringComparison.OrdinalIgnoreCase)) ?? false; protected override bool IsInnerInstance(SonarSyntaxNodeReportingContext context) => context.Node.HasAncestor(SyntaxKind.ClassBlock, SyntaxKind.StructureBlock); protected override IEnumerable FindLiteralExpressions(SyntaxNode node) => node.DescendantNodes(n => !n.IsKind(SyntaxKind.AttributeList)) .Where(les => les.IsKind(SyntaxKind.StringLiteralExpression)) .Cast(); protected override SyntaxToken LiteralToken(LiteralExpressionSyntax literal) => literal.Token; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SwitchCasesMinimumThree.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SwitchCasesMinimumThree : SwitchCasesMinimumThreeBase { private const string MessageFormat = "Replace this 'Select' statement with 'If' statements to increase readability."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var selectNode = (SelectBlockSyntax)c.Node; if (!HasAtLeastThreeLabels(selectNode)) { c.ReportIssue(rule, selectNode.SelectStatement.SelectKeyword); } }, SyntaxKind.SelectBlock); } private static bool HasAtLeastThreeLabels(SelectBlockSyntax node) => node.CaseBlocks.Sum(caseBlock => caseBlock.CaseStatement.Cases.Count) >= 3; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SwitchSectionShouldNotHaveTooManyStatements.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SwitchSectionShouldNotHaveTooManyStatements : SwitchSectionShouldNotHaveTooManyStatementsBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { var caseBlock = (CaseBlockSyntax)c.Node; if (caseBlock.IsMissing) { return; } var statementsCount = caseBlock.Statements.SelectMany(GetInnerStatements).Count(); if (statementsCount > Threshold) { c.ReportIssue(rule, caseBlock.CaseStatement, "'Case' block", statementsCount.ToString(), Threshold.ToString(), "procedure"); } }, SyntaxKind.CaseBlock, SyntaxKind.CaseElseBlock); } private IEnumerable GetInnerStatements(StatementSyntax statement) { return statement.DescendantNodesAndSelf() .OfType() .Where(s => !IsExcludedFromCount(s)); bool IsExcludedFromCount(StatementSyntax s) { switch (s.Kind()) { // Blocks are excluded because they contain statements (duplicating the interesting part) case SyntaxKind.CatchBlock: case SyntaxKind.DoLoopUntilBlock: case SyntaxKind.DoLoopWhileBlock: case SyntaxKind.DoUntilLoopBlock: case SyntaxKind.DoWhileLoopBlock: case SyntaxKind.ElseBlock: case SyntaxKind.ElseIfBlock: case SyntaxKind.FinallyBlock: case SyntaxKind.ForBlock: case SyntaxKind.ForEachBlock: case SyntaxKind.MultiLineIfBlock: case SyntaxKind.SelectBlock: case SyntaxKind.SimpleDoLoopBlock: case SyntaxKind.SyncLockBlock: case SyntaxKind.TryBlock: case SyntaxKind.UsingBlock: case SyntaxKind.WhileBlock: case SyntaxKind.WithBlock: // Don't count End statements case SyntaxKind.EndIfStatement: case SyntaxKind.EndSelectStatement: case SyntaxKind.EndSyncLockStatement: case SyntaxKind.EndTryStatement: case SyntaxKind.EndUsingStatement: case SyntaxKind.EndWhileStatement: case SyntaxKind.EndWithStatement: // Don't count the Do from Do...While and Do...Until case SyntaxKind.SimpleDoStatement: // Don't count the Next from For...Next case SyntaxKind.NextStatement: // Don't count single line if statements // We will count the next statement on the line anyway // Example: // If foo Then bar = 2 case SyntaxKind.SingleLineIfStatement: return true; default: return false; } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SwitchShouldNotBeNested.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SwitchShouldNotBeNested : SwitchShouldNotBeNestedBase { private const string MessageFormat = "Refactor the code to eliminate this nested 'Select Case'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var selectBlock = (SelectBlockSyntax)c.Node; if (selectBlock.Parent?.FirstAncestorOrSelf() != null) { c.ReportIssue(rule, selectBlock.SelectStatement); } }, SyntaxKind.SelectBlock); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/SwitchWithoutDefault.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class SwitchWithoutDefault : SwitchWithoutDefaultBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private static readonly ImmutableArray kindsOfInterest = ImmutableArray.Create(SyntaxKind.SelectBlock); public override ImmutableArray SyntaxKindsOfInterest => kindsOfInterest; protected override bool TryGetDiagnostic(SyntaxNode node, out Diagnostic diagnostic) { diagnostic = null; var switchNode = (SelectBlockSyntax)node; if (!HasDefaultLabel(switchNode)) { diagnostic = Diagnostic.Create(rule, switchNode.SelectStatement.SelectKeyword.GetLocation(), "Case Else", "Select"); return true; } return false; } private static bool HasDefaultLabel(SelectBlockSyntax node) { return node.CaseBlocks.Any(SyntaxKind.CaseElseBlock); } protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/TabCharacter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TabCharacter : TabCharacterBase { private const string DiagnosticId = "S105"; private const string MessageFormat = "Replace all tab characters in this file by sequences of white-spaces."; internal static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/TestsShouldNotUseThreadSleep.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TestsShouldNotUseThreadSleep : TestsShouldNotUseThreadSleepBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode MethodDeclaration(MethodBlockSyntax method) => method.SubOrFunctionStatement; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ThreadResumeOrSuspendShouldNotBeCalled.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ThreadResumeOrSuspendShouldNotBeCalled : ThreadResumeOrSuspendShouldNotBeCalledBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ThrowReservedExceptions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ThrowReservedExceptions : ThrowReservedExceptionsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => Process(c, ((ThrowStatementSyntax)c.Node).Expression), SyntaxKind.ThrowStatement); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ToStringShouldNotReturnNull.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ToStringShouldNotReturnNull : ToStringShouldNotReturnNullBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind MethodKind => SyntaxKind.FunctionBlock; protected override IEnumerable Conditionals(SyntaxNode expression) => expression is TernaryConditionalExpressionSyntax conditional ? new SyntaxNode[] { conditional.WhenTrue, conditional.WhenFalse } : Array.Empty(); protected override bool IsLocalOrLambda(SyntaxNode node) => node.IsKind(SyntaxKind.MultiLineFunctionLambdaExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/TooManyLabelsInSwitch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TooManyLabelsInSwitch : TooManyLabelsInSwitchBase { private static readonly ISet IgnoredStatementsInSwitch = new HashSet { SyntaxKind.ReturnStatement, SyntaxKind.ThrowStatement }; private static readonly ISet TransparentSyntax = new HashSet { SyntaxKind.CatchBlock, SyntaxKind.CaseBlock, SyntaxKind.DoWhileStatement, SyntaxKind.DoLoopUntilBlock, SyntaxKind.DoWhileLoopBlock, SyntaxKind.DoLoopWhileBlock, SyntaxKind.ElseIfBlock, SyntaxKind.ElseIfStatement, SyntaxKind.FinallyBlock, SyntaxKind.FinallyStatement, SyntaxKind.ForEachBlock, SyntaxKind.ForEachStatement, SyntaxKind.ForBlock, SyntaxKind.ForStatement, SyntaxKind.IfStatement, SyntaxKind.MultiLineIfBlock, SyntaxKind.SelectBlock, SyntaxKind.SelectStatement, SyntaxKind.SingleLineIfStatement, SyntaxKind.SingleLineElseClause, SyntaxKind.TryBlock, SyntaxKind.TryStatement, SyntaxKind.UsingBlock, SyntaxKind.UsingStatement, SyntaxKind.WhileBlock, SyntaxKind.WhileStatement }; protected override DiagnosticDescriptor Rule { get; } = DescriptorFactory.Create(DiagnosticId, string.Format(MessageFormat, "Select Case", "Case"), isEnabledByDefault: false); protected override SyntaxKind[] SyntaxKinds { get; } = [SyntaxKind.SelectStatement]; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxNode GetExpression(SelectStatementSyntax statement) => statement.Expression; protected override int GetSectionsCount(SelectStatementSyntax statement) => ((SelectBlockSyntax)statement.Parent).CaseBlocks.Count; protected override bool AllSectionsAreOneLiners(SelectStatementSyntax statement) => ((SelectBlockSyntax)statement.Parent).CaseBlocks.All(HasOneLine); protected override Location GetKeywordLocation(SelectStatementSyntax statement) => statement.SelectKeyword.GetLocation(); private static bool HasOneLine(CaseBlockSyntax switchSection) => switchSection.Statements .SelectMany(x => x.DescendantNodesAndSelf(descendIntoChildren: c => c.IsAnyKind(TransparentSyntax))) .Count(x => !x.IsAnyKind(IgnoredStatementsInSwitch)) is 0 or 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/TooManyParameters.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TooManyParameters : TooManyParametersBase { private static readonly ImmutableDictionary NodeToDeclarationName = new Dictionary { { SyntaxKind.SubNewStatement, "Constructor" }, { SyntaxKind.FunctionStatement, "Function" }, { SyntaxKind.SubStatement, "Sub" }, { SyntaxKind.DelegateFunctionStatement, "Delegate" }, { SyntaxKind.DelegateSubStatement, "Delegate" }, { SyntaxKind.SubLambdaHeader, "Lambda" }, { SyntaxKind.FunctionLambdaHeader, "Lambda" }, { SyntaxKind.PropertyStatement, "Property" }, { SyntaxKind.EventStatement, "Event" }, } .ToImmutableDictionary(); private static readonly SyntaxKind[] LambdaHeaders = [ SyntaxKind.FunctionLambdaHeader, SyntaxKind.SubLambdaHeader ]; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string UserFriendlyNameForNode(SyntaxNode node) => NodeToDeclarationName[node.Kind()]; protected override int CountParameters(ParameterListSyntax parameterList) => parameterList.Parameters.Count; protected override bool CanBeChanged(SyntaxNode node, SemanticModel semanticModel) => node.IsAnyKind(LambdaHeaders) || (NodeToDeclarationName.ContainsKey(node.Kind()) && VerifyCanBeChangedBySymbol(node, semanticModel)); protected override int BaseParameterCount(SyntaxNode node) => node.Parent is ConstructorBlockSyntax constructorBlock && constructorBlock.SubNewStatement.ParameterList?.Parameters.Count > Maximum // Performance optimization ? constructorBlock.Statements.Select(x => MyBaseNewParameterCount(x)).SingleOrDefault(x => x > 0) : 0; private static int MyBaseNewParameterCount(StatementSyntax statement) => statement is ExpressionStatementSyntax expression && expression.Expression is InvocationExpressionSyntax invocation && invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression is MyBaseExpressionSyntax && memberAccess.Name.Identifier.Text.Equals("New", System.StringComparison.OrdinalIgnoreCase) ? invocation.ArgumentList.Arguments.Count : 0; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnaryPrefixOperatorRepeated.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UnaryPrefixOperatorRepeated : UnaryPrefixOperatorRepeatedBase { protected override ISet SyntaxKinds { get; } = new HashSet { SyntaxKind.NotExpression }; protected override DiagnosticDescriptor Rule { get; } = DescriptorFactory.Create(DiagnosticId, MessageFormat); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer { get; } = VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxNode GetOperand(UnaryExpressionSyntax unarySyntax) => unarySyntax.Operand; protected override SyntaxToken GetOperatorToken(UnaryExpressionSyntax unarySyntax) => unarySyntax.OperatorToken; protected override bool SameOperators(UnaryExpressionSyntax expression1, UnaryExpressionSyntax expression2) => expression1.OperatorToken.IsKind(expression2.OperatorToken.Kind()); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnconditionalJumpStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UnconditionalJumpStatement : UnconditionalJumpStatementBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override ISet LoopStatements { get; } = new HashSet { SyntaxKind.ForBlock, SyntaxKind.ForEachBlock, SyntaxKind.WhileBlock, SyntaxKind.DoLoopWhileBlock, SyntaxKind.DoLoopUntilBlock, SyntaxKind.SimpleDoLoopBlock }; protected override LoopWalkerBase GetWalker(SonarSyntaxNodeReportingContext context) => new LoopWalker(context, LoopStatements); private class LoopWalker : LoopWalkerBase { protected override ISet StatementsThatCanThrow { get; } = new HashSet { SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression, SyntaxKind.SimpleMemberAccessExpression }; protected override ISet LambdaSyntaxes { get; } = new HashSet { SyntaxKind.FunctionLambdaHeader, SyntaxKind.MultiLineFunctionLambdaExpression, SyntaxKind.MultiLineSubLambdaExpression, SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression, SyntaxKind.SubLambdaHeader }; protected override ISet LocalFunctionSyntaxes { get; } = new HashSet(); protected override ISet ConditionalStatements { get; } = new HashSet { SyntaxKind.SingleLineIfStatement, SyntaxKind.SingleLineIfPart, SyntaxKind.MultiLineIfBlock, SyntaxKind.CaseBlock, SyntaxKind.CaseElseBlock, SyntaxKind.CatchBlock }; protected override ILanguageFacade Language => VisualBasicFacade.Instance; public LoopWalker(SonarSyntaxNodeReportingContext context, ISet loopStatements) : base(context, loopStatements) { } public override void Visit() { var vbWalker = new VbLoopwalker(this); vbWalker.SafeVisit(rootExpression); } protected override bool IsPropertyAccess(StatementSyntax node) => node.DescendantNodes().OfType().Any(x => semanticModel.GetSymbolInfo(x).Symbol is { } symbol && symbol.Kind == SymbolKind.Property); protected override bool TryGetTryAncestorStatements(StatementSyntax node, List ancestors, out IEnumerable tryAncestorStatements) { var tryAncestor = (TryBlockSyntax)ancestors.FirstOrDefault(n => n.IsKind(SyntaxKind.TryBlock)); if (tryAncestor == null || tryAncestor.CatchBlocks.Count == 0) { tryAncestorStatements = null; return false; } tryAncestorStatements = tryAncestor.Statements; return true; } private class VbLoopwalker : SafeVisualBasicSyntaxWalker { private readonly LoopWalker walker; public VbLoopwalker(LoopWalker loopWalker) { walker = loopWalker; } public override void VisitContinueStatement(ContinueStatementSyntax node) { base.VisitContinueStatement(node); walker.StoreVisitData(node, walker.ConditionalContinues, walker.UnconditionalContinues); } public override void VisitExitStatement(ExitStatementSyntax node) { base.VisitExitStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } public override void VisitReturnStatement(ReturnStatementSyntax node) { base.VisitReturnStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } public override void VisitThrowStatement(ThrowStatementSyntax node) { base.VisitThrowStatement(node); walker.StoreVisitData(node, walker.ConditionalTerminates, walker.UnconditionalTerminates); } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnnecessaryBitwiseOperation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UnnecessaryBitwiseOperation : UnnecessaryBitwiseOperationBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => CheckBinary(c, -1), SyntaxKind.AndExpression); context.RegisterNodeAction( c => CheckBinary(c, 0), SyntaxKind.OrExpression, SyntaxKind.ExclusiveOrExpression); } private void CheckBinary(SonarSyntaxNodeReportingContext context, int constValueToLookFor) { var binary = (BinaryExpressionSyntax)context.Node; CheckBinary(context, binary.Left, binary.OperatorToken, binary.Right, constValueToLookFor); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnnecessaryBitwiseOperationCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class UnnecessaryBitwiseOperationCodeFix : UnnecessaryBitwiseOperationCodeFixBase { protected override Func CreateNewRoot(SyntaxNode root, TextSpan diagnosticSpan, bool isReportingOnLeft) { if (root.FindNode(diagnosticSpan, getInnermostNodeForTie: true) is BinaryExpressionSyntax binary) { var newNode = isReportingOnLeft ? binary.Right : binary.Left; return () => root.ReplaceNode(binary, newNode.WithTrailingTrivia(binary.GetTrailingTrivia()).WithAdditionalAnnotations(Formatter.Annotation)); } else { return null; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnsignedTypesUsage.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UnsignedTypesUsage : SonarDiagnosticAnalyzer { internal const string DiagnosticId = "S2374"; private const string MessageFormat = "Change this unsigned type to '{0}'."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) { context.RegisterNodeAction( c => { var typeSyntax = (TypeSyntax)c.Node; if (typeSyntax.Parent is QualifiedNameSyntax) { return; } var typeSymbol = c.Model.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol; if (typeSymbol.IsAny(KnownType.UnsignedIntegers)) { c.ReportIssue(rule, typeSyntax, SignedPairs[typeSymbol.SpecialType]); } }, SyntaxKind.PredefinedType, SyntaxKind.IdentifierName, SyntaxKind.QualifiedName); } private static readonly IDictionary SignedPairs = new Dictionary { {SpecialType.System_UInt16, "Short"}, {SpecialType.System_UInt32, "Integer"}, {SpecialType.System_UInt64, "Long"} }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UnusedStringBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UnusedStringBuilder : UnusedStringBuilderBase { private static readonly HashSet SkipChildren = []; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode Scope(VariableDeclaratorSyntax declarator) => declarator is { Parent: LocalDeclarationStatementSyntax { Parent: { } block } } ? block : null; protected override ILocalSymbol RetrieveStringBuilderObject(SemanticModel model, VariableDeclaratorSyntax declarator) => declarator is { Parent: LocalDeclarationStatementSyntax, Initializer.Value: ObjectCreationExpressionSyntax { } objectCreation, Names: { Count: 1 } names, // Must be 1, because otherwise "BC30671: Explicit initialization is not permitted with multiple variables declared with a single type specifier." is raised. } && objectCreation.Type.IsKnownType(KnownType.System_Text_StringBuilder, model) ? model.GetDeclaredSymbol(names[0]) as ILocalSymbol : null; protected override SyntaxNode StringBuilderReadExpression(SemanticModel model, SyntaxNode node) => node switch { InvocationExpressionSyntax invocation when IsAccessInvocation(invocation.GetName()) || model.GetSymbolInfo(invocation).Symbol is IPropertySymbol { IsIndexer: true } => invocation.Expression, ReturnStatementSyntax returnStatement => returnStatement.Expression, InterpolationSyntax interpolation => interpolation.Expression, ArgumentSyntax argument => argument.GetExpression(), MemberAccessExpressionSyntax memberAccess when IsAccessExpression(memberAccess.Name.GetName()) => memberAccess.Expression, VariableDeclaratorSyntax { Initializer.Value: IdentifierNameSyntax identifier } => identifier, AssignmentStatementSyntax { Right: IdentifierNameSyntax identifier } => identifier, _ => null, }; protected override bool DescendIntoChildren(SyntaxNode node) => !node.IsAnyKind(SkipChildren); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UriShouldNotBeHardcoded.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UriShouldNotBeHardcoded : UriShouldNotBeHardcodedBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxKind[] StringConcatenateExpressions => [SyntaxKind.AddExpression, SyntaxKind.ConcatenateExpression]; protected override SyntaxKind[] InvocationOrObjectCreationKind => [SyntaxKind.InvocationExpression, SyntaxKind.ObjectCreationExpression]; protected override SyntaxNode GetRelevantAncestor(SyntaxNode node) => (SyntaxNode)node.FirstAncestorOrSelf() ?? node.FirstAncestorOrSelf(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseCharOverloadOfStringMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseCharOverloadOfStringMethods : UseCharOverloadOfStringMethodsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool HasCorrectArguments(InvocationExpressionSyntax invocation) => invocation.HasExactlyNArguments(1) && invocation.ArgumentList.Arguments[0].GetExpression() is { } literal && literal.IsKind(SyntaxKind.StringLiteralExpression) && ((LiteralExpressionSyntax)literal).Token.ValueText.Length == 1; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseDateTimeOffsetInsteadOfDateTime.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseDateTimeOffsetInsteadOfDateTime : UseDateTimeOffsetInsteadOfDateTimeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string[] ValidNames { get; } = new[] { "DateTime", "Date" }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseFindSystemTimeZoneById.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseFindSystemTimeZoneById : UseFindSystemTimeZoneByIdBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseIFormatProviderForParsingDateAndTime.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseIFormatProviderForParsingDateAndTime : UseIFormatProviderForParsingDateAndTimeBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseIndexingInsteadOfLinqMethods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseIndexingInsteadOfLinqMethods : UseIndexingInsteadOfLinqMethodsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override int GetArgumentCount(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.Count; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseLambdaParameterInConcurrentDictionary.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseLambdaParameterInConcurrentDictionary : UseLambdaParameterInConcurrentDictionaryBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SeparatedSyntaxList GetArguments(InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments; protected override bool IsLambdaAndContainsIdentifier(ArgumentSyntax argument, string keyName) => argument.GetExpression() is SingleLineLambdaExpressionSyntax lambda && lambda.Body.DescendantNodesAndSelf().OfType().Any(p => p.GetName().Equals(keyName) && p.Parent is not NameOfExpressionSyntax); protected override bool TryGetKeyName(ArgumentSyntax argument, out string keyName) { keyName = string.Empty; if (argument.GetExpression() is IdentifierNameSyntax identifier) { keyName = identifier.GetName(); return true; } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseReturnStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseReturnStatement : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S5944"; private const string MessageFormat = "{0}"; private const string UseReturnStatementMessage = "Use a 'Return' statement; assigning returned values to function names is obsolete."; private const string DontUseImplicitMessage = "Do not make use of the implicit return value."; private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(c => { var method = (MethodStatementSyntax)((MethodBlockSyntax)c.Node).BlockStatement; new IdentifierWalker(c, method.Identifier.ValueText).SafeVisit(c.Node); }, SyntaxKind.FunctionBlock); private class IdentifierWalker : SafeVisualBasicSyntaxWalker { private readonly SonarSyntaxNodeReportingContext context; private readonly string name; public IdentifierWalker(SonarSyntaxNodeReportingContext context, string name) { this.context = context; this.name = name; } public override void VisitIdentifierName(IdentifierNameSyntax node) { if (IsImplicitReturnValue(node)) { context.ReportIssue(Rule, node, IsAssignmentStatement(node) ? UseReturnStatementMessage : DontUseImplicitMessage); } } public override void VisitImplementsClause(ImplementsClauseSyntax node) { /* Skip */ } public override void VisitAttributeList(AttributeListSyntax node) { /* Skip */ } private bool IsImplicitReturnValue(IdentifierNameSyntax node) => name.Equals(node.Identifier.ValueText, StringComparison.InvariantCultureIgnoreCase) && !IsExcluded(node); private static bool IsExcluded(SyntaxNode node) => node.Parent switch { InvocationExpressionSyntax => true, MemberAccessExpressionSyntax => true, QualifiedNameSyntax => true, NamedFieldInitializerSyntax => true, NameOfExpressionSyntax => true, AsClauseSyntax => true, ObjectCreationExpressionSyntax => true, TypeArgumentListSyntax => true, UnaryExpressionSyntax unary => unary.IsKind(SyntaxKind.AddressOfExpression), NameColonEqualsSyntax nameColon => nameColon.Name == node, _ => false, }; private static bool IsAssignmentStatement(SyntaxNode node) => node.Parent is AssignmentStatementSyntax assignement && assignement.Left == node; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseShortCircuitingOperator.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseShortCircuitingOperator : UseShortCircuitingOperatorBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override string GetSuggestedOpName(BinaryExpressionSyntax node) => OperatorNames[ShortCircuitingAlternative[node.Kind()]]; protected override string GetCurrentOpName(BinaryExpressionSyntax node) => OperatorNames[node.Kind()]; protected override SyntaxToken GetOperator(BinaryExpressionSyntax expression) => expression.OperatorToken; internal static readonly IDictionary ShortCircuitingAlternative = new Dictionary { { SyntaxKind.AndExpression, SyntaxKind.AndAlsoExpression }, { SyntaxKind.OrExpression, SyntaxKind.OrElseExpression } }.ToImmutableDictionary(); private static readonly IDictionary OperatorNames = new Dictionary { { SyntaxKind.AndExpression, "And" }, { SyntaxKind.OrExpression, "Or" }, { SyntaxKind.AndAlsoExpression, "AndAlso" }, { SyntaxKind.OrElseExpression, "OrElse" }, }.ToImmutableDictionary(); protected override ImmutableArray SyntaxKindsOfInterest => ImmutableArray.Create( SyntaxKind.AndExpression, SyntaxKind.OrExpression); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseShortCircuitingOperatorCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class UseShortCircuitingOperatorCodeFix : UseShortCircuitingOperatorCodeFixBase { internal override bool IsCandidateExpression(BinaryExpressionSyntax expression) { return UseShortCircuitingOperator.ShortCircuitingAlternative.ContainsKey(expression.Kind()); } protected override BinaryExpressionSyntax GetShortCircuitingExpressionNode(BinaryExpressionSyntax expression) { return expression.IsKind(SyntaxKind.AndExpression) ? SyntaxFactory.AndAlsoExpression(expression.Left, expression.Right) : SyntaxFactory.OrElseExpression(expression.Left, expression.Right); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseTestableTimeProvider.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseTestableTimeProvider : UseTestableTimeProviderBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool Ignore(SyntaxNode ancestor, SemanticModel semanticModel) => ancestor.IsAnyKind(SyntaxKind.NameOfKeyword, SyntaxKind.XmlCrefAttribute); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseTrueForAll.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseTrueForAll : UseTrueForAllBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseUnixEpoch.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseUnixEpoch : UseUnixEpochBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override bool IsDateTimeKindUtc(MemberAccessExpressionSyntax memberAccess) => memberAccess.NameIs("Utc") && memberAccess.Expression.NameIs("DateTimeKind"); protected override bool IsGregorianCalendar(SyntaxNode node) => node is ObjectCreationExpressionSyntax objectCreation && objectCreation.Type.NameIs("GregorianCalendar"); protected override bool IsZeroTimeOffset(SyntaxNode node) => node switch { MemberAccessExpressionSyntax memberAccess => memberAccess.NameIs("Zero") && memberAccess.Expression.NameIs("TimeSpan"), ObjectCreationExpressionSyntax objectCreation => objectCreation.Type.NameIs("TimeSpan") && objectCreation?.ArgumentList != null && objectCreation.ArgumentList.Arguments.Count is 1 && objectCreation.ArgumentList.Arguments[0].GetExpression() is LiteralExpressionSyntax literal && IsValueEqualTo(literal, 0), _ => false }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseUnixEpochCodeFix.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [ExportCodeFixProvider(LanguageNames.VisualBasic)] public sealed class UseUnixEpochCodeFix : UseUnixEpochCodeFixBase { protected override SyntaxNode ReplaceConstructorWithField(SyntaxNode root, SyntaxNode node, SonarCodeFixContext context) { var leadingTrivia = node.GetLeadingTrivia(); var trailingTrivia = node.GetTrailingTrivia(); return root.ReplaceNode(node, SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, ((ObjectCreationExpressionSyntax)node).Type, SyntaxFactory.Token(SyntaxKind.DotToken), SyntaxFactory.IdentifierName("UnixEpoch")).WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseWhereBeforeOrderBy.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseWhereBeforeOrderBy : UseWhereBeforeOrderByBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/UseWithStatement.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class UseWithStatement : ParametrizedDiagnosticAnalyzer { internal const string DiagnosticId = "S2375"; private const string MessageFormat = "Wrap this and the following {0} statement{2} that use '{1}' in a 'With' statement."; private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat, isEnabledByDefault: false); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); private const int DefaultMinimumSeriesLength = 6; [RuleParameter("minimumSeriesLength", PropertyType.Integer, "Minimum length a series must have to trigger an issue.", DefaultMinimumSeriesLength)] public int MinimumSeriesLength { get; set; } = DefaultMinimumSeriesLength; private static readonly ISet WhiteListedStatementTypes = new HashSet { typeof(AssignmentStatementSyntax), typeof(WithBlockSyntax), typeof(ExpressionStatementSyntax) }; protected override void Initialize(SonarParametrizedAnalysisContext context) { context.RegisterNodeAction( c => { if (MinimumSeriesLength <= 1) { return; } var simpleMemberAccess = (MemberAccessExpressionSyntax)c.Node; var parenthesized = simpleMemberAccess.SelfOrTopParenthesizedExpression; if (parenthesized.Parent is MemberAccessExpressionSyntax) { // Only process top level member access expressions return; } var currentMemberExpression = simpleMemberAccess.Expression.RemoveParentheses(); while (simpleMemberAccess != null && !CheckExpression(c, currentMemberExpression)) { simpleMemberAccess = currentMemberExpression as MemberAccessExpressionSyntax; currentMemberExpression = simpleMemberAccess?.Expression.RemoveParentheses(); } }, SyntaxKind.SimpleMemberAccessExpression); } private bool CheckExpression(SonarSyntaxNodeReportingContext context, ExpressionSyntax expression) { if (!IsCandidateForExtraction(context, expression)) { return false; } var executableStatement = GetParentStatement(expression); if (executableStatement == null) { return false; } // check previous statement if it contains var prevStatement = executableStatement.PrecedingStatement as ExecutableStatementSyntax; if (IsCandidateStatement(prevStatement, expression)) { return false; } var matchCount = 1; // check following statements var nextStatement = executableStatement.SucceedingStatement as ExecutableStatementSyntax; while (IsCandidateStatement(nextStatement, expression)) { matchCount++; nextStatement = nextStatement.SucceedingStatement as ExecutableStatementSyntax; } if (matchCount >= MinimumSeriesLength) { var nextStatementCount = matchCount - 1; context.ReportIssue(rule, executableStatement, nextStatementCount.ToString(), expression.ToString(), nextStatementCount == 1 ? string.Empty : "s"); return true; } return false; } private static bool IsCandidateForExtraction(SonarSyntaxNodeReportingContext context, ExpressionSyntax currentMemberExpression) { return currentMemberExpression != null && !currentMemberExpression.IsKind(SyntaxKind.IdentifierName) && !(currentMemberExpression is InstanceExpressionSyntax) && !(context.Model.GetSymbolInfo(currentMemberExpression).Symbol is ITypeSymbol); } private static ExecutableStatementSyntax GetParentStatement(ExpressionSyntax expression) { var expr = expression; var parent = expr.Parent as ExpressionSyntax; while (parent != null) { expr = parent; parent = expr.Parent as ExpressionSyntax; } return expr.Parent as ExecutableStatementSyntax; } private static bool IsCandidateStatement(ExecutableStatementSyntax statement, ExpressionSyntax expression) { return statement != null && IsAllowedStatement(statement) && !ContainsEmptyMemberAccess(statement) && ContainsExpression(statement, expression); } private static bool ContainsEmptyMemberAccess(ExecutableStatementSyntax container) { return container.DescendantNodes() .OfType() .Any(m => m.Expression == null); } private static bool IsAllowedStatement(ExecutableStatementSyntax statement) { return WhiteListedStatementTypes.Contains(statement.GetType()); } private static bool ContainsExpression(ExecutableStatementSyntax container, ExpressionSyntax contained) { return contained != null && container.DescendantNodes() .OfType() .Any(m => m.Expression != null && VisualBasicEquivalenceChecker.AreEquivalent(contained, m.Expression)); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/AnalysisWarningAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class AnalysisWarningAnalyzer : AnalysisWarningAnalyzerBase { } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/CopyPasteTokenAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class CopyPasteTokenAnalyzer : CopyPasteTokenAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override bool IsUsingDirective(SyntaxNode node) => node is ImportsStatementSyntax; protected override string GetCpdValue(SyntaxToken token) { if (token.Kind() is SyntaxKind.DecimalLiteralToken or SyntaxKind.FloatingLiteralToken or SyntaxKind.IntegerLiteralToken) { return "$num"; } else if (token.Kind() is SyntaxKind.StringLiteralToken or SyntaxKind.InterpolatedStringTextToken) { return "$str"; } else if (token.IsKind(SyntaxKind.CharacterLiteralToken)) { return "$char"; } else { return token.Text; } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/FileMetadataAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class FileMetadataAnalyzer : FileMetadataAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/LogAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class LogAnalyzer : LogAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override string LanguageVersion(Compilation compilation) => ((VisualBasicCompilation)compilation).LanguageVersion.ToString(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/MetricsAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; using SonarAnalyzer.VisualBasic.Metrics; namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class MetricsAnalyzer : MetricsAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override MetricsBase GetMetrics(SyntaxTree syntaxTree, SemanticModel semanticModel) => new VisualBasicMetrics(syntaxTree, semanticModel); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/SymbolReferenceAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class SymbolReferenceAnalyzer : SymbolReferenceAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override SyntaxNode GetBindableParent(SyntaxToken token) => token.GetBindableParent(); protected override ReferenceInfo[] CreateDeclarationReferenceInfo(SyntaxNode node, SemanticModel model) => node switch { TypeStatementSyntax typeStatement => new[] { CreateDeclarationReferenceInfo(node, typeStatement.Identifier, model) }, MethodStatementSyntax methodStatement => new[] { CreateDeclarationReferenceInfo(node, methodStatement.Identifier, model) }, EventStatementSyntax eventStatement => new[] { CreateDeclarationReferenceInfo(node, eventStatement.Identifier, model) }, FieldDeclarationSyntax fieldDeclaration => CreateDeclarationReferenceInfo(fieldDeclaration, model), VariableDeclaratorSyntax variableDeclarator => CreateDeclarationReferenceInfo(variableDeclarator, model), ParameterSyntax parameter => new[] { CreateDeclarationReferenceInfo(node, parameter.Identifier.Identifier, model) }, LocalDeclarationStatementSyntax localDeclaration => CreateDeclarationReferenceInfo(localDeclaration, model), PropertyStatementSyntax propertyStatement => new[] { CreateDeclarationReferenceInfo(node, propertyStatement.Identifier, model) }, TypeParameterSyntax typeParameter => new[] { CreateDeclarationReferenceInfo(node, typeParameter.Identifier, model) }, _ => null }; protected override IList GetDeclarations(SyntaxNode node) { var walker = new DeclarationsFinder(); walker.SafeVisit(node); return walker.Declarations; } private static ReferenceInfo[] CreateDeclarationReferenceInfo(LocalDeclarationStatementSyntax declaration, SemanticModel model) => declaration.Declarators.SelectMany(x => CreateDeclarationReferenceInfo(x, model)).ToArray(); private static ReferenceInfo[] CreateDeclarationReferenceInfo(FieldDeclarationSyntax fieldDeclaration, SemanticModel model) => fieldDeclaration.Declarators.SelectMany(x => CreateDeclarationReferenceInfo(x, model)).ToArray(); private static ReferenceInfo[] CreateDeclarationReferenceInfo(VariableDeclaratorSyntax variableDeclarator, SemanticModel model) => variableDeclarator.Names.Select(x => CreateDeclarationReferenceInfo(x, x.Identifier, model)).ToArray(); private static ReferenceInfo CreateDeclarationReferenceInfo(SyntaxNode node, SyntaxToken identifier, SemanticModel model) => new(node, identifier, model.GetDeclaredSymbol(node), true); private sealed class DeclarationsFinder : SafeVisualBasicSyntaxWalker { private readonly ISet declarationKinds = new HashSet { SyntaxKind.ClassStatement, SyntaxKind.EnumStatement, SyntaxKind.EventStatement, SyntaxKind.FieldDeclaration, SyntaxKind.FunctionStatement, SyntaxKind.InterfaceStatement, SyntaxKind.LocalDeclarationStatement, SyntaxKind.Parameter, SyntaxKind.PropertyStatement, SyntaxKind.StructureStatement, SyntaxKind.SubStatement, SyntaxKind.TypeParameter, SyntaxKind.VariableDeclarator }.Cast().ToHashSet(); public readonly List Declarations = new(); public override void Visit(SyntaxNode node) { if (declarationKinds.Contains((ushort)node.RawKind)) { Declarations.Add(node); } base.Visit(node); } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/TelemetryAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class TelemetryAnalyzer : TelemetryAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override string LanguageVersion(Compilation compilation) => ((VisualBasicCompilation)compilation).LanguageVersion.ToString(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/TestMethodDeclarationsAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class TestMethodDeclarationsAnalyzer : TestMethodDeclarationsAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override IEnumerable GetMethodDeclarations(SyntaxNode node) => node.DescendantNodes().OfType(); protected override IEnumerable GetTypeDeclarations(SyntaxNode node) => node.DescendantNodes().OfType(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/Utilities/TokenTypeAnalyzer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public class TokenTypeAnalyzer : TokenTypeAnalyzerBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override TokenClassifierBase GetTokenClassifier(SemanticModel semanticModel, bool skipIdentifierTokens) => new TokenClassifier(semanticModel, skipIdentifierTokens); protected override TriviaClassifierBase GetTriviaClassifier() => new TriviaClassifier(); private sealed class TokenClassifier : TokenClassifierBase { public TokenClassifier(SemanticModel semanticModel, bool skipIdentifiers) : base(semanticModel, skipIdentifiers) { } protected override SyntaxNode GetBindableParent(SyntaxToken token) => token.GetBindableParent(); protected override bool IsIdentifier(SyntaxToken token) => token.IsKind(SyntaxKind.IdentifierToken); protected override bool IsKeyword(SyntaxToken token) => SyntaxFacts.IsKeywordKind(token.Kind()); protected override bool IsNumericLiteral(SyntaxToken token) => token.Kind() is SyntaxKind.DecimalLiteralToken or SyntaxKind.FloatingLiteralToken or SyntaxKind.IntegerLiteralToken; protected override bool IsStringLiteral(SyntaxToken token) => token.Kind() is SyntaxKind.StringLiteralToken or SyntaxKind.CharacterLiteralToken or SyntaxKind.InterpolatedStringTextToken or SyntaxKind.EndOfInterpolatedStringToken; } private sealed class TriviaClassifier : TriviaClassifierBase { protected override bool IsRegularComment(SyntaxTrivia trivia) => trivia.IsKind(SyntaxKind.CommentTrivia); protected override bool IsDocComment(SyntaxTrivia trivia) => trivia.IsKind(SyntaxKind.DocumentationCommentTrivia); } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/VariableUnused.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class VariableUnused : VariableUnusedBase { protected override ILanguageFacade Language { get; } = VisualBasicFacade.Instance; protected override bool IsExcludedDeclaration(SyntaxNode node) => node is ModifiedIdentifierSyntax { Parent.Parent: UsingStatementSyntax or ForStatementSyntax or ForEachStatementSyntax } // Using/For/For Each x ... in VB or ModifiedIdentifierSyntax { Parent: CatchStatementSyntax } // Catch e As Exception in VB or ModifiedIdentifierSyntax { Parent: CollectionRangeVariableSyntax or VariableNameEqualsSyntax }; // From x In / Select x = / Let x = / Into x = in VB LINQ } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/WcfNonVoidOneWay.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules { [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class WcfNonVoidOneWay : WcfNonVoidOneWayBase { private static readonly DiagnosticDescriptor rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule); protected override GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; protected override SyntaxKind MethodDeclarationKind => SyntaxKind.FunctionStatement; protected override Location GetReturnTypeLocation(MethodStatementSyntax method) => method.AsClause.Type.GetLocation(); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/Rules/WeakSslTlsProtocols.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Rules; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] public sealed class WeakSslTlsProtocols : WeakSslTlsProtocolsBase { protected override ILanguageFacade Language => VisualBasicFacade.Instance; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/SonarAnalyzer.VisualBasic.csproj ================================================  netstandard2.0 true SonarAnalyzer Visual Basic .NET NU1605, NU1701 ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "I5Z2WBgFsx0G22Na1uVFPDkT6Ob4XI+g91GPN8JWldYUMlmIBcUDBfGmfr8oQPdUipvThpaU1x1xZrnNwRR8JA==", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "yllH3rSYEc0bV15CJ2T9Jtx+tSXO5/OVNb+xofuWrACn65Q5VqeFBKgcbgwpyVY/98ypPcGQIWNQL2A/L1seJg==", "dependencies": { "Microsoft.CodeAnalysis.Common": "1.3.2" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.visualbasic.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[1.3.2, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json ================================================ { "rules-metadata-path": "../../rspec/vbnet/", "languages": [ "VBNET" ], "latest-update": "2026-04-27T06:52:08.926315Z", "options": { "no-language-in-filenames": true } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Common/DescriptorFactory.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Core.Rspec; namespace SonarAnalyzer.VisualBasic.Core.Common; public static class DescriptorFactory { public static DiagnosticDescriptor Create(string id, string messageFormat, bool? isEnabledByDefault = null, bool fadeOutCode = false, bool isCompilationEnd = false) => // RuleCatalog class is created from SonarAnalyzer.SourceGenerator DiagnosticDescriptorFactory.Create(AnalyzerLanguage.VisualBasic, RuleCatalog.Rules[id], messageFormat, isEnabledByDefault, fadeOutCode, isCompilationEnd); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/ICompilationReportExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; // Don't change the this parameter to (this IAnalysisContext context) because it would cause boxing public static class ICompilationReportExtensions { extension(TContext context) where TContext : ICompilationReport { public void ReportIssue(DiagnosticDescriptor rule, SyntaxNode locationSyntax, params string[] messageArgs) => context.ReportIssue(VisualBasicGeneratedCodeRecognizer.Instance, rule, locationSyntax, messageArgs); public void ReportIssue(DiagnosticDescriptor rule, SyntaxToken locationToken, params string[] messageArgs) => context.ReportIssue(VisualBasicGeneratedCodeRecognizer.Instance, rule, locationToken, messageArgs); public void ReportIssue(DiagnosticDescriptor rule, Location location, params string[] messageArgs) => context.ReportIssue(VisualBasicGeneratedCodeRecognizer.Instance, rule, location, messageArgs); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/ISymbolExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; public static class ISymbolExtensions { extension(ISymbol symbol) { public IEnumerable LocationNodes(SyntaxNode node) => symbol.Locations.SelectMany(location => GetDescendantNodes(location, node)); } public static IEnumerable GetDescendantNodes(Location location, SyntaxNode node) => location.SourceTree?.GetRoot() is { } locationRootNode && locationRootNode == node.SyntaxTree.GetRoot() && locationRootNode.FindNode(location.SourceSpan) .FirstAncestorOrSelf() is { } declaration ? declaration.DescendantNodes() : Enumerable.Empty(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/SonarAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; public static class SonarAnalysisContextExtensions { extension(SonarAnalysisContext context) { public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(VisualBasicGeneratedCodeRecognizer.Instance, action, syntaxKinds); public void RegisterTreeAction(Action action) => context.RegisterTreeAction(VisualBasicGeneratedCodeRecognizer.Instance, action); public void RegisterSemanticModelAction(Action action) => context.RegisterSemanticModelAction(VisualBasicGeneratedCodeRecognizer.Instance, action); public void RegisterCodeBlockStartAction(Action> action) => context.RegisterCodeBlockStartAction(VisualBasicGeneratedCodeRecognizer.Instance, action); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/SonarCompilationStartAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; public static class SonarCompilationStartAnalysisContextExtensions { extension(SonarCompilationStartAnalysisContext context) { public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(VisualBasicGeneratedCodeRecognizer.Instance, action, syntaxKinds); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/SonarParametrizedAnalysisContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; public static class SonarParametrizedAnalysisContextExtensions { extension(SonarParametrizedAnalysisContext context) { public void RegisterNodeAction(Action action, params SyntaxKind[] syntaxKinds) => context.RegisterNodeAction(VisualBasicGeneratedCodeRecognizer.Instance, action, syntaxKinds); public void RegisterTreeAction(Action action) => context.RegisterTreeAction(VisualBasicGeneratedCodeRecognizer.Instance, action); public void RegisterSemanticModelAction(Action action) => context.RegisterSemanticModelAction(VisualBasicGeneratedCodeRecognizer.Instance, action); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Extensions/SonarSyntaxNodeReportingContextExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Extensions; public static class SonarSyntaxNodeReportingContextExtensions { extension(SonarSyntaxNodeReportingContext context) { public bool IsInExpressionTree() => context.Node.IsInExpressionTree(context.Model); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Facade/Implementation/VisualBasicSyntaxFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using ComparisonKindEnum = SonarAnalyzer.Core.Syntax.Utilities.ComparisonKind; namespace SonarAnalyzer.VisualBasic.Core.Facade.Implementation; internal sealed class VisualBasicSyntaxFacade : SyntaxFacade { public override bool AreEquivalent(SyntaxNode firstNode, SyntaxNode secondNode) => SyntaxFactory.AreEquivalent(firstNode, secondNode); public override IEnumerable ArgumentExpressions(SyntaxNode node) => ArgumentList(node)?.OfType().Select(x => x.GetExpression()).WhereNotNull() ?? Enumerable.Empty(); public override IReadOnlyList ArgumentList(SyntaxNode node) => node.ArgumentList()?.Arguments; public override int? ArgumentIndex(SyntaxNode argument) => Cast(argument).GetArgumentIndex(); public override SyntaxToken? ArgumentNameColon(SyntaxNode argument) => (argument as SimpleArgumentSyntax)?.NameColonEquals?.Name.Identifier; public override SyntaxNode AssignmentLeft(SyntaxNode assignment) => Cast(assignment).Left; public override SyntaxNode AssignmentRight(SyntaxNode assignment) => Cast(assignment).Right; public override ImmutableArray AssignmentTargets(SyntaxNode assignment) => ImmutableArray.Create(Cast(assignment).Left); public override SyntaxNode BinaryExpressionLeft(SyntaxNode binary) => Cast(binary).Left; public override SyntaxNode BinaryExpressionRight(SyntaxNode binary) => Cast(binary).Right; public override SyntaxNode CastType(SyntaxNode cast) => Cast(cast).Type; public override SyntaxNode CastExpression(SyntaxNode cast) => Cast(cast).Expression; public override ComparisonKind ComparisonKind(SyntaxNode node) => node is BinaryExpressionSyntax { OperatorToken: var token } ? token.ToComparisonKind() : ComparisonKindEnum.None; public override IEnumerable EnumMembers(SyntaxNode @enum) => @enum is null ? Enumerable.Empty() : Cast(@enum).Parent.ChildNodes().OfType(); public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declarators.SelectMany(x => x.Names.Select(n => n.Identifier)).ToImmutableArray(); public override bool HasExactlyNArguments(SyntaxNode invocation, int count) => Cast(invocation).HasExactlyNArguments(count); public override SyntaxToken? InvocationIdentifier(SyntaxNode invocation) => invocation is null ? null : Cast(invocation).GetMethodCallIdentifier(); public override bool IsAnyKind(SyntaxNode node, ISet syntaxKinds) => node.IsAnyKind(syntaxKinds); [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public override bool IsAnyKind(SyntaxNode node, params SyntaxKind[] syntaxKinds) => node.IsAnyKind(syntaxKinds); public override bool IsAnyKind(SyntaxTrivia trivia, ISet syntaxKinds) => trivia.IsAnyKind(syntaxKinds); public override bool IsInExpressionTree(SemanticModel model, SyntaxNode node) => node.IsInExpressionTree(model); public override bool IsKind(SyntaxNode node, SyntaxKind kind) => node.IsKind(kind); public override bool IsKind(SyntaxToken token, SyntaxKind kind) => token.IsKind(kind); public override bool IsKind(SyntaxTrivia trivia, SyntaxKind kind) => trivia.IsKind(kind); public override bool IsKnownAttributeType(SemanticModel model, SyntaxNode attribute, KnownType knownType) => AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownType, model); public override bool IsMemberAccessOnKnownType(SyntaxNode memberAccess, string name, KnownType knownType, SemanticModel model) => Cast(memberAccess).IsMemberAccessOnKnownType(name, knownType, model); public override bool IsNullLiteral(SyntaxNode node) => node.IsNothingLiteral(); public override bool IsPartOfBinaryNegationOrCondition(SyntaxNode node) => node.IsPartOfBinaryNegationOrCondition(); public override bool IsStatic(SyntaxNode node) => Cast(node).IsShared(); /// public override bool IsWrittenTo(SyntaxNode expression, SemanticModel model, CancellationToken cancel) => Cast(expression).IsWrittenTo(model, cancel); public override SyntaxKind Kind(SyntaxNode node) => node.Kind(); public override string LiteralText(SyntaxNode literal) => Cast(literal).Token.ValueText; public override ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declarators.SelectMany(x => x.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 { ArgumentSyntax x => x.GetExpression(), InterpolationSyntax x => x.Expression, InvocationExpressionSyntax x => x.Expression, SyncLockStatementSyntax x => x.Expression, ReturnStatementSyntax x => x.Expression, MemberAccessExpressionSyntax x => x.Expression, null => null, _ => throw InvalidOperation(node, nameof(NodeExpression)), }; public override SyntaxToken? NodeIdentifier(SyntaxNode node) => node.GetIdentifier(); public override SyntaxToken? ObjectCreationTypeIdentifier(SyntaxNode objectCreation) => objectCreation is null ? null : Cast(objectCreation).GetObjectCreationTypeIdentifier(); public override SyntaxNode RemoveConditionalAccess(SyntaxNode node) { var whenNotNull = node.RemoveParentheses(); while (whenNotNull is ConditionalAccessExpressionSyntax conditionalAccess) { whenNotNull = conditionalAccess.WhenNotNull.RemoveParentheses(); } return whenNotNull; } public override SyntaxNode RemoveParentheses(SyntaxNode node) => node.RemoveParentheses(); public override string StringValue(SyntaxNode node, SemanticModel model) => node.StringValue(model); public override string InterpolatedTextValue(SyntaxNode node, SemanticModel model) => Cast(node).InterpolatedTextValue(model); public override Pair Operands(SyntaxNode invocation) => Cast(invocation).Operands(); public override SyntaxNode ParseExpression(string expression) => SyntaxFactory.ParseExpression(expression); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Facade/Implementation/VisualBasicSyntaxKindFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Facade.Implementation; internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade { public SyntaxKind Attribute => SyntaxKind.Attribute; public SyntaxKind AttributeArgument => SyntaxKind.SimpleArgument; public SyntaxKind[] CastExpressions => new[] { SyntaxKind.CTypeExpression, SyntaxKind.DirectCastExpression }; public SyntaxKind ClassDeclaration => SyntaxKind.ClassBlock; public SyntaxKind[] ClassAndRecordDeclarations => new[] { SyntaxKind.ClassBlock }; public SyntaxKind[] ClassAndModuleDeclarations => new[] { SyntaxKind.ClassBlock, SyntaxKind.ModuleBlock }; public SyntaxKind[] ComparisonKinds => new[] { SyntaxKind.GreaterThanExpression, SyntaxKind.GreaterThanOrEqualExpression, SyntaxKind.LessThanExpression, SyntaxKind.LessThanOrEqualExpression, SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression, }; public SyntaxKind ConstructorDeclaration => SyntaxKind.ConstructorBlock; public SyntaxKind[] DefaultExpressions => new[] { SyntaxKind.NothingLiteralExpression }; public SyntaxKind EndOfLineTrivia => SyntaxKind.EndOfLineTrivia; public SyntaxKind EnumDeclaration => SyntaxKind.EnumStatement; public SyntaxKind FieldDeclaration => SyntaxKind.FieldDeclaration; public SyntaxKind IdentifierName => SyntaxKind.IdentifierName; public SyntaxKind IdentifierToken => SyntaxKind.IdentifierToken; public SyntaxKind InvocationExpression => SyntaxKind.InvocationExpression; public SyntaxKind InterpolatedStringExpression => SyntaxKind.InterpolatedStringExpression; public SyntaxKind LeftShiftAssignmentStatement => SyntaxKind.LeftShiftAssignmentStatement; public SyntaxKind LeftShiftExpression => SyntaxKind.LeftShiftExpression; public SyntaxKind LocalDeclaration => SyntaxKind.LocalDeclarationStatement; public SyntaxKind[] MethodDeclarations => new[] { SyntaxKind.FunctionStatement, SyntaxKind.SubStatement }; public SyntaxKind[] ObjectCreationExpressions => 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 RightShiftAssignmentStatement => SyntaxKind.RightShiftAssignmentStatement; public SyntaxKind RightShiftExpression => SyntaxKind.RightShiftExpression; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentStatement; public SyntaxKind SimpleCommentTrivia => SyntaxKind.CommentTrivia; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression }; public SyntaxKind StructDeclaration => SyntaxKind.StructureBlock; public SyntaxKind SubtractExpression => SyntaxKind.SubtractExpression; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.EnumBlock }; public SyntaxKind VariableDeclarator => SyntaxKind.VariableDeclarator; public SyntaxKind[] LocalDeclarationKinds => [SyntaxKind.ModifiedIdentifier]; public SyntaxKind WhitespaceTrivia => SyntaxKind.WhitespaceTrivia; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Facade/Implementation/VisualBasicTrackerFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.VisualBasic.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Facade.Implementation; internal sealed class VisualBasicTrackerFacade : ITrackerFacade { public ArgumentTracker Argument => new VisualBasicArgumentTracker(); public BaseTypeTracker BaseType { get; } = new VisualBasicBaseTypeTracker(); public ElementAccessTracker ElementAccess { get; } = new VisualBasicElementAccessTracker(); public FieldAccessTracker FieldAccess { get; } = new VisualBasicFieldAccessTracker(); public InvocationTracker Invocation { get; } = new VisualBasicInvocationTracker(); public MethodDeclarationTracker MethodDeclaration { get; } = new VisualBasicMethodDeclarationTracker(); public ObjectCreationTracker ObjectCreation { get; } = new VisualBasicObjectCreationTracker(); public PropertyAccessTracker PropertyAccess { get; } = new VisualBasicPropertyAccessTracker(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Facade/VisualBasicFacade.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; using SonarAnalyzer.VisualBasic.Core.Facade.Implementation; using SonarAnalyzer.VisualBasic.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Facade; public sealed class VisualBasicFacade : ILanguageFacade { private static readonly Lazy Singleton = new(() => new VisualBasicFacade()); private static readonly Lazy AssignmentFinderLazy = new(() => new VisualBasicAssignmentFinder()); private static readonly Lazy ExpressionNumericConverterLazy = new(() => new VisualBasicExpressionNumericConverter()); private static readonly Lazy> SyntaxLazy = new(() => new VisualBasicSyntaxFacade()); private static readonly Lazy> SyntaxKindLazy = new(() => new VisualBasicSyntaxKindFacade()); private static readonly Lazy> TrackerLazy = new(() => new VisualBasicTrackerFacade()); public static VisualBasicFacade Instance => Singleton.Value; public AssignmentFinder AssignmentFinder => AssignmentFinderLazy.Value; public StringComparison NameComparison => StringComparison.OrdinalIgnoreCase; public StringComparer NameComparer => StringComparer.OrdinalIgnoreCase; public GeneratedCodeRecognizer GeneratedCodeRecognizer => VisualBasicGeneratedCodeRecognizer.Instance; public IExpressionNumericConverter ExpressionNumericConverter => ExpressionNumericConverterLazy.Value; public SyntaxFacade Syntax => SyntaxLazy.Value; public ISyntaxKindFacade SyntaxKind => SyntaxKindLazy.Value; public ITrackerFacade Tracker => TrackerLazy.Value; private VisualBasicFacade() { } public DiagnosticDescriptor CreateDescriptor(string id, string messageFormat, bool? isEnabledByDefault = null, bool fadeOutCode = false) => DescriptorFactory.Create(id, messageFormat, isEnabledByDefault, fadeOutCode); public object FindConstantValue(SemanticModel model, SyntaxNode node) => node.FindConstantValue(model); public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, IMethodSymbol methodSymbol) => invocation switch { null => null, AttributeSyntax x => new VisualBasicAttributeParameterLookup(x.ArgumentList.Arguments, methodSymbol), IdentifierNameSyntax { Parent: NameColonEqualsSyntax { Parent: SimpleArgumentSyntax { IsNamed: true, Parent.Parent: AttributeSyntax attribute } } } => new VisualBasicAttributeParameterLookup(attribute.ArgumentList.Arguments, methodSymbol), _ => new VisualBasicMethodParameterLookup(invocation.ArgumentList(), methodSymbol), }; public IMethodParameterLookup MethodParameterLookup(SyntaxNode invocation, SemanticModel model) => invocation?.ArgumentList() is { } argumentList ? new VisualBasicMethodParameterLookup(argumentList, model) : null; public string GetName(SyntaxNode expression) => expression.GetName(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Properties/AssemblyInfo.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SonarAnalyzer.Test")] [assembly: InternalsVisibleTo("SonarAnalyzer.VisualBasic.Core.Test")] ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/SonarAnalyzer.VisualBasic.Core.csproj ================================================  netstandard2.0 $(DefineConstants);VB NU1605, NU1701 vbnet ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/ArgumentListSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class ArgumentListSyntaxExtensions { public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => argumentList is not 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(this ArgumentListSyntax argumentList, SemanticModel model, string parameterName) => argumentList is not null && new VisualBasicMethodParameterLookup(argumentList, model).TryGetSyntax(parameterName, out var expressions) ? expressions : ImmutableArray.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/ArgumentSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class ArgumentSyntaxExtensions { internal static int? GetArgumentIndex(this ArgumentSyntax argument) => (argument.Parent as ArgumentListSyntax)?.Arguments.IndexOf(argument); internal static IEnumerable GetSymbolsOfKnownType(this SeparatedSyntaxList syntaxList, KnownType knownType, SemanticModel semanticModel) => syntaxList.GetArgumentsOfKnownType(knownType, semanticModel) .Select(argument => semanticModel.GetSymbolInfo(argument.GetExpression()).Symbol); private static IEnumerable GetArgumentsOfKnownType(this SeparatedSyntaxList syntaxList, KnownType knownType, SemanticModel semanticModel) => syntaxList.Where(argument => semanticModel.GetTypeInfo(argument.GetExpression()).Type.Is(knownType)); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/AttributeSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; internal static class AttributeSyntaxExtensions { private const int AttributeLength = 9; public static bool IsKnownType(this AttributeSyntax attribute, KnownType knownType, SemanticModel semanticModel) => attribute.Name.GetName().Contains(GetShortNameWithoutAttributeSuffix(knownType)) && ((SyntaxNode)attribute).IsKnownType(knownType, semanticModel); private static string GetShortNameWithoutAttributeSuffix(KnownType knownType) => knownType.TypeName == nameof(Attribute) || !knownType.TypeName.EndsWith(nameof(Attribute)) ? knownType.TypeName : knownType.TypeName.Remove(knownType.TypeName.Length - AttributeLength); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/ExpressionSyntaxExtensions.Roslyn.cs ================================================ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.VisualBasic.Extensions; [ExcludeFromCodeCoverage] internal static class ExpressionSyntaxExtensions { /// /// Returns if represents a node where a value is written to, like on the left side of an assignment expression. This method /// also returns for potentially writeable expressions, like parameters. /// See also . /// /// /// Copied from /// public static bool IsWrittenTo(this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { if (expression == null) return false; if (expression.IsOnlyWrittenTo()) return true; if (expression.IsRightSideOfDot()) expression = expression.Parent as ExpressionSyntax; if (expression != null) { if (expression.IsInRefContext(semanticModel, cancellationToken)) return true; if (expression.Parent is AssignmentStatementSyntax) { var assignmentStatement = (AssignmentStatementSyntax)expression.Parent; if (expression == assignmentStatement.Left) return true; } if (expression.IsChildNode(n => n.Name)) return true; // Extension method with a 'ref' parameter can write to the value it is called on. if (expression.Parent is MemberAccessExpressionSyntax) { var memberAccess = (MemberAccessExpressionSyntax)expression.Parent; if (memberAccess.Expression == expression) { var method = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol as IMethodSymbol; if (method != null) { if (method.MethodKind == MethodKind.ReducedExtension && method.ReducedFrom.Parameters.Length > 0 && method.ReducedFrom.Parameters.First().RefKind == RefKind.Ref) return true; } } } return false; } return false; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L325 public static bool IsOnlyWrittenTo(this ExpressionSyntax expression) { if (expression.IsRightSideOfDot()) expression = expression.Parent as ExpressionSyntax; if (expression != null) { // Sonar: IsInOutContext deleted because not relevant for VB if (expression.IsParentKind(SyntaxKind.SimpleAssignmentStatement)) { var assignmentStatement = (AssignmentStatementSyntax)expression.Parent; if (expression == assignmentStatement.Left) return true; } if (expression.IsParentKind(SyntaxKind.NameColonEquals) && expression.Parent.IsParentKind(SyntaxKind.SimpleArgument)) // // this is only a write to Prop return true; if (expression.IsChildNode(n => n.Name)) return true; return false; } return false; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L73 public static bool IsRightSideOfDot(this ExpressionSyntax expression) { return expression.IsSimpleMemberAccessExpressionName() || expression.IsRightSideOfQualifiedName(); } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L56 public static bool IsSimpleMemberAccessExpressionName(this ExpressionSyntax expression) { return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) && ((MemberAccessExpressionSyntax)expression.Parent).Name == expression; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L78 public static bool IsRightSideOfQualifiedName(this ExpressionSyntax expression) { return expression.IsParentKind(SyntaxKind.QualifiedName) && ((QualifiedNameSyntax)expression.Parent).Right == expression; } // Copy of // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/ExpressionSyntaxExtensions.vb#L277 public static bool IsInRefContext(this ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) { var simpleArgument = expression?.Parent as SimpleArgumentSyntax; if (simpleArgument == null) return false; else if (simpleArgument.IsNamed) { var info = semanticModel.GetSymbolInfo(simpleArgument.NameColonEquals.Name, cancellationToken); var parameter = info.Symbol as IParameterSymbol; return parameter != null && parameter.RefKind != RefKind.None; } else { var argumentList = simpleArgument.Parent as ArgumentListSyntax; if (argumentList != null) { var parent = argumentList.Parent; var index = argumentList.Arguments.IndexOf(simpleArgument); var info = semanticModel.GetSymbolInfo(parent, cancellationToken); var symbol = info.Symbol; if (symbol is IMethodSymbol) { var method = (IMethodSymbol)symbol; if (index < method.Parameters.Length) return method.Parameters[index].RefKind != RefKind.None; } else if (symbol is IPropertySymbol) { var prop = (IPropertySymbol)symbol; if (index < prop.Parameters.Length) return prop.Parameters[index].RefKind != RefKind.None; } } } return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/ExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class ExpressionSyntaxExtensions { private static readonly HashSet LiteralSyntaxKinds = [ SyntaxKind.CharacterLiteralExpression, SyntaxKind.FalseLiteralExpression, SyntaxKind.NothingLiteralExpression, SyntaxKind.NumericLiteralExpression, SyntaxKind.StringLiteralExpression, SyntaxKind.TrueLiteralExpression, ]; extension(ExpressionSyntax expression) { public ExpressionSyntax SelfOrTopParenthesizedExpression => (ExpressionSyntax)SyntaxNodeExtensionsVisualBasic.GetSelfOrTopParenthesizedExpression(expression); public bool IsOnBase => expression.IsOn(SyntaxKind.MyBaseExpression); /// /// On member access like operations, like a.b, c>a.b(), or a[b], the most left hand /// member (a) is returned. is skipped, so this.a returns a. /// public ExpressionSyntax LeftMostInMemberAccess => expression switch { IdentifierNameSyntax identifier => identifier, // Prop MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax identifier } => identifier, // Prop.Something -> Prop MemberAccessExpressionSyntax { Expression: MeExpressionSyntax, Name: IdentifierNameSyntax identifier } => identifier, // this.Prop -> Prop MemberAccessExpressionSyntax { Expression: PredefinedTypeSyntax predefinedType } => predefinedType, // int.MaxValue -> int MemberAccessExpressionSyntax { Expression: { } left } => left.LeftMostInMemberAccess, // Prop.Something.Something -> Prop InvocationExpressionSyntax { Expression: { } left } => left.LeftMostInMemberAccess, // Method() -> Method, also this.Method() and Method().Something ConditionalAccessExpressionSyntax conditional => conditional.RootConditionalAccessExpression.Expression.LeftMostInMemberAccess, // a?.b -> a ParenthesizedExpressionSyntax { Expression: { } inner } => inner.LeftMostInMemberAccess, // (a.b).c -> a _ => null, }; public ConditionalAccessExpressionSyntax RootConditionalAccessExpression => expression.AncestorsAndSelf().TakeWhile(x => x is ExpressionSyntax).OfType().LastOrDefault(); public bool IsLeftSideOfAssignment { get { var topParenthesizedExpression = expression.SelfOrTopParenthesizedExpression; return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentStatement) && topParenthesizedExpression.Parent is AssignmentStatementSyntax assignment && assignment.Left == topParenthesizedExpression; } } public ExpressionSyntax RemoveParentheses() => (ExpressionSyntax)SyntaxNodeExtensionsVisualBasic.RemoveParentheses(expression); public bool NameIs(string name) => expression.GetName().Equals(name, StringComparison.InvariantCultureIgnoreCase); public bool HasConstantValue(SemanticModel model) => expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(model) is not null; private bool IsOn(SyntaxKind onKind) => expression switch { InvocationExpressionSyntax invocation => invocation.Expression.IsOn(onKind), // This is a simplification as we don't check where the method is defined (so this could be this or base) GlobalNameSyntax or GenericNameSyntax or IdentifierNameSyntax or QualifiedNameSyntax => true, MemberAccessExpressionSyntax memberAccessExpression when memberAccessExpression.IsKind(SyntaxKind.SimpleMemberAccessExpression) => memberAccessExpression.Expression.RemoveParentheses().IsKind(onKind), ConditionalAccessExpressionSyntax conditionalAccess => conditionalAccess.Expression.RemoveParentheses().IsKind(onKind), _ => false, }; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/InterpolatedStringExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class InterpolatedStringExpressionSyntaxExtensions { public static string ContentsText(this InterpolatedStringExpressionSyntax interpolatedStringExpression) => interpolatedStringExpression.Contents.JoinStr(null, x => x.ToString()); public static string InterpolatedTextValue(this InterpolatedStringExpressionSyntax interpolatedStringExpression, SemanticModel model) => VisualBasicStringInterpolationConstantValueResolver.Instance.InterpolatedTextValue(interpolatedStringExpression, model); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/InvocationExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class InvocationExpressionSyntaxExtensions { public static bool IsMemberAccessOnKnownType(this InvocationExpressionSyntax invocation, string identifierName, KnownType knownType, SemanticModel semanticModel) => invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.IsMemberAccessOnKnownType(identifierName, knownType, semanticModel); public static IEnumerable GetArgumentSymbolsOfKnownType(this InvocationExpressionSyntax invocation, KnownType knownType, SemanticModel semanticModel) => invocation.ArgumentList.Arguments.GetSymbolsOfKnownType(knownType, semanticModel); public static bool HasExactlyNArguments(this InvocationExpressionSyntax invocation, int count) => invocation?.ArgumentList is null ? count == 0 : invocation.ArgumentList.Arguments.Count == count; public static Pair Operands(this InvocationExpressionSyntax invocation) => invocation is { Expression: MemberAccessExpressionSyntax access } ? new(access.Expression ?? invocation.GetParentConditionalAccessExpression()?.Expression, access.Name) : default; public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) => invocation?.Expression.GetIdentifier(); 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, StringComparison.InvariantCultureIgnoreCase); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/MemberAccessExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class MemberAccessExpressionSyntaxExtensions { public static bool IsMemberAccessOnKnownType(this MemberAccessExpressionSyntax memberAccess, string name, KnownType knownType, SemanticModel semanticModel) => memberAccess.NameIs(name) && semanticModel.GetSymbolInfo(memberAccess).Symbol is { } symbol && symbol.ContainingType.DerivesFrom(knownType); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/MethodBlockBaseSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class MethodBlockBaseSyntaxExtensions { public static Location FindIdentifierLocation(this MethodBlockBaseSyntax methodBlockBase) => GetIdentifierOrDefault(methodBlockBase)?.GetLocation(); public static SyntaxToken? GetIdentifierOrDefault(this MethodBlockBaseSyntax methodBlockBase) => methodBlockBase?.BlockStatement switch { SubNewStatementSyntax subNewStatement => subNewStatement.NewKeyword, MethodStatementSyntax methodStatement => methodStatement.Identifier, _ => null, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/MethodBlockSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class MethodBlockSyntaxExtensions { public static bool IsShared(this MethodBlockSyntax methodBlock) => methodBlock.SubOrFunctionStatement.Modifiers.Any(SyntaxKind.SharedKeyword); public static string GetIdentifierText(this MethodBlockSyntax method) => method.SubOrFunctionStatement.Identifier.ValueText; public static SeparatedSyntaxList? GetParameters(this MethodBlockSyntax method) => method.BlockStatement?.ParameterList?.Parameters; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/ObjectCreationExpressionSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; internal static class ObjectCreationExpressionSyntaxExtensions { public static SyntaxToken? GetObjectCreationTypeIdentifier(this ObjectCreationExpressionSyntax objectCreation) => objectCreation?.Type.GetIdentifier(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/StatementSyntaxExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class StatementSyntaxExtensions { extension(StatementSyntax currentStatement) { public StatementSyntax PrecedingStatement => currentStatement.Parent.ChildNodes() .TakeWhile(x => x != currentStatement) .LastOrDefault() as StatementSyntax; public StatementSyntax SucceedingStatement => currentStatement.Parent.ChildNodes() .SkipWhile(x => x != currentStatement) .Skip(1) .FirstOrDefault() as StatementSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/SyntaxNodeExtensions.Roslyn.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.VisualBasic.Extensions; [ExcludeFromCodeCoverage] internal static class SyntaxNodeExtensions { // Copied and converted from // https://github.com/dotnet/roslyn/blob/5a1cc5f83e4baba57f0355a685a5d1f487bfac66/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Extensions/SyntaxNodeExtensions.vb#L16 public static bool IsParentKind(this SyntaxNode node, SyntaxKind kind) { return node != null && node.Parent.IsKind(kind); } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/SyntaxNodeExtensionsVisualBasic.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.VisualBasic.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class SyntaxNodeExtensionsVisualBasic { private static readonly ControlFlowGraphCache CfgCache = new(); public static ControlFlowGraph CreateCfg(this SyntaxNode block, SemanticModel model, CancellationToken cancel) => CfgCache.FindOrCreate(block, model, cancel); public static bool IsPartOfBinaryNegationOrCondition(this SyntaxNode node) { if (node.Parent is not MemberAccessExpressionSyntax) { return false; } var current = node; while (current.Parent is not null && !current.Parent.IsAnyKind(SyntaxKind.IfStatement, SyntaxKind.WhileStatement)) { current = current.Parent; } return current.Parent switch { IfStatementSyntax ifStatement => ifStatement.Condition == current, WhileStatementSyntax whileStatement => whileStatement.Condition == current, _ => false }; } public static object FindConstantValue(this SyntaxNode node, SemanticModel semanticModel) => new VisualBasicConstantValueFinder(semanticModel).FindConstant(node); public static string FindStringConstant(this SyntaxNode node, SemanticModel semanticModel) => FindConstantValue(node, semanticModel) as string; // This is a refactored version of internal Roslyn SyntaxNodeExtensions.IsInExpressionTree public static bool IsInExpressionTree(this SyntaxNode node, SemanticModel model) { return node.AncestorsAndSelf().Any(x => IsExpressionLambda(x) || IsExpressionQuery(x)); bool IsExpressionLambda(SyntaxNode node) => node is LambdaExpressionSyntax && model.GetTypeInfo(node).ConvertedType.DerivesFrom(KnownType.System_Linq_Expressions_Expression); bool IsExpressionQuery(SyntaxNode node) => node is OrderingSyntax or QueryClauseSyntax or FunctionAggregationSyntax or ExpressionRangeVariableSyntax && TakesExpressionTree(model.GetSymbolInfo(node)); static bool TakesExpressionTree(SymbolInfo info) { var symbols = info.Symbol is null ? info.CandidateSymbols : ImmutableArray.Create(info.Symbol); return symbols.Any(x => x is IMethodSymbol method && method.Parameters.Length > 0 && method.Parameters[0].Type.DerivesFrom(KnownType.System_Linq_Expressions_Expression)); } } public static SyntaxToken? GetIdentifier(this SyntaxNode node) => node?.RemoveParentheses() switch { AttributeSyntax x => x.Name?.GetIdentifier(), 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, ModuleBlockSyntax x => x.ModuleStatement.Identifier, EnumStatementSyntax x => x.Identifier, EnumMemberDeclarationSyntax x => x.Identifier, InvocationExpressionSyntax x => x.Expression?.GetIdentifier(), ModifiedIdentifierSyntax x => x.Identifier, ObjectCreationExpressionSyntax x => x.Type?.GetIdentifier(), 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, VariableDeclaratorSyntax x => x.Names.FirstOrDefault()?.Identifier, _ => null, }; // based on kind="ArgumentList" in https://github.com/dotnet/roslyn/blob/main/src/Compilers/VisualBasic/Portable/Syntax/Syntax.xml public static ArgumentListSyntax ArgumentList(this SyntaxNode node) => node switch { ArgumentListSyntax argumentList => argumentList, ArrayCreationExpressionSyntax arrayCreation => arrayCreation.ArrayBounds, AttributeSyntax attribute => attribute.ArgumentList, InvocationExpressionSyntax invocation => invocation.ArgumentList, MidExpressionSyntax mid => mid.ArgumentList, ModifiedIdentifierSyntax modified => modified.ArrayBounds, ObjectCreationExpressionSyntax creation => creation.ArgumentList, RaiseEventStatementSyntax raise => raise.ArgumentList, RedimClauseSyntax reDim => reDim.ArrayBounds, null => null, _ => throw new InvalidOperationException($"The {nameof(node)} of kind {node.Kind()} does not have an {nameof(ArgumentList)}."), }; /// /// Returns the left hand side of a conditional access expression. Returns c in case like a?.b?[0].c?.d.e?.f if d is passed. /// /// Adapted from /// /// Roslyn SyntaxNodeExtensions VB.NET version. public static ConditionalAccessExpressionSyntax GetParentConditionalAccessExpression(this SyntaxNode node) { // Walk upwards based on the grammar/parser rules around ?. expressions (can be seen in // LanguageParser.ParseConsequenceSyntax). // These are the parts of the expression that the ?... expression can end with. Specifically: // // 1. x?.y.M() // invocation // 2. x?.y[...]; // element access // 3. x?.y.z // member access // 4. x?.y // member binding // 5. x?[y] // element binding if (node.IsAnyMemberAccessExpressionName()) { node = node.Parent; } // Effectively, if we're on the RHS of the ? we have to walk up the RHS spine first until we hit the first // conditional access. while (node is InvocationExpressionSyntax or MemberAccessExpressionSyntax or XmlMemberAccessExpressionSyntax && node.Parent is not ConditionalAccessExpressionSyntax) { node = node.Parent; } // Two cases we have to care about: // // 1. a?.b.$$c.d and // 2. a?.b.$$c.d?.e... // // Note that `a?.b.$$c.d?.e.f?.g.h.i` falls into the same bucket as two. i.e. the parts after `.e` are // lower in the tree and are not seen as we walk upwards. // // // To get the root ?. (the one after the `a`) we have to potentially consume the first ?. on the RHS of the // right spine (i.e. the one after `d`). Once we do this, we then see if that itself is on the RHS of a // another conditional, and if so we hten return the one on the left. i.e. for '2' this goes in this direction: // // a?.b.$$c.d?.e // it will do: // -----> // <--------- // // Note that this only one CAE consumption on both sides. GetRootConditionalAccessExpression can be used to // get the root parent in a case like: // // x?.y?.z?.a?.b.$$c.d?.e.f?.g.h.i // it will do: // -----> // <--------- // <--- // <--- // <--- if (node.Parent is ConditionalAccessExpressionSyntax conditional1 && conditional1.Expression == node) { node = node.Parent; } if (node.Parent is ConditionalAccessExpressionSyntax conditional2 && conditional2.WhenNotNull == node) { node = node.Parent; } return node as ConditionalAccessExpressionSyntax; } public static bool IsAnyMemberAccessExpressionName(this SyntaxNode node) => node.Parent is MemberAccessExpressionSyntax memberAccess && memberAccess.Name == node; public static bool IsTrue(this SyntaxNode node) => node switch { { RawKind: (int)SyntaxKind.TrueLiteralExpression } => true, // True { RawKind: (int)SyntaxKind.NotExpression } => IsFalse(((UnaryExpressionSyntax)node).Operand), // Not False _ => false, }; public static bool IsFalse(this SyntaxNode node) => node switch { { RawKind: (int)SyntaxKind.FalseLiteralExpression } => true, // False { RawKind: (int)SyntaxKind.NotExpression } => IsTrue(((UnaryExpressionSyntax)node).Operand), // Not True _ => false, }; public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => node.AncestorsAndSelf().LastOrDefault(x => x is MethodBaseSyntax || x is PropertyBlockSyntax); public static SyntaxNode RemoveParentheses(this SyntaxNode expression) { var current = expression; while (current is ParenthesizedExpressionSyntax parenthesized) { current = parenthesized.Expression; } return current; } public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) { var current = node; while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) { current = current.Parent; } return current; } public static bool HasAncestor(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => syntaxNode.Ancestors().Any(x => x.IsAnyKind(syntaxKinds)); public static bool IsNothingLiteral(this SyntaxNode syntaxNode) => syntaxNode is not null && syntaxNode.IsKind(SyntaxKind.NothingLiteralExpression); [Obsolete("Either use '.Kind() is A or B' or the overload with the ISet instead.")] public static bool IsAnyKind(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => syntaxNode is not null && syntaxKinds.Contains((SyntaxKind)syntaxNode.RawKind); public static bool IsAnyKind(this SyntaxNode syntaxNode, ISet collection) => syntaxNode is not null && collection.Contains((SyntaxKind)syntaxNode.RawKind); public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => node.GetSelfOrTopParenthesizedExpression().Parent; public static string GetName(this SyntaxNode expression) => expression.GetIdentifier()?.ValueText ?? string.Empty; public static string StringValue(this SyntaxNode node, SemanticModel model) => node switch { LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression) => literal.Token.ValueText, InterpolatedStringExpressionSyntax expression => expression.InterpolatedTextValue(model) ?? expression.ContentsText(), _ => null }; public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => nodes.Any(x => x.RawKind == (int)kind); private sealed class ControlFlowGraphCache : ControlFlowGraphCacheBase { protected override bool IsLocalFunction(SyntaxNode node) => false; protected override bool HasNestedCfg(SyntaxNode node) => node is LambdaExpressionSyntax; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/SyntaxTokenExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class SyntaxTokenExtensions { public static bool IsAnyKind(this SyntaxToken token, ISet collection) => collection.Contains((SyntaxKind)token.RawKind); public static ComparisonKind ToComparisonKind(this SyntaxToken token) => token.Kind() switch { SyntaxKind.EqualsToken => ComparisonKind.Equals, SyntaxKind.LessThanGreaterThanToken => ComparisonKind.NotEquals, SyntaxKind.LessThanToken => ComparisonKind.LessThan, SyntaxKind.LessThanEqualsToken => ComparisonKind.LessThanOrEqual, SyntaxKind.GreaterThanToken => ComparisonKind.GreaterThan, SyntaxKind.GreaterThanEqualsToken => ComparisonKind.GreaterThanOrEqual, _ => ComparisonKind.None, }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/SyntaxTriviaExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class SyntaxTriviaExtensions { private static readonly HashSet CommentKinds = [ SyntaxKind.CommentTrivia, SyntaxKind.DocumentationCommentExteriorTrivia, SyntaxKind.DocumentationCommentTrivia ]; public static bool IsAnyKind(this SyntaxTrivia trivia, ISet syntaxKinds) => syntaxKinds.Contains((SyntaxKind)trivia.RawKind); public static bool IsComment(this SyntaxTrivia trivia) => trivia.IsAnyKind(CommentKinds); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Extensions/VisualBasicCompilationExtensions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; public static class VisualBasicCompilationExtensions { public static bool IsAtLeastLanguageVersion(this Compilation compilation, LanguageVersion languageVersion) => compilation.VB()?.LanguageVersion.CompareTo(languageVersion) >= 0; public static VisualBasicCompilation VB(this Compilation compilation) => compilation as VisualBasicCompilation; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/SafeVisualBasicSyntaxWalker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public class SafeVisualBasicSyntaxWalker : VisualBasicSyntaxWalker, ISafeSyntaxWalker { public bool SafeVisit(SyntaxNode syntaxNode) { try { Visit(syntaxNode); return true; } catch (InsufficientExecutionStackException) { // Roslyn walker overflows the stack when the depth of the call is around 2050. // See https://github.com/SonarSource/sonar-dotnet/issues/2115 return false; } } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicAttributeParameterLookup.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; internal class VisualBasicAttributeParameterLookup(SeparatedSyntaxList argumentList, IMethodSymbol methodSymbol) : MethodParameterLookupBase(argumentList, methodSymbol) { protected override SyntaxNode Expression(ArgumentSyntax argument) => argument.GetExpression(); protected override SyntaxToken? GetNameColonIdentifier(ArgumentSyntax argument) => null; protected override SyntaxToken? GetNameEqualsIdentifier(ArgumentSyntax argument) => argument is SimpleArgumentSyntax { NameColonEquals.Name.Identifier: var identifier } ? identifier : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicEquivalenceChecker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public static class VisualBasicEquivalenceChecker { public static bool AreEquivalent(SyntaxNode node1, SyntaxNode node2) => EquivalenceChecker.AreEquivalent(node1, node2, (n1, n2) => SyntaxFactory.AreEquivalent(n1, n2)); public static bool AreEquivalent(SyntaxList nodeList1, SyntaxList nodeList2) => EquivalenceChecker.AreEquivalent(nodeList1, nodeList2, (n1, n2) => SyntaxFactory.AreEquivalent(n1, n2)); } public class VisualBasicSyntaxNodeEqualityComparer : IEqualityComparer, IEqualityComparer> where T : SyntaxNode { public bool Equals(T x, T y) => VisualBasicEquivalenceChecker.AreEquivalent(x, y); public bool Equals(SyntaxList x, SyntaxList y) => VisualBasicEquivalenceChecker.AreEquivalent(x, y); public int GetHashCode(T obj) => obj.GetType().FullName.GetHashCode(); public int GetHashCode(SyntaxList obj) => (obj.Count + string.Join(", ", obj.Select(x => x.GetType().FullName).Distinct())).GetHashCode(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicExpressionNumericConverter.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; internal class VisualBasicExpressionNumericConverter : ExpressionNumericConverterBase { private static readonly ISet SupportedOperatorTokens = new HashSet { SyntaxKind.MinusToken, SyntaxKind.PlusToken }; protected override object TokenValue(LiteralExpressionSyntax literalExpression) => literalExpression.Token.Value; protected override SyntaxNode Operand(UnaryExpressionSyntax unaryExpression) => unaryExpression.Operand; protected override bool IsSupportedOperator(UnaryExpressionSyntax unaryExpression) => SupportedOperatorTokens.Contains(unaryExpression.OperatorToken.Kind()); protected override bool IsMinusOperator(UnaryExpressionSyntax unaryExpression) => unaryExpression.OperatorToken.Kind() == SyntaxKind.MinusToken; protected override SyntaxNode RemoveParentheses(SyntaxNode expression) => expression.RemoveParentheses(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicGeneratedCodeRecognizer.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public sealed class VisualBasicGeneratedCodeRecognizer : GeneratedCodeRecognizer { #region Singleton implementation private VisualBasicGeneratedCodeRecognizer() { } private static readonly Lazy Lazy = new Lazy(() => new VisualBasicGeneratedCodeRecognizer()); public static VisualBasicGeneratedCodeRecognizer Instance => Lazy.Value; #endregion Singleton implementation protected override bool IsTriviaComment(SyntaxTrivia trivia) => trivia.IsKind(SyntaxKind.CommentTrivia); protected override string GetAttributeName(SyntaxNode node) => node.IsKind(SyntaxKind.Attribute) ? ((AttributeSyntax)node).Name.ToString() : string.Empty; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicMethodParameterLookup.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public class VisualBasicMethodParameterLookup : MethodParameterLookupBase { public VisualBasicMethodParameterLookup(InvocationExpressionSyntax invocation, SemanticModel semanticModel) : this(invocation.ArgumentList, semanticModel) { } public VisualBasicMethodParameterLookup(InvocationExpressionSyntax invocation, IMethodSymbol methodSymbol) : this(invocation.ArgumentList, methodSymbol) { } public VisualBasicMethodParameterLookup(ArgumentListSyntax argumentList, SemanticModel semanticModel) : base(argumentList.Arguments, semanticModel.GetSymbolInfo(argumentList.Parent)) { } public VisualBasicMethodParameterLookup(ArgumentListSyntax argumentList, IMethodSymbol methodSymbol) : base(argumentList.Arguments, methodSymbol) { } protected override SyntaxToken? GetNameColonIdentifier(ArgumentSyntax argument) => (argument as SimpleArgumentSyntax)?.NameColonEquals?.Name.Identifier; protected override SyntaxToken? GetNameEqualsIdentifier(ArgumentSyntax argument) => null; protected override SyntaxNode Expression(ArgumentSyntax argument) => argument.GetExpression(); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicRemovableDeclarationCollector.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using NodeSymbolAndModel = SonarAnalyzer.Core.Common.NodeSymbolAndModel; namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public class VisualBasicRemovableDeclarationCollector : RemovableDeclarationCollectorBase { public VisualBasicRemovableDeclarationCollector(INamedTypeSymbol namedType, Compilation compilation) : base(namedType, compilation) { } public static bool IsNodeStructOrClassDeclaration(SyntaxNode node) => node.IsKind(SyntaxKind.ClassBlock) || node.IsKind(SyntaxKind.StructureBlock); public static bool IsNodeContainerTypeDeclaration(SyntaxNode node) => IsNodeStructOrClassDeclaration(node) || node.IsKind(SyntaxKind.InterfaceBlock); protected override IEnumerable MatchingDeclarations(NodeAndModel container, ISet kinds) => container.Node.DescendantNodes(IsNodeContainerTypeDeclaration).Where(node => kinds.Contains(node.Kind())); public override IEnumerable RemovableFieldLikeDeclarations(ISet kinds, Accessibility maxAccessibility) { var fieldLikeNodes = TypeDeclarations .SelectMany(typeDeclaration => MatchingDeclarations(typeDeclaration, kinds) .Select(x => new NodeAndModel((FieldDeclarationSyntax)x, typeDeclaration.Model))); return fieldLikeNodes .SelectMany(fieldLikeNode => fieldLikeNode.Node.Declarators.SelectMany(x => x.Names) .Select(name => CreateNodeSymbolAndModel(name, fieldLikeNode.Model)) .Where(x => IsRemovable(x.Symbol, maxAccessibility))); } public override TypeBlockSyntax OwnerOfSubnodes(TypeStatementSyntax node) => node.Parent as TypeBlockSyntax; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicStringInterpolationConstantValueResolver.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public class VisualBasicStringInterpolationConstantValueResolver : StringInterpolationConstantValueResolver { private static readonly Lazy Singleton = new(() => new VisualBasicStringInterpolationConstantValueResolver()); public static VisualBasicStringInterpolationConstantValueResolver Instance => Singleton.Value; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IEnumerable Contents(InterpolatedStringExpressionSyntax interpolatedStringExpression) => interpolatedStringExpression.Contents; protected override SyntaxToken TextToken(InterpolatedStringTextSyntax interpolatedStringText) => interpolatedStringText.TextToken; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Syntax/Utilities/VisualBasicSyntaxClassifier.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Syntax.Utilities; namespace SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; public sealed class VisualBasicSyntaxClassifier : SyntaxClassifierBase { private static VisualBasicSyntaxClassifier instance; public static VisualBasicSyntaxClassifier Instance => instance ??= new(); private VisualBasicSyntaxClassifier() { } public override SyntaxNode MemberAccessExpression(SyntaxNode node) => (node as MemberAccessExpressionSyntax)?.Expression; protected override bool IsStatement(SyntaxNode node) => node is StatementSyntax; protected override SyntaxNode ParentLoopCondition(SyntaxNode node) => node.Parent switch { DoStatementSyntax doStatement => doStatement.WhileOrUntilClause, LoopStatementSyntax loopStatement => loopStatement.WhileOrUntilClause, ForBlockSyntax forBlock => forBlock.ForStatement, ForEachStatementSyntax foreachStatement => foreachStatement.Expression, WhileStatementSyntax whileStatement => whileStatement.Condition, _ => null }; protected override bool IsCfgBoundary(SyntaxNode node) => node is LambdaExpressionSyntax; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicArgumentTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicArgumentTracker : ArgumentTracker { protected override SyntaxKind[] TrackedSyntaxKinds => [SyntaxKind.SimpleArgument]; protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override IReadOnlyCollection ArgumentList(SyntaxNode argumentNode) => argumentNode is ArgumentSyntax { Parent: ArgumentListSyntax { Arguments: { } list } } ? list : null; protected override int? Position(SyntaxNode argumentNode) => argumentNode is ArgumentSyntax { IsNamed: true } ? null : ArgumentList(argumentNode)?.IndexOf(x => x == argumentNode); protected override RefKind? ArgumentRefKind(SyntaxNode argumentNode) => null; protected override bool InvocationMatchesMemberKind(SyntaxNode invokedExpression, MemberKind memberKind) => memberKind switch { MemberKind.Method => invokedExpression is InvocationExpressionSyntax, MemberKind.Constructor => invokedExpression is ObjectCreationExpressionSyntax, MemberKind.Indexer => invokedExpression is InvocationExpressionSyntax, MemberKind.Attribute => invokedExpression is AttributeSyntax, _ => false, }; protected override bool InvokedMemberMatches(SemanticModel model, SyntaxNode invokedExpression, MemberKind memberKind, Func invokedMemberNameConstraint) => invokedMemberNameConstraint(invokedExpression.GetName()); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicAssignmentFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicAssignmentFinder : AssignmentFinder { protected override SyntaxNode GetTopMostContainingMethod(SyntaxNode node) => node.GetTopMostContainingMethod(); /// 'true' will find any AssignmentExpressionSyntax like =, +=, -=, &=. 'false' will find only '=' SimpleAssignmentExpression. protected override bool IsAssignmentToIdentifier(SyntaxNode node, string identifierName, bool anyAssignmentKind, out SyntaxNode rightExpression) { if ((anyAssignmentKind || node.IsKind(SyntaxKind.SimpleAssignmentStatement)) && node is AssignmentStatementSyntax assignment && assignment.Left.NameIs(identifierName)) { rightExpression = assignment.Right; return true; } rightExpression = null; return false; } protected override bool IsIdentifierDeclaration(SyntaxNode node, string identifierName, out SyntaxNode initializer) { if (node is LocalDeclarationStatementSyntax declarationStatement && declarationStatement.Declarators.SingleOrDefault(MatchesIdentifierName) is { } declaration) { initializer = declaration.Initializer?.Value ?? (declaration.AsClause as AsNewClauseSyntax)?.NewExpression; return true; } initializer = null; return false; bool MatchesIdentifierName(VariableDeclaratorSyntax declarator) => declarator.Names.Any(n => identifierName.Equals(n.Identifier.ValueText, StringComparison.OrdinalIgnoreCase)); } protected override bool IsLoop(SyntaxNode node) => node.IsAnyKind(SyntaxKind.ForBlock, SyntaxKind.ForEachBlock, SyntaxKind.WhileBlock, SyntaxKind.DoLoopUntilBlock, SyntaxKind.DoLoopWhileBlock, SyntaxKind.DoUntilLoopBlock, SyntaxKind.DoWhileLoopBlock); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicBaseTypeTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicBaseTypeTracker : BaseTypeTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.InheritsStatement, SyntaxKind.ImplementsStatement }; protected override IEnumerable GetBaseTypeNodes(SyntaxNode contextNode) => // VB has separate Inherits and Implements keywords so the base types // are in separate lists under different types of syntax node. // If a class both inherits and implements then this tracker will check // the conditions against Inherits and Implements *separately* // i.e. the conditions will be called twice contextNode switch { InheritsStatementSyntax inherits => inherits.Types, ImplementsStatementSyntax implements => implements.Types, _ => Enumerable.Empty(), }; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicBuilderPatternCondition.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicBuilderPatternCondition : BuilderPatternCondition { public VisualBasicBuilderPatternCondition(bool constructorIsSafe, params BuilderPatternDescriptor[] descriptors) : base(constructorIsSafe, descriptors, new VisualBasicAssignmentFinder()) { } protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxNode GetExpression(InvocationExpressionSyntax node) => node.Expression; protected override string GetIdentifierName(InvocationExpressionSyntax node) => node.Expression.GetName(); protected override bool IsMemberAccess(SyntaxNode node, out SyntaxNode memberAccessExpression) { if (node is MemberAccessExpressionSyntax memberAccess) { memberAccessExpression = memberAccess.Expression; return true; } memberAccessExpression = null; return false; } protected override bool IsObjectCreation(SyntaxNode node) => node is ObjectCreationExpressionSyntax; protected override bool IsIdentifier(SyntaxNode node, out string identifierName) { if (node is IdentifierNameSyntax identifier) { identifierName = identifier.Identifier.ValueText; return true; } identifierName = null; return false; } } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicConstantValueFinder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicConstantValueFinder : ConstantValueFinder { public VisualBasicConstantValueFinder(SemanticModel semanticModel) : base(semanticModel, new VisualBasicAssignmentFinder(), (int)SyntaxKind.NothingLiteralExpression) { } protected override string IdentifierName(IdentifierNameSyntax node) => node.Identifier.ValueText; protected override SyntaxNode InitializerValue(VariableDeclaratorSyntax node) => node.Initializer?.Value; protected override VariableDeclaratorSyntax VariableDeclarator(SyntaxNode node) => node?.Parent as VariableDeclaratorSyntax; protected override bool IsPtrZero(SyntaxNode node) => node is MemberAccessExpressionSyntax memberAccess && memberAccess.IsPtrZero(Model); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicElementAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicElementAccessTracker : ElementAccessTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = { SyntaxKind.InvocationExpression }; public override object AssignedValue(ElementAccessContext context) => context.Node.Ancestors().FirstOrDefault(x => x.IsKind(SyntaxKind.SimpleAssignmentStatement)) is AssignmentStatementSyntax assignment ? assignment.Right.FindConstantValue(context.Model) : null; public override Condition ArgumentAtIndexEquals(int index, string value) => context => ((InvocationExpressionSyntax)context.Node).ArgumentList is { } argumentList && index < argumentList.Arguments.Count && argumentList.Arguments[index].GetExpression().FindStringConstant(context.Model) == value; public override Condition MatchSetter() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment; public override Condition MatchProperty(MemberDescriptor member) => context => ((InvocationExpressionSyntax)context.Node).Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) && context.Model.GetTypeInfo(memberAccess.Expression) is TypeInfo enclosingClassType && member.IsMatch(memberAccess.Name.Identifier.ValueText, enclosingClassType.Type, Language.NameComparison); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicFieldAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicFieldAccessTracker : FieldAccessTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.IdentifierName }; public override Condition WhenRead() => context => !((ExpressionSyntax)context.Node).IsLeftSideOfAssignment; public override Condition MatchSet() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment; public override Condition AssignedValueIsConstant() => context => { var assignment = (AssignmentStatementSyntax)context.Node.Ancestors().FirstOrDefault(ancestor => ancestor.IsKind(SyntaxKind.SimpleAssignmentStatement)); return assignment != null && assignment.Right.HasConstantValue(context.Model); }; protected override bool IsIdentifierWithinMemberAccess(SyntaxNode expression) => expression.IsKind(SyntaxKind.IdentifierName) && expression.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicInvocationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicInvocationTracker : InvocationTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = [SyntaxKind.InvocationExpression, SyntaxKind.SimpleMemberAccessExpression]; public override Condition ArgumentAtIndexIsStringConstant(int index) => ArgumentAtIndexConformsTo(index, (argument, model) => argument.GetExpression().FindStringConstant(model) is not null); public override Condition ArgumentAtIndexIsAny(int index, params string[] values) => ArgumentAtIndexConformsTo(index, (argument, model) => values.Contains(argument.GetExpression().FindStringConstant(model))); public override Condition ArgumentAtIndexIs(int index, Func predicate) => ArgumentAtIndexConformsTo(index, (argument, model) => predicate(argument, model)); public override Condition MatchProperty(MemberDescriptor member) => context => ((InvocationExpressionSyntax)context.Node).Expression is MemberAccessExpressionSyntax methodMemberAccess && methodMemberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) && methodMemberAccess.Expression is MemberAccessExpressionSyntax propertyMemberAccess && propertyMemberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) && context.Model.GetTypeInfo(propertyMemberAccess.Expression) is var enclosingClassType && member.IsMatch(propertyMemberAccess.Name.Identifier.ValueText, enclosingClassType.Type, Language.NameComparison); public override object ConstArgumentForParameter(InvocationContext context, string parameterName) { var argumentList = ((InvocationExpressionSyntax)context.Node).ArgumentList; var values = argumentList.ArgumentValuesForParameter(context.Model, parameterName); return values.Length == 1 && values[0] is ExpressionSyntax valueSyntax ? valueSyntax.FindConstantValue(context.Model) : null; } protected override SyntaxNode NodeExpression(SyntaxNode node) => node switch { MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax } => null, // Avoid duplicate handling, this is handled by InvocationExpression MemberAccessExpressionSyntax => node, _ => base.NodeExpression(node) }; protected override SyntaxToken? ExpectedExpressionIdentifier(SyntaxNode expression) => ((ExpressionSyntax)expression).GetIdentifier(); private static Condition ArgumentAtIndexConformsTo(int index, Func predicate) => context => context.Node is InvocationExpressionSyntax { ArgumentList: { } argumentList } && index < argumentList.Arguments.Count && argumentList.Arguments[index] is { } argument && predicate(argument, context.Model); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicMethodDeclarationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicMethodDeclarationTracker : MethodDeclarationTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public override Condition ParameterAtIndexIsUsed(int index) => context => { var parameterSymbol = context.MethodSymbol.Parameters.ElementAtOrDefault(0); if (parameterSymbol == null) { return false; } var methodDeclaration = context.MethodSymbol.DeclaringSyntaxReferences .Select(r => (MethodBlockSyntax)r.GetSyntax().Parent) .FirstOrDefault(HasImplementation); if (methodDeclaration == null) { return false; } var semanticModel = context.GetSemanticModel(methodDeclaration); var descendantNodes = methodDeclaration.Statements .SelectMany(statement => statement.DescendantNodes()); return descendantNodes.Any( node => node.IsKind(SyntaxKind.IdentifierName) && ((IdentifierNameSyntax)node).Identifier.ValueText == parameterSymbol.Name && parameterSymbol.Equals(semanticModel.GetSymbolInfo(node).Symbol)); }; protected override SyntaxToken? GetMethodIdentifier(SyntaxNode methodDeclaration) => methodDeclaration switch { SubNewStatementSyntax constructor => constructor.NewKeyword, MethodStatementSyntax method => method.Identifier, OperatorStatementSyntax op => op.OperatorToken, _ => methodDeclaration?.Parent.Parent switch { EventBlockSyntax e => e.EventStatement.Identifier, PropertyBlockSyntax p => p.PropertyStatement.Identifier, _ => null } }; private static bool HasImplementation(MethodBlockSyntax methodBlock) => methodBlock.Statements.Count > 0; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicObjectCreationTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicObjectCreationTracker : ObjectCreationTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; public override Condition ArgumentAtIndexIsConst(int index) => context => ((ObjectCreationExpressionSyntax)context.Node).ArgumentList is { } argumentList && argumentList.Arguments.Count > index && argumentList.Arguments[index].GetExpression().HasConstantValue(context.Model); public override object ConstArgumentForParameter(ObjectCreationContext context, string parameterName) => ((ObjectCreationExpressionSyntax)context.Node).ArgumentList is { } argumentList && argumentList.ArgumentValuesForParameter(context.Model, parameterName) is { Length: 1 } values && values[0] is ExpressionSyntax valueSyntax ? valueSyntax.FindConstantValue(context.Model) : null; } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/Trackers/VisualBasicPropertyAccessTracker.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.VisualBasic.Core.Trackers; public class VisualBasicPropertyAccessTracker : PropertyAccessTracker { protected override ILanguageFacade Language => VisualBasicFacade.Instance; protected override SyntaxKind[] TrackedSyntaxKinds { get; } = new[] { SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.IdentifierName }; public override object AssignedValue(PropertyAccessContext context) => context.Node.Ancestors().FirstOrDefault(ancestor => ancestor.IsKind(SyntaxKind.SimpleAssignmentStatement)) is AssignmentStatementSyntax assignment ? assignment.Right.FindConstantValue(context.Model) : null; public override Condition MatchGetter() => context => !((ExpressionSyntax)context.Node).IsLeftSideOfAssignment; public override Condition MatchSetter() => context => ((ExpressionSyntax)context.Node).IsLeftSideOfAssignment; public override Condition AssignedValueIsConstant() => context => AssignedValue(context) != null; protected override bool IsIdentifierWithinMemberAccess(SyntaxNode expression) => expression.IsKind(SyntaxKind.IdentifierName) && expression.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression); } ================================================ FILE: analyzers/src/SonarAnalyzer.VisualBasic.Core/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Direct", "requested": "[1.3.2, )", "resolved": "1.3.2", "contentHash": "I5Z2WBgFsx0G22Na1uVFPDkT6Ob4XI+g91GPN8JWldYUMlmIBcUDBfGmfr8oQPdUipvThpaU1x1xZrnNwRR8JA==", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "NETStandard.Library": { "type": "Direct", "requested": "[2.0.3, )", "resolved": "2.0.3", "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "System.Collections.Immutable": { "type": "Direct", "requested": "[1.1.37, )", "resolved": "1.1.37", "contentHash": "fTpqwZYBzoklTT+XjTRK8KxvmrGkYHzBiylCcKyQcxiOM8k+QvhNBxRvFHDWzy4OEP5f8/9n+xQ9mEgEXY+muA==", "dependencies": { "System.Collections": "4.0.0", "System.Diagnostics.Debug": "4.0.0", "System.Globalization": "4.0.0", "System.Linq": "4.0.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.0", "System.Runtime.Extensions": "4.0.0", "System.Threading": "4.0.0" } }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "HS3iRWZKcUw/8eZ/08GXKY2Bn7xNzQPzf8gRPHGSowX7u7XXu9i9YEaBeBNKUXWfI7qjvT2zXtLUvbN0hds8vg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "lOinFNbjpCvkeYQHutjKi+CfsjoKu88wAFT6hAumSR/XJSJmmVGvmnbzCWW8kUJnDVrw1RrcqS8BzgPMj263og==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "1.1.0", "System.AppContext": "4.1.0", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Collections.Immutable": "1.2.0", "System.Console": "4.0.0", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.FileVersionInfo": "4.0.0", "System.Diagnostics.StackTrace": "4.0.1", "System.Diagnostics.Tools": "4.0.1", "System.Dynamic.Runtime": "4.0.11", "System.Globalization": "4.0.11", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.X509Certificates": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.CodePages": "4.0.1", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", "System.Threading.Thread": "4.0.0", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath.XDocument": "4.0.1", "System.Xml.XmlDocument": "4.0.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "GrYMp6ScZDOMR0fNn/Ce6SegNVFw1G/QRT/8FiKv7lAP+V6lEZx9e42n0FvFUgjjcKgcEJOI4muU6i+3LSvOBA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "MwGmrrPx3okEJuCogSn4TM3yTtJUDdmTt8RXpnjVo0dPund0YSAq4bHQQ9bxgArbrrapcopJmkb7UOLAvanXkg==", "dependencies": { "Microsoft.CodeAnalysis.CSharp": "[1.3.2]", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2]" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "yllH3rSYEc0bV15CJ2T9Jtx+tSXO5/OVNb+xofuWrACn65Q5VqeFBKgcbgwpyVY/98ypPcGQIWNQL2A/L1seJg==", "dependencies": { "Microsoft.CodeAnalysis.Common": "1.3.2" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "1.3.2", "contentHash": "kvdo+rkImlx5MuBgkayl4OV3Mg8/qirUdYgCIfQ9EqN15QasJFlQXmDAtCGqpkK9sYLLO/VK+y+4mvKjfh/FOA==", "dependencies": { "Microsoft.CodeAnalysis.Common": "[1.3.2]", "Microsoft.Composition": "1.0.27" } }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0", "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.0.1", "contentHash": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==" }, "runtime.native.System": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Net.Http": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "runtime.native.System.Security.Cryptography": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.AppContext": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Collections": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Collections.Concurrent": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Console": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.FileVersionInfo": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "qjF74OTAU+mRhLaL4YSfiWy3vj6T3AOz8AW37l5zCwfbBfj0k7E94XnEsRaf2TnhE/7QaV6Hvqakoy2LoV8MVg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0" } }, "System.Diagnostics.StackTrace": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "6i2EbRq0lgGfiZ+FDf0gVaw9qeEU+7IS2+wbZJmFVpvVzVOgZEt0ScZtyenuBvs6iDYbGiF51bMAa0oDP/tujQ==", "dependencies": { "System.Collections.Immutable": "1.2.0", "System.IO.FileSystem": "4.0.1", "System.Reflection": "4.1.0", "System.Reflection.Metadata": "1.3.0", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Diagnostics.Tracing": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Linq": "4.1.0", "System.Linq.Expressions": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Globalization": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Globalization.Calendars": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Runtime": "4.1.0" } }, "System.IO": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.IO.FileSystem.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Text.Encoding": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Linq": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Linq.Expressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.ObjectModel": "4.0.12", "System.Reflection": "4.1.0", "System.Reflection.Emit": "4.0.1", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Emit.Lightweight": "4.0.1", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.ObjectModel": { "type": "Transitive", "resolved": "4.0.12", "contentHash": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11" } }, "System.Reflection": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.IO": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", "dependencies": { "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.ILGeneration": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Emit.Lightweight": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", "dependencies": { "System.Reflection": "4.1.0", "System.Reflection.Emit.ILGeneration": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.Extensions": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "1.3.0", "contentHash": "jMSCxA4LSyKBGRDm/WtfkO03FkcgRzHxwvQRib1bm2GZ8ifKM1MX1al6breGCEQK280mdl9uQS7JNPXRYk90jw==", "dependencies": { "System.Collections": "4.0.11", "System.Collections.Immutable": "1.2.0", "System.Diagnostics.Debug": "4.0.11", "System.IO": "4.1.0", "System.Linq": "4.1.0", "System.Reflection": "4.1.0", "System.Reflection.Extensions": "4.0.1", "System.Reflection.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Threading": "4.0.11" } }, "System.Reflection.Primitives": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Reflection.TypeExtensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", "dependencies": { "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Resources.ResourceManager": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Globalization": "4.0.11", "System.Reflection": "4.1.0", "System.Runtime": "4.1.0" } }, "System.Runtime": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1" } }, "System.Runtime.Extensions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.Handles": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Runtime.InteropServices": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Reflection": "4.1.0", "System.Reflection.Primitives": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Handles": "4.0.1" } }, "System.Runtime.Numerics": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", "dependencies": { "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0" } }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.2.0", "contentHash": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11" } }, "System.Security.Cryptography.Csp": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Security.Cryptography.Encoding": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Collections.Concurrent": "4.0.12", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", "dependencies": { "System.Collections": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Security.Cryptography.Primitives": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.Globalization.Calendars": "4.0.1", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Runtime.Numerics": "4.0.1", "System.Security.Cryptography.Algorithms": "4.2.0", "System.Security.Cryptography.Cng": "4.2.0", "System.Security.Cryptography.Csp": "4.0.0", "System.Security.Cryptography.Encoding": "4.0.0", "System.Security.Cryptography.OpenSsl": "4.0.0", "System.Security.Cryptography.Primitives": "4.0.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "runtime.native.System": "4.0.0", "runtime.native.System.Net.Http": "4.0.1", "runtime.native.System.Security.Cryptography": "4.0.0" } }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "h4z6rrA/hxWf4655D18IIZ0eaLRa3tQC/j+e26W+VinIHY0l07iEXaAvO0YSYq3MvCjMYy8Zs5AdC1sxNQOB7Q==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.Handles": "4.0.1", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11" } }, "System.Text.Encoding.Extensions": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0", "System.Text.Encoding": "4.0.11" } }, "System.Text.RegularExpressions": { "type": "Transitive", "resolved": "4.1.0", "contentHash": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", "dependencies": { "System.Collections": "4.0.11", "System.Globalization": "4.0.11", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11" } }, "System.Threading": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", "dependencies": { "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "Microsoft.NETCore.Targets": "1.0.1", "System.Runtime": "4.1.0" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", "dependencies": { "System.Collections": "4.0.11", "System.Runtime": "4.1.0", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Tasks.Parallel": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "7Pc9t25bcynT9FpMvkUw4ZjYwUiGup/5cJFW72/5MgCG+np2cfVUMdh29u8d7onxX7d8PS3J+wL73zQRqkdrSA==", "dependencies": { "System.Collections.Concurrent": "4.0.12", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tracing": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11" } }, "System.Threading.Thread": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "gIdJqDXlOr5W9zeqFErLw3dsOsiShSCYtF9SEHitACycmvNvY8odf9kiKvp6V7aibc8C4HzzNBkWXjyfn7plbQ==", "dependencies": { "System.Runtime": "4.1.0" } }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.IO.FileSystem": "4.0.1", "System.IO.FileSystem.Primitives": "4.0.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Text.Encoding.Extensions": "4.0.11", "System.Text.RegularExpressions": "4.1.0", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Extensions": "4.0.0" } }, "System.Xml.XDocument": { "type": "Transitive", "resolved": "4.0.11", "contentHash": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Diagnostics.Tools": "4.0.1", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Reflection": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XmlDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "2eZu6IP+etFVBBFUFzw2w6J21DqIN5eL9Y8r8JfJWUmV28Z5P0SNU01oCisVHQgHsDhHPnmq2s1hJrJCFZWloQ==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Text.Encoding": "4.0.11", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "UWd1H+1IJ9Wlq5nognZ/XJdyj8qPE4XufBUkAW59ijsCPjZkZe0MUzKKJFBr+ZWBe5Wq1u1d5f2CYgE93uH7DA==", "dependencies": { "System.Collections": "4.0.11", "System.Diagnostics.Debug": "4.0.11", "System.Globalization": "4.0.11", "System.IO": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11" } }, "System.Xml.XPath.XDocument": { "type": "Transitive", "resolved": "4.0.1", "contentHash": "FLhdYJx4331oGovQypQ8JIw2kEmNzCsjVOVYY/16kZTUoquZG85oVn7yUhBE2OZt1yGPSXAL0HTEfzjlbNpM7Q==", "dependencies": { "System.Diagnostics.Debug": "4.0.11", "System.Linq": "4.1.0", "System.Resources.ResourceManager": "4.0.1", "System.Runtime": "4.1.0", "System.Runtime.Extensions": "4.1.0", "System.Threading": "4.0.11", "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XDocument": "4.0.11", "System.Xml.XPath": "4.0.1" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/stylecop.json ================================================ { "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "orderingRules": { "usingDirectivesPlacement": "outsideNamespace", "elementOrder": [ "constant", "readonly" ] }, "layoutRules": { "newlineAtEndOfFile": "require" } } } ================================================ FILE: analyzers/tests/Directory.Build.targets ================================================ true ================================================ FILE: analyzers/tests/FrameworkMocks/src/Directory.Build.targets ================================================ ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mocks.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.12.35527.113 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mscorlib", "Mscorlib3.5Mock\mscorlib.csproj", "{ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.5", "3.5", "{EE67D0BB-112E-437F-BE6F-9B2F28560BB8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.0", "4.0", "{103EC28B-25FF-4CC9-9588-F43A8A4D5789}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mscorlib", "Mscorlib4.0Mock\mscorlib.csproj", "{8FF129EE-F38C-4B15-9E4A-25BD50B22FF0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.0-with-io", "4.0-with-io", "{F6BA4FFC-4130-4928-BA35-0E6397DDDFC7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mscorlib", "Mscorlib4.0MockWithIo\mscorlib.csproj", "{C353F0EB-DA0B-4872-925B-827E69707513}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.8", "4.8", "{3BA66093-5811-41DB-AF8C-89EDB201B2C9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mscorlib", "Mscorlib4.8Mock\mscorlib.csproj", "{4E22A2C7-6B61-42D6-83EE-E511BBF573AF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3}.Release|Any CPU.Build.0 = Release|Any CPU {8FF129EE-F38C-4B15-9E4A-25BD50B22FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8FF129EE-F38C-4B15-9E4A-25BD50B22FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FF129EE-F38C-4B15-9E4A-25BD50B22FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FF129EE-F38C-4B15-9E4A-25BD50B22FF0}.Release|Any CPU.Build.0 = Release|Any CPU {C353F0EB-DA0B-4872-925B-827E69707513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C353F0EB-DA0B-4872-925B-827E69707513}.Debug|Any CPU.Build.0 = Debug|Any CPU {C353F0EB-DA0B-4872-925B-827E69707513}.Release|Any CPU.ActiveCfg = Release|Any CPU {C353F0EB-DA0B-4872-925B-827E69707513}.Release|Any CPU.Build.0 = Release|Any CPU {4E22A2C7-6B61-42D6-83EE-E511BBF573AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E22A2C7-6B61-42D6-83EE-E511BBF573AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E22A2C7-6B61-42D6-83EE-E511BBF573AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E22A2C7-6B61-42D6-83EE-E511BBF573AF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {ED2CFD01-54DB-4251-8A2D-FD005AE2F0B3} = {EE67D0BB-112E-437F-BE6F-9B2F28560BB8} {8FF129EE-F38C-4B15-9E4A-25BD50B22FF0} = {103EC28B-25FF-4CC9-9588-F43A8A4D5789} {C353F0EB-DA0B-4872-925B-827E69707513} = {F6BA4FFC-4130-4928-BA35-0E6397DDDFC7} {4E22A2C7-6B61-42D6-83EE-E511BBF573AF} = {3BA66093-5811-41DB-AF8C-89EDB201B2C9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5C7D9932-C1AB-407E-B1CD-4971BDAE10F0} EndGlobalSection EndGlobal ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib3.5Mock/Debugger.cs ================================================  namespace System.Diagnostics { public class Debugger { public Debugger() { } public string Foo() { return ""; } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib3.5Mock/mscorlib.csproj ================================================  net35 true ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib3.5Mock/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v3.5": { "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net35": "1.0.3" } }, "Microsoft.NETFramework.ReferenceAssemblies.net35": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "Mhbr13IGgsW4AHj50uAiVNMPWdvUPWePRsI4x1NiTkhHUc4rqi9XvY5xxBC4d56bJypQbxAZ8bPuZIpz+TjBVQ==" } } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0Mock/Debugger.cs ================================================  namespace System.Diagnostics { public class Debugger { [Obsolete] public Debugger() { } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0Mock/mscorlib.csproj ================================================  net40 true ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0Mock/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.0": { "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net40": "1.0.3" } }, "Microsoft.NETFramework.ReferenceAssemblies.net40": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "3ctXnCpHdoYJNH9ATfXKwckkkdHvHc1Xls12QB9kf1tjh3b+VzxtiwpkZN4GxOawakfH6CJAkqhlDKSiz6Ujbg==" } } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0MockWithIo/Debugger.cs ================================================  namespace System.Diagnostics { public class Debugger { [Obsolete] public Debugger() { } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0MockWithIo/UnmanagedMemoryStream.cs ================================================ namespace System.IO { public class UnmanagedMemoryStream { // no FlushAsync } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0MockWithIo/mscorlib.csproj ================================================  net40 true ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.0MockWithIo/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.0": { "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net40": "1.0.3" } }, "Microsoft.NETFramework.ReferenceAssemblies.net40": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "3ctXnCpHdoYJNH9ATfXKwckkkdHvHc1Xls12QB9kf1tjh3b+VzxtiwpkZN4GxOawakfH6CJAkqhlDKSiz6Ujbg==" } } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.8Mock/Debugger.cs ================================================  namespace System.Diagnostics { public class Debugger { [Obsolete] public Debugger() { } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.8Mock/UnmanagedMemoryStream.cs ================================================ namespace System.IO { public class UnmanagedMemoryStream { public void FlushAsync() { } } } ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.8Mock/mscorlib.csproj ================================================  net48 true ================================================ FILE: analyzers/tests/FrameworkMocks/src/Mscorlib4.8Mock/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.8": {} } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Extensions/BaseArgumentListSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Extensions; [TestClass] public class BaseArgumentListSyntaxExtensionsTest { [TestMethod] public void GivenEmptyList_GetArgumentByName_ReturnsNull() => CreateNamedArgumentList().GetArgumentByName("argument").Should().BeNull(); [TestMethod] public void GivenListWithAnotherNamedArgument_GetArgumentByName_ReturnsNull() => CreateNamedArgumentList("p1").GetArgumentByName("p2").Should().BeNull(); [TestMethod] public void GivenListWithNamedArgument_GetArgumentByName_ReturnsArgument() => CreateNamedArgumentList("p1").GetArgumentByName("p1").Should().Match(x => ((ArgumentSyntax)x).NameColon.Name.Identifier.Text == "p1"); [TestMethod] public void GivenListWithMultipleNamedArguments_GetArgumentByName_ReturnsArgument() => CreateNamedArgumentList("p1", "p2", "p3").GetArgumentByName("p2").Should().Match(x => ((ArgumentSyntax)x).NameColon.Name.Identifier.Text == "p2"); [TestMethod] public void GivenListWithNotNamedArguments_GetArgumentByName_ReturnsNull() => SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { CreateNotNamedArgument("p1"), CreateNotNamedArgument("p2") })) .GetArgumentByName("p1") .Should().BeNull(); [TestMethod] public void GivenListWithMixedNotNamedAndNamedArguments_GetArgumentByName_ReturnsNull() => SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList( new[] { CreateNamedArgument("p1"), CreateNotNamedArgument("p2"), CreateNotNamedArgument("p3") })) .GetArgumentByName("p1") .Should().Match(x => ((ArgumentSyntax)x).NameColon.Name.Identifier.Text == "p1"); private static ArgumentListSyntax CreateNamedArgumentList(params string[] names) => SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(names.Select(CreateNamedArgument))); private static ArgumentSyntax CreateNamedArgument(string name) => SyntaxFactory.Argument(SyntaxFactory.NameColon(name), SyntaxFactory.Token(SyntaxKind.None), SyntaxFactory.IdentifierName(name)); private static ArgumentSyntax CreateNotNamedArgument(string identifierName) => SyntaxFactory.Argument(SyntaxFactory.IdentifierName(identifierName)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Extensions/BaseMethodDeclarationSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Core.Test.Extensions; [TestClass] public class BaseMethodDeclarationSyntaxExtensionsTest { [TestMethod] public void GivenNullMethodDeclaration_GetBodyDescendantNodes_ThrowsArgumentNullException() { #if NETFRAMEWORK var messageFormat = "Value cannot be null." + Environment.NewLine + "Parameter name: {0}"; #else var messageFormat = "Value cannot be null. (Parameter '{0}')"; #endif BaseMethodDeclarationSyntax sut = null; var exception = Assert.Throws(sut.GetBodyDescendantNodes); exception.Message.Should().Be(string.Format(messageFormat, "method")); } [TestMethod] [DynamicData(nameof(GetMethodDeclarationsAndExpectedBody))] public void HasBodyOrExpressionBody(BaseMethodDeclarationSyntax methodDeclaration, SyntaxNode expectedBody) { var hasBody = methodDeclaration.HasBodyOrExpressionBody(); if (expectedBody is null) { hasBody.Should().BeFalse(); } else { hasBody.Should().BeTrue(); } } [TestMethod] [DynamicData(nameof(GetMethodDeclarationsAndExpectedBody), DynamicDataSourceType.Method)] public void GetBodyOrExpressionBody(BaseMethodDeclarationSyntax methodDeclaration, SyntaxNode expectedBody) => methodDeclaration.GetBodyOrExpressionBody().Should().Be(expectedBody); private static IEnumerable GetMethodDeclarationsAndExpectedBody() { var methodWithBody = Method().WithBody(Block()); var expressionBody = ArrowExpressionClause(LiteralExpression(SyntaxKind.TrueLiteralExpression)); var methodWithExpressionBody = Method().WithExpressionBody(expressionBody); var methodWithBoth = Method().WithBody(Block()).WithExpressionBody(expressionBody); // Corresponds to (BaseMethodDeclarationSyntax methodDeclaration, SyntaxNode expectedBody) yield return new object[] { null, null }; yield return new object[] { Method(), null }; yield return new object[] { methodWithBody, methodWithBody.Body }; yield return new object[] { methodWithExpressionBody, methodWithExpressionBody.ExpressionBody.Expression }; yield return new object[] { methodWithBoth, methodWithBoth.Body }; static BaseMethodDeclarationSyntax Method() => MethodDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "Test"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Extensions/ISymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using ISymbolExtensionsCS = SonarAnalyzer.CSharp.Core.Extensions.ISymbolExtensions; namespace SonarAnalyzer.Test.Extensions; [TestClass] public class ISymbolExtensionsTest { [TestMethod] [DataRow("class SymbolMember();", true)] [DataRow("class SymbolMember() { }", true)] [DataRow("class SymbolMember(int a) { }", true)] [DataRow("class SymbolMember { }", false)] [DataRow("class SymbolMember(int a) { @SymbolMember() : this(1) { } };", true)] [DataRow("class SymbolMember { SymbolMember() { } };", false)] [DataRow("class Base(int i); class SymbolMember() : Base(1);", true)] [DataRow(""" class Base(int i); class SymbolMember : Base { @SymbolMember() : base(1) { } } """, false)] [DataRow("struct SymbolMember();", true)] [DataRow("struct SymbolMember() { }", true)] [DataRow("struct SymbolMember(int a) { }", true)] [DataRow("struct SymbolMember { }", false)] [DataRow("struct SymbolMember(int a) { public @SymbolMember() : this(1) { } };", true)] [DataRow("struct SymbolMember { public @SymbolMember() { } };", false)] [DataRow("record SymbolMember();", true)] [DataRow("record SymbolMember() { }", true)] [DataRow("record SymbolMember(int a) { }", true)] [DataRow("record SymbolMember { }", false)] [DataRow("record SymbolMember(int a) { @SymbolMember() : this(1) { } };", true)] [DataRow("record SymbolMember { SymbolMember() { } };", false)] [DataRow("record struct SymbolMember();", true)] [DataRow("record struct SymbolMember() { }", true)] [DataRow("record struct SymbolMember(int a) { }", true)] [DataRow("record struct SymbolMember { }", false)] [DataRow("record struct SymbolMember(int a) { public @SymbolMember() : this(1) { } };", true)] [DataRow("record struct SymbolMember { public @SymbolMember() { } };", false)] [DataRow("record class SymbolMember();", true)] [DataRow("record class SymbolMember() { }", true)] [DataRow("record class SymbolMember(int a) { }", true)] [DataRow("record class SymbolMember { }", false)] [DataRow("record class SymbolMember(int a) { @SymbolMember() : this(1) { } };", true)] [DataRow("record class SymbolMember { @SymbolMember() { } };", false)] [DataRow("readonly struct SymbolMember();", true)] [DataRow("readonly struct SymbolMember() { }", true)] [DataRow("readonly struct SymbolMember(int a) { }", true)] [DataRow("readonly struct SymbolMember { }", false)] [DataRow("readonly struct SymbolMember(int a) { public @SymbolMember() : this(1) { } };", true)] [DataRow("readonly struct SymbolMember { public @SymbolMember() { } };", false)] public void IsPrimaryConstructor(string code, bool hasPrimaryConstructor) { var typeSymbol = new SnippetCompiler(code).DeclaredSymbol("SymbolMember"); var methodSymbols = typeSymbol.GetMembers().OfType(); methodSymbols.Count(x => x.IsPrimaryConstructor).Should().Be(hasPrimaryConstructor ? 1 : 0); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Extensions/TypeDeclarationSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Extensions; [TestClass] public class TypeDeclarationSyntaxExtensionsTest { [TestMethod] public void GetMethodDeclarations_EmptyClass_ReturnsEmpty() { var typeDeclaration = SyntaxFactory.ClassDeclaration(SyntaxFactory.Identifier("TestClass")); typeDeclaration.GetMethodDeclarations().Should().BeEmpty(); } [TestMethod] public void GetMethodDeclarations_SingleMethod_ReturnsMethod() { const string code = """ namespace Test { class TestClass { private void WriteLine() {} } } """; var snippet = new SnippetCompiler(code); var typeDeclaration = snippet.Tree.Single(); typeDeclaration.GetMethodDeclarations().Single().Identifier.Text.Should().Be("WriteLine"); } [TestMethod] public void GetMethodDeclarations_MultipleMethodsWithLocalFunctions_ReturnsMethodsAndFunctions() { const string code = """ namespace Test { class TestClass { private void Method1() { Function1(); Function2(); void Function1() {} void Function2() {} } private void Method2() { Function3(); void Function3() {} } } } """; var snippet = new SnippetCompiler(code); var typeDeclaration = snippet.Tree.Single(); typeDeclaration .GetMethodDeclarations() .Select(x => x.Identifier.Text) .Should() .BeEquivalentTo(new List { "Method1", "Method2", "Function1", "Function2", "Function3" }); } [TestMethod] [DataRow("class")] [DataRow("struct")] [DataRow("readonly struct")] [DataRow("record struct")] #if NET [DataRow("record")] [DataRow("record class")] [DataRow("readonly record struct")] #endif public void ParameterList_ReturnsList(string type) { var tree = TestCompiler.CompileCS($$"""{{type}} Test(int i) { }""").Tree; var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var parameterList = typeDeclaration.ParameterList(); parameterList.Should().NotBeNull(); var entry = parameterList.Parameters.Should().ContainSingle().Which; entry.Type.Should().BeOfType(); entry.Identifier.ValueText.Should().Be("i"); } [TestMethod] public void ParameterList_Interface() { var tree = TestCompiler.CompileCS("interface Test { }").Tree; var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var parameterList = typeDeclaration.ParameterList(); parameterList.Should().BeNull(); } [TestMethod] [DataRow(LanguageVersion.CSharp1)] [DataRow(LanguageVersion.CSharp2)] [DataRow(LanguageVersion.CSharp3)] [DataRow(LanguageVersion.CSharp4)] [DataRow(LanguageVersion.CSharp5)] [DataRow(LanguageVersion.CSharp6)] [DataRow(LanguageVersion.CSharp7)] [DataRow(LanguageVersion.CSharp7_1)] [DataRow(LanguageVersion.CSharp7_2)] [DataRow(LanguageVersion.CSharp7_3)] [DataRow(LanguageVersion.CSharp8)] [DataRow(LanguageVersion.CSharp9)] [DataRow(LanguageVersion.CSharp10)] [DataRow(LanguageVersion.CSharp11)] [DataRow(LanguageVersion.CSharp12)] public void PrimaryConstructor_NoPrimaryConstructor(LanguageVersion languageVersion) { var tree = CSharpSyntaxTree.ParseText("public class Test { }", new CSharpParseOptions(languageVersion)); var compilation = CSharpCompilation.Create(assemblyName: null, syntaxTrees: new[] { tree }); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); typeDeclaration.PrimaryConstructor(compilation.GetSemanticModel(tree)).Should().BeNull(); } [TestMethod] [DataRow(LanguageVersion.CSharp9)] [DataRow(LanguageVersion.CSharp10)] [DataRow(LanguageVersion.CSharp11)] [DataRow(LanguageVersion.CSharp12)] public void PrimaryConstructor_PrimaryConstructorRecord(LanguageVersion languageVersion) { var tree = CSharpSyntaxTree.ParseText("public record Test(int i) { }", new CSharpParseOptions(languageVersion)); var compilation = CSharpCompilation.Create(assemblyName: null, syntaxTrees: new[] { tree }); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(compilation.GetSemanticModel(tree)); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); var entry = methodSymbol.Parameters.Should().ContainSingle().Which; entry.Name.Should().Be("i"); entry.Type.SpecialType.Should().Be(SpecialType.System_Int32); } [TestMethod] [DataRow("class")] [DataRow("struct")] [DataRow("readonly struct")] [DataRow("record struct")] #if NET [DataRow("record")] [DataRow("record class")] #endif public void PrimaryConstructor_PrimaryConstructorOnClass(string type) { var (tree, model) = TestCompiler.CompileCS($$"""{{type}} Test(int i) { }"""); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(model); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); var entry = methodSymbol.Parameters.Should().ContainSingle().Which; entry.Name.Should().Be("i"); entry.Type.SpecialType.Should().Be(SpecialType.System_Int32); } [TestMethod] public void PrimaryConstructor_EmptyPrimaryConstructor() { var (tree, model) = TestCompiler.CompileCS("public class Test() { }"); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(model); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); methodSymbol.Parameters.Should().BeEmpty(); } [TestMethod] public void PrimaryConstructor_EmptyPrimaryConstructor_SecondConstructor() { var (tree, model) = TestCompiler.CompileCS(""" public class Test() { public Test(int i) : this() { } } """); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(model); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); methodSymbol.Parameters.Should().BeEmpty(); } [TestMethod] public void PrimaryConstructor_EmptyPrimaryConstructorAndStaticConstructor() { var (tree, model) = TestCompiler.CompileCS(""" public class Test() { static Test() { } } """); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(model); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); methodSymbol.Parameters.Should().BeEmpty(); methodSymbol.IsStatic.Should().BeFalse(); } [TestMethod] [DataRow("__arglist", 0)] [DataRow("int i, __arglist", 1)] [DataRow("int i, int j, __arglist", 2)] public void PrimaryConstructor_ArglistPrimaryConstructor(string parameterList, int expectedNumberOfParameters) { var (tree, model) = TestCompiler.CompileCS($$"""public class Test({{parameterList}}) { }"""); var typeDeclaration = tree.GetCompilationUnitRoot().DescendantNodesAndSelf().OfType().Single(); var methodSymbol = typeDeclaration.PrimaryConstructor(model); methodSymbol.Should().NotBeNull(); methodSymbol.MethodKind.Should().Be(MethodKind.Constructor); methodSymbol.Parameters.Should().HaveCount(expectedNumberOfParameters); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Extensions/TypeSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Extensions; [TestClass] public class TypeSyntaxExtensionsTest { [TestMethod] [DataRow("""$$int$$ field;""", "int")] // identity — no wrapper [DataRow("""$$int[]$$ field;""", "int")] // array [DataRow("""$$int[][]$$ field;""", "int")] // nested array [DataRow("""$$int?$$ field;""", "int")] // nullable [DataRow("""$$int*$$ field;""", "int")] // pointer [DataRow("""$$int?[]$$ field;""", "int")] // nullable inside array [DataRow("""void M(ref int p) { $$ref int$$ x = ref p; }""", "int")] // ref [DataRow("""void M(ref int p) { $$scoped ref int$$ x = ref p; }""", "int")] // scoped ref [DataRow("""$$List$$ field;""", "List")] // generic — not unwrapped public void Unwrap(string snippet, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; using System.Collections.Generic; unsafe class Test { public Test(int i) { } {{snippet}} } """).Node; ((TypeSyntax)node).Unwrap().ToString().Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Facade/CSharpFacadeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Facade.Test; [TestClass] public class CSharpFacadeTest { [TestMethod] public void MethodParameterLookup_ForInvocation() { var sut = CSharpFacade.Instance; var code = """ public class C { public int M(int arg) => M(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var invocation = root.DescendantNodes().OfType().First(); var method = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First()); var actual = sut.MethodParameterLookup(invocation, method); actual.Should().NotBeNull().And.BeOfType(); } [TestMethod] public void MethodParameterLookup_SemanticModelOverload() { var code = """ public class C { public int M(int arg) => M(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var lookup = CSharpFacade.Instance.MethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().First(), model); lookup.Should().NotBeNull().And.BeOfType(); } [TestMethod] public void MethodParameterLookup_ForObjectCreation() { var sut = CSharpFacade.Instance; var code = """ public class C { public C(int arg) { } public C M() => new C(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var creation = root.DescendantNodes().OfType().First(); var constructor = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First()); var actual = sut.MethodParameterLookup(creation, constructor); actual.Should().NotBeNull().And.BeOfType(); } [TestMethod] public void MethodParameterLookup_ForImplicitObjectCreation() { var sut = CSharpFacade.Instance; var code = """ public class C { public C(int arg) { } public C M() => new(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var creation = root.DescendantNodes().First(x => x.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression)); var constructor = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First()); var actual = sut.MethodParameterLookup(creation, constructor); actual.Should().NotBeNull().And.BeOfType(); } [TestMethod] public void MethodParameterLookup_ForArgumentList() { var sut = CSharpFacade.Instance; var code = """ public class C { public int M(int arg) => M(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var argumentList = root.DescendantNodes().OfType().First(); var method = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First()); var actual = () => sut.MethodParameterLookup(argumentList, method); actual.Should().Throw(); } [TestMethod] public void MethodParameterLookup_UnsupportedSyntaxKind() { var sut = CSharpFacade.Instance; var code = """ public class C { public int M(int arg) => M(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var methodDeclaration = root.DescendantNodes().OfType().First(); var method = model.GetDeclaredSymbol(methodDeclaration); var actual = () => sut.MethodParameterLookup(methodDeclaration, method); // MethodDeclarationSyntax passed instead of invocation actual.Should().Throw().Which.Message.Should().StartWith("The node of kind MethodDeclaration does not have an ArgumentList."); } [TestMethod] public void MethodParameterLookup_Null() { var sut = CSharpFacade.Instance; var code = """ public class C { public int M(int arg) => M(1); } """; var (tree, model) = TestCompiler.CompileCS(code); var root = tree.GetRoot(); var method = model.GetDeclaredSymbol(root.DescendantNodes().OfType().First()); var actual = sut.MethodParameterLookup(null, method); actual.Should().BeNull(); } [TestMethod] public void MethodParameterLookup_Null_SemanticModelOverload() { var sut = CSharpFacade.Instance; var code = """ public class C { public int M(int arg) => M(1); } """; var (_, model) = TestCompiler.CompileCS(code); var actual = sut.MethodParameterLookup(null, model); actual.Should().BeNull(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Facade/Implementation/CSharpSyntaxFacadeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Utilities; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Core.Facade.Implementation.Test; [TestClass] public class CSharpSyntaxFacadeTest { private readonly CSharpSyntaxFacade cs = new(); [TestMethod] public void EnumMembers_Null_CS() => cs.EnumMembers(null).Should().BeEmpty(); [TestMethod] public void InvocationIdentifier_Null_CS() => cs.InvocationIdentifier(null).Should().BeNull(); [TestMethod] public void ObjectCreationTypeIdentifier_Null_CS() => cs.ObjectCreationTypeIdentifier(null).Should().BeNull(); [TestMethod] public void InvocationIdentifier_UnexpectedTypeThrows_CS() => cs.Invoking(x => x.InvocationIdentifier(IdentifierName("ThisIsNotInvocation"))).Should().Throw(); [TestMethod] public void ModifierKinds_Null_CS() => cs.ModifierKinds(null).Should().BeEmpty(); [TestMethod] public void NodeExpression_Null_CS() => cs.NodeExpression(null).Should().BeNull(); [TestMethod] public void NodeExpression_UnexpectedTypeThrows_CS() => cs.Invoking(x => x.NodeExpression(IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); [TestMethod] public void NodeIdentifier_Null_CS() => cs.NodeIdentifier(null).Should().BeNull(); [TestMethod] public void NodeIdentifier_Unexpected_Returns_Null_CS() => cs.NodeIdentifier(AttributeList()).Should().BeNull(); [TestMethod] public void StringValue_UnexpectedType_CS() => cs.StringValue(ThrowStatement(), null).Should().BeNull(); [TestMethod] public void StringValue_NodeIsNull_CS() => cs.StringValue(null, null).Should().BeNull(); [TestMethod] public void RemoveConditionalAccess_Null_CS() => cs.RemoveConditionalAccess(null).Should().BeNull(); [TestMethod] [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(ParseExpression(invocation)).ToString().Should().Be(expected); [TestMethod] public void ArgumentNameColon_CS_WithNameColon() { var expression = LiteralExpression(SyntaxKind.TrueLiteralExpression); var argument = Argument(NameColon(IdentifierName("a")), Token(SyntaxKind.None), expression); cs.ArgumentNameColon(argument).Should().BeOfType().Subject.ValueText.Should().Be("a"); } [TestMethod] public void ArgumentNameColon_CS_WithoutNameColon() { var expression = LiteralExpression(SyntaxKind.TrueLiteralExpression); var argument = Argument(expression); cs.ArgumentNameColon(argument).Should().BeNull(); } [TestMethod] public void ArgumentNameColon_CS_UnsupportedSyntaxKind() { var expression = LiteralExpression(SyntaxKind.TrueLiteralExpression, Token(SyntaxKind.TrueKeyword)); cs.ArgumentNameColon(expression).Should().BeNull(); } [TestMethod] public void ComparisonKind_BinaryExpression_CS() { var binary = BinaryExpression(SyntaxKind.EqualsExpression, IdentifierName("a"), IdentifierName("b")); cs.ComparisonKind(binary).Should().Be(ComparisonKind.Equals); } [TestMethod] public void ComparisonKind_NonBinaryExpression_CS() => cs.ComparisonKind(IdentifierName("a")).Should().Be(ComparisonKind.None); [TestMethod] public void ComparisonKind_Null_CS() => cs.ComparisonKind(null).Should().Be(ComparisonKind.None); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/RegularExpressions/MessageTemplateParserTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.RegularExpressions.Test; [TestClass] public class MessageTemplateParserTest { [TestMethod] [DataRow("")] [DataRow("}")] [DataRow("{{}}")] [DataRow("{{")] [DataRow("}}")] [DataRow("}}{{{{")] [DataRow("hello world")] [DataRow("hello {{world}}")] [DataRow("{{hello {{world}}}}")] public void Parse_NoPlaceholder(string template) { var result = MessageTemplatesParser.Parse(template, MessageTemplatesParser.TemplateRegex); ShouldBeSuccess(result); } [TestMethod] // named [DataRow("{world}", "world", 1, 5)] [DataRow("hello {world}", "world", 7, 5)] [DataRow("hello {{{world42}}}", "world42", 9, 7)] [DataRow("hello {{ {42world}", "42world", 10, 7)] [DataRow("hello {{ {w_0_rld}", "w_0_rld", 10, 7)] [DataRow("hello {{ {_}", "_", 10, 1)] [DataRow("hello {{ {_world}", "_world", 10, 6)] // index [DataRow("{0}", "0", 1, 1)] [DataRow("hello {0}", "0", 7, 1)] [DataRow("hello {{{42}}}", "42", 9, 2)] [DataRow("hello {{ {199}", "199", 10, 3)] // prefix optional operator [DataRow("{@world}", "world", 2, 5)] [DataRow("{$199}", "199", 2, 3)] [DataRow("{@0}", "0", 2, 1)] [DataRow("{$world_42}", "world_42", 2, 8)] [DataRow(""" "hello" + "{world}" + "!" """, "world", 13, 5)] public void Parse_Placeholder(string template, string placeholder, int start, int length) { var result = MessageTemplatesParser.Parse(template, MessageTemplatesParser.TemplateRegex); ShouldBeSuccess(result, 1); ShouldBe(result.Placeholders[0], placeholder, start, length); } [TestMethod] // alignment [DataRow("hello {world,1}")] [DataRow("hello {world,42}")] [DataRow("hello {world,-1}")] [DataRow("hello {world,-199}")] // format [DataRow("hello {world:format}")] [DataRow("hello {world:42}")] [DataRow("hello {world:{}")] [DataRow("hello {world:!@#$%^&*()_}")] [DataRow("hello {world:42}")] [DataRow("hello {world:#for_mat42}")] // mixed [DataRow("hello {world,1:format}")] [DataRow("hello {world,42:!@#$%}")] [DataRow("hello {world,-1:dd-MM-yyy}")] [DataRow("hello {world,-42:3,14159}")] [DataRow("hello {world:format,42}")] // semantically looks like a typo, format and alignment are reversed, but it's syntactically valid. public void Parse_Placeholder_Named_Alignment_Format(string template) { var result = MessageTemplatesParser.Parse(template, MessageTemplatesParser.TemplateRegex); ShouldBeSuccess(result, 1); ShouldBe(result.Placeholders[0], "world", 7, 5); } [TestMethod] public void Parse_Placeholder_Multiple() { var template = """ In code's {$silent} realm, Logic weaves through lines {@0}f text, Errors {@teach,42} us well. Syntax {sym_phony,42:dd-MM}, Logic {@_orchestrates42} the mind, Programmer's {ballet:_}. """; var result = MessageTemplatesParser.Parse(template, MessageTemplatesParser.TemplateRegex); ShouldBeSuccess(result, 6); ShouldBe(result.Placeholders[0], "silent", 12, 6); ShouldBe(result.Placeholders[1], "0", 56, 1); ShouldBe(result.Placeholders[2], "teach", 75, 5); ShouldBe(result.Placeholders[3], "sym_phony", 103, 9); ShouldBe(result.Placeholders[4], "_orchestrates42", 132, 15); ShouldBe(result.Placeholders[5], "ballet", 173, 6); } [TestMethod] [DataRow("{")] // Left bracket is not allowed [DataRow("{{{")] // Third left bracket is not allowed (first two are valid) [DataRow("{}")] // Empty placeholder is not allowed [DataRow("{{{}}}")] // Empty placeholder is not allowed [DataRow("Login failed for {User")] // Missing closing bracket [DataRow("Login failed for {&User}")] // Only '@' and '$' are allowed as prefix [DataRow("Login failed for {User_%Name}")] // Only alphanumerics and '_' are allowed for placeholders [DataRow("Retry attempt {Cnt,r}")] // The alignment specifier must be numeric [DataRow("Retry attempt {Cnt,}")] // Empty alignment specifier is not allowed [DataRow("Retry attempt {Cnt:}")] // Empty format specifier is not allowed [DataRow(""" "hello {" + "world" + "}" """)] // '+' and '"' is not allowed in placeholders public void Parse_Placeholder_Failure(string template) { var result = MessageTemplatesParser.Parse(template, MessageTemplatesParser.TemplateRegex); result.Should().NotBeNull(); result.Success.Should().BeFalse(); result.Placeholders.Should().BeNull(); } private static void ShouldBeSuccess(MessageTemplatesParser.ParseResult actual, int placeholderCount = 0) { actual.Should().NotBeNull(); actual.Success.Should().BeTrue(); actual.Placeholders.Should().NotBeNull().And.HaveCount(placeholderCount); } private static void ShouldBe(MessageTemplatesParser.Placeholder actual, string name, int start, int length) { actual.Should().NotBeNull(); actual.Name.Should().Be(name); actual.Start.Should().Be(start); actual.Length.Should().Be(length); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/SonarAnalyzer.CSharp.Core.Test.csproj ================================================  net10.0 false ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/ArgumentSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class ArgumentSyntaxExtensionsTest { [TestMethod] [DataRow("($$a, b) = (42, 42);")] [DataRow("(a, $$b) = (42, 42);")] [DataRow("(a, (b, $$c)) = (42, (42, 42));")] [DataRow("(a, (b, ($$c, d))) = (42, (42, (42, 42)));")] public void IsInTupleAssignmentTarget_IsTrue(string assignment) { var code = $$""" public class Sample { public void Method() { int a, b, c, d; {{assignment}} } } """; var argument = GetTupleArgumentAtMarker(ref code); argument.IsInTupleAssignmentTarget().Should().BeTrue(); } [TestMethod] public void IsInTupleAssignmentTarget_IsFalse() { const string code = """ public class Sample { public void TupleArg((int, int) tuple) { } public void Method(int methodArgument) { int a = 42; var t = (a, methodArgument); var x = (42, 42); var nested = (42, (42, 42)); TupleArg((42, 42)); Method(0); } } """; var arguments = CSharpSyntaxTree.ParseText(code).GetRoot().DescendantNodes().OfType().ToArray(); arguments.Should().HaveCount(12); foreach (var argument in arguments) { argument.IsInTupleAssignmentTarget().Should().BeFalse(); } } [TestMethod] // Simple tuple [DataRow("($$1, (2, 3))", "(1, (2, 3))")] [DataRow("(1, ($$2, 3))", "(1, (2, 3))")] [DataRow("(1, (2, $$3))", "(1, (2, 3))")] // With method call with single argument [DataRow("($$1, (M(2), 3))", "(1, (M(2), 3))")] [DataRow("(1, ($$M(2), 3))", "(1, (M(2), 3))")] [DataRow("(1, (M($$2), 3))", null)] // With method call with two arguments [DataRow("(1, $$M(2, 3))", "(1, M(2, 3))")] [DataRow("(1, M($$2, 3))", null)] [DataRow("(1, M(2, $$3))", null)] // With method call with tuple argument [DataRow("($$M((1, 2)), 3)", "(M((1, 2)), 3)")] [DataRow("(M($$(1, 2)), 3)", null)] [DataRow("(M(($$1, 2)), 3)", "(1, 2)")] public void OutermostTuple_DifferentPositions(string tuple, string expectedOuterTuple) { var code = $$""" public class C { public void Test() { _ = {{tuple}}; } static int M(int a) => 0; static int M(int a, int b) => 0; static int M((int a, int b) t) => 0; } """; var argument = GetTupleArgumentAtMarker(ref code); var outerMostTuple = argument.OutermostTuple(); if (expectedOuterTuple is null) { outerMostTuple.Should().BeNull(); } else { outerMostTuple.Should().NotBeNull(); outerMostTuple.Value.SyntaxNode.ToString().Should().Be(expectedOuterTuple); } } private static ArgumentSyntax GetTupleArgumentAtMarker(ref string code) { var nodePosition = code.IndexOf("$$"); code = code.Replace("$$", string.Empty); var tree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(code); tree.GetDiagnostics().Should().BeEmpty(); var nodeAtPosition = tree.GetRoot().FindNode(new TextSpan(nodePosition, 0), getInnermostNodeForTie: true); var argument = nodeAtPosition?.AncestorsAndSelf().OfType().First(); return argument; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/AssignmentExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class AssignmentExpressionSyntaxExtensionsTest { [TestMethod] public void MapAssignmentArguments_TupleElementsAreExtracted() => AssertMapAssignmentArguments("(var x, var y) = (1, 2);", new[] { new { Left = WithDesignation("x"), Right = WithToken("1"), }, new { Left = WithDesignation("y"), Right = WithToken("2"), }, }); [TestMethod] public void MapAssignmentArguments_SimpleAssignmentReturnsSingleElementArray() => AssertMapAssignmentArguments("int x; x = 1;", new[] { new { Left = WithIdentifier("x"), Right = WithToken("1"), }, }); [TestMethod] public void MapAssignmentArguments_NestedDeconstruction() => AssertMapAssignmentArguments("(var a, (var b, (var c, var d)), var e) = (1, (2, (3, 4)), 5);", new[] { new { Left = WithDesignation("a"), Right = WithToken("1"), }, new { Left = WithDesignation("b"), Right = WithToken("2"), }, new { Left = WithDesignation("c"), Right = WithToken("3"), }, new { Left = WithDesignation("d"), Right = WithToken("4"), }, new { Left = WithDesignation("e"), Right = WithToken("5"), }, }); [TestMethod] public void MapAssignmentArguments_RightSideNotATupleExpression() => AssertMapAssignmentArguments("(var x, var y) = M(); static (int, int) M() => (1, 2);", new[] { new { Left = WithDesignationArguments("x", "y"), Right = new { Expression = WithIdentifier("M") }, }, }); [TestMethod] public void MapAssignmentArguments_LeftSideNotATupleExpression() => AssertMapAssignmentArguments("(int, int) tuple; tuple = (1, 2);", new[] { new { Left = WithIdentifier("tuple"), Right = WithTokenArguments("1", "2"), }, }); [TestMethod] public void MapAssignmentArguments_SimpleDeconstructionAssignment() => AssertMapAssignmentArguments("var (x, y) = (1, 2);", new[] { new { Left = WithIdentifier("x"), Right = WithToken("1"), }, new { Left = WithIdentifier("y"), Right = WithToken("2"), }, }); [TestMethod] public void MapAssignmentArguments_NestedDeconstructionAssignment() => AssertMapAssignmentArguments("var (a, (b, c), d) = (1, (2, 3), 4);", new[] { new { Left = WithIdentifier("a"), Right = WithToken("1"), }, new { Left = WithIdentifier("b"), Right = WithToken("2"), }, new { Left = WithIdentifier("c"), Right = WithToken("3"), }, new { Left = WithIdentifier("d"), Right = WithToken("4"), }, }); [TestMethod] public void MapAssignmentArguments_MisalignedDeconstructionAssignment() => AssertMapAssignmentArguments("var (a, (b, c, d)) = (1, (2, 3), 4);", new[] { new { Left = new { Designation = new { Variables = new object[] { WithIdentifier("a"), WithIdentifierVariables("b", "c", "d"), }, }, }, Right = new { Arguments = new object[] { new { Expression = WithToken("1") }, new { Expression = WithTokenArguments("2", "3") }, new { Expression = WithToken("4") }, } }, }, }); [TestMethod] public void MapAssignmentArguments_MisalignedDeconstructionAssignmentInNestedTuple() => AssertMapAssignmentArguments("var (a, (b, c, d)) = (1, (2, 3));", new object[] { new { Left = new { Designation = new { Variables = new object[] { WithIdentifier("a"), WithIdentifierVariables("b", "c", "d"), }, }, }, Right = new { Arguments = new object[] { new { Expression = WithToken("1") }, new { Expression = WithTokenArguments("2", "3") }, } }, }, }); [TestMethod] public void MapAssignmentArguments_MixedAssignment() => AssertMapAssignmentArguments("int a; (a, var b) = (1, 2);", new[] { new { Left = WithIdentifier("a"), Right = WithToken("1"), }, new { Left = WithDesignation("b"), Right = WithToken("2"), }, }); [TestMethod] public void MapAssignmentArguments_MisalignedLeft() => AssertMapAssignmentArguments("(var x, var y) = (1, 2, 3);", new[] { new { Left = WithDesignationArguments("x", "y"), Right = WithTokenArguments("1", "2", "3"), }, }); [TestMethod] public void MapAssignmentArguments_MisalignedRight() => AssertMapAssignmentArguments("(var x, var y, var z) = (1, 2);", new[] { new { Left = WithDesignationArguments("x", "y", "z"), Right = WithTokenArguments("1", "2"), }, }); [TestMethod] public void MapAssignmentArguments_MisalignedNested1() => AssertMapAssignmentArguments("(var a, (var b, var c)) = (1, (2, 3, 4));", new[] { new { Left = new { Arguments = new object[] { new { Expression = WithDesignation("a") }, new { Expression = WithDesignationArguments("b", "c") }, } }, Right = new { Arguments = new object[] { new { Expression = WithToken("1") }, new { Expression = WithTokenArguments("2", "3", "4") }, } }, }, }); [TestMethod] public void MapAssignmentArguments_MisalignedNested2() => AssertMapAssignmentArguments("(var a, (var b, var c), var d) = (1, (2, 3, 4));", new[] { new { Left = new { Arguments = new object[] { new { Expression = WithDesignation("a") }, new { Expression = WithDesignationArguments("b", "c") }, new { Expression = WithDesignation("d") }, } }, Right = new { Arguments = new object[] { new { Expression = WithToken("1") }, new { Expression = WithTokenArguments("2", "3", "4") }, } }, }, }); [TestMethod] public void MapAssignmentArguments_DifferentConventions() => AssertMapAssignmentArguments( @"int a; int M() => 1; ((a), (var b, _), object c, double d, _) = (1, (two: 2, (3)), string.Empty, M(), new object());", new object[] { new { Left = new { Expression = WithIdentifier("a") }, Right = WithToken("1"), }, new { Left = WithDesignation("b"), Right = WithToken("2"), }, new { Left = WithIdentifier("_"), Right = new { Expression = WithToken("3") }, }, new { Left = WithDesignation("c"), Right = new { Expression = new { Keyword = new { Text = "string" } }, Name = WithIdentifier("Empty"), }, }, new { Left = WithDesignation("d"), Right = new { Expression = WithIdentifier("M") }, }, new { Left = WithIdentifier("_"), Right = new { Type = new { Keyword = new { Text = "object" } } }, }, }); [TestMethod] // Tuples. [DataRow("(var a, (x, var b)) = (0, (x++, 1));", "var a | 0", "x | x++", "var b | 1")] [DataRow("(var a, (var b, var c, var d), var e) = (0, (1, 2, 3), 4);", "var a | 0", "var b | 1", "var c | 2", "var d | 3", "var e | 4")] // Designation. [DataRow("var (a, (b, c)) = (0, (1, 2));", "a | 0", "b | 1", "c | 2")] [DataRow("var (a, (b, _)) = (0, (1, 2));", "a | 0", "b | 1", "_ | 2")] [DataRow("var (a, _) = (0, (1, 2));", "a | 0", "_ | (1, 2)")] // Unaligned tuples. [DataRow("(var a, var b) = (0, 1, 2);", "(var a, var b) | (0, 1, 2)")] [DataRow("(var a, var b) = (0, 1, 2);", "(var a, var b) | (0, 1, 2)")] [DataRow("(var a, var b) = (0, (1, 2));", "var a | 0", "var b | (1, 2)")] [DataRow("(var a, (var b, var c)) = (0, 1);", "var a | 0", "(var b, var c) | 1")] // Syntacticly correct [DataRow("(var a, var b, var c) = (0, (1, 2));", "(var a, var b, var c) | (0, (1, 2))")] [DataRow("(var a, (var b, var c)) = (0, 1, 2);", "(var a, (var b, var c)) | (0, 1, 2)")] // Unaligned designation. [DataRow("var (a, (b, c)) = (0, (1, 2, 3));", "var (a, (b, c)) | (0, (1, 2, 3))")] [DataRow("var (a, (b, c)) = (0, (1, (2, 3)));", "a | 0", "b | 1", "c | (2, 3)")] [DataRow("var (a, (b, (c, d))) = (0, (1, 2));", "a | 0", "b | 1", "(c, d) | 2")] [DataRow("var (a, (b, c, d)) = (0, (1, 2));", "var (a, (b, c, d)) | (0, (1, 2))")] // Mixed. [DataRow("(var a, var (b, c)) = (0, (1, 2));", "var a | 0", "b | 1", "c | 2")] [DataRow("(var a, var (b, (c, (d, e)))) = (0, (1, (2, (3, 4))));", "var a | 0", "b | 1", "c | 2", "d | 3", "e | 4")] [DataRow("(var a, (var b, var (c, d))) = (0, (1, (2, 3)));", "var a | 0", "var b | 1", "c | 2", "d | 3")] public void MapAssignmentArguments_DataTest(string code, params string[] pairs) { var actualMapping = ParseAssignmentExpression(code).MapAssignmentArguments(); var actualMappingPairs = actualMapping.Select(x => $"{x.Left} | {x.Right}"); actualMappingPairs.Should().BeEquivalentTo(pairs); } [TestMethod] // Normal assignment [DataRow("int a; a = 1;", "a")] // Deconstruction into tuple [DataRow("(var a, var b) = (1, 2);", "a", "b")] [DataRow("(var a, var b) = (1, (2, 3));", "a", "b")] [DataRow("(var a, _) = (1, 2);", "a", "_")] // "_" can refer to a local variable or be a discard. [DataRow("(var _, var _) = (1, 2);")] // "var _" is always a discard. [DataRow("(var _, _) = (1, 2);", "_")] [DataRow("(_, _) = (1, 2);", "_", "_")] [DataRow("_ = (1, 2);", "_")] [DataRow("(var a, (var b, var c), var d) = (1, (2, 3), 4);", "a", "b", "c", "d")] [DataRow("int b; (var a, (b, var c), _) = (1, (2, 3), 4);", "a", "b", "c", "_")] [DataRow("(var a, (int, int) b) = (1, (2, 3));", "a", "b")] // Deconstruction into declaration expression designation [DataRow("var (a, b) = (1, 2);", "a", "b")] [DataRow("var (a, _) = (1, 2);", "a")] [DataRow("var (_, _) = (1, 2);")] // Mixed [DataRow("(var a, var (b, c), var d, (int, int) e) = (1, (2, 3), (4, 5), (6, 7));", "a", "b", "c", "d", "e")] public void AssignmentTargets_DeconstructTargets(string assignment, params string[] expectedTargets) { var allTargets = ParseAssignmentExpression(assignment).AssignmentTargets(); var allTargetsAsString = allTargets.Select(x => x.ToString()); allTargetsAsString.Should().BeEquivalentTo(expectedTargets); } private static void AssertMapAssignmentArguments(string code, T[] expectation) { var mapping = ParseAssignmentExpression(code).MapAssignmentArguments(); mapping.Should().BeEquivalentTo(expectation); } private static object WithDesignation(string identifier) => new { Designation = WithIdentifier(identifier) }; private static object WithIdentifier(string identifier) => new { Identifier = new { Text = identifier } }; private static object WithToken(string identifier) => new { Token = new { Text = identifier } }; private static object WithTokenArguments(params string[] tokens) => new { Arguments = tokens.Select(x => new { Expression = WithToken(x) }) }; private static object WithDesignationArguments(params string[] designations) => new { Arguments = designations.Select(x => new { Expression = WithDesignation(x) }) }; private static object WithIdentifierVariables(params string[] identifier) => new { Variables = identifier.Select(x => WithIdentifier(x)) }; private static AssignmentExpressionSyntax ParseAssignmentExpression(string code) { var syntaxTree = CSharpSyntaxTree.ParseText($@" public class C {{ public void M() {{ {code} }} }}"); syntaxTree.GetDiagnostics().Should().BeEmpty(); return syntaxTree.GetRoot().DescendantNodesAndSelf().OfType().Single(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/AttributeDataExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using NSubstitute; using SonarAnalyzer.Core.Syntax.Extensions; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class AttributeDataExtensionsTest { [TestMethod] [DataRow(true, "Test", "Test")] [DataRow(true, "TestAttribute", "TestAttribute")] [DataRow(false, "TestAttribute", null)] [DataRow(false, "Test", "test")] [DataRow(false, "Test", "TEST")] [DataRow(false, "TestAttribute", "Test")] [DataRow(false, "TestAttribute", "testAttribute")] [DataRow(false, "TestAttribute", "test")] [DataRow(false, "TestAttribute", "testAttr")] [DataRow(false, "TestAttribute", "TestAttr")] [DataRow(false, "TestAttribute", "TestAttributes")] [DataRow(false, "TestAttribute", "TestTest")] [DataRow(false, "Test", "PrefixTest")] [DataRow(false, "Test", "TestSuffix")] public void HasName(bool expected, string attributeClassName, string testName) => AttributeDataWithName(attributeClassName).HasName(testName).Should().Be(expected); [TestMethod] [DataRow(true, "Test", "Test", "Test")] [DataRow(true, "TestAttribute", "TestAttribute", "TestAttribute")] [DataRow(false, "Test", "test", "other")] [DataRow(false, "Test", "other", "test")] [DataRow(false, "TestAttribute", "test", "other")] [DataRow(false, "TestAttribute", "other", "other")] [DataRow(false, "TestAttribute", "other1", "other2", "Test")] [DataRow(true, "TestAttribute", "other1", "other2", "other3", "TestAttribute")] [DataRow(false, "TestAttribute", "Test", "test", "SomeAttribute")] public void HasAnyName(bool expected, string attributeClassName, params string[] testNames) => AttributeDataWithName(attributeClassName).HasAnyName(testNames).Should().Be(expected); [TestMethod] public void HasAnyNameThrowsForNull() => new Action(() => AttributeDataWithName("TestAttribute").HasAnyName(null)).Should().Throw(); [TestMethod] public void TryGetAttributeValue_Arguments() { var arguments = new Dictionary { { "SomeBool", true }, { "SomeInt", 1_234_567 }, { "SomeByte", (byte)24 }, { "SomeString", "Text" }, { "SomeNumberString", "42" }, { "SomeNull", null }, }; var named = AttributeDataWithArguments(namedArguments: arguments); var constructor = AttributeDataWithArguments(constructorArguments: arguments); AssertTryGetAttributeValue("SomeBool", true, true); AssertTryGetAttributeValue("someBool", true, true); AssertTryGetAttributeValue("somebool", true, true); AssertTryGetAttributeValue("SOMEBOOL", true, true); AssertTryGetAttributeValue("SomeInt", true, 1_234_567); AssertTryGetAttributeValue("SomeInt", false, 0); // SomeInt is too big AssertTryGetAttributeValue("SomeByte", true, 24); AssertTryGetAttributeValue("SomeByte", true, 24); AssertTryGetAttributeValue("SomeString", true, "Text"); AssertTryGetAttributeValue("SomeNull", true, null); AssertTryGetAttributeValue("SomeNull", true, null); AssertTryGetAttributeValue("SomeNull", true, 0); AssertTryGetAttributeValue("Missing", false, null); AssertTryGetAttributeValue("Missing", false, null); AssertTryGetAttributeValue("Missing", false, 0); AssertTryGetAttributeValue("SomeString", false, 0); AssertTryGetAttributeValue("SomeNumberString", true, 42); void AssertTryGetAttributeValue(string valueName, bool expectedSuccess, T expectedResult) { var success = named.TryGetAttributeValue(valueName, out T result); success.Should().Be(expectedSuccess); result.Should().Be(expectedResult); success = constructor.TryGetAttributeValue(valueName, out result); success.Should().Be(expectedSuccess); result.Should().Be(expectedResult); } } [TestMethod] public void TryGetAttributeValue_ConstructorArgumentAndNamedArgumentNamedTheSame() { var attributeData = AttributeDataWithArguments(namedArguments: new() { { "Result", true } }, constructorArguments: new() { { "Result", false } }); var actualSuccess = attributeData.TryGetAttributeValue("Result", out bool actualValue); actualSuccess.Should().BeTrue(); actualValue.Should().BeTrue(); // Named argument takes precedence } [TestMethod] public void TryGetAttributeValue_DateTimeConversion() { var attributeData = AttributeDataWithArguments(namedArguments: new() { { "Result", "2022-12-24" } }); var actualSuccess = attributeData.TryGetAttributeValue("Result", out DateTime actualValue); actualSuccess.Should().BeTrue(); actualValue.Should().Be(new DateTime(2022, 12, 24)); } [TestMethod] [DataRow("SomeText", typeof(string))] [DataRow(42, typeof(int))] [DataRow(null, null)] public void TryGetAttributeValue_ObjectConversion(object value, Type expectedType) { var attributeData = AttributeDataWithArguments(namedArguments: new() { { "Result", value } }); var actualSuccess = attributeData.TryGetAttributeValue("Result", out object actualValue); actualSuccess.Should().BeTrue(); if (expectedType != null) { actualValue.Should().BeOfType(expectedType); } actualValue.Should().Be(value); } [TestMethod] [DataRow(true)] [DataRow(false)] public void HasAttributeUsageInherited_InheritedSpecified(bool inherited) { var code = $$""" using System; [AttributeUsage(AttributeTargets.All, Inherited = {{inherited.ToString().ToLower()}})] public class MyAttribute: Attribute { } [My] public class Program { } """; CompileAttribute(code).HasAttributeUsageInherited().Should().Be(inherited); } [TestMethod] public void HasAttributeUsageInherited_InheritedUnSpecified() { const string code = """ using System; [AttributeUsage(AttributeTargets.All)] public class MyAttribute: Attribute { } [My] public class Program { } """; CompileAttribute(code).HasAttributeUsageInherited().Should().Be(true); // The default for Inherited = true } [TestMethod] public void HasAttributeUsageInherited_NoUsageAttribute() { const string code = """ using System; public class MyAttribute: Attribute { } [My] public class Program { } """; CompileAttribute(code).HasAttributeUsageInherited().Should().Be(true); // The default for Inherited = true } [TestMethod] [DataRow(true, true)] [DataRow(false, true)] // The "Inherited" flag is not inherited for the AttributeUsage attribute itself. See also the SymbolHelperTest.GetAttributesWithInherited... tests, // where the reflection behavior of MemberInfo.GetCustomAttributes is also tested. public void HasAttributeUsageInherited_UsageInherited(bool inherited, bool expected) { var code = $$""" using System; [AttributeUsage(AttributeTargets.All, Inherited = {{inherited.ToString().ToLower()}})] public class BaseAttribute: Attribute { } public class MyAttribute: BaseAttribute { } [My] public class Program { } """; CompileAttribute(code).HasAttributeUsageInherited().Should().Be(expected); } [TestMethod] public void HasAttributeUsageInherited_DuplicateAttributeUsage() { const string code = """ using System; [AttributeUsage(AttributeTargets.All, Inherited = true)] [AttributeUsage(AttributeTargets.All, Inherited = false)] // Compiler error public class MyAttribute: Attribute { } [My] public class Program { } """; CompileAttribute(code, ignoreErrors: true).HasAttributeUsageInherited().Should().BeTrue(); } private static AttributeData CompileAttribute(string code, bool ignoreErrors = false) => new SnippetCompiler(code, ignoreErrors, AnalyzerLanguage.CSharp).DeclaredSymbol("Program").GetAttributes().Single(x => x.HasName("MyAttribute")); private static AttributeDataMock AttributeDataWithName(string attributeClassName) { var namedType = Substitute.For(); namedType.Name.Returns(attributeClassName); return new AttributeDataMock(namedType); } private static AttributeData AttributeDataWithArguments(Dictionary namedArguments = null, Dictionary constructorArguments = null) { namedArguments ??= new(); constructorArguments ??= new(); var separator = constructorArguments.Any() && namedArguments.Any() ? ", " : string.Empty; var code = $$""" using System; public class MyAttribute: Attribute { public MyAttribute({{constructorArguments.Select(x => $"{TypeName(x.Value)} {x.Key}").JoinStr(", ")}}) { } {{namedArguments.Select(x => $@"public {TypeName(x.Value)} {x.Key} {{ get; set; }}").JoinStr("\r\n")}} } [My({{constructorArguments.Select(x => Quote(x.Value)).JoinStr(", ")}}{{separator}}{{namedArguments.Select(x => $"{x.Key}={Quote(x.Value)}").JoinStr(", ")}})] public class Dummy { } """; var snippet = new SnippetCompiler(code); var classDeclaration = snippet.Tree.GetRoot().DescendantNodes().OfType().Last(); var symbol = snippet.Model.GetDeclaredSymbol(classDeclaration); return symbol.GetAttributes().First(); static string TypeName(object value) => value == null ? "object" : value.GetType().FullName; static string Quote(object value) => value switch { string s => @$"""{s}""", bool b => b ? "true" : "false", null => "null", var v => v.ToString(), }; } private class AttributeDataMock : AttributeData { protected override INamedTypeSymbol CommonAttributeClass { get; } protected override IMethodSymbol CommonAttributeConstructor => throw new NotSupportedException(); protected override SyntaxReference CommonApplicationSyntaxReference => throw new NotSupportedException(); protected override ImmutableArray CommonConstructorArguments => throw new NotSupportedException(); protected override ImmutableArray> CommonNamedArguments => throw new NotSupportedException(); public AttributeDataMock(INamedTypeSymbol commonAttributeClass) => CommonAttributeClass = commonAttributeClass; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/AttributeSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class AttributeSyntaxExtensionsTest { [TestMethod] [DataRow("[System.ObsoleteAttribute] public class X{}", true)] [DataRow("using System; [ObsoleteAttribute] public class X{}", true)] [DataRow("using System; [Obsolete] public class X{}", true)] [DataRow("using System; [Attribute] public class X{}", false)] [DataRow("using System; [AttributeUsageAttribute] public class X{}", false)] public void IsKnownType_ChecksAttributeType(string code, bool isKnownType) { var compilation = CreateCompilation(code); var syntaxTree = compilation.SyntaxTrees.First(); var attribute = syntaxTree.First(); attribute.IsKnownType(KnownType.System_ObsoleteAttribute, compilation.GetSemanticModel(syntaxTree)).Should().Be(isKnownType); } [TestMethod] public void IsKnownType_TypeNotAnAttribute() { var compilation = CreateCompilation("[System.ObsoleteAttribute] public class X{}"); var syntaxTree = compilation.SyntaxTrees.First(); var attribute = syntaxTree.First(); attribute.IsKnownType(KnownType.System_String, compilation.GetSemanticModel(syntaxTree)).Should().Be(false); } [TestMethod] public void IsKnownType_EmptyImmutableArray_ReturnsFalse() { var compilation = CreateCompilation("[System.ObsoleteAttribute] public class X{}"); var syntaxTree = compilation.SyntaxTrees.First(); var attribute = syntaxTree.First(); attribute.IsKnownType(ImmutableArray.Empty, compilation.GetSemanticModel(syntaxTree)).Should().Be(false); } private static CSharpCompilation CreateCompilation(string code) => CSharpCompilation.Create("TempAssembly.dll") .AddSyntaxTrees(CSharpSyntaxTree.ParseText(code)) .AddReferences(MetadataReferenceFacade.ProjectDefaultReferences) .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/AwaitExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class AwaitExpressionSyntaxExtensionsTest { [TestMethod] [DataRow("[|await t|];", "t")] [DataRow("[|await t.ConfigureAwait(false)|];", "t")] [DataRow("[|await (t.ConfigureAwait(false))|];", "t")] [DataRow("[|await (t).ConfigureAwait(false)|];", "(t)")] [DataRow("[|await M(t).ConfigureAwait(false)|];", "M(t)")] [DataRow("[|await await TaskOfTask().ConfigureAwait(false)|];", "await TaskOfTask().ConfigureAwait(false)")] [DataRow("await [|await TaskOfTask().ConfigureAwait(false)|];", "TaskOfTask()")] [DataRow("[|await (await TaskOfTask()).ConfigureAwait(false)|];", "(await TaskOfTask())")] public void AwaitedExpressionWithoutConfigureAwait(string expression, string expected) { var code = $$""" using System.Threading.Tasks; class C { async Task M(Task t) { {{expression}} } Task TaskOfTask() => default; } """; var start = code.IndexOf("[|"); code = code.Replace("[|", string.Empty); var end = code.IndexOf("|]"); code = code.Replace("|]", string.Empty); var root = CSharpSyntaxTree.ParseText(code).GetRoot(); var node = root.FindNode(TextSpan.FromBounds(start, end)) as AwaitExpressionSyntax; var actual = node.AwaitedExpressionWithoutConfigureAwait(); actual.ToString().Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/BlockSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class BlockSyntaxExtensionsTest { [TestMethod] public void IsEmpty_BlockMethodEmpty() { var declaration = MethodDeclarationForBlock(@" { }"); declaration.Body.IsEmpty().Should().BeTrue(); } [TestMethod] public void IsEmpty_BlockMethodWithCode() { var declaration = MethodDeclarationForBlock(@" { var i = 0; }"); declaration.Body.IsEmpty().Should().BeFalse(); } [TestMethod] public void IsEmpty_ArgumentExceptionOnNull() { var code = @" public class C { public int M() => 1; }"; var declaration = MethodDeclaration(code); declaration.Body.Should().BeNull(); var isEmpty = () => declaration.Body.IsEmpty(); isEmpty.Should().Throw().Which.Message.Should().Contain("Value cannot be null").And.Contain("block"); } [TestMethod] public void IsEmpty_BlockMethodWithComment_Singleline_On() { var declaration = MethodDeclarationForBlock(@" { // Single line }"); declaration.Body.IsEmpty(treatCommentsAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_BlockMethodWithComment_Singleline_Off() { var declaration = MethodDeclarationForBlock(@" { // Single line }"); declaration.Body.IsEmpty(treatCommentsAsContent: false).Should().BeTrue(); } [TestMethod] public void IsEmpty_BlockMethodWithComment_Empty_On() { var declaration = MethodDeclarationForBlock(@" { // }"); declaration.Body.IsEmpty(treatCommentsAsContent: true).Should().BeFalse(); // This is a questionable behavior. } [TestMethod] public void IsEmpty_BlockMethodWithComment_Multiline_On() { var declaration = MethodDeclarationForBlock(@" { /* Multi line */ }"); declaration.Body.IsEmpty(treatCommentsAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_BlockMethodWithComment_Multiline_Off() { var declaration = MethodDeclarationForBlock(@" { /* Multi line */ }"); declaration.Body.IsEmpty(treatCommentsAsContent: false).Should().BeTrue(); } [TestMethod] public void IsEmpty_BlockMethodWithConditionalCompilation_WithDisabledCode_On() { var declaration = MethodDeclarationForBlock(@" { #if SomeThing var i = 0; #endif }"); declaration.Body.IsEmpty(treatConditionalCompilationAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_BlockMethodWithConditionalCompilation_WithDisabledCode_Off() { var declaration = MethodDeclarationForBlock(@" { #if SomeThing var i = 0; #endif }"); declaration.Body.IsEmpty(treatConditionalCompilationAsContent: false).Should().BeTrue(); } [TestMethod] public void IsEmpty_BlockMethodWithConditionalCompilation_WithEmptyRegion_On() { var declaration = MethodDeclarationForBlock(@" { #region SomeRegion #endregion }"); declaration.Body.IsEmpty(treatConditionalCompilationAsContent: true).Should().BeTrue(); } [TestMethod] public void IsEmpty_BlockMethodWithConditionalCompilation_WithEmptyConditional_On() { var declaration = MethodDeclarationForBlock(@" { #if SomeCondition #endif }"); declaration.Body.IsEmpty(treatConditionalCompilationAsContent: true).Should().BeTrue(); } [TestMethod] [DataRow(true, true, false)] [DataRow(false, true, false)] // isEmpty should be true here, because the content of the conditional is just a comment and a comment should not be treated as content. [DataRow(true, false, true)] [DataRow(false, false, true)] public void IsEmpty_BlockMethodCombinations_CommentInConditional(bool treatCommentsAsContent, bool treatConditionalCompilationAsContent, bool isEmpty) { var declaration = MethodDeclarationForBlock(@" { #if SomeCondition // Some comment #endif }"); if (isEmpty) { declaration.Body.IsEmpty(treatCommentsAsContent, treatConditionalCompilationAsContent).Should().BeTrue(); } else { declaration.Body.IsEmpty(treatCommentsAsContent, treatConditionalCompilationAsContent).Should().BeFalse(); } } [TestMethod] public void IsEmpty_TrailingTriviaOnOpenBrace_Comment() { var block = CreateBlock(afterOpen: CreateTriviaListWithComment()); block.IsEmpty(treatCommentsAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_LeadingTriviaOnCloseBrace_Comment() { var block = CreateBlock(beforeClose: CreateTriviaListWithComment()); block.IsEmpty(treatCommentsAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_TrailingTriviaOnOpenBrace_ConditionalCompilation() { var block = CreateBlock(afterOpen: CreateTriviaListWithConditionalCompilation()); block.IsEmpty(treatConditionalCompilationAsContent: true).Should().BeFalse(); } [TestMethod] public void IsEmpty_LeadingTriviaOnCloseBrace_ConditionalCompilation() { var block = CreateBlock(beforeClose: CreateTriviaListWithConditionalCompilation()); block.IsEmpty(treatConditionalCompilationAsContent: true).Should().BeFalse(); } private static SyntaxTriviaList CreateTriviaListWithComment() => TriviaList(Comment("// Comment")); private static SyntaxTriviaList CreateTriviaListWithConditionalCompilation() => TriviaList( Trivia(IfDirectiveTrivia(IdentifierName("Something"), isActive: false, branchTaken: false, conditionValue: false)), DisabledText(@"var i= 0;"), Trivia(EndIfDirectiveTrivia(isActive: false))); private static BlockSyntax CreateBlock(SyntaxTriviaList beforeOpen = default, SyntaxTriviaList afterOpen = default, SyntaxTriviaList beforeClose = default, SyntaxTriviaList afterClose = default) => Block(Token(beforeOpen, SyntaxKind.OpenBraceToken, afterOpen), statements: default, Token(beforeClose, SyntaxKind.CloseBraceToken, afterClose)); private static MethodDeclarationSyntax MethodDeclarationForBlock(string methodBlock) => MethodDeclaration(WrapInClass(methodBlock)); private static string WrapInClass(string methodBlockOrArrow) => $@" public class C {{ public void M() {methodBlockOrArrow} }}"; private static MethodDeclarationSyntax MethodDeclaration(string source) { var root = CSharpSyntaxTree.ParseText(source).GetRoot(); root.ContainsDiagnostics.Should().BeFalse(); return root.DescendantNodes().OfType().Single(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/DiagnosticDescriptorExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class DiagnosticDescriptorExtensionsTest { [TestMethod] public void IsSecurityHotspot_IsHotspot() { var descriptor = new DiagnosticDescriptor("S2092", "title", "message", "category", DiagnosticSeverity.Warning, true); descriptor.IsSecurityHotspot().Should().BeTrue(); } [TestMethod] public void IsSecurityHotspot_NonexistentId() { var descriptor = new DiagnosticDescriptor("Sxxxx", "title", "message", "category", DiagnosticSeverity.Warning, true); descriptor.IsSecurityHotspot().Should().BeFalse(); } [TestMethod] [DataRow("S101")] // Both C# and VB rules [DataRow("S100")] // C# rule [DataRow("S117")] // VB rule public void IsSecurityHotspot_NotHotspot(string ruleId) { var descriptor = new DiagnosticDescriptor(ruleId, "title", "message", "category", DiagnosticSeverity.Warning, true); descriptor.IsSecurityHotspot().Should().BeFalse(); } // Repro: https://sonarsource.atlassian.net/browse/NET-3543 [TestMethod] public void IsEnabled_NullSyntaxTreeOptionsProvider_DoesNotThrow() { var tree = CSharpSyntaxTree.ParseText("class C { void M() {} }", cancellationToken: default); var compilation = CSharpCompilation.Create("test", [tree], options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); compilation.Options.SyntaxTreeOptionsProvider.Should().BeNull("this is the precondition that triggers the bug"); var context = AnalysisScaffolding.CreateNodeReportingContext(tree.GetRoot(default), compilation.GetSemanticModel(tree), _ => { }); AnalysisScaffolding.CreateDescriptorMain().IsEnabled(context).Should().BeTrue(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/ExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class ExpressionSyntaxExtensionsTest { [TestMethod] [DataRow("null", false)] [DataRow("var o = new object();", true)] [DataRow("int? x = 1", true)] [DataRow("int x = 1;", false)] public void CanBeNull(string code, bool expected) { var (expression, semanticModel) = Compile(code); expression.CanBeNull(semanticModel).Should().Be(expected); } [TestMethod] [DataRow("a", "a")] [DataRow("a + b", "a", "b")] [DataRow("a++", "a")] [DataRow("++a", "a")] [DataRow("a.b", "a.b")] [DataRow("a.b()", "a.b()")] [DataRow("a.b() + 1", "a.b()")] [DataRow("a.b() + b().c", "a.b()", "b().c")] [DataRow("a!.b()", "a!.b()")] [DataRow("a?.b()", "a?.b()")] [DataRow("a.b()?.c.d?[e].f?.g", "a.b()?.c.d?[e].f?.g")] // Should also return "e" [DataRow("a(b, c)", "a(b, c)", "b", "c")] [DataRow("a[b, c]]", "a[b, c]", "b", "c")] [DataRow("(a)", "a")] [DataRow("a as b", "a")] [DataRow("a is b", "a")] [DataRow("a is b c", "a")] [DataRow("(a)b", "b")] [DataRow("await a", "a")] [DataRow("a!", "a")] [DataRow(""" $"{a} {b}" """, "a", "b")] [DataRow("""" $"""{a} {b}""" """", "a", "b")] [DataRow("a switch { b c => d }", "a", "d")] [DataRow("a switch { b => c, { d: { } } => e }", "a", "c", "e")] public void ExtractMemberIdentifier(string expression, params string[] memberIdentifiers) { var parsed = SyntaxFactory.ParseExpression(expression); var result = parsed.ExtractMemberIdentifier(); var asString = result.Select(x => x.ToString()); asString.Should().BeEquivalentTo(memberIdentifiers); } [TestMethod] [DataRow("a", "a")] [DataRow("null", "null")] [DataRow("a + b", "null")] [DataRow("this.a", "a")] [DataRow("this.a.b", "a")] [DataRow("a.b", "a")] [DataRow("a.b()", "a")] [DataRow("a.b().c", "a")] [DataRow("a()", "a")] [DataRow("a().b", "a")] [DataRow("a()!.b", "a")] [DataRow("(a.b).c", "a")] [DataRow("a.b?.c.d[e]?[f].g?.h", "a")] [DataRow("a[b]", "a")] [DataRow("a?[b]", "a")] [DataRow("a->b", "a")] [DataRow("int.MaxValue", "int")] public void GetLeftMostInMemberAccess(string expression, string expected) { var parsed = SyntaxFactory.ParseExpression(expression); var result = parsed.LeftMostInMemberAccess(); var asString = result?.ToString() ?? "null"; asString.Should().BeEquivalentTo(expected); } [TestMethod] [DataRow("default", true)] [DataRow("default!", true)] [DataRow("(default)!", true)] [DataRow("(default!)", true)] [DataRow("((default)!)", true)] [DataRow("default(int)", false)] [DataRow("default(int)!", false)] [DataRow("(default(int)!)", false)] [DataRow("(1 + 1)", false)] [DataRow("", false)] [DataRow("()", false)] public void IsDefaultLiteral(string expression, bool expected) { var parsed = SyntaxFactory.ParseExpression(expression); var result = parsed.IsDefaultLiteral(); result.Should().Be(expected); } [TestMethod] public void IsDefaultLiteral_Null() => ((ExpressionSyntax)null).IsDefaultLiteral().Should().BeFalse(); private static (ExpressionSyntax Expression, SemanticModel Model) Compile(string code) { var tree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("TempAssembly.dll").AddSyntaxTrees(tree).AddReferences(MetadataReferenceFacade.ProjectDefaultReferences); var model = compilation.GetSemanticModel(tree); return (tree.First(), model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/ITupleOperationWrapperExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using FluentAssertions.Extensions; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class ITupleOperationWrapperExtensionsTest { [TestMethod] // Tuple expression on the right side of assignment [DataRow("_ = (1, 2);", "1", "2")] [DataRow("_ = (x: 1, y: 2);", "1", "2")] [DataRow("_ = (1, M());", "1", "M()")] [DataRow("_ = (1, (2, 3));", "1", "2", "3")] [DataRow("_ = (1, (2, 3), 4, (5, (6, 7)));", "1", "2", "3", "4", "5", "6", "7")] // Tuple deconstruction on the left side of assignment [DataRow("(var a, (var b, var c)) = (1, (2, 3));", "var a", "var b", "var c")] [DataRow("(var a, var b) = (1, 2);", "var a", "var b")] [DataRow("(var a, _) = (1, 2);", "var a", "_")] [DataRow("int a; (a, var b) = (1, M());", "a", "var b")] // Tuple declaration expression [DataRow("var (a, b) = (1, 2);", "a", "b")] [DataRow("var (a, (b, c)) = (1, (2, 3));", "a", "b", "c")] [DataRow("var (a, (_, c)) = (1, (2, 3));", "a", "_", "c")] public void AllElements_ElementsOfFirstFoundTupleAreExtracted(string tuple, params string[] expectedElements) { var tupleOperation = CompileFirstTupleOperation(tuple); var allElements = tupleOperation.AllElements(); allElements.Select(x => x.Syntax.ToString()).Should().BeEquivalentTo(expectedElements); } #if NET [TestMethod] public void AllElements_Performance_DeepNesting() { // NET48 does not support deeply nested tuples and fails with CS8078: An expression is too long or complex to compile var deeplyNestedTuple = DeeplyNestedTuple(500); // (1, (2,... , 500))..) // Actual execution time is about 0.5 - 10.0 ms on enterprise CI but 15-30ms on sonar-dotnet due to different UT parallelization AssertAllElementsExecutionTimeBeLessThan(deeplyNestedTuple, 75.Milliseconds()); static string DeeplyNestedTuple(int depth) { var sb = new StringBuilder(); for (var i = 1; i < depth; i++) { sb.Append($"({i}, "); } sb.Append($"{depth}{new string(')', depth - 1)}"); return sb.ToString(); } } #endif [TestMethod] public void AllElements_Performance_LargeTuple() { var largeTuple = LargeTuple(500); // (1, 2,... , 500) // Actual execution time is about 0.4ms - 0.7ms AssertAllElementsExecutionTimeBeLessThan(largeTuple, 20.Milliseconds()); static string LargeTuple(int length) { var sb = new StringBuilder(); sb.Append('('); for (var i = 1; i < length; i++) { sb.Append($"{i}, "); } sb.Append($"{length})"); return sb.ToString(); } } private static void AssertAllElementsExecutionTimeBeLessThan(string tuple, TimeSpan maxDuration) { var tupleOperation = CompileFirstTupleOperation($"_ = {tuple};"); Action allElements = () => tupleOperation.AllElements(); // Warm-up (make sure method is jitted) allElements(); allElements.ExecutionTime().Should().BeLessThan(maxDuration); } private static ITupleOperationWrapper CompileFirstTupleOperation(string tuple) { var syntaxTree = CSharpSyntaxTree.ParseText(WrapInMethod(tuple)); var compilation = CSharpCompilation.Create("TempAssembly.dll") .AddSyntaxTrees(syntaxTree) .AddReferences(MetadataReferenceFacade.ProjectDefaultReferences) .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); compilation.GetDiagnostics().Should().BeEmpty(); var model = compilation.GetSemanticModel(syntaxTree); var tupleExpression = syntaxTree.GetRoot().DescendantNodes().First(x => x.Kind() is SyntaxKind.TupleExpression or SyntaxKindEx.ParenthesizedVariableDesignation); return ITupleOperationWrapper.FromOperation(model.GetOperation(tupleExpression)); } private static string WrapInMethod(string code) => $@"public class C {{ public int M() {{ {code}; return 0; }} }}"; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/InterpolatedStringExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class InterpolatedStringExpressionSyntaxExtensionsTest { [TestMethod] [DataRow(@"var methodCall = $""{Foo()}"";")] [DataRow(@"var nestedMethodCall = $""{$""{$""{Foo()}""}""}"";")] [DataRow(@"const int constant = 1; var mixConstantNonConstant = $""{notConstant}{constant}"";")] [DataRow(@"const int constant = 1; var mixConstantAndLiteral = $""TextValue {constant}"";")] [DataRow(@"const int constant = 1; var mix = $""{constant}{$""{Foo()}""}{""{notConstant}""}"";")] public void TryGetGetInterpolatedTextValue_UnsupportedSyntaxKinds_ReturnsFalse_CS(string snippet) { var (expression, model) = CompileCS(snippet); InterpolatedStringExpressionSyntaxExtensions.InterpolatedTextValue(expression, model).Should().BeNull(); } [TestMethod] [DataRow(@"var textOnly = $""TextOnly"";", "TextOnly")] [DataRow(@"const string constantString = ""Foo""; const string constantInterpolation = $""{constantString} with text."";", "Foo with text.")] [DataRow(@"const string constantString = ""Foo""; const string constantInterpolation = $""{$""Nested {constantString}""} with text."";", "Nested Foo with text.")] [DataRow(@"notConstantString = ""SomeValue""; string interpolatedString = $""{notConstantString}"";", "SomeValue")] public void TryGetGetInterpolatedTextValue_SupportedSyntaxKinds_ReturnsTrue_CS(string snippet, string expectedTextValue) { var (expression, model) = CompileCS(snippet); InterpolatedStringExpressionSyntaxExtensions.InterpolatedTextValue(expression, model).Should().Be(expectedTextValue); } private static (InterpolatedStringExpressionSyntax InterpolatedStringExpression, SemanticModel SemanticModel) CompileCS(string snippet) { var code = $$""" public class C { public void M(int notConstant, string notConstantString) { {{snippet}} } string Foo() => "x"; } """; var tree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create("TempAssembly.dll").AddSyntaxTrees(tree).AddReferences(MetadataReferenceFacade.ProjectDefaultReferences); var model = compilation.GetSemanticModel(tree); return (tree.First(), model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/InvocationExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class InvocationExpressionSyntaxExtensionsTest { [TestMethod] [DataRow("System.Array.$$Empty()$$", "System.Array", "Empty")] [DataRow("this.$$M()$$", "this", "M")] [DataRow("A?.$$M()$$", "A", "M")] public void TryGetOperands_InvocationNode_ShouldReturnsTrue_CS(string expression, string expectedLeft, string expectedRight) { var code = $$""" public class X { public X A { get; } public int M() { var _ = {{expression}}; return 42; } } """; var node = NodeBetweenMarkers(code, LanguageNames.CSharp) as InvocationExpressionSyntax; var (left, right) = InvocationExpressionSyntaxExtensions.Operands(node); left.Should().NotBeNull(); left.ToString().Should().Be(expectedLeft); right.Should().NotBeNull(); right.ToString().Should().Be(expectedRight); } [TestMethod] [DataRow("$$M()$$")] [DataRow("new System.Func(() => 1)$$()$$")] public void TryGetOperands_InvocationNodeDoesNotContainMemberAccess_ShouldReturnsFalse_CS(string expression) { var code = $$""" public class X { public X A { get; } public int M() { var _ = {{expression}}; return 42; } } """; var node = NodeBetweenMarkers(code, LanguageNames.CSharp) as InvocationExpressionSyntax; var (left, right) = InvocationExpressionSyntaxExtensions.Operands(node); left.Should().BeNull(); right.Should().BeNull(); } [TestMethod] public void HasExactlyNArguments_Null_CS() => InvocationExpressionSyntaxExtensions.HasExactlyNArguments(null, 42).Should().BeFalse(); [TestMethod] public void GetMethodCallIdentifier_Null_CS() => InvocationExpressionSyntaxExtensions.GetMethodCallIdentifier(null).Should().BeNull(); private static SyntaxNode NodeBetweenMarkers(string code, string language) { var position = code.IndexOf("$$"); var lastPosition = code.LastIndexOf("$$"); var length = lastPosition == position ? 0 : lastPosition - position - "$$".Length; code = code.Replace("$$", string.Empty); return TestCompiler.CompileCS(code).Tree.GetRoot().FindNode(new TextSpan(position, length)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/ObjectCreationExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class ObjectCreationExpressionSyntaxExtensionsTest { [TestMethod] [DataRow("new System.DateTime()", true)] [DataRow("using System; class T { DateTime field = new DateTime(); }", true)] [DataRow("new double()", false)] [DataRow("using DT = System.DateTime; class T { DT field = new DT(); }", false)] public void IsKnownType_ChecksCtorType(string code, bool expectedResult) { var compilation = CreateCompilation(code); var syntaxTree = compilation.SyntaxTrees.First(); var objectCreation = syntaxTree.First(); objectCreation.IsKnownType(KnownType.System_DateTime, compilation.GetSemanticModel(syntaxTree)).Should().Be(expectedResult); } [TestMethod] public void GetObjectCreationTypeIdentifier_Null_CS() => ObjectCreationExpressionSyntaxExtensions.GetObjectCreationTypeIdentifier(null).Should().BeNull(); private static CSharpCompilation CreateCompilation(string code) => CSharpCompilation.Create("TempAssembly.dll") .AddSyntaxTrees(CSharpSyntaxTree.ParseText(code)) .AddReferences(MetadataReferenceFacade.ProjectDefaultReferences) .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/PatternSyntaxWrapperExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Extensions; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class PatternSyntaxWrapperExtensionsTest { [TestMethod] public void IsNull_ForNullPattern_ReturnsTrue() { var isPattern = (IsPatternExpressionSyntaxWrapper)SyntaxFactory.ParseExpression("is null"); isPattern.Pattern.IsNull().Should().BeTrue(); } [TestMethod] public void IsNull_ForDifferentPattern_ReturnsFalse() { var isPattern = (IsPatternExpressionSyntaxWrapper)SyntaxFactory.ParseExpression("is not 1"); isPattern.Pattern.IsNull().Should().BeFalse(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/PropertyDeclarationSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class PropertyDeclarationSyntaxExtensionsTest { [TestMethod] public void IsAutoProperty_AccessorWithBody_ReturnsFalse() { const string code = @"class TestCases { public int Property { get { return 0; } } } "; GetPropertyDeclaration(code).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_AccessorWithExpressionBody_ReturnsFalse() { const string code = @"record TestCases { public int Property { get => 0; } } "; GetPropertyDeclaration(code).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_ExpressionBody_ReturnsFalse() { const string code = @"record TestCases { public int Property => 0; } "; GetPropertyDeclaration(code).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_AccessorsWithoutBody_ReturnsTrue() { const string code = @"class TestCases { public int Property { get; set; } } "; GetPropertyDeclaration(code).IsAutoProperty().Should().BeTrue(); } private static PropertyDeclarationSyntax GetPropertyDeclaration(string code) => SyntaxFactory.ParseSyntaxTree(code) .GetRoot() .DescendantNodes() .OfType() .First(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/StatementSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class StatementSyntaxExtensionsTest { [TestMethod] public void PrecedingStatement() { var ifMethodStatements = DescendantNodes() .First(x => x.Identifier.ValueText == "IfMethod") .Body.Statements.ToList(); ifMethodStatements[2].PrecedingStatement.Should().BeEquivalentTo(ifMethodStatements[1]); ifMethodStatements[1].PrecedingStatement.Should().BeEquivalentTo(ifMethodStatements[0]); ifMethodStatements[0].PrecedingStatement.Should().Be(null); } [TestMethod] public void FollowingStatement() { var ifMethodStatements = DescendantNodes() .First(x => x.Identifier.ValueText == "IfMethod") .Body.Statements.ToList(); ifMethodStatements[0].FollowingStatement.Should().BeEquivalentTo(ifMethodStatements[1]); ifMethodStatements[1].FollowingStatement.Should().BeEquivalentTo(ifMethodStatements[2]); ifMethodStatements[2].FollowingStatement.Should().Be(null); } [TestMethod] public void PrecedingStatementTopLevelStatements() { var sourceTopLevelStatement = """ var a = 1; var b = 2; if (a == b) { DoSomething(); } void DoSomething() { } """; var variableDeclarators = DescendantNodes(sourceTopLevelStatement).ToArray(); var aDeclaration = variableDeclarators[0]; var bDeclaration = variableDeclarators[1]; aDeclaration.PrecedingStatement.Should().Be(null); bDeclaration.PrecedingStatement.Should().BeEquivalentTo(aDeclaration); } [TestMethod] public void FirstNonBlockStatement_NoBlock() { var ifStatements = DescendantNodes() .Select(x => x.Statement) .ToArray(); var expressionStatements = DescendantNodes().ToArray(); ifStatements[0].FirstNonBlockStatement.Span.Should().Be(expressionStatements[0].Span); ifStatements[1].FirstNonBlockStatement.Span.Should().Be(expressionStatements[1].Span); ifStatements[2].FirstNonBlockStatement.Span.Should().Be(expressionStatements[2].Span); } private static IEnumerable DescendantNodes() { var source = """ namespace Test { class TestClass { public void IfMethod() { if (a > 1) DoSomething(); if (a < 0) { DoSomething(); } if (a == 42) { { DoSomething(); DoSomething(); } } } } } """; return DescendantNodes(source); } private static IEnumerable DescendantNodes(string source) => CSharpSyntaxTree.ParseText(source) .GetRoot() .DescendantNodes() .OfType(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/SyntaxNodeExtensionsCSharpTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Syntax.Extensions; [TestClass] public class SyntaxNodeExtensionsCSharpTest { [TestMethod] public void NameIs() { const string code = """ public class Sample { public string Method(int arg) => arg.ToString(); } """; var toString = CSharpSyntaxTree.ParseText(code).GetRoot().DescendantNodes().OfType().Single(); toString.NameIs("ToString").Should().BeTrue(); toString.NameIs("TOSTRING").Should().BeFalse(); toString.NameIs("tostring").Should().BeFalse(); toString.NameIs("test").Should().BeFalse(); toString.NameIs("").Should().BeFalse(); toString.NameIs(null).Should().BeFalse(); } [TestMethod] [DataRow(true, "Test")] [DataRow(true, "Test", "Test")] [DataRow(true, "Other", "Test")] [DataRow(false)] [DataRow(false, "TEST")] public void NameIsOrNames(bool expected, params string[] orNames) { var identifier = SyntaxFactory.IdentifierName("Test"); identifier.NameIs("other", orNames).Should().Be(expected); } [TestMethod] [DataRow("Strasse", "Straße", false)] // StringComparison.InvariantCulture returns in this case and so do other cultures like de-DE [DataRow("\u00F6", "\u006F\u0308", false)] // 00F6 = ö; 006F = o; 0308 = https://www.fileformat.info/info/unicode/char/0308/index.htm [DataRow("ö", "Ö", false)] [DataRow("ö", "\u00F6", true)] public void NameIs_CultureSensitivity(string identifierName, string actual, bool expected) { var identifier = SyntaxFactory.IdentifierName(identifierName); identifier.NameIs(actual).Should().Be(expected); } [TestMethod] [DataRow(false, "Strasse", "Straße")] // StringComparison.InvariantCulture returns in this case and so do other cultures like de-DE [DataRow(false, "\u00F6", "\u006F\u0308")] // 00F6 = ö; 006F = o; 0308 = https://www.fileformat.info/info/unicode/char/0308/index.htm [DataRow(false, "ö", "\u006F\u0308", "ä", "oe")] // 006F = o; 0308 = https://www.fileformat.info/info/unicode/char/0308/index.htm [DataRow(false, "Köln", "Koeln", "Cologne, ", "köln")] [DataRow(true, "Köln", "Koeln", "Cologne, ", "K\u00F6ln")] // 00F6 = ö public void NameIsOrNames_CultureSensitivity(bool expected, string identifierName, string name, params string[] orNames) { var identifier = SyntaxFactory.IdentifierName(identifierName); identifier.NameIs(name, orNames).Should().Be(expected); } [TestMethod] public void NameIsOrNamesNodeWithoutName() { var returnStatement = SyntaxFactory.ReturnStatement(); returnStatement.NameIs("A", "B", "C").Should().BeFalse(); } [TestMethod] [DataRow("""void M() { var i = 42; }""")] [DataRow("""int M() => 42;""")] [DataRow("""Sample() : this(42) { }""")] [DataRow("""int field = 42;""")] [DataRow("""int Property { get; } = 42;""")] [DataRow("""int Property { get => 42; }""")] [DataRow("""int Property { get { return 42; } }""")] [DataRow("""[Priority(42)] void M() { }""")] [DataRow("""[Priority(Order = 42)] void M() { }""")] [DataRow("""void M(int i = 42) { }""")] public void ChangeSyntaxElement_ReturnsNewNodeAndModel(string expressionScope) { var code = $$""" using System; public class PriorityAttribute : Attribute { public PriorityAttribute() { } public PriorityAttribute(int priority) { } public int Order { get; set; } } public class Sample { public Sample(int i) { } {{expressionScope}} } """; var (tree, model) = TestCompiler.CompileCS(code); var literal = tree.GetRoot().DescendantNodes().OfType().Single(); var newLiteral = literal.ChangeSyntaxElement(literal.WithToken(SyntaxFactory.Literal(-42)), model, out var newModel); newLiteral.Should().NotBeNull(); newModel.Should().NotBeNull(); newLiteral.Token.ValueText.Should().Be("-42"); model.GetConstantValue(literal).Value.Should().Be(42); newModel.GetConstantValue(newLiteral).Value.Should().Be(-42); } [TestMethod] public void ChangeSyntaxElement_FailsForUnsupportedSyntaxKind() { var (tree, model) = TestCompiler.CompileCS("""namespace N { }"""); var namespaceDeclaration = tree.GetRoot().DescendantNodes().OfType().Single(); var newNamespaceDeclaration = namespaceDeclaration.ChangeSyntaxElement(namespaceDeclaration.WithName(SyntaxFactory.IdentifierName("M")), model, out var newModel); newNamespaceDeclaration.Should().BeNull(); newModel.Should().BeNull(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/SyntaxTokenExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Utilities; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace SonarAnalyzer.CSharp.Core.Syntax.Extensions.Test; [TestClass] public class SyntaxTokenExtensionsTest { [TestMethod] [DataRow(SyntaxKind.EqualsEqualsToken, ComparisonKind.Equals)] [DataRow(SyntaxKind.ExclamationEqualsToken, ComparisonKind.NotEquals)] [DataRow(SyntaxKind.LessThanToken, ComparisonKind.LessThan)] [DataRow(SyntaxKind.LessThanEqualsToken, ComparisonKind.LessThanOrEqual)] [DataRow(SyntaxKind.GreaterThanToken, ComparisonKind.GreaterThan)] [DataRow(SyntaxKind.GreaterThanEqualsToken, ComparisonKind.GreaterThanOrEqual)] [DataRow(SyntaxKind.PlusToken, ComparisonKind.None)] public void ToComparisonKind(SyntaxKind tokenKind, ComparisonKind expected) => Token(tokenKind).ToComparisonKind().Should().Be(expected); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/SyntaxTreeExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Extensions; using SonarAnalyzer.Core.Syntax.Utilities; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class SyntaxTreeExtensionsTest { [TestMethod] public void IsGenerated_On_GeneratedTree() { const string source = @"namespace Generated { class MyClass { [System.Diagnostics.DebuggerNonUserCodeAttribute()] void M() { ;;;; } } }"; var result = IsGenerated(source, CSharpGeneratedCodeRecognizer.Instance); result.Should().BeTrue(); } [TestMethod] public void IsGenerated_On_GeneratedLocalFunctionTree() { const string source = @"namespace Generated { class MyClass { void M() { ;;;; [System.Diagnostics.DebuggerNonUserCodeAttribute()] void LocalFunction() { ;;; } } } }"; var result = IsGenerated(source, CSharpGeneratedCodeRecognizer.Instance); result.Should().BeTrue(); } [TestMethod] public void IsGenerated_On_NonGeneratedTree() { const string source = @"namespace NonGenerated { class MyClass { } }"; var result = IsGenerated(source, CSharpGeneratedCodeRecognizer.Instance); result.Should().BeFalse(); } private static bool IsGenerated(string content, GeneratedCodeRecognizer generatedCodeRecognizer) { var compilation = SolutionBuilder .Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(content) .GetCompilation(); return compilation.SyntaxTrees.First().IsGenerated(generatedCodeRecognizer); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/TupleExpressionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class TupleExpressionSyntaxExtensionsTest { [TestMethod] [DataRow("(1, 2)", "1,2")] [DataRow("(1, (2, 3))", "1,2,3")] [DataRow("(1, (2, 3), 4)", "1,2,3,4")] [DataRow("(1, (2, 3), 4, M())", "1,2,3,4,M()")] [DataRow("(1, (2, 3, (4, 5, 6), 7), 8, M())", "1,2,3,4,5,6,7,8,M()")] public void TupleExpressionSyntaxExtensions_FlatteningTests(string tuple, string expectedArguments) { var syntaxTree = CSharpSyntaxTree.ParseText(WrapInMethod(tuple)); var tupleExpression = (TupleExpressionSyntaxWrapper)syntaxTree.GetRoot().DescendantNodesAndSelf().First(x => TupleExpressionSyntaxWrapper.IsInstance(x)); var allArguments = tupleExpression.AllArguments(); var allArgumentsAsString = string.Join(",", allArguments.Select(x => x.ToString())); allArgumentsAsString.Should().Be(expectedArguments); } private static string WrapInMethod(string code) => $@" public class C {{ public int M() {{ var t = {code}; return 0; }} }} "; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/TypeExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Analyzers; namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class TypeExtensionsTest { [TestMethod] public void AnalyzerTargetLanguage_NotSupportedType() { var analyzerType = typeof(TypeExtensionsTest); var action = () => analyzerType.AnalyzerTargetLanguage(); action.Should().Throw().WithMessage("Can not find any language for the given type TypeExtensionsTest!"); } [TestMethod] public void AnalyzerTargetLanguage_MultiLanguage() { var analyzerType = typeof(MultiLanguageAnalyzer); var action = () => analyzerType.AnalyzerTargetLanguage(); action.Should().Throw().WithMessage("Analyzer can not have multiple languages: MultiLanguageAnalyzer"); } [TestMethod] public void AnalyzerTargetLanguage_SingleLanguage() { var analyzerType = typeof(SingleLanguageAnalyzer); var language = analyzerType.AnalyzerTargetLanguage(); language.Should().Be(AnalyzerLanguage.CSharp); } [DiagnosticAnalyzer(LanguageNames.CSharp)] private class SingleLanguageAnalyzer : SonarDiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => throw new NotImplementedException(); protected override void Initialize(SonarAnalysisContext context) => throw new NotImplementedException(); } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] private class MultiLanguageAnalyzer : SonarDiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => throw new NotImplementedException(); protected override void Initialize(SonarAnalysisContext context) => throw new NotImplementedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Extensions/VariableDesignationSyntaxWrapperTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Extensions; [TestClass] public class VariableDesignationSyntaxWrapperTest { [TestMethod] [DataRow("var (a, b) = (1, 2);", "a,b")] [DataRow("var (a, _) = (1, 2);", "a")] [DataRow("var (a, (b, c), d) = (1, (2, 3), 4);", "a,b,c,d")] [DataRow("_ = (1, 2) is var (a, b);", "a,b")] [DataRow("_ = (1, 2) switch { var (a, b) => true };", "a,b")] public void VariableDesignationSyntaxWrapper_DifferentDesignations(string designation, string expectedVariables) { var syntaxTree = CSharpSyntaxTree.ParseText(WrapInMethod(designation)); var variableDesignation = (VariableDesignationSyntaxWrapper)syntaxTree.GetRoot().DescendantNodesAndSelf().First(VariableDesignationSyntaxWrapper.IsInstance); var allVariables = variableDesignation.AllVariables(); var allVariablesAsString = string.Join(",", allVariables.Select(x => x.SyntaxNode.ToString())); allVariablesAsString.Should().Be(expectedVariables); } private static string WrapInMethod(string code) => $@" public class C {{ public void M() {{ {code} }} }} "; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Syntax/Utilities/SafeCSharpSyntaxWalkerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Syntax.Utilities; [TestClass] public class SafeCSharpSyntaxWalkerTest { [TestMethod] public void GivenSyntaxNodeWithReasonableDepth_SafeVisit_ReturnsTrue() => new Walker().SafeVisit(SyntaxFactory.ParseSyntaxTree("void Method() { }").GetRoot()).Should().BeTrue(); [TestMethod] public void GivenSyntaxNodeWithHighDepth_SafeVisit_ReturnsFalse() { var method = SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Method"); var condition = SyntaxFactory.BinaryExpression(SyntaxKind.NotEqualsExpression, SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("a")), SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("b"))); var ifStatement = SyntaxFactory.IfStatement(condition, SyntaxFactory.Block()); var node = ifStatement; for (var index = 0; index < 5000; index++) { node = SyntaxFactory.IfStatement(condition, SyntaxFactory.Block().AddStatements(node)); } method = method.AddBodyStatements(node); new Walker().SafeVisit(method).Should().BeFalse(); } private class Walker : SafeCSharpSyntaxWalker { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Trackers/FieldAccessTrackerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Trackers; namespace SonarAnalyzer.CSharp.Core.Trackers.Test; [TestClass] public class FieldAccessTrackerTest { [TestMethod] public void MatchSet_CS() { var tracker = new CSharpFieldAccessTracker(); var context = CreateContext("assignConst"); tracker.MatchSet()(context).Should().BeTrue(); context = CreateContext("read"); tracker.MatchSet()(context).Should().BeFalse(); } [TestMethod] public void AssignedValueIsConstant_CS() { var tracker = new CSharpFieldAccessTracker(); var context = CreateContext("assignConst"); tracker.AssignedValueIsConstant()(context).Should().BeTrue(); context = CreateContext("assignVariable"); tracker.AssignedValueIsConstant()(context).Should().BeFalse(); context = CreateContext("invocationArg"); tracker.AssignedValueIsConstant()(context).Should().BeFalse(); } private static FieldAccessContext CreateContext(string fieldName) { const string code = """ public class Sample { private int assignConst; private int assignVariable; private int read; private int invocationArg; private void Usage() { var x = read; assignConst = 42; assignVariable = x; Method(invocationArg); } private void Method(int arg) { } } """; var testCode = new SnippetCompiler(code, false, AnalyzerLanguage.CSharp); var node = testCode.Nodes().First(x => x.ToString() == fieldName); return new FieldAccessContext(testCode.CreateAnalysisContext(node), fieldName); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Wrappers/MethodDeclarationFactoryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Wrappers; [TestClass] public class MethodDeclarationFactoryTest { [TestMethod] public void MethodDeclarationFactory_WithMethodDeclaration() { const string code = @" public class Foo { public void Bar(int y) { } }"; var snippet = new SnippetCompiler(code); var method = snippet.Tree.Single(); var wrapper = MethodDeclarationFactory.Create(method); wrapper.Body.Should().BeEquivalentTo(method.Body); wrapper.ExpressionBody.Should().BeEquivalentTo(method.ExpressionBody); wrapper.Identifier.Should().BeEquivalentTo(method.Identifier); wrapper.ParameterList.Should().BeEquivalentTo(method.ParameterList); wrapper.HasImplementation.Should().BeTrue(); wrapper.IsLocal.Should().BeFalse(); } [TestMethod] public void MethodDeclarationFactory_WithLocalFunctionDeclaration() { const string code = @" public class Foo { public void Bar(int a) { LocalFunction(); int LocalFunction() => 1; } }"; var snippet = new SnippetCompiler(code); var method = snippet.Tree.Single(); var wrapper = MethodDeclarationFactory.Create(method); wrapper.Body.Should().BeEquivalentTo(method.Body); wrapper.ExpressionBody.Should().BeEquivalentTo(method.ExpressionBody); wrapper.Identifier.Should().BeEquivalentTo(method.Identifier); wrapper.ParameterList.Should().BeEquivalentTo(method.ParameterList); wrapper.HasImplementation.Should().BeTrue(); wrapper.IsLocal.Should().BeTrue(); } [TestMethod] public void MethodDeclarationFactory_WithMethodDeclaration_NoImplementation() { const string code = @" partial class Foo { partial void Bar(int a); }"; var snippet = new SnippetCompiler(code); var method = snippet.Tree.Single(); var wrapper = MethodDeclarationFactory.Create(method); wrapper.HasImplementation.Should().BeFalse(); } [TestMethod] public void MethodDeclarationFactory_Throws_WhenNull() { Action a = () => MethodDeclarationFactory.Create(null); a.Should().Throw().WithMessage("*node*"); } [TestMethod] public void MethodDeclarationFactory_Throws_WhenNotMethodOrLocalFunction() { const string code = @" public partial class Foo { }"; var snippet = new SnippetCompiler(code); var method = snippet.Tree.Single(); Action a = () => MethodDeclarationFactory.Create(method); a.Should().Throw().WithMessage("Unexpected type: ClassDeclarationSyntax"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/Wrappers/ObjectCreationFactoryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Core.Test.Wrappers; [TestClass] public class ObjectCreationFactoryTest { public TestContext TestContext { get; set; } [TestMethod] public void ObjectCreationSyntax() { var snippet = new SnippetCompiler(""" public class A { public int X; public A(int y) { } } public class B { void Foo() { var bar = new A(1) { X = 2 }; } } """); var objectCreation = snippet.Tree.Single(); var wrapper = ObjectCreationFactory.Create(objectCreation); wrapper.Expression.Should().BeEquivalentTo(objectCreation); wrapper.Initializer.Should().BeEquivalentTo(objectCreation.Initializer); wrapper.ArgumentList.Should().BeEquivalentTo(objectCreation.ArgumentList); wrapper.InitializerExpressions.Should().BeEquivalentTo(objectCreation.Initializer.Expressions); wrapper.TypeAsString(snippet.Model).Should().Be("A"); wrapper.TypeSymbol(snippet.Model).Name.Should().Be("A"); wrapper.MethodSymbol(snippet.Model).Parameters.Length.Should().Be(1); } [TestMethod] public void ObjectCreationEmptyInitializerSyntax() { var snippet = new SnippetCompiler(""" public class A { public int X; public A(int y) { } } public class B { void Foo() { var bar = new A(1); } } """); var wrapper = ObjectCreationFactory.Create(snippet.Tree.Single()); wrapper.Initializer.Should().BeNull(); wrapper.InitializerExpressions.Should().BeNull(); } [TestMethod] public void ImplicitObjectCreationSyntax() { var snippet = new SnippetCompiler(""" public class A { public int X; public A(int y) { } } public class B { void Foo() { A bar =new(1) { X = 2 }; } } """); var objectCreation = (ImplicitObjectCreationExpressionSyntaxWrapper)snippet.Tree.GetRoot(TestContext.CancellationToken).DescendantNodes() .First(x => x.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression)); var wrapper = ObjectCreationFactory.Create(objectCreation); wrapper.Expression.Should().BeEquivalentTo(objectCreation.Node); wrapper.Initializer.Should().BeEquivalentTo(objectCreation.Initializer); wrapper.ArgumentList.Should().BeEquivalentTo(objectCreation.ArgumentList); wrapper.InitializerExpressions.Should().BeEquivalentTo(objectCreation.Initializer.Expressions); wrapper.TypeAsString(snippet.Model).Should().Be("A"); wrapper.TypeSymbol(snippet.Model).Name.Should().Be("A"); wrapper.MethodSymbol(snippet.Model).Parameters.Length.Should().Be(1); } [TestMethod] public void ImplicitObjectCreationEmptyInitializerSyntax() { var snippet = new SnippetCompiler(""" public class A { public int X; public A(int y) { } } public class B { void Foo() { A bar = new (1); } } """); var wrapper = ObjectCreationFactory.Create(snippet.Tree.Single()); wrapper.Initializer.Should().BeNull(); wrapper.InitializerExpressions.Should().BeNull(); } [TestMethod] public void GivenImplicitObjectCreationSyntaxWithMissingType_HasEmptyType() { var snippet = new SnippetCompiler(""" public class B { void Foo() { var bar = new(); } } """, true, AnalyzerLanguage.CSharp); ObjectCreationFactory.Create((ImplicitObjectCreationExpressionSyntaxWrapper)snippet.Tree.GetRoot(TestContext.CancellationToken).DescendantNodes() .First(x => x.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression))) .TypeAsString(snippet.Model) .Should().BeEmpty(); } [TestMethod] public void ImplicitObjectCreation_UserDefinedNullable_DoesNotCrash() { var snippet = new SnippetCompiler(""" public class Repro_3596_AD0001 // https://sonarsource.atlassian.net/browse/NET-3596 { private readonly Nullable field; public Repro_3596_AD0001() => field = new(this); private class Nullable(Repro_3596_AD0001 outer) { } } """); ObjectCreationFactory.Create((ImplicitObjectCreationExpressionSyntaxWrapper)snippet.Tree.GetRoot(TestContext.CancellationToken).DescendantNodes() .First(x => x.IsKind(SyntaxKindEx.ImplicitObjectCreationExpression))) .TypeAsString(snippet.Model) .Should().Be("Nullable"); } [TestMethod] public void GivenNull_ThrowsException() => FluentActions.Invoking(() => ObjectCreationFactory.Create(null)).Should().Throw(); [TestMethod] public void GivenNonConstructor_ThrowsException() => FluentActions.Invoking(() => ObjectCreationFactory.Create(new SnippetCompiler("public class A{}").Tree.Single())) .Should().Throw() .WithMessage("Unexpected type: ClassDeclarationSyntax"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Core.Test/packages.lock.json ================================================ { "version": 1, "dependencies": { "net10.0": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0", "Microsoft.TestPlatform.TestHost": "18.4.0" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "17.11.48", "contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ==" }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.11.48", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Composition": "9.0.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.4.0", "Newtonsoft.Json": "13.0.3" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Security.Cryptography.Pkcs": "8.0.1" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { "System.Collections.Immutable": "8.0.0" } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Common/StylingRuleTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using FluentAssertions; using Microsoft.CodeAnalysis.Diagnostics; using SonarAnalyzer.CSharp.Styling.Common; namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class StylingRuleTest { private readonly Type[] stylingAnalyzers = typeof(StylingAnalyzer).Assembly.GetExportedTypes().Where(x => typeof(DiagnosticAnalyzer).IsAssignableFrom(x) && !x.IsAbstract).ToArray(); [TestMethod] public void StylingRuleTestScaffolding_FindsAnalyzers() => stylingAnalyzers.Should().NotBeEmpty(); [TestMethod] public void Analyzers_InheritStylingAnalyzer() { foreach (var type in stylingAnalyzers) { type.Should().BeAssignableTo(typeof(StylingAnalyzer), "Styling rules should have a simple strucure. StylingAnalyzer should be enough."); } } [TestMethod] public void RuleIDs_AreUnique() { var ids = new Dictionary(); foreach (var type in stylingAnalyzers) { var analyzer = (DiagnosticAnalyzer)Activator.CreateInstance(type); foreach (var descriptor in analyzer.SupportedDiagnostics) { ids.TryAdd(descriptor.Id, type).Should().BeTrue("Each rule ID should be registered by a single analyzer but {0} is used by at least the two analyzers {1} and {2}.", descriptor.Id, type, ids[descriptor.Id]); } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Common/StylingVerifierBuilder.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Diagnostics; namespace SonarAnalyzer.CSharp.Styling.Common.Test; public static class StylingVerifierBuilder { // This should solve only simple cases. Do not add parametrized overloads to preserve the builder logic. public static VerifierBuilder Create() where TAnalyzer : DiagnosticAnalyzer, new() => new VerifierBuilder().WithOptions(LanguageOptions.CSharpLatest); // We don't use older version on our codebase => we don't need to waste time testing them. // This should solve only simple cases. Do not add parametrized overloads. public static void Verify() where TAnalyzer : DiagnosticAnalyzer, new() => new VerifierBuilder() .WithOptions(LanguageOptions.CSharpLatest) .AddPaths(typeof(TAnalyzer).Name + ".cs") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AllArgumentsOnSameLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AllArgumentsOnSameLineTest { [TestMethod] public void AllArgumentsOnSameLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AllParametersOnSameColumnTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AllParametersOnSameColumnTest { [TestMethod] public void AllParametersOnSameColumn() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AllParametersOnSameLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AllParametersOnSameLineTest { [TestMethod] public void AllParametersOnSameLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/ArrowLocationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class ArrowLocationTest { [TestMethod] public void ArrowLocation() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidArrangeActAssertCommentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidArrangeActAssertCommentTest { [TestMethod] public void AvoidArrangeActAssertComment() => StylingVerifierBuilder .Create() .AddPaths("AvoidArrangeActAssertComment.cs") .AddTestReference() .Verify(); [TestMethod] public void AvoidArrangeActAssertComment_NonTestProject() => StylingVerifierBuilder .Create() .AddPaths("AvoidArrangeActAssertComment.cs") .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidDeconstructionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidDeconstructionTest { [TestMethod] public void AvoidDeconstruction() => StylingVerifierBuilder.Verify(); [TestMethod] public void AvoidDeconstruction_TopLevelStatement() => StylingVerifierBuilder.Create() .AddPaths("AvoidDeconstruction.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidGetTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidGetTest { [TestMethod] public void AvoidGet() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidListForEachTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidListForEachTest { [TestMethod] public void AvoidListForEach() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidNullableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidNullableTest { [TestMethod] public void AvoidNullable() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidPrimaryConstructorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidPrimaryConstructorTest { [TestMethod] public void AvoidPrimaryConstructor() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidUnusedInterpolationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidUnusedInterpolationTest { [TestMethod] public void AvoidUnusedInterpolation() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidValueTupleTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidValueTupleTest { private readonly VerifierBuilder builder = StylingVerifierBuilder.Create().AddPaths("AvoidValueTuple.cs"); [TestMethod] public void AvoidValueTuple() => builder.Verify(); [TestMethod] public void AvoidValueTuple_TestCode() => builder.AddTestReference().VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/AvoidVarPatternTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class AvoidVarPatternTest { [TestMethod] public void AvoidVarPattern() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/FieldOrderingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class FieldOrderingTest { [TestMethod] public void FieldOrdering() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/FileScopeNamespaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.TestFramework.Common; namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class FileScopeNamespaceTest { private readonly VerifierBuilder builder = StylingVerifierBuilder.Create().WithConcurrentAnalysis(false); public TestContext TestContext { get; set; } [TestMethod] public void FileScopeNamespace() => builder.AddPaths("FileScopeNamespace.cs").Verify(); [TestMethod] public void FileScopeNamespace_TestCode() => builder.AddPaths("FileScopeNamespace.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Test)) .Verify(); [TestMethod] public void FileScopeNamespace_Compliant() => builder.AddPaths("FileScopeNamespace.Compliant.cs").VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/HelperInTypeNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class HelperInTypeNameTest { private readonly VerifierBuilder builder = StylingVerifierBuilder.Create().WithConcurrentAnalysis(false); [TestMethod] public void HelperInTypeName() => builder.AddPaths("HelperInTypeName.cs").Verify(); [TestMethod] public void HelperInTypeName_FileScopedNamespace() => builder.AddSnippet("namespace Something.Helpers; // Noncompliant").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/IndentArgumentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class IndentArgumentTest { [TestMethod] public void IndentArgument() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/IndentInvocationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class IndentInvocationTest { [TestMethod] public void IndentInvocation() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/IndentOperatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class IndentOperatorTest { [TestMethod] public void IndentOperator() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/IndentRawStringTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class IndentRawStringTest { [TestMethod] public void IndentRawString() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/IndentTernaryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class IndentTernaryTest { [TestMethod] public void IndentTernary() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/InitializerLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class InitializerLineTest { [TestMethod] public void InitializerLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/LambdaParameterNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class LambdaParameterNameTest { [TestMethod] public void LambdaParameterName() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/LocalFunctionLocationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class LocalFunctionLocationTest { [TestMethod] public void LocalFunctionLocation() => StylingVerifierBuilder.Verify(); [TestMethod] public void LocalFunctionLocation_TopLevelStatements() => StylingVerifierBuilder.Create() .AddPaths("LocalFunctionLocation.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/MemberAccessLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class MemberAccessLineTest { [TestMethod] public void MemberAccessLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/MemberVisibilityOrderingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class MemberVisibilityOrderingTest { [TestMethod] public void MemberVisibilityOrdering() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/MethodExpressionBodyLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class MethodExpressionBodyLineTest { [TestMethod] public void MethodExpressionBodyLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/MoveMethodToDedicatedExtensionClassTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Test.Rules; [TestClass] public class MoveMethodToDedicatedExtensionClassTest { [TestMethod] public void MoveMethodToDedicatedExtensionClass() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/NamespaceNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class NamespaceNameTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithOptions(LanguageOptions.CSharpLatest) .AddTestReference() // For now, we raise only in UTs .AddSnippet("namespace SonarAnalyzer.Project.Folder.Something;", "ProductionCode.cs"); // Production code to import via using statement [TestMethod] [DataRow("SonarAnalyzer.Project.Folder.Something")] [DataRow("SonarAnalyzer.Project.Folder.Something.Test")] [DataRow("SonarAnalyzer.Project.Test.Folder.Something")] public void NamespaceName_Compliant(string name) => builder .AddSnippet($"namespace {name};") // No using to remove, no issue raised here .VerifyNoIssues(); [TestMethod] [DataRow("SonarAnalyzer.Test.Project.Folder.Something")] // Anywhere the .Test is [DataRow("SonarAnalyzer.Project.Test.Folder.Something")] [DataRow("SonarAnalyzer.Project.Folder.Test.Something")] public void NamespaceName_Noncompliant(string name) => builder .AddSnippet($$$""" using SonarAnalyzer.Project.Folder.Something; namespace {{{name}}}; // Noncompliant {{Use SonarAnalyzer.Project.Folder.Something.Test namespace.}} """) .Verify(); [TestMethod] public void NamespaceName_NoncompliantLocation() => builder .AddSnippet(""" using SonarAnalyzer.Project.Folder.Something; namespace SonarAnalyzer.Project.Test.Folder.Something; // Noncompliant {{Use SonarAnalyzer.Project.Folder.Something.Test namespace.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """) .Verify(); [TestMethod] public void NamespaceName_ExpectedWithUsing() => builder .AddSnippet($""" using SonarAnalyzer.Project.Folder.Something; // This is useless now namespace SonarAnalyzer.Project.Folder.Something.Test; // Compliant """) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/NullPatternMatchingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class NullPatternMatchingTest { [TestMethod] public void NullPatternMatching() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/OperatorLocationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class OperatorLocationTest { [TestMethod] public void OperatorLocation() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/PropertyOrderingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class PropertyOrderingTest { [TestMethod] public void PropertyOrdering() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/ProtectedFieldsCaseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class ProtectedFieldsCaseTest { [TestMethod] public void ProtectedFieldsCase() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/SeparateDeclarationsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class SeparateDeclarationsTest { [TestMethod] public void SeparateDeclarations() => StylingVerifierBuilder.Create().AddPaths("SeparateDeclarations.cs").WithConcurrentAnalysis(false).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/TernaryLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class TernaryLineTest { [TestMethod] public void TernaryLine() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/TypeMemberOrderingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class TypeMemberOrderingTest { [TestMethod] public void TypeMemberOrdering() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseDifferentMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseDifferentMemberTest { private readonly VerifierBuilder builder = StylingVerifierBuilder.Create().AddReferences(NuGetMetadataReference.MicrosoftCodeAnalysisCSharp()); [TestMethod] public void UseIsExtension() => builder.AddPaths("UseDifferentMember.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseFieldTest { [TestMethod] public void UseField() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseInnermostRegistrationContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.TestFramework.MetadataReferences; namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseInnermostRegistrationContextTest { private readonly VerifierBuilder builder = StylingVerifierBuilder.Create() .AddReferences([MetadataReference.CreateFromFile(typeof(SonarAnalysisContext).Assembly.Location)]) .AddReferences(NuGetMetadataReference.MicrosoftCodeAnalysisCSharp()); [TestMethod] public void UseInnermostRegistrationContext() => builder.AddPaths("UseInnermostRegistrationContext.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseLinqExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseLinqExtensionsTest { [TestMethod] public void UseLinqExtensions() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseNullInsteadOfDefaultTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseNullInsteadOfDefaultTest { [TestMethod] public void UseNullInsteadOfDefault() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UsePositiveLogicTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UsePositiveLogicTest { [TestMethod] public void UsePositiveLogic() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseRawStringTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseRawStringTest { [TestMethod] public void UseRawString() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseRegexSafeIsMatchTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.TestFramework.MetadataReferences; namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseRegexSafeIsMatchTest { [TestMethod] public void UseRegexSafeIsMatch() => StylingVerifierBuilder.Create().AddPaths("UseRegexSafeIsMatch.cs").AddReferences(MetadataReferenceFacade.RegularExpressions).Verify(); [TestMethod] public void UseRegexSafeIsMatchWithoutRegexExtensions() => StylingVerifierBuilder.Create() .AddSnippet(""" using System.Text.RegularExpressions; class UseRegexSafeIsMatchNonCompliant { private Regex regex; void InstanceRegex(string content) { regex.IsMatch(content); // Compliant regex.Matches(content); // Compliant regex.Match(content); // Compliant } } """) .AddReferences(MetadataReferenceFacade.RegularExpressions) .VerifyNoIssues(); [TestMethod] public void UseRegexSafeIsMatchWithRegexExtensionOnlyIsMatch() => StylingVerifierBuilder.Create() .AddSnippet(""" using System.Text.RegularExpressions; using System; namespace RegexNamespace { class UseRegexSafeIsMatchNonCompliant { private Regex regex; void InstanceRegex(string content) { regex.IsMatch(content); // Noncompliant regex.Matches(content); // Compliant regex.Match(content); // Compliant } } } namespace SafeRegexNamespace { public static class RegexExtensions { public static bool SafeIsMatch(this Regex regex, string input) => throw new NotImplementedException(); } } """) .AddReferences(MetadataReferenceFacade.RegularExpressions) .Verify(); [TestMethod] public void UseRegexSafeIsMatchWithRegexExtensionOnlyMatch() => StylingVerifierBuilder.Create() .AddSnippet(""" using System.Text.RegularExpressions; using System; class UseRegexSafeIsMatchNonCompliant { private Regex regex; void InstanceRegex(string content) { regex.IsMatch(content); // Compliant regex.Matches(content); // Compliant regex.Match(content); // Noncompliant } } public static class RegexExtensions { public static bool SafeMatch(this Regex regex, string input) => throw new NotImplementedException(); } """) .AddReferences(MetadataReferenceFacade.RegularExpressions) .Verify(); [TestMethod] public void UseRegexSafeIsMatchWithRegexExtensionOnlyMatches() => StylingVerifierBuilder.Create() .AddSnippet(""" using System.Text.RegularExpressions; using System; class UseRegexSafeIsMatchNonCompliant { private Regex regex; void InstanceRegex(string content) { regex.IsMatch(content); // Compliant regex.Matches(content); // Noncompliant regex.Match(content); // Compliant } } public static class RegexExtensions { public static bool SafeMatches(this Regex regex, string input) => throw new NotImplementedException(); } """) .AddReferences(MetadataReferenceFacade.RegularExpressions) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseShortNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseShortNameTest { [TestMethod] public void UseShortName() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/Rules/UseVarTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CSharp.Styling.Rules.Test; [TestClass] public class UseVarTest { [TestMethod] public void UseVar() => StylingVerifierBuilder.Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/SonarAnalyzer.CSharp.Styling.Test.csproj ================================================  net10.0 false true true PreserveNewest ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AllArgumentsOnSameLine.cs ================================================ using System; using System.Linq; using System.Collections.Generic; [Obsolete( "Because I said so", true, DiagnosticId = "ID0001")] // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^ class IAmObsolete { } [Obsolete("Yes", true, DiagnosticId = "ID0001")] class AlsoObolete { } [Obsolete( "Yes", true, DiagnosticId = "ID0001")] class LastOneObsolete { } [Obsolete("Yes", // Noncompliant {{All arguments should be on the same line or on separate lines.}} // ^^^^^ true, DiagnosticId = "ID0001")] class ILiedObsolete { } class ArgumentsOnSameLine { static object Method(params object[] args) => null; static void TypedMethod() { } static void RefOutInMethod(int a, in int b, ref int c, out int d) { d = 0; } static void OptionalParameters(int a = 0, int b = 0, int c = 0, int d = 0) { d = 0; } public string this[params object[] args] => null; void Compliant(List list, int a) { Method(); Method(1); Method(1, 2); Method(1, 2, 3); Method( 1, 2, 3, 4); // Noncompliant {{All arguments should be on the same line or on separate lines.}} Method( 1, 2, 3); Method( 1, list .Where(x => x.Length > 4) .Select(x => x + x) .ToArray(), 42); RefOutInMethod(0, in a, ref a, out a); RefOutInMethod( 0, in a, ref a, out a); // Noncompliant RefOutInMethod( 0, in a, ref a, out a); _ = this[1, 2, 3]; _ = this[ 1, 2, 3]; TypedMethod(); TypedMethod< int, int, int, int>(); OptionalParameters(0, 0, 0, 0); OptionalParameters(d: 0, c: 0, b: 0, a:0); OptionalParameters( d: 0, c: 0, b: 0, a: 0); Method(""" We want to ignore this """, true); Method($""" We want to ignore {this} """, true); Method(""" We want to ignore this """, """ And also this """); Method($""" We want to ignore {this} """, $""" And also {this} """); Method( true, true, """ This isn't great, but also ignorable """); Method( // Noncompliant@+1, because this is evaluated as a single-line scenario true, """ This isn't great, but also ignorable """); Method(true, """ This isn't great, but also ignorable """); // We don't want to raise on the outer invocation if the inner one is too long Method("first", Method(""" Last argument is long, we start to raise here """)); } void NonCompliant(List list, int a) { Method( 1, 2, 3, 42); // Noncompliant {{All arguments should be on the same line or on separate lines.}} // ^^ Method(1, // Noncompliant 2, 3, 4, // Noncompliant 5, 6, 7); // Noncompliant Method( 1, 2, 3, 4, // Noncompliant 5, 6, 7); // Noncompliant Method(1, // Noncompliant {{All arguments should be on the same line or on separate lines.}} // ^ 2, 3); Method(1, list // Noncompliant // Noncompliant@-1 .Where(x => x.Length > 4), 42); // Noncompliant Method(1, // Noncompliant 2, 3); // Noncompliant RefOutInMethod(0, // Noncompliant in a, ref a, // Noncompliant out a); RefOutInMethod( 0, in a, ref a, // Noncompliant out a); _ = this[1, // Noncompliant 2, 3]; // Noncompliant _ = this[ 1, 2, 3]; // Noncompliant _ = this[ 1, 2, 3]; // Noncompliant TypedMethod(); // Noncompliant // Noncompliant@-1 TypedMethod< int, int, int, int>(); // Noncompliant TypedMethod< int, int, int, // Noncompliant int>(); OptionalParameters(d: 0, // Noncompliant c: 0, b: 0, a: 0); OptionalParameters( d: 0, c: 0, b: 0, // Noncompliant a: 0); Method("""Not OK""", // Noncompliant true); Method($"""Not {42} OK""", // Noncompliant true); Method($"Not {42} OK", // Noncompliant true); } void LocalFunction() { Local(1, 2, 3); Local( 1, 2, 3); Local( 1, 2, 3); // Noncompliant void Local(params object[] args) { } } void Lambdas(Func predicate, Action action) { predicate(1, 2, 3); predicate( 1, 2, 3); predicate(1, // Noncompliant 2, 3); predicate( 1, 2, 3); // Noncompliant action(1, 2, 3); action( 1, 2, 3); action(1, // Noncompliant 2, 3); action( 1, 2, 3); // Noncompliant var lambda = (int a, int b, int c) => { }; lambda(1, 2, 3); lambda( 1, 2, 3); lambda(1, // Noncompliant 2, 3); lambda( 1, 2, 3); // Noncompliant } } public class RuleRegistration { public void Initialize() { RegisterSonarWhateverAnalysisContext(c => { // something }, 0, 0); // Noncompliant // Noncompliant@-1 RegisterSonarWhateverAnalysisContext(context => { // something }, 0, 0); RegisterSonarWhateverAnalysisContext(whateverContext => { // something }, 0, 0); RegisterSonarWhateverReportingContext(c => { // something }, 0, 0); RegisterSonarWhateverReportingContextActionNotFirst("first", c => // Noncompliant { // something }, "last"); RegisterSonarSomething(c => // Noncompliant { // something }, 0, 0); RegisterSomethingAnalysisContext(c => { // something }, 0, 0); RegisterSomethingReportingContext(c => // Noncompliant { // something }, 0, 0); RegisterSonarSomethingContext(c => // Noncompliant { // something }, 0, 0); RegisterSonarWhateverReportingContext(c => { }, 0, 0); RegisterSonarWhateverReportingContext( c => { }, 0, 0); RegisterSonarWhateverReportingContext( c => { }, 0, 0); // Noncompliant@+1 UnrelatedContext((c => { }, 42), 0, 0); } protected void RegisterSonarWhateverAnalysisContext(Action action, params object[] kind) { } protected void RegisterSonarWhateverReportingContext(Action action, params object[] kind) { } protected void RegisterSonarWhateverReportingContextActionNotFirst(string first, Action action, string last) { } protected void RegisterSonarWhateverReportingContext(Func> action, params object[] kind) { } protected void RegisterSonarSomething(Action action, params object[] kind) { } protected void RegisterSomethingAnalysisContext(Action action, params object[] kind) { } protected void RegisterSomethingReportingContext(Action action, params object[] kind) { } protected void RegisterSonarSomethingContext(Action action, params object[] kind) { } protected void UnrelatedContext((Action, int) tuple, params object[] kind) { } // Well-known expected classes patterns public class SonarWhateverAnalysisContext { } public class SonarWhateverReportingContext { } public class SomethingAnalysisContext { } // Unexpected types public class SonarSomething { } public class SomethingReportingContext { } public class SonarSomethingContext { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AllParametersOnSameColumn.cs ================================================ using System; using System.Runtime.CompilerServices; abstract class MyClass { public int this[int a, int b, // Noncompliant // ^^^^^ int c] // Noncompliant { get { return a; } set { } } public int this[int a, int b, string c] { get { return a; } set { } } public int this[int a, float b, string c] { get { return a; } set { } } delegate string AnotherDelegate(string param1, int param2, float param3); delegate string AlignedDelegate(string param1, int param2, float param3); delegate string Delegate(string param1, int param2); // Noncompliant MyClass(int a, int b, // Noncompliant int c) { } // Noncompliant void SameLine(int a, int b, int c) { Delegate _ = (string param1, int param2) => ""; // Noncompliant } protected abstract void Aligned(int a, int b, int c); protected abstract void AlignedNewLine( int a, int b, int c); protected abstract void MiddleNotAligned(int a, int b, // Noncompliant int c); void NotAligned(int a, int b, // Noncompliant int c) // Noncompliant { void LocalMethod(int a, int b, // Noncompliant int c) { } // Noncompliant } protected abstract void RefParameter(ref int a, ref int b, ref int c); protected abstract void RefParameter(ref long a, ref long b, // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^ ref long c); // Noncompliant protected abstract void OutParameter(out int a, out int b, out int c); protected abstract void OutParameter(out long a, out long b, // Noncompliant out long c); // Noncompliant protected abstract void MixedParameter(int a, ref int b, out int c); protected abstract void MixedParameter2(int a, ref int b, // Noncompliant out int c); // Noncompliant protected abstract void AttributeParameter([CallerMemberName] string a = "", [CallerFilePath] string b = "", [CallerLineNumber] int c = 0); protected abstract void AttributeParameter2([CallerMemberName] string a = "", [CallerFilePath] string b = "", // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [CallerLineNumber] int c = 0); // Noncompliant protected abstract void ValidTypeParameter(); protected abstract void ValidTypeParameter(T1 a, T2 b, T3 c); protected abstract void AlignedTypeParameter(T1 a, T2 b, T3 c); protected abstract void TypeParameter(); // Noncompliant protected abstract void TypeParameter(T1 a, T2 b, // Noncompliant T3 c); // Noncompliant } unsafe class FunctionPointer { public delegate* Compliant; public delegate* Noncompliant; //Noncompliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AllParametersOnSameLine.cs ================================================ using System; public abstract class Sample { public abstract void Empty(); // Compliant public abstract void SingleParam(int param); // Compliant public abstract void DoubleParam1(int a, int b); // Compliant public abstract void DoubleParam2(int a, int b); // Compliant public abstract void DoubleParam3( int a, int b); // Compliant public abstract void CompliantSingleLineMethod(string first, string second, string third, string fourth); // Compliant public abstract void CompliantMultiLineMethod(string first, // Compliant string second, string third, string fourth); public abstract void NonCompliantMethod(string first, string second, string third, string fourth); // Noncompliant {{Parameters should be on the same line or all on separate lines.}} public abstract void NonCompliantMethodStart(string first, string second, string third, // Noncompliant // ^^^^^^^^^^^^ string fourth); public abstract void NonCompliantMethodMiddle(string first, string second, string third, // Noncompliant // ^^^^^^^^^^^^ string fourth); public abstract void NonCompliantMethodEnd(string first, string second, string third, string fourth); // Noncompliant // ^^^^^^^^^^^^^ public abstract void Foo( string first, // Compliant string second, string third, string fourth); public abstract void Foo1( string first , string second, string third); // Noncompliant public abstract void Foo2( // Compliant string first , string second, string third); public abstract void Foo3(string first , string second, string third); // Noncompliant public abstract void Foo4(string first // Compliant , string second, string third); public abstract void Foo5( // Compliant string first , string second , string third); public abstract void Foo6( // Compliant string first , string second , string third, string fourth); public abstract void Foo7( string first // compliant , string second , string third, string fourth); // DelegateDeclarationSyntax public delegate void Compliant(int a, int b, int c); public delegate void Noncompliant(int a, int b, int c); // Noncompliant // ParenthesizedLambdaExpressionSyntax Func pleC = (int x, int y) => x + y; // Compliant Func pleNC = (int x, int y , int z) => x + y + z; // Noncompliant // AnonymousMethodExpression public void Ame() { Action ame = delegate (int x, int y, int z) // Compliant { }; ame = delegate (int x, int y, int z) // Noncompliant { }; } // LocalFunctionStatementSyntax public void lfs() { void localFS(int x, int y, int z) // Compliant { }; void localFSNoncompliant(int x, int y, int z) // Noncompliant { }; } public abstract void EndOfLineInParameter(int x, string y, params // Compliant int [] otherParams); public abstract void EndOfLineEndParameters(int x, string y, params int[] otherParams // Compliant ); public abstract void CommaInParams1((int, int) x, int y, int z); // Noncompliant public abstract void CommaInParams2((int, int) x, int y, int z); // Compliant public abstract void CommaInParams3( Action a, int b, int c); // Noncompliant public abstract void CommaInParams4( Action a, int b, int c); // Compliant public abstract void CommaInParams5((int, int) x, int y, int z); // Compliant - FN public abstract void CommaInParams6( Action a, int b , int c); // Compliant - FN public abstract void CommaInParams7( Action a, int b, int c); // Noncompliant public abstract void CommaInParams8( Func a, Action b , string c = "Hello"); // Compliant } // FunctionPointerSyntax public unsafe class Example { public delegate* Compliant; // Compliant public delegate* Noncompliant; // Noncompliant } // ClassDeclarationSyntax public class cdsC(int a, int b, int c) { } // Compliant public class cdsNC(int a, int b, int c) // Noncompliant { } // StructDeclarationSyntax public struct sdsC(int a, int b, int c) { } // Compliant public struct sdsNC(int a, int b, int c) // Noncompliant { } // InterfaceDeclarationSyntax public interface ICompliant // Compliant { } public interface INoncompliant // Noncompliant { } // RecordDeclarationSyntax public record rdsC(int a, int b, int c); // Compliant public record rdsNC(int a, int b, int c); // Noncompliant // ConstructorDeclarationSyntax public abstract class Constructor { public abstract void Compliant( int a, int b, int c, int d); public abstract void Noncompliant( int a, int b, int c, int d); // Noncompliant } // IndexerDeclarationSyntax public class idsC { public int this[int a, int b, int c] // Compliant { get { return a; } set { } } } public class idsNC { public int this[int a, int b, int c] // Noncompliant { get { return a; } set { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/ArrowLocation.cs ================================================  using System; public class NoncompliantCases { public string Wrong() => "wrong"; // Noncompliant {{Place the arrow at the end of the previous line.}} // ^^ public string AloneOnLine() => // Noncompliant "wrong"; public string NoWhitespace() => "wrong"; // Noncompliant public string TooMuchWhitespace() => "wrong"; // Noncompliant public string CommentInTheWay() // There's a comment around here // And here => "wrong"; // Noncompliant public int Region() #region Why would you do this? => 0; // Noncompliant #endregion public int Directive() #if SOME_DIRECTIVE => 0; // Not analyzed #else => 1; // Noncompliant #endif public void LocalFunction() { int Local() => 1; // Noncompliant static int StaticLocal() => 1; // Noncompliant } public int SwitchExpression(object arg) { return arg switch { EventArgs => 1, // Noncompliant Exception ex => 2, // Noncompliant _ => 3 // Noncompliant }; } public int NoToken(object arg) { return arg switch { EventArgs // Error [CS1003] Syntax error, '=>' expected 42 // Compliant, while Roslyn gives us the Token with correct kind, it has IsMissing=true }; } public int Property => 42; // Noncompliant public int Accessors { get => 42; // Noncompliant set => throw new NotImplementedException(); // Noncompliant } public int InitAccessor { init => throw new NotImplementedException(); // Noncompliant } public event EventHandler Event { add => DoSomething(); // Noncompliant remove => DoSomething(); // Noncompliant } public NoncompliantCases() => throw new Exception("Constructor"); // Noncompliant public static int operator +(NoncompliantCases a, NoncompliantCases b) => 42; // Noncompliant public void DoSomething() { } } public class CompliantCases { public string CommentInTheWay() => // There's a comment around here // And here "good"; public int Region() => #region Why would you do this? 0; #endregion public int Directive() => #if SOME_DIRECTIVE 0; // Not analyzed #else 1; #endif public void LocalFunction() { int Local() => 1; static int StaticLocal() => 1; } public int SwitchExpression(object arg) { return arg switch { EventArgs => 1, Exception ex => 2, _ => 3 }; } public int Property => 42; public int Accessors { get => 42; set => throw new NotImplementedException(); } public int InitAccessor { init => throw new NotImplementedException(); } public event EventHandler Event { add => DoSomething(); remove => DoSomething(); } public CompliantCases() => throw new Exception("Constructor"); public static int operator +(CompliantCases a, CompliantCases b) => 42; public int Generic() where T : struct => 0; // Noncompliant public int WrongParenthesis( ) => 0; // Noncompliant public void DoSomething() { } } public class ReturnValueSameLine { public int CompliantSingleLine => 0; public int WithOneParameter(int a) => 0; public int WithMultilineParameters(int a, int b, int c) => 0; public int Generic() where T : struct => 0; public int WrongParenthesis( ) => 0; // Compliant, this is another problem } public class ReturnValueNextLine { public int CompliantSingleLine => 0; public int WithOneParameter(int a) => 0; public int WithMultilineParameters(int a, int b, int c) => 0; public int Generic() where T : struct => 0; public int WrongParenthesis( ) => 0; // Compliant, this is another problem } public class Lambdas { public void Method() { Method(x => x + 1); Method((x, y) => x + y); Method(() => 1 + 2); Method(x => x + 1); Method((x, y) => x + y); Method(() => 1 + 2); Method(x => { return 0; }); Method(x => x + 1); // Noncompliant // ^^ Method((x, y) => x + y); // Noncompliant Method(() => 1 + 2); // Noncompliant } public void Method(Func lambda) { } public void Method(Func lambda) { } public void Method(Func lambda) { } } public class Others { public void Comparison(int a, int b) { if (a >= b) { return; } if (a >= b) // Bad, but compliant with this rule { return; } if (a <= b) // Bad, but compliant with this rule { return; } if (a < b) // Bad, but compliant with this rule { return; } if (a < b) // Bad, but compliant with this rule { return; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidArrangeActAssertComment.cs ================================================ // Arrange // Act // Assert public class TestClass { public void TestMethod() { // Noncompliant@+1 {{Remove this Arrange, Act, Assert comment.}} // Arrange int number1 = 2; int number2 = 2; // Noncompliant@+1 {{Remove this Arrange, Act, Assert comment.}} // Act int result = number1 + number2; // Noncompliant@+1 {{Remove this Arrange, Act, Assert comment.}} // Assert _ = 4 == result; // Noncompliant@+1 //Arrange // Noncompliant@+1 // Act // Noncompliant@+1 ^9#27 /* * Assert */ // Noncompliant@+1 ^9#32 /* * // Act */ // Noncompliant@+1 // /* Assert */ // Noncompliant@+1 /// Arrange // Noncompliant@+1 /** * Arrange */ // Noncompliant@+1 // Arrange Act Assert /* * This not an Act comment or even Arrange or Assert */ // Noncompliant@+3 ^9#10 // Noncompliant@+3 ^9#6 // Noncompliant@+3 ^9#9 // Arrange // Act // Assert // Noncompliant@+1 // Act & Assert // Noncompliant@+1 /// Arrange /// Act /// Assert } // Noncompliant@+1 - FP This comment is part of the method declaration /* * Act */ public void Multiline_Comment(string input) { // Noncompliant@+1 /* Arrange */ int number1 = 2; int number2 = 2; // Noncompliant@+1 ^9#29 /* * Act */ int result = number1 + number2; // Noncompliant@+1 ^9#32 /* Assert */ _ = 4 == result; } public void DifferentCase() { // Noncompliant@+1 // arrange // Noncompliant@+1 // aRRange // Noncompliant@+1 // act // Noncompliant@+1 // acT // Noncompliant@+1 // assert // Noncompliant@+1 // assERT } private void AreEqual(object expected, object actual) { } } /// /// Assert /// public class NotATestClassInTestProject { // Noncompliant@+1 - FP This comment is part of the method declaration /** * Arrange */ public void Method() { // Noncompliant@+1 // Arrange int number1 = 2; int number2 = 2; // Noncompliant@+1 // Act int result = number1 + number2; // Noncompliant@+1 // Assert _ = 4 == result; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidDeconstruction.TopLevelStatements.cs ================================================ object value = null; _ = value is System.Exception { Message: var message }; // Noncompliant _ = value is System.Exception { Message: var used } // Noncompliant FP, we use this for coverage and don't support top-level statements && used is not null && used is not null && used is not null; ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidDeconstruction.cs ================================================ using System.Linq; public class Sample { private Person person; public void Basic(string expected) { _ = person is { Name: var _ }; // Noncompliant {{Don't use this deconstruction. It's pointless.}} // ^^^^^ _ = person is { Name: var unused }; // Noncompliant {{Don't use this deconstruction. Reference the member from the instance instead.}} // ^^^^^^^^^^ _ = person is { Name: var usedOnce } // Noncompliant && usedOnce is null; _ = person is { Name: var usedTwice } // Noncompliant && usedTwice is null && usedTwice is null; _ = person is { Name: var usedThrice } // Compliant, used 3 or more times && usedThrice is null && usedThrice is null && usedThrice is null; _ = person is { Address: (var city, var street) } && city.Name == street; // ^^^^^^^^ Noncompliant // ^^^^^^^^^^ Noncompliant@-1 // The best way is to do this, but that does not demonstrate the purpose of the rule _ = person is { Name: not null }; // What should be used is this, if we imagine that "person" was a method invocation instead and we need to rename it: _ = MayReturnPerson() is Person { } renamedPerson && renamedPerson.Name == expected; _ = person is var renamed && renamed.Name == "Lorem"; // T0034, this is not a deconstruction } public void Nested() { _ = person is { Name: var name, // Noncompliant Address: { City: { Country: var country, // Noncompliant Name: var city, // Compliant, used 3 times Code: var code // Noncompliant } } } && name is not null && country is not null && city is not null && city is not null && city is not null && code is not null; _ = person is { Address.City.Name: var propertyName } // Noncompliant && propertyName is not null; } public void Usages() { if (false || person is { Name: var name } || false) { name.ToString(); _ = string.Format("{0}", name); _ = name[0]; } if (!(person is { Name: var usedInElse })) { // Nothing to see here } else { usedInElse.ToString(); _ = string.Format("{0}", usedInElse); _ = usedInElse[0]; } } public bool PropertyNoncompliant => person is { Name: var name } // Noncompliant && name is null && name is null; public bool PropertyCompliant => person is { Name: var name } // Used 3 times && name is null && name is null && name is null; public bool AccessorNoncompliant { get => person is { Name: var name } // Noncompliant && name is null && name is null; } public bool Accessor { get => person is { Name: var name } // Used 3 times && name is null && name is null && name is null; } public string Lambda(Person[] list) { list.Where(x => x is { Name: var name } && name is not null); // Noncompliant // ^^^^^^^^ string name = null; return name + name + name; // Same name, different symbol } public object SwitchExpressionVar(object o) => MayReturnPerson(o) switch { var necessary when necessary.ToString() == "" => necessary, var _ when o is null => null, var acceptable => acceptable }; public object SwitchExpressionDiscard(object o) => MayReturnPerson(o) switch { _ when o is null => "Also needed", _ => null }; public object MayReturnPerson(object arg = null) => null; } public record Person(string Name, Address Address); public record Address(City City, string Street); public record City(string Country, string Name, string Code); ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidGet.cs ================================================  public class Sample { public string GetName() => "Lorem ipsum"; // Noncompliant {{Do not use 'Get' prefix. Just don't.}} // ^^^^^^^ public int GetValue() => 42; // Noncompliant public int Get2items() => 2; // Noncompliant, and wrong public int Getvalue() => 42; // Wrong, but compliant public int getValue() => 42; // Wrong, but compliant public int Get() => 42; // Wrong, but compliant public object FindGetGetterGet() => null; // Compliant public object Getter() => null; // Compliant public object Getaway() => null; // Compliant public object Getup() => null; // Compliant public void GetLier() { } // Wrong due to void, but compliant public bool GetIsTrue(bool value) // Noncompliant { return value == true; } public int Parameter(int GetSometing) => 42; // Wrong, but compliant public string LocalFunction() { return GetName(GetValue(GetLost())); // Ugly, but compliant. We raise on declarations int GetValue(bool arg) => 42; // Noncompliant string GetName(int count) // Noncompliant { return count.ToString(); } static bool GetLost() => true; // Noncompliant } public int GetProperty => 42; // Wrong, but compliant public int GetField = 42; // Wrong, but compliant private class Nested { public int GetValue() => 42; // Noncompliant } } public abstract class Base { public abstract int GetValue(); // Noncompliant public abstract int GetProperty { get; } // Wrong, but compliant public virtual string GetName() => null; // Noncompliant } public class Derived : Base { public override int GetValue() => 42; // Compliant, unfortunately public override int GetProperty => 42; // Compliant, unfortunately public override string GetName() => "Lorem"; // Compliant, unfortunately } public interface IGet { int GetProperty { get; } // Wrong, but compliant int GetValue(); // Noncompliant } public class ImplicitInterface : IGet { public int GetValue() => 42; // Compliant, unfortunately public int GetProperty => 42; // Wrong, but compliant } public class ExplicitInterface : IGet { int IGet.GetValue() => 42; // Compliant, unfortunately int IGet.GetProperty => 42; // Wrong, unfortunately } public class GetGettingGetter { public GetGettingGetter() { } // Wrong, but compliant } public partial class Partial { public partial int GetValue(); // Noncompliant, both are wrong } public partial class Partial { public partial int GetValue() => 42; // Noncompliant, both are wrong } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidListForEach.cs ================================================ using System.Collections.Generic; public class Sample { public void Method(List list, CustomList custom, Sample otherType) { list.ForEach(x => { }); // Noncompliant {{Use 'foreach' iteration instead of 'List.ForEach'.}} // ^^^^^^^ custom.ForEach(x => { }); // Noncompliant ForEach(); // Compliant otherType.ForEach(); // Compliant, we don't know what it does list.Add(1); list.TrueForAll(x => true); } public void Errors(List list) { list.(); // Error [CS1001] Identifier expected list.Undefined(); // Error [CS1061] 'List' does not contain a definition for 'Undefined' list.ForEach(); // Error [CS7036] There is no argument given that corresponds to the required parameter 'action' of 'List.ForEach(Action)' unknown.ForEach(); // Error [CS0103] The name 'unknown' does not exist in the current context } public void ErrorNoExpression() { // Error [CS1513] Closing curly brace expected .ForEach(); } public void ForEach() { } } public class CustomList : List { } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidNullable.cs ================================================  // This is not expected to raise when the whole project is configured with nullable context. // Noncompliant@+1 {{Do not use nullable context.}} #nullable enable //^^^^^^^^^^^^^^^^ #nullable disable // Compliant #nullable restore // Compliant ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidPrimaryConstructor.cs ================================================ using System; public class Class(string param1, int param2); // Noncompliant {{Do not use primary constructors.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ public abstract class AbstractClass(); // Noncompliant // ^^ public sealed class SealedClass(string param1, int param2); // Noncompliant public class Box(string param1, int param2); // Noncompliant public partial class PartialClass(string param1, int param2); // Noncompliant public partial class PartialClass; class BothConstructor(string param1, int param2) // Noncompliant { BothConstructor(int param2) : this("", param2) { } } public struct Struct(string param1, int param2); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ public readonly struct ReadonlyStruct(string param1, int param2); // Noncompliant public ref struct RefStruct(string param1, int param2); // Noncompliant public readonly ref struct ReadonlyRefStruct(string param1, int param2); // Noncompliant public record class RecordClass(string param1, int param2); public record struct RecordStruct(string param1, int param2); public record Record(string param1, int param2); public static class StaticClass(string param1, int param2); // Noncompliant // Error@-1 [CS0710] public interface Interface(string param1, int param2); // Error [CS9122] class NormalConstructor { delegate string Delegate(string param1, int param2); NormalConstructor(string param1, int param2) { Delegate _ = (string param1, int param2) => ""; } void Method(string param1, int param2) { string Local(string param1, int param2) => ""; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidUnusedInterpolation.cs ================================================  public class Sample { public void ToRemove() { _ = $"Unused"; // Noncompliant {{Remove unused interpolation from this string.}} // ^^ _ = $@"Unused"; // Noncompliant {{Remove unused interpolation from this string.}} // ^^^ _ = @$"Unused"; // Noncompliant {{Remove unused interpolation from this string.}} // ^^^ _ = $"""Unused"""; // Noncompliant {{Remove unused interpolation from this string.}} _ = $$"""Unused"""; // Noncompliant _ = $$$"""Unused"""; // Noncompliant _ = $""" Unused """; // Noncompliant@-2 _ = $$""" Unused """; // Noncompliant@-2 _ = $$$""" Unused """; // Noncompliant@-2 } public void ToReduce(int value) { _ = $$"""Too many {{value}}"""; // Noncompliant {{Reduce the number of $ in this string.}} // ^^^^^ _ = $$"""Too many {{value}} somewhere {{42}}"""; // Noncompliant _ = $$$"""Too many { {{{value}}}"""; // Noncompliant _ = $$$"""Too many } {{{value}}}"""; // Noncompliant _ = $$$"""Too many {}{} {{{value}}}"""; // Noncompliant _ = $$$"""Too many {{{value}}} somewhere {{{42}}} {}{}"""; // Noncompliant _ = $$$$"""Way too many } {{{{value}}}}"""; // Noncompliant // ^^^^^^^ _ = $$""" Too many {{value}} """; // Noncompliant@-2 _ = $$$$""" Way too many } {{{{value}}}} """; // Noncompliant@-2 _ = $$"""{{value}}"""; // Noncompliant, no text _ = "Leave me alone"; _ = $"Needed {value}"; _ = $"""Needed {value}"""; _ = $$"""Needed { {{value}}"""; _ = $$"""Needed } {{value}}"""; _ = $$"""Needed {}{} {{value}}"""; _ = $$"""Needed {{value}} somewhere {{42}} {}{}"""; _ = $$$"""Needed {{ {{{value}}}"""; _ = $$$"""Needed }} {{{value}}}"""; _ = $$$"""Needed {{}}{{}} {{{value}}}"""; _ = $$$"""Needed {{{value}}} somewhere {{{42}}} {{}}{{}}"""; _ = $""" Needed {value} """; _ = $$""" Needed } {{value}} """; _ = $"""{value}"""; // No text _ = $"{value}"; // No text } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidValueTuple.cs ================================================ using System; public class Sample { public (string, int) ReturnedType() // Noncompliant {{Do not use ValueTuple in the production code due to missing System.ValueTuple.dll.}} // ^^^^^^^^^^^^^ { return ("Lorem", 42); // Noncompliant // ^^^^^^^^^^^^^ } public (string Name, int Count) NamedReturnedType() => // Noncompliant ("Lorem", 42); // Noncompliant public void Use() { (string, int) noName; // Noncompliant (string Name, int Count) withName; // Noncompliant noName = ("Lorem", 42); // Noncompliant withName = ("Lorem", 42); // Noncompliant } public System.Tuple ExplicitType() // Complaint { return new Tuple("Lorem", 42); } public int SwitchExpression(int a, int b) { var result = (a, b) switch // Noncompliant: a ValueTuple is created for the SwitchExpressionException that is thrown for uncovered cases { (0, 0) => 0, (1, 1) => 2, _ => 42 }; return result; } public int SwitchStatement(int a, int b) { switch (a, b) // Noncompliant: unlike switch expressions, switch statements use ValueTuple under the hood { case (0, 0): return 0; case (1, 1): return 1; default: return 42; }; } public int PositionalPatternMatching(Sample s) => s switch { (0, 0) => 0, // Compliant, Sample.Deconstruct is called _ => 42 }; void Deconstruct(out int i, out int j) { i = 42; j = 12; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/AvoidVarPattern.cs ================================================ using System.Linq; public class Sample { private string name; private int min; public void Method() { if (Invocation() is var value // Noncompliant {{Avoid embedding this var pattern. Declare it in var statement instead.}} // ^^^^^^^^^ && value.Name == name && value.Count > min) { } if (Invocation() is var single) // Noncompliant { } if (Invocation() is { } prerequisite && Invocation() is var afterPrerequisite // Compliant, can not be extracted without adding more nesting && afterPrerequisite.Name == name && afterPrerequisite.Count > min) { } if (true && Invocation() is var middle && middle.Name == name) { } if (true && Invocation() is var last) // Noncompliant { } if (Invocation() is var first // Noncompliant && Invocation() is var second // Bad, but we don't raise. Once the previous one is fixed, this will light up && first.Name == name && first.Count > min && second.Name == name && second.Count > min) { } else if (Invocation() is var unavoidable && unavoidable.Name == name && unavoidable.Count > min) { } while (Invocation() is var inWhile // Noncompliant && inWhile.Name == name && inWhile.Count > min) { } for (var i = 0; Invocation() is var inFor && inFor.Name == name && inFor.Count > min; i++) // Noncompliant // ^^^^^^^^^ { } var compliant = Invocation(); // Compliant if (compliant.Name == name && compliant.Count > min) { } if ((Invocation() is var parenthesized)) // We don't raise, some more tricky logic is going on around { } _ = Invocation() is { Name: var declaredName }; // T0035 } public bool Arrow() => Invocation() is var value // Compliant && value.Name == name && value.Count > min && Invocation() is var last; // Useless, but compliant public void Lambda(Pair[] list) { list.Where(x => x.Inc() is var value // Compliant && value.Name == name && value.Count > min); } private static Pair Invocation() => null; } public record Pair(string Name, int Count) { public Pair Inc() => new(Name, Count + 1); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/FieldOrdering.cs ================================================ public class AllValid { public static int s1; public static int s2 = 42; public static int s3, s4 = 42; public int i1; public int i2 = 42; public int i3, i4 = 42; internal static int internalS; internal int internalI; protected static int protectedS; // Compliant protected int protectedI; private static int privateS; // Compliant private int privateI; public const string C = "C"; // Compliant, while this is FieldDeclarationSyntax, it's not in the scope of this rule. } public class SomeValid { public static int s1; public int i1; public int i2; public static int s2, s3; // Noncompliant {{Move this static field above the public instance ones.}} // ^^^^^^^^^^ public int i3; // Compliant, there're no other private/protected/internal instance fields above internal static int internalS; protected static int protectedS; protected internal static int protectedInternalS; protected private static int protectedPrivateS; private static int privateS; } public class ProtectedInternal { protected internal int protectedI; protected static int protectedS; // Noncompliant {{Move this static field above the protected instance ones.}} } public class ProtectedPrivate { protected private int protectedI; protected static int protectedS; // Noncompliant {{Move this static field above the protected instance ones.}} } public class AllWrong { public int i1; public int i2 = 42; public int i3, i4 = 42; protected int protectedI; private int privateI; internal int internalI; public static int s1; // Noncompliant {{Move this static field above the public instance ones.}} public static int s2 = 42; // Noncompliant public static int s3, s4 = 42; // Noncompliant internal static int internalS; // Noncompliant {{Move this static field above the internal instance ones.}} private static int privateS; // Noncompliant {{Move this static field above the private instance ones.}} protected static int protectedS; // Noncompliant {{Move this static field above the protected instance ones.}} } public record R { private int i; private static int s; // Noncompliant } public record struct RS { private int i; private static int s; // Noncompliant } public struct S { private int i; private static int s; // Noncompliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/FileScopeNamespace.Compliant.cs ================================================ namespace Outer; // Compliant public class NotRelevant { } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/FileScopeNamespace.cs ================================================  namespace Outer // Noncompliant {{Use file-scoped namespace.}} // ^^^^^ { public class NotRelevant { } namespace Inner // Noncompliant, nested namespace should not be used in general { } } namespace // Error [CS1001] Identifier expected { // Noncompliant^1#0 } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/HelperInTypeName.cs ================================================ using SomeName.WithHelper; // Error [CS0246] using AliasHelper = SatchelPerks; // Noncompliant {{Do not use 'Helper' in type names.}} // ^^^^^^^^^^^ class SonarHelper // Noncompliant {{Do not use 'Helper' in type names.}} // ^^^^^^^^^^^ { public delegate int HelperDelegate(int x, int y); void HelperMethod() { } } class HelperSonarContext // Noncompliant { } class SonarHelperContext // Noncompliant { } interface ISonarHelper // Noncompliant { void MethodHelper(); } struct ValueTypeHelper // Noncompliant { public bool HelperEnabled { get; set; } } enum EnumHelper // Noncompliant { } record RecordHelper // Noncompliant { } record struct RecordStructHelper // Noncompliant { } class SatchelPerks { } interface IMonsterWhelpEradicator { } namespace Something.Helper { } // Noncompliant {{Do not use 'Helper' in type names.}} namespace Something.Helpers { } // Noncompliant namespace Something.SyntaxHelper { } // Noncompliant namespace Something.SyntaxHelpers { } // Noncompliant namespace Something.Helpers.Extensions { } // Noncompliant namespace Helper.Helping.Helpers.Work { } // Noncompliant, just once namespace { } // Error [CS1001] Identifier expected namespace; // Error [CS1001] Identifier expected // Error@-1 [CS8956] File-scoped namespace must precede all other members in a file. ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/IndentArgument.cs ================================================ using System; using System.Linq; public class Sample { public void Method() { Invocation( // 0 "Argument", // Noncompliant {{Indent this argument at line position 13.}} "Another"); // Noncompliant {{Indent this argument at line position 13.}} Invocation( // 1 "Argument", // Noncompliant {{Indent this argument at line position 13.}} "Another"); // Noncompliant {{Indent this argument at line position 13.}} Invocation( // 2 "Argument", // Noncompliant {{Indent this argument at line position 13.}} "Another"); // Noncompliant {{Indent this argument at line position 13.}} Invocation( // 3 "Argument", // Noncompliant {{Indent this argument at line position 13.}} "Another"); // Noncompliant {{Indent this argument at line position 13.}} Invocation( // 4 "Argument", "Another"); Invocation( // 5 "Argument", // Noncompliant {{Indent this argument at line position 13.}} // ^^^^^^^^^^ "Another"); // Noncompliant {{Indent this argument at line position 13.}} // ^^^^^^^^^ Invocation("Argument"); Invocation("Argument", "Another"); Invocation( "Argument", "Another"); Invocation("Argument", // T0028 "Another"); Invocation( "Argument", "Another"); // T0028 new Builder() .And.Build([ "Some", "Argument", "Correct"]); // Collection expression in fluent chain - compliant cases new Builder() .Build([ "Aligned", "Correctly"]); new Builder() .And .Build([ "Multi", "Line", "Chain"]); new Builder().And .Build([ "Mixed", "Style"]); // Collection expression in fluent chain - noncompliant cases new Builder() .Build([ "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"]);// Noncompliant {{Indent this argument at line position 17.}} new Builder() .And.Build([ "Way too close", // Noncompliant {{Indent this argument at line position 17.}} "Way too far"]); // Noncompliant {{Indent this argument at line position 17.}} new Builder() .And .Build([ "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"]);// Noncompliant {{Indent this argument at line position 17.}} // Multiple chained calls with collection expressions - compliant new Builder() .And.Build([ "First", "Chain"]) .And.Build([ "Second", "Chain"]); new Builder() .Build([ "One"]) .Build([ "Two"]) .Build([ "Three"]); // Multiple chained calls with collection expressions - noncompliant new Builder() .And.Build([ "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"]) // Noncompliant {{Indent this argument at line position 17.}} .And.Build([ "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"]);// Noncompliant {{Indent this argument at line position 17.}} } public bool ArrowNoncompliant() => Invocation( "Arg", // Noncompliant "Another"); // Noncompliant public bool ArrowCompliant() => Invocation( "Arg", "Another", "One more"); public bool ArrowNotInScope() => Invocation("Arg", "Another"); public bool ReturnNoncompliant() { return Invocation( "Arg", // Noncompliant "Another"); // Noncompliant } public bool ReturnCompliant() { return Invocation( "Arg", "Another"); } public Builder ReturnNested() { return new Builder() .Build( "Arg", "Too far"); // Noncompliant } public Builder FluentChainArguments() { // Arguments in fluent chain where ( is not on its own line return new Builder() .And.Build( "Compliant", "Arguments"); } public Builder FluentChainArgumentsNoncompliant() { return new Builder() .And.Build( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"); // Noncompliant {{Indent this argument at line position 17.}} } public Builder FluentChainArgumentsMultiple() { // Multiple chained calls with arguments return new Builder() .And.Build( "First", "Chain") .And.Build( "Second", "Chain"); } public void LongFluentChainWithCollectionExpression() { new Builder().Build() .And.AllSatisfy(x => Invocation(x)) .And.AllSatisfy(x => Invocation(x)) .And.Contain([ "First", "Second", "Third"]); new Builder().Build() .And.AllSatisfy(x => Invocation(x)) .And.AllSatisfy(x => Invocation(x)) .And.Contain([ "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far"]);// Noncompliant {{Indent this argument at line position 17.}} new Builder().Should().ContainKey("x").And.ContainSingle().Which.Value.Should().BeOfType() .Which.Members.Select(x => x).Should().BeEquivalentTo([ ("First", true), ("Second", true)]); new Builder().Should().ContainKey("x").And.ContainSingle().Which.Value.Should().BeOfType() .Which.Members.Select(x => x).Should().BeEquivalentTo([ ("Too close", true), // Noncompliant {{Indent this argument at line position 17.}} ("Too far", true)]);// Noncompliant {{Indent this argument at line position 17.}} new Builder().Build() .And.AllSatisfy(x => Invocation(x)) .And.AllSatisfy(x => Invocation(x)) .And.Contain( [ "First", "Second", "Third"]); new Builder().Build() .And.AllSatisfy(x => Invocation(x)) .And.AllSatisfy(x => Invocation(x)) .And.Contain( [ "Too close", // Noncompliant "Just right", "Too far"]); // Noncompliant } public void FluentChainWithConstructor() { // Constructor arguments in fluent chain - noncompliant new Builder() .Build(new Builder( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Just right", "Too far"));// Noncompliant {{Indent this argument at line position 17.}} new Builder() .And.Build(new Builder( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Just right", "Too far"));// Noncompliant {{Indent this argument at line position 17.}} new Builder() .Build( new Builder( "Too close", // Noncompliant {{Indent this argument at line position 21.}} "Just right", "Too far"));// Noncompliant {{Indent this argument at line position 21.}} // Constructor on same line as fluent chain method - compliant new Builder() .And.Build(new Builder( "Compliant", "Args")); // Constructor on same line as fluent chain method - noncompliant new Builder() .And.Build(new Builder( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far")); // Noncompliant {{Indent this argument at line position 17.}} } public void TargetTypedNewExpression() { PrepareProjectZip( new( "Project.json", "Content")); PrepareProjectZip( new( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Too far")); // Noncompliant {{Indent this argument at line position 17.}} PrepareProjectZip(new( "Project.json", "Content")); PrepareProjectZip(new( "Too close", // Noncompliant {{Indent this argument at line position 13.}} "Too far")); // Noncompliant {{Indent this argument at line position 13.}} } public void Invocations() { Invocation("First", "Too close", // Noncompliant {{Indent this argument at line position 13.}} "Good", "Too far"); // Noncompliant Invocation( "First", "Second"); global::Sample.Invocation("First", "Too close", // Noncompliant {{Indent this argument at line position 13.}} "Good", "Too far"); // Noncompliant global::Sample.Invocation( "First", "Second"); Invocation(Invocation(Invocation("First", // This is bad already for other reasons "Too close", // Noncompliant {{Indent this argument at line position 13.}} "Good", "Too far"))); // Noncompliant Invocation(Invocation(Invocation( "First", "Second"))); Invocation( Invocation( "Nested too close", // Noncompliant {{Indent this argument at line position 17.}} "Nested too far"), // Noncompliant "Some other argument"); Invocation( Invocation( "First", "Second"), "Some other argument"); global::Sample.Invocation( // Longer invocation name does not matter "First", "Second"); Invocation(Invocation(Invocation( // This is bad already for other reasons "First", "Second"))); // Simple lambda RegisterNodeAction(c => { // Noncompliant }, 42); RegisterNodeAction(c => { }, 42); RegisterNodeAction(c => { }, 42); // Parenthesized lambda RegisterNodeAction((c) => { // Noncompliant }, 42); RegisterNodeAction((c) => { }, 42); RegisterNodeAction((c) => { }, 42); // Expression-body lambda AcceptFunc(c => c, // Noncompliant 42); AcceptFunc(c => c, 42); AcceptFunc(c => c, 42); } public void ObjectInitializer() { _ = new WithProperty { Value = Invocation( "Good", "Too close", // Noncompliant "Too far") // Noncompliant }; } public void CollectionExpression() { _ = string.Join( " ", [ "Too Close", // Noncompliant {{Indent this argument at line position 17.}} "Good", "Too far" // Noncompliant {{Indent this argument at line position 17.}} ]); _ = string.Join(" ", [ "Too Close", // Noncompliant {{Indent this argument at line position 13.}} "Good", "Too far" // Noncompliant {{Indent this argument at line position 13.}} ]); } public void InsideTernary() { Builder x = new Builder() is { } builder ? builder.Build() : new( "Too close", // Noncompliant "Just right", "Too far"); // Noncompliant Builder y = new Builder() is { } other ? other.Build() : new Builder().And.Build( "Too close", // Noncompliant "Just right", "Too far"); // Noncompliant } public void Lambdas(int[] list, int[] longer) { list.Where(x => Invocation( "Too close", // Noncompliant {{Indent this argument at line position 29.}} "Too close", // Noncompliant "Too close", // Noncompliant "Too close", // Noncompliant "Good", "Too far")); // Noncompliant list.Where(x => Invocation( // Simple lambda "Too close", // Noncompliant {{Indent this argument at line position 29.}} "Good")); list.Where((x) => Invocation( // Parenthesized lambda "Too close", // Noncompliant {{Indent this argument at line position 29.}} "Good")); longer.Where(x => Invocation( // Simple lambda, longer name "Good")); // Compliant, as long as it's after the invocation and aligned to the grid of 4 longer.Where((x) => Invocation( // Parenthesized lambda, longer name "Good")); // Compliant, as long as it's after the invocation and aligned to the grid of 4 list.Where(x => { // Noncompliant return Invocation( "Good"); }); } public void If() { if (Invocation( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Good", "Too far")) // Noncompliant { } if (Invocation( "Good")) { } else if (Invocation( "Good", "Too far")) // Noncompliant { } } public void While() { while (Invocation( "Too close", // Noncompliant {{Indent this argument at line position 17.}} "Good", "Too far")) // Noncompliant { } while (Invocation( "Good")) { } } public void For() { for (var i = 0; Invocation( "Too close", // Noncompliant {{Indent this argument at line position 29.}} "Good", "Too far"); i++) // Noncompliant { } for (int i = 0; Invocation( "Good"); i++) { } } public void ConditionalAccess(Builder builder) { builder? .Build( "Too close", // Noncompliant "Good"); builder? .Build( "Too close", // Noncompliant "Good")? .Build( "Too close", // Noncompliant "Good"); builder?.Build()?.Build( "Too close", // Noncompliant "Good"); } public void Global() { global::Sample.Invocation( "Too close", // Noncompliant "Good", "Too far"); // Noncompliant global::Sample.Invocation( "First", "Second"); } public void SwitchExpressions(object arg) { _ = arg switch { Exception someLongerName => Invocation( // This is already bad "Good", "Too far"), // Noncompliant _ => Invocation( "Good", "Too far"), // Noncompliant }; } public void OtherSyntaxes() { _ = new int[ 1, 2, // FN, we don't care. Nobody should specify array ranks on multiple lines 3 ]; int[] array = [ 1, 2, // Noncompliant 3 // Noncompliant ]; } public static bool Invocation(params object[] args) => true; public static string ReturnString(string arg) => arg; public void RegisterNodeAction(Action action, params object[] syntaxKinds) { } public void AcceptFunc(Func func, params object[] args) { } public static Builder PrepareProjectZip(Builder arg) => new Builder(); [Obsolete(ReturnString("For coverage"))] // Error [CS0182] An attribute argument must be a constant expression public void Coverage() { } } public class Builder { public Builder(params object[] args) { } public Builder And => this; public Builder Which => this; public Builder Members => this; public Builder Value => this; public Builder Build(params object[] args) => this; public Builder Should() => this; public Builder AllSatisfy(Func predicate) => this; public Builder Contain(params object[] args) => this; public Builder ContainKey(object key) => this; public Builder ContainSingle() => this; public Builder BeEquivalentTo(params object[] args) => this; public Builder BeOfType() => this; public Builder Select(Func selector) => this; } public class WithProperty { public object Value { get; set; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/IndentInvocation.cs ================================================ using System; using System.Linq; public class Sample { private Builder builder; public void Method() { builder // 0 .Variable // Noncompliant {{Indent this member access at line position 13.}} .Property // Noncompliant {{Indent this member access at line position 13.}} .Build(); // Noncompliant {{Indent this member access at line position 13.}} builder // 1 .Variable // Noncompliant .Property // Noncompliant .Build(); // Noncompliant builder // 2 .Variable // Noncompliant .Property // Noncompliant .Build(); // Noncompliant builder // 3 .Variable // Noncompliant .Property // Noncompliant .Build(); // Noncompliant builder // 4 .Variable .Property .Build(); builder // 5 .Variable // Noncompliant // ^^^^^^^^^ .Property // Noncompliant // ^^^^^^^^^ .Indexed[424242] // Noncompliant // ^^^^^^^^ .Build("Same with arguments"); // Noncompliant // ^^^^^^ builder .Build() .Build() // Noncompliant, too far .Build() .Build() // Noncompliant, too close .Build(); builder.Build(); builder.Build().Build().Build(); builder.Build() .Build().Build(); // Compliant, this is T0027 } public Builder ArrowNoncompliant() => builder .Build(); // Noncompliant public Builder ArrowCompliant() => builder .Build() .Build() .Build(); public Builder ArrowNotInScope() => builder.Build(); public object ArrowInInvocationArgument() => Something(builder .Build() // Noncompliant .Build()); public object ArrowInConstructorArgument() => new Exception(builder .Build() // Noncompliant .ToString()); public Builder ReturnNoncompliant() { return builder .Build() // Noncompliant, too little .Build(); // Noncompliant, too much } public Builder ReturnCompliant() { return builder .Build() .Build() .Build() .Build("Are we sure it's really built?"); } public bool ReturnNestedExpressions() { return true && true && builder .Build() .IsTrue(); // Noncompliant } public bool ArrowNestedExpressions() => true && true && builder .Build() .IsTrue(); // Noncompliant public object ArrowNestedExpressionTernary() => true && true ? builder .Build() .Build() // Noncompliant : null; public void Invocations() { Something(builder .Build() // Noncompliant {{Indent this member access at line position 13.}} .Build(), // Noncompliant, too far "Some other argument"); Something(builder // This is bad already .Build() .Build(), "Some other argument"); global::Sample.Something(builder // This is bad already .Build() // Noncompliant {{Indent this member access at line position 13.}} .Build(), // Noncompliant "Some other argument"); global::Sample.Something(builder // This is bad already .Build() .Build(), "Some other argument"); Something(Something(Something(builder // This is bad already .Build() // Noncompliant {{Indent this member access at line position 13.}} .Build(), // Noncompliant "Some other argument"))); Something(Something(Something(builder // This is bad already .Build() .Build(), "Some other argument"))); Something( builder .Build() // Noncompliant {{Indent this member access at line position 17.}} .Build(), // Noncompliant "Some other argument"); Something( builder .Build() .Build(), "Some other argument"); global::Sample.Something( // Longer invocation name does not matter builder .Build() .Build(), "Some other argument"); Something(Something(Something( // This is bad already for other reasons builder .Build() .Build(), "Some other argument"))); } public void ObjectInitializer() { _ = new Builder { Variable = builder.Build() .Build() .Build() // Noncompliant .Build() // Noncompliant }; } public void Lambdas(int[] list, int[] longer) { list.Where(x => builder .Build() // Noncompliant, too close .IsTrue()); // Noncompliant, too close list.Where(x => builder .IsTrue()); // Noncompliant, too close list.Where(x => builder .IsTrue()); // Noncompliant, too far list.Where(x => builder // Simple lambda .IsTrue()); list.Where((x) => builder // Parenthesized lambda .IsTrue()); longer.Where(x => builder // Simple lambda, longer name .Build() // Compliant, as long as it's after the builder and aligned to the grid of 4 .IsTrue()); longer.Where((x) => builder // Parenthesized lambda, longer name .Build() // Compliant, as long as it's after the builder and aligned to the grid of 4 .IsTrue()); list.Where(x => { return builder .IsTrue(); }); } public void If() { if (builder .Build() // Noncompliant {{Indent this member access at line position 17.}} .IsTrue()) // Noncompliant { } if (builder .Build() .IsTrue()) { } else if (builder .Build() .IsTrue()) // Noncompliant { } } public void While() { while (builder .Build() // Noncompliant {{Indent this member access at line position 17.}} .IsTrue()) // Noncompliant { } while (builder .Build() .IsTrue()) { } } public void For() { for (var i = 0; builder .Build() // Noncompliant {{Indent this member access at line position 29.}} .IsTrue(); i++) // Noncompliant { } for (int i = 0; builder .Build() .IsTrue(); i++) { } } public void ConditionalAccess() { builder? .Build()? // Noncompliant // ^^^^^^ .Build(); // Noncompliant builder? .Build()? .Build(); builder ?.Build() // Another problem that is out of scope ?.Build(); builder?.Build()?.Build(); } public void Global() { global::Builder .StaticMethod(); // Noncompliant global::Builder .StaticMethod(); } public void NestedClasses() { Builder.NestedOnce .StaticMethod(); // Noncompliant Builder.NestedOnce .StaticMethod(); Builder.NestedOnce.NestedTwice .StaticMethod(); // Noncompliant Builder.NestedOnce.NestedTwice .StaticMethod(); } public void SwitchExpressions(object arg) { _ = arg switch { ArgumentException someLongerName => builder .Build() .Build(), // Noncompliant Exception someLongerName => builder .Build() .Build(), // Noncompliant _ => builder .Build() .Build() // Noncompliant }; } public static bool Something(object arg, object another = null) => true; } public class Builder { public Builder Variable; public Builder Property => null; public Builder[] Indexed; public Builder Build() => this; public Builder Build(object arg) => this; public static Builder StaticMethod() => null; public bool IsTrue() => true; public class NestedOnce { public static Builder StaticMethod() => null; public class NestedTwice { public static Builder StaticMethod() => null; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/IndentOperator.cs ================================================ using System; using System.Linq; public class Sample { private bool condition; private object first, middle, last; public void Method() { bool longVariableName; longVariableName = condition // 0 && true; // Noncompliant {{Indent this operator at line position 13.}} longVariableName = condition // 1 && true; // Noncompliant longVariableName = condition // 2 && true; // Noncompliant longVariableName = condition // 3 && true; // Noncompliant longVariableName = condition // 4 && true; longVariableName = condition // 5 && true; // Noncompliant // ^^^^^^^ longVariableName = condition // One of the branches is too far && true; longVariableName = condition // One of the branches is too far && true; // Noncompliant longVariableName = condition && true; } public bool ArrowNoncompliant() => condition && true // Noncompliant {{Indent this operator at line position 9.}} && true; // Noncompliant public bool ArrowCompliant() => condition && true && true && true; public object ArrowNullCoalescing() => first ?? middle ?? middle ?? last; // Noncompliant public bool ArrowNotInScope() => condition && true; public object ArrowInInvocationArgument() => Something(condition && true && true && true); // Noncompliant public object ArrowInInvocationArgumentStartsOnNextLIne() => Something( condition && true && true && true); // Noncompliant public object ArrowInConstructorArgument() => new Nullable(condition && true // Noncompliant && true); public bool ReturnNoncompliant() { return condition && true; // Noncompliant, too much } public bool ReturnCompliant() { return condition && true && true; } public bool ReturnTernary() { return condition && true ? true : false; } public void Invocations() { Something(condition // This is bad already && true, // Noncompliant {{Indent this operator at line position 13.}} "Some other argument"); Something(condition // This is bad already && true, // Noncompliant "Some other argument"); global::Sample.Something(condition // This is bad already && true // Noncompliant {{Indent this operator at line position 13.}} && true, // Noncompliant "Some other argument"); global::Sample.Something(condition // This is bad already && true, // Noncompliant "Some other argument"); Something(Something(Something(condition // This is bad already && true, // Noncompliant {{Indent this operator at line position 13.}} "Some other argument"))); Something(Something(Something(condition // This is bad already && true, // Noncompliant "Some other argument"))); Something( condition && true, // Noncompliant {{Indent this operator at line position 13.}} "Some other argument"); Something( condition && true, "Some other argument"); global::Sample.Something( // Longer invocation name does not matter condition && true, "Some other argument"); Something(Something(Something( // This is bad already for other reasons condition && true && true, "Some other argument"))); } public void ObjectInitializer() { _ = new WithProperty { Value = condition && true && true // Noncompliant && true // Noncompliant }; } public void CollectionExpression() { _ = string.Join(" ", [ "" + "Too close" // Noncompliant + "Good" + "Too far" // Noncompliant ]); _ = string.Join(" ", [ "" + "Too close" // Noncompliant + "Good" + "Too far" // Noncompliant ]); } public void Lambdas(int[] list, int[] longer) { list.Where(x => condition && true // Noncompliant, too close && true); // Noncompliant, too close list.Where(x => condition && true // Noncompliant, too far && true); // Noncompliant, too far list.Where(x => condition // Simple lambda && true && true && true); list.Where((x) => condition // Parenthesized lambda && true && true); longer.Where(x => condition // Simple lambda, longer name && true); // Compliant, as long as it's after the condition and aligned to the grid of 4 longer.Where((x) => condition // Parenthesized lambda, longer name && true); // Compliant, as long as it's after the condition and aligned to the grid of 4 list.Select(xxxx => first ?? middle ?? middle ?? last); // Noncompliant list.Where(x => { return condition && true; }); } public void If() { if (condition && true // Noncompliant {{Indent this operator at line position 13.}} && true) // Noncompliant { } if (condition && true && true) { } else if (condition && true && true) // Noncompliant { } } public void While() { while (condition && true // Noncompliant {{Indent this operator at line position 13.}} && true) // Noncompliant { } while (condition && true && true) { } } public void For() { for (var i = 0; condition && true // Noncompliant {{Indent this operator at line position 25.}} && true; i++) // Noncompliant { } for (int i = 0; condition && true; i++) { } for (int i = 0; condition && true && true; i++) { } for (int ii = 0; condition && true && true; ii++) { } for (int iii = 0; condition && true && true; iii++) { } for (int iiii = 0; condition && true && true; iiii++) { } for (int iiiii = 0; condition && true && true; iiiii++) { } } public void Nested() { _ = true || true || (false && true) || (false && true) // Noncompliant {{Indent this operator at line position 17.}} || ((((false)) && ((true)))) // Noncompliant {{Indent this operator at line position 17.}} || (((bool)(false)) && ((bool)(true))) // Noncompliant {{Indent this operator at line position 17.}} || (false && (true // Noncompliant {{Indent this operator at line position 17.}} || false)) // Noncompliant {{Indent this operator at line position 17.}} This should be 21, but because the previous line is misplaced, it self-aligns with it to 17. || (false && (true || false)); // Noncompliant {{Indent this operator at line position 21.}} Once the previous line is fixed, this will be 21 as expected. _ = true || true || (false && true) || (false && true) || ((((false)) && ((true)))) || (((bool)(false)) && ((bool)(true))) || (false && (true || false)); _ = true || (false && (condition is true or false)); // Noncompliant {{Indent this operator at line position 21.}} _ = true || (false && (condition is true or false)); _ = true || (false && (condition is true)); // Noncompliant {{Indent this operator at line position 21.}} _ = true || (false && (condition is true)); } public void ConditionalAccess(Builder builder) { builder? .Build(true || false || false); // Noncompliant builder? .Build(true || false || false)? // Noncompliant .Build(true || false || false); // Noncompliant builder?.Build()?.Build(true || false || false); // Noncompliant } public void SwitchExpressions(object arg) { _ = arg switch { Exception someLongerName => condition && true && true, // Noncompliant _ => condition && true && true // Noncompliant }; } public object Property => first ?? middle ?? middle ?? last; // Noncompliant public object AccessorCoalesce { get => first ?? middle ?? middle ?? last; // Noncompliant } public bool Accessor { get => condition && true && true; // Noncompliant } public static bool Something(bool arg, object another = null) => true; } class Operators { object first, middle, last; void LogicalOperator(bool a, bool b) { _ = a && b; // Noncompliant {{Indent this operator at line position 13.}} // ^^^^ _ = a || b; // Noncompliant _ = a & b; // Noncompliant _ = a | b; // Noncompliant _ = a ^ b; // Noncompliant } void Ternary() { _ = true ? 1 // Compliant, handled by T0025 : 2; _ = true && true // Noncompliant || false // Noncompliant ? 1 : 2; _ = true && true || false ? 1 : 2; _ = true ? true && false // Noncompliant && false // Noncompliant : false || true // Noncompliant || true; // Noncompliant _ = true ? true && false : false || true; _ = true ? first ?? middle ?? middle ?? last // Noncompliant : null; } void Coelesce(object o1, object o2, object o3) { _ = o1 ?? o2 ?? o2 ?? o2 ?? o3; // Noncompliant } void AsIs(object o) { _ = o as string; // Noncompliant // ^^^^^^^^^ _ = o is string; // Noncompliant } void Pattern(object o1, object o2) { _ = o1 is int or float; // Noncompliant // ^^^^^^^^ _ = o1 is string { Length: > 5 } and { Length: < 10 }; // Noncompliant {{Indent this operator at line position 13.}} } void Arithmetic(int a, int b, string str) { _ = a + b; // Noncompliant _ = a - b; // Noncompliant _ = a * b; // Noncompliant _ = a / b; // Noncompliant _ = a % b; // Noncompliant _ = str + "asdf"; // Noncompliant } void Comparison(int a, int b) { _ = a == b; // Noncompliant _ = a != b; // Noncompliant _ = a < b; // Noncompliant _ = a > b; // Noncompliant _ = a <= b; // Noncompliant _ = a >= b; // Noncompliant } void Shift(int a, int b) { _ = a << b; // Noncompliant _ = a >> b; // Noncompliant _ = a >>> b; // Noncompliant } void Range() { _ = 1 ..2; // Noncompliant // ^^^ } } public class WithProperty { public bool Value { get; set; } } public class Coverage { [Obsolete("For" + " coverage", true is true or true)] // Error [CS0182] An attribute argument must be a constant expression public void BinaryAndPatterns() { } [Obsolete(1..2)] // Error [CS1503] Argument 1: cannot convert from 'System.Range' to 'string?' public void RangeOperator() { } } public class Builder { public Builder Build(params object[] args) => this; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/IndentRawString.cs ================================================ using System; using System.Collections.Generic; using System.Linq; public class Sample { public void Method() { _ = """ 0 """; // Noncompliant {{Indent this raw string literal at line position 13.}} _ = """ 1 """; // Noncompliant {{Indent this raw string literal at line position 13.}} _ = """ 2 """; // Noncompliant {{Indent this raw string literal at line position 13.}} _ = """ 3 """; // Noncompliant {{Indent this raw string literal at line position 13.}} _ = """ 4 """; _ = """ 5 """; // Noncompliant {{Indent this raw string literal at line position 13.}} // ^^^ _ = $""" Interpolated{5} """; // Noncompliant _ = $$$""" Interpolated{{{5}}} """; // Noncompliant _ = """ Utf8 """u8; // Noncompliant _ = """Text"""; _ = """ Wrong start is not in scope """; _ = """ Good """; _ = $"""{this} is not relevant"""; _ = $$"""{{this}} is not relevant"""; } public string ArrowNoncompliant() => """ Too close """; // Noncompliant public string ArrowCompliant() => """ Good """; public string ArrowInterpolatedNoncompliant(string value) => $""" Too close {value} """; // Noncompliant public string ArrowInterpolatedCompliant(string value) => $""" Good interpolated {value} """; public object ArrowInInvocationArgument() => Invocation(""" Good """); public object ArrowInConstructorArgument() => new Exception(""" Good """); public Exception ArrowInConstructorArgumentImplicit() => new(""" Good """); public object ArrowBuilderArgument() => new Builder() .Build(""" Good """) .Build(""" Too close """); // Noncompliant public string ReturnNoncompliant() { return """ Too close """; // Noncompliant } public string ReturnCompliant() { return """ Good """; } public string ArrowPropertyNoncompliant { get => """ Too close """; // Noncompliant } public string ArrowPropertyCompliant { get => """ Good """; } public void Assignment() { _ = Invocation(""" Good """); _ = Invocation(""" Too close """); // Noncompliant _ = Invocation(""" Too far """); // Noncompliant } public void Invocations() { var value = "Previous is not string"; Invocation(""" Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good, standalone start line """, """ Good, sharing start line with comma """, """ Too far """); // Noncompliant Invocation( value, """ Good, sharing start line with comma """, """ Too far """); // Noncompliant Invocation(""" Good """, """ Good """); global::Sample.Invocation(""" Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good """, """ Too far """); // Noncompliant global::Sample.Invocation(""" Good """); // This is bad already for other reasons Invocation(Invocation(Invocation(""" Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good """, """ Too far """))); // Noncompliant // This is bad already for other reasons Invocation(Invocation(Invocation(""" Good """))); Invocation( Invocation(""" Nested too close """, // Noncompliant {{Indent this raw string literal at line position 17.}} """ Nested too far """), // Noncompliant "Some other argument"); Invocation( Invocation(""" Good """, """ Good """), "Some other argument"); global::Sample.Invocation( // Longer invocation name does not matter """ Good """, """ Good """); Invocation(Invocation(Invocation( // This is bad already for other reasons """ Good """, """ Good """))); } public void ObjectInitializer() { _ = new WithProperty { First = """ Good """, Second = """ Good """, // Noncompliant }; } public void CollectionExpression() { _ = string.Join(" ", [ """ Too close """, // Noncompliant {{Indent this raw string literal at line position 17.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 17.}} ]); _ = string.Join(" ", [ """ Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 13.}} ]); } public void Constructors() { Invoke(new( """ Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 13.}} )); Invoke( new( """ Too close """, // Noncompliant {{Indent this raw string literal at line position 17.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 17.}} )); Invoke(new WithThreeArguments( """ Too close """, // Noncompliant {{Indent this raw string literal at line position 13.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 13.}} )); Invoke( new WithThreeArguments( """ Too close """, // Noncompliant {{Indent this raw string literal at line position 17.}} """ Good """, """ Too far """ // Noncompliant {{Indent this raw string literal at line position 17.}} )); void Invoke(WithThreeArguments ex) { } } public void Lambdas(int[] list) { // Simple lambda list.Where(x => """ We don't care, it's wrong already to use it here """ == ""); list.Where(x => """ We don't care, it's wrong already to use it here """ == ""); // Parenthesized lambda list.Where((x) => """ We don't care, it's wrong already to use it here """ == ""); list.Where((x) => """ We don't care, it's wrong already to use it here """ == ""); list.Where(x => { return """ Too close """ == ""; // Noncompliant return """ Good """ == ""; return """ Too far """ == ""; // Noncompliant }); } public void If() { if (""" We don't care, it's wrong already to use it here """ == """ We don't care, it's wrong already to use it here """) { _ = """ Alignment inside IF body is supported """; // Noncompliant } if (true) _ = """ Too close, even without parenthesis """; // Noncompliant } public void While() { while (""" We don't care, it's wrong already to use it here """ == """ We don't care, it's wrong already to use it here """) { _ = """ Alignment inside WHILE body is supported """; // Noncompliant } } public void For() { for (var i = 0; """ We don't care, it's wrong already to use it here """ == """ We don't care, it's wrong already to use it here """; i++) { _ = """ Alignment inside FOR body is supported """; // Noncompliant } } public void ConditionalAccess(Builder builder) { builder? .Build(""" Good """, """ Too Far """); // Noncompliant builder? .Build(""" Good """, """ Too Far """)? // Noncompliant .Build(""" Good """, """ Too Far """); // Noncompliant builder?.Build()?.Build(""" Good """, """ Too Far """); // Noncompliant } public void SwitchExpressions(object arg) { _ = arg switch { ArgumentException someLongerName => """ Good """, Exception someLongerName => """ Too close """, // Noncompliant _ => """ Good """ }; } public void Throw(bool condition, object arg) { _ = arg ?? new Exception(""" Good """); if (condition) { throw new Exception(""" Good """); } else { throw new ArgumentException(""" Too close """, // Noncompliant """ Too far """); // Noncompliant } } public void Builders() { //EqualsValue var builder = new Builder(""" Good """, """ Too far """); // Noncompliant // Assignment builder = new Builder(""" Good """, """ Too far """); // Noncompliant builder.Build(""" Good """, """ Too far """); // Noncompliant } public void ArgumentWithInvocation() { Invocation(""" Good """ .ToString()); Invocation( """ Good """ .ToString()); Invocation(""" Too far """ // Noncompliant .ToString()); } public void CollectionInitializer() { var list = new List { """ Good """, """ Too far """ // Noncompliant }; var dict = new Dictionary { { "Key", """ Good """ }, { "Key", """ Too far """ // Noncompliant } }; } public void Ternary(bool condition) { _ = condition ? """ Good """ : """ Good """; _ = condition ? """ Too close """ // Noncompliant : """ Too close """; // Noncompliant Invocation(condition ? """ Good """ : """ Too close """); // Noncompliant Invocation(condition ? """ Good, although the opening quote is questionable (not in scope of this rule) """ : """ Too close """); // Noncompliant } public static bool Invocation(params object[] args) => true; [Obsolete(""" For coverage """)] public void Coverage() { } } public class Builder { public Builder(params object[] args) { } public Builder Build(params object[] args) => this; } public class WithProperty { public string First { get; set; } public string Second { get; set; } } public class WithThreeArguments { public WithThreeArguments(string a, string b, string c) { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/IndentTernary.cs ================================================ using System; using System.Linq; public class Sample { private bool condition; public void Method() { bool longVariableName; longVariableName = condition // 0 ? true // Noncompliant {{Indent this ternary at line position 13.}} : false; // Noncompliant {{Indent this ternary at line position 13.}} longVariableName = condition // 1 ? true // Noncompliant : false; // Noncompliant longVariableName = condition // 2 ? true // Noncompliant : false; // Noncompliant longVariableName = condition // 3 ? true // Noncompliant : false; // Noncompliant longVariableName = condition // 4 ? true : false; longVariableName = condition // 5 ? true // Noncompliant // ^^^^^^ : false; // Noncompliant // ^^^^^^^ longVariableName = condition // One of the branches is too far ? true // Noncompliant : false; longVariableName = condition // One of the branches is too far ? true : false; // Noncompliant longVariableName = condition && true ? true : false; // Noncompliant longVariableName = condition ? true : false; longVariableName = condition ? true : false; // Compliant, this is T0024 longVariableName = condition ? true : false; // Compliant, this is T0024 } public bool ArrowNoncompliant() => condition ? true // Noncompliant : false; // Noncompliant public bool ArrowCompliant() => condition ? true : false; public bool ArrowLongerCondition() => condition && true ? true : false; // Noncompliant public bool ArrowNotInScope() => condition ? true : false; public object ArrowInInvocationArgument() => Something(condition ? true // Noncompliant : false); public object ArrowInInvocationArgumentLongerCondition() => Something(condition && true ? true // Noncompliant : false); public object ArrowInConstructorArgument() => new Nullable(condition ? true // Noncompliant : false); public bool ReturnNoncompliant() { return condition ? true // Noncompliant, too little : false; // Noncompliant, too much } public bool ReturnCompliant() { return condition ? true : false; } public bool ReturnLongerCondition() { return condition && true ? true : false; // Noncompliant return (condition && true) ? true : false; // FN, we're only looking for binary expression at top level } public void Invocations() { Something(condition // This is bad already ? true // Noncompliant {{Indent this ternary at line position 13.}} : false, "Some other argument"); Something(condition // This is bad already ? true : false, "Some other argument"); global::Sample.Something(condition // This is bad already ? true // Noncompliant {{Indent this ternary at line position 13.}} : false, // Noncompliant "Some other argument"); global::Sample.Something(condition // This is bad already ? true : false, "Some other argument"); Something(Something(Something(condition // This is bad already ? true // Noncompliant {{Indent this ternary at line position 13.}} : false, // Noncompliant "Some other argument"))); Something(Something(Something(condition // This is bad already ? true : false, "Some other argument"))); Something( condition ? true // Noncompliant {{Indent this ternary at line position 17.}} : false, // Noncompliant "Some other argument"); Something( condition ? true : false, "Some other argument"); global::Sample.Something( // Longer invocation name does not matter condition ? true : false, "Some other argument"); Something(Something(Something( // This is bad already for other reasons condition ? true : false, "Some other argument"))); Something(condition && true ? true : false); // Noncompliant) Something( condition && true ? true : false); // Noncompliant) } public void ObjectInitializer() { _ = new WithProperty { Value = condition ? true :false // Noncompliant }; } public void CollectionExpression() { _ = string.Join(" ", [ condition ? "true" : "false" // Noncompliant ]); _ = string.Join(" ", [ condition ? "true" : "false" // Noncompliant ]); } public void Lambdas(int[] list, int[] longer) { list.Where(x => condition ? true // Noncompliant, too close : false); // Noncompliant, too close list.Where(x => condition ? true // Noncompliant, too close : false); // Noncompliant, too close list.Where(x => condition ? true // Noncompliant, too far : false); // Noncompliant, too far list.Where(x => condition // Simple lambda ? true : false); list.Where((x) => condition // Parenthesized lambda ? true : false); longer.Where(x => condition // Simple lambda, longer name ? true // Compliant, as long as it's after the condition and aligned to the grid of 4 : false); longer.Where((x) => condition // Parenthesized lambda, longer name ? true // Compliant, as long as it's after the condition and aligned to the grid of 4 : false); list.Where(x => { return condition ? true : false; }); } public void If() { if (condition ? true // Noncompliant {{Indent this ternary at line position 17.}} : false) // Noncompliant { } if (condition ? true : false) { } else if (condition ? true : false) // Noncompliant { } if(condition && true ? true : false) // Noncompliant { } } public void While() { while (condition ? true // Noncompliant {{Indent this ternary at line position 17.}} : false) // Noncompliant { } while (condition ? true : false) { } } public void For() { for (var i = 0; condition ? true // Noncompliant {{Indent this ternary at line position 29.}} : false; i++) // Noncompliant { } for (int i = 0; condition ? true : false; i++) { } } public void ConditionalAccess(Builder builder) { builder? .Build(condition ? true : false); // Noncompliant builder? .Build(condition ? true : false)? // Noncompliant .Build(condition ? true : false); // Noncompliant builder?.Build()?.Build(condition ? true : false); // Noncompliant } public void SwitchExpressions(object arg) { _ = arg switch { ArgumentException someLongerName => condition && true ? true : false, // Noncompliant Exception someLongerName => condition ? true : false, // Noncompliant _ => condition ? true : false // Noncompliant }; } [Obsolete(true // Not supported, used for coverage ? "true" : "false")] public static bool Something(bool arg, object another = null) => true; } public class Builder { public Builder Build(params object[] args) => this; } public class WithProperty { public bool Value { get; set; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/InitializerLine.cs ================================================ using System; using System.Collections.Generic; public class Fields { private string nextLineString = "Ipsum"; // Noncompliant {{Move this initializer to the previous line.}} // ^^^^^^^ private string sameLine = "Lorem"; private string nextLineWhereTheFinalLinewouldBeLongButWithin200Limit_Noncompliant = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus faucibus elit et phasellus, Length is 200."; // Noncompliant, it fits private string nextLineWhereTheFinalLinewouldBeTooLongSoItMustBeOnTheNextLineAnyway_Length201_Compliant = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus. Final Length is 201."; private string nextLineWhereTheFinalLinewouldBeTooLongSoItMustBeOnTheNextLineAnyway_Length223_Compliant = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus. Final Length is definitely more than 200."; private int[] values = [ ]; public List newOnNextLine = new() // Noncompliant, multiline only in this case // ^^^^^ { }; public List newOnNextLineWithArguments_Implicit = new(42) // Noncompliant // ^^^^^^^ { }; public List newOnNextLineWithArguments_Explicit = new List(42) // Noncompliant // ^^^^^^^^^^^^^^^^^ { }; public List newOnNextLineWithArguments_Explicit_NoArgumentList = new List // Noncompliant // ^^^^^^^^^^^^^ { }; public List newOnNextLineWithArgumentsWhereTheFinalLineWouldBeTooLongSoItMustBeOnTheNextLineAnyway_Implicit_Compliant = new(1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1) { }; public List newOnNextLineWithArgumentsWhereTheFinalLineWouldBeTooLongSoItMustBeOnTheNextLineAnyway_Explicit_Compliant = new List(1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1) { }; public List newOnSameLine = new() { }; private string multiLine = "Lorem" + "Ipsum"; private int computedNextLine = Compute(); // Noncompliant private int computedSameLine = Compute(); private int computedMultipleLines = Compute() + Compute(); private static int Compute() => 42; private int multipleSameLineA = 1, multipleSameLineB = 2; private int multipleMixedLineA = 1, multipleMixedLineB = 2; // Noncompliant private int multipleMultiLineA = 1, multipleMultiLineB = // Noncompliant 2; // Noncompliant [Obsolete] public string sameLineAttribute = "Lorem"; [Obsolete] public string nextLineAttribute = "Ipsum"; // Noncompliant } public class Properties { private int field; private event EventHandler eventField; public string NextLineStringExpressionBody => "Ipsum"; // Noncompliant {{Move this expression to the previous line.}} // ^^^^^^^ public string NextLineStringInitializer { get; } = "Ipsum"; // Noncompliant {{Move this initializer to the previous line.}} // ^^^^^^^ public string SameLineExpressionBody => "Lorem"; public string SameLineInitializer { get; } = "Lorem"; public string NextLineWhereTheFinalLinewouldBeLongButWithin200Limit_Noncompliant => "Lorem ipsum dolor sit amet consectetur adipiscing elit. Phasellus faucibus elit et phasellus, Length is 200."; // Noncompliant, it fits public string NextLineWhereTheFinalLinewouldBeTooLongSoItMustBeOnTheNextLineAnyway_Length201_Compliant => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus. Final Length is 201."; public string NextLineWhereTheFinalLinewouldBeTooLongSoItMustBeOnTheNextLineAnyway_Length223_Compliant = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus. Final Length is definitely more than 200."; public int[] Values => [ ]; public string MultiLine => "Lorem" + "Ipsum"; public int ArrowNoncompliant { get => field; // Noncompliant set => field = value; // Noncompliant } public int ArrowInitNoncompliant { get => field; // Noncompliant init => field = value; // Noncompliant } public event EventHandler MyEvent { add => eventField += value; // Noncompliant remove => eventField -= value; // Noncompliant } public int ArrowCompliant { get => field; set => field = value; } public int BodyGetter { get { return 42; } } public int AutoImplemented { get; set; } [Obsolete] public string SameLineAttribute => "Lorem"; [Obsolete] public string NextLineAttribute { get; } = "Ipsum"; // Noncompliant public int AccessorsWithAttributes { [Obsolete] get => field; [Obsolete] set => field = value; // Noncompliant } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/LambdaParameterName.cs ================================================ using System; using System.Collections.Generic; using System.Linq; public class Sample { public void SimpleLambdas() { Method(x => 42); Method(x => { }); Method(item => 42); // Noncompliant {{Use 'x' for the lambda parameter name.}} Method(item => { }); // Noncompliant // ^^^^ Method(a => 42); // Noncompliant Method(b => 42); // Noncompliant Method(y => 42); // Noncompliant Method(z => 42); // Noncompliant Method(_ => 42); // Compliant Method(node => 42); // Noncompliant Method(dataGridViewCellContextMenuStripNeededEventArgs => 42); // Noncompliant Method(x => Enumerable.Range(x, 10).Where(item => item == 42)); // Compliant, nested Method(item => Enumerable.Range(item, 10).Where(x => x == 42)); // Noncompliant, the outer one should be x // ^^^^ } public void ParenthesizedLambdas() { Method(() => 42); Method(() => { }); Method((a, b) => 42); Method((_, _) => 42); Method((a, b) => { }); } public void Errors() { // Error@+5 [CS1001] Identifier expected // Error@+4 [CS1003] Syntax error, '=>' expected // Error@+3 [CS1026] ) expected // Error@+2 [CS1593] Delegate 'Func' does not take 0 arguments // Error@+1 [CS1003] Syntax error, ',' expected Func f = ( => 42); // Error@+2 [CS1001] Identifier expected // Error@+1 [CS1503] Argument 1: cannot convert from 'int' to 'System.Func' Method( => 42); // Error@+5 [CS1501] No overload for method 'Method' takes 0 arguments // Error@+4 [CS1001] Identifier expected // Error@+3 [CS1002] ; expected // Error@+2 [CS1026] ) expected // Error@+1 [CS1513] Closing curly brace expected Method( => { }); } private void Method(Func f) { } private void Method(Func f) { } private void Method(Func f) { } private void Method(Action a) { } private void Method(Action a) { } private void Method(Action a) { } } public class RuleRegistration { public void Initialize() { RegisterSonarWhateverAnalysisContext(c => { }); RegisterSonarWhateverAnalysisContext(context => { }); RegisterSonarWhateverAnalysisContext(whateverContext => { }); RegisterSonarWhateverReportingContext(c => { }); RegisterSonarSomething(c => { }); // Noncompliant, wrong suffix RegisterSomethingAnalysisContext(c => { }); RegisterSomethingReportingContext(c => { }); // Noncompliant, wrong prefix RegisterSonarSomethingContext(c => { }); // Noncompliant, wrong suffix } protected void RegisterSonarWhateverAnalysisContext(Action action) { } protected void RegisterSonarWhateverReportingContext(Action action) { } protected void RegisterSonarSomething(Action action) { } protected void RegisterSomethingAnalysisContext(Action action) { } protected void RegisterSomethingReportingContext(Action action) { } protected void RegisterSonarSomethingContext(Action action) { } // Well-known expected classes patterns public class SonarWhateverAnalysisContext { } public class SonarWhateverReportingContext { } public class SomethingAnalysisContext { } // Unexpected types public class SonarSomething { } public class SomethingReportingContext { } public class SonarSomethingContext { } } public class CustomDelegates { public delegate void ParameterNamedI(int i); public delegate void ParameterNamedTest(int test); public delegate void ParameterNamedCamelCasing(int camelCasing); public void Test() { ParameterNamedI delegate1 = i => { }; // Compliant "i" matches the parameter name of the delegate ParameterNamedI delegate2 = j => { }; // Noncompliant ParameterNamedI delegate3 = x => { }; // Compliant ParameterNamedTest delegate4 = test => { }; // Compliant ParameterNamedTest delegate5 = someTest => { }; // Noncompliant ParameterNamedTest delegate6 = testSome => { }; // Noncompliant ParameterNamedCamelCasing delegate7 = camelCasing => { }; // Compliant ParameterNamedCamelCasing delegate8 = camelcasing => { }; // Noncompliant ParameterNamedCamelCasing delegate9 = camel => { }; // Noncompliant ParameterNamedCamelCasing delegate10 = casing => { }; // Noncompliant Func function = arg => 0; // Noncompliant, the delegate parameter is named "arg" (https://learn.microsoft.com/en-us/dotnet/api/system.func-2) but we do not allow that for Func Action action = obj => { }; // Noncompliant, the delegate parameter is named "obj" (https://learn.microsoft.com/en-us/dotnet/api/system.action-1) but we do not allow that for Action new List().Exists(obj => true); // Compliant. List.Exists uses System.Predicate instead of System.Func } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/LocalFunctionLocation.TopLevelStatements.cs ================================================ string LocalFunction() => "Empty"; // Noncompliant {{This local function should be at the end of the method.}} // ^^^^^^^^^^^^^ _ = LocalFunction(); string LocalFunction2() => "Empty"; static string StaticLocalFunction() => "Empty"; class MyClass { public void Method() { void LocalFunction() { } // Noncompliant LocalFunction(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/LocalFunctionLocation.cs ================================================ class Noncompliant { public string Value { get { string LocalFunction() => "Empty"; // Noncompliant {{This local function should be at the end of the method.}} return LocalFunction(); } set { static void LocalFunction(string _) { }; // Noncompliant {{This local function should be at the end of the method.}} LocalFunction(value); } } public string this[int a] { get { string LocalFunction() => "Empty"; // Noncompliant {{This local function should be at the end of the method.}} return LocalFunction(); } set { static void LocalFunction(string _) { } // Noncompliant {{This local function should be at the end of the method.}} LocalFunction(value); } } public Noncompliant() { void LocalFunction() { } // Noncompliant {{This local function should be at the end of the method.}} // ^^^^^^^^^^^^^ LocalFunction(); } void SingleLocalFunction() { void LocalFunction() { } // Noncompliant {{This local function should be at the end of the method.}} // ^^^^^^^^^^^^^ LocalFunction(); } void StaticLocalFunction() { static int LocalFunction() => 42; // Noncompliant LocalFunction(); } void MultipleLocalFunctions() { LocalFunction(); void LocalFunction() { } // Noncompliant LocalFunction2(); void LocalFunction2() { } // Noncompliant {{This local function should be at the end of the method.}} LocalFunction(); } void WithinBlock(bool a) { if (a) { LocalFunction(); void LocalFunction() { } // Noncompliant } } void MixCompliantNoncompliant() { LocalFunction(); void LocalFunction() { } // Noncompliant LocalFunction2(); LocalFunction(); void LocalFunction2() { } } } class Compliant { public string Value { get { return LocalFunction(); string LocalFunction() => "Empty"; } set { LocalFunction(value); static void LocalFunction(string _) { } } } public string this[int a] { get { return LocalFunction(); string LocalFunction() => "Empty"; } set { LocalFunction(value); static void LocalFunction(string _) { } } } public Compliant() { LocalFunction(); void LocalFunction() { } } void SingleLocalFunction() { LocalFunction(); void LocalFunction() { } } void WithinBlock(bool a) { if (a) { LocalFunction(); } void LocalFunction() { } } void MultipleLocalFunctions() { LocalFunction(); LocalFunction(); LocalFunction2(); void LocalFunction() { } void LocalFunction2() { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/MemberAccessLine.cs ================================================ using System.Linq; public class Sample { Builder builder; public void Method() { builder.Build().Build() .Build(); _ = builder .Build().Build() // Noncompliant {{Move this member access to the next line.}} // ^^^^^^ .Build().Variable; // Noncompliant // ^^^^^^^^^ builder .Build().Build(); // Noncompliant // ^^^^^^ builder.Build().Variable.Build() .Build() .Build().Build() // Noncompliant .Build().Variable // Noncompliant .Build().Property // Noncompliant .Build()[42] .Build(); builder.Build() .Build().Build().Build().Build(); // ^^^^^^ Noncompliant // ^^^^^^ Noncompliant@-1 // ^^^^^^ Noncompliant@-2 builder.Build() .Variable .Build() .Property .Build() .Indexed[42] .Build() .Property[42] .Variable[42] .Build()[42] .Build(); } public void Prerequisites() // These nodes start a multi-line chain { builder .Build().Build(); // Noncompliant builder .Variable.Build(); // Noncompliant builder .Build()[42].Build(); // Noncompliant builder .Variable[42].Build(); // Noncompliant } public void FluentAssertions() { builder.Should().BeSomething() .And.BeSomething().And.BeSomething(); builder.Should().BeSomething() .And.BeSomething() .And // Allowed .BeSomething(); builder.Build("Something long") .Should().BeSomething(); builder .And.Variable.Should().BeSomething(); builder .And.Variable.Build().Should().BeSomething(); builder .Subject.Variable.Should().BeSomething(); builder .Which.Variable.Should().BeSomething(); } public void ConditionalAccess() { builder? .Build()?.Build(); // FN, ConditionalAccessExpression and MemberBindingExpression are too different from MemberAccessExpression to deal with builder?.Build()?.Build(); } public void Global() { global::Builder .StaticMethod().Build(); // Noncompliant // ^^^^^^ global::Builder .StaticMethod() .Build(); } public void Nested() { Builder.NestedOnce.NestedTwice .StaticMethod().Build(); // Noncompliant Builder.NestedOnce.NestedTwice .StaticMethod() .Build(); } public void ContinuesLines() { var typicallyForImmutableField = new int[] { 0, 1, 2 }.ToList(); _ = builder .Build(""" This is a long argument """).Variable; // Noncompliant, because there's .Build() already in the chain _ = builder.Build(""" This is a long argument """).Variable.Build(); // Useful for TestSnippet(...).Model.Compilation } } public class Builder { public Builder Variable; public Builder Property => null; public Builder[] Indexed; public Builder this[int index] => null; public Builder Build(params object[] args) => this; public static Builder StaticMethod() => null; public bool IsTrue() => true; // FluentAssertions-like methods public Builder And => null; public Builder Which => null; public Builder Subject => null; public Builder Should() => null; public Builder BeSomething() => null; public class NestedOnce { public class NestedTwice { public static Builder StaticMethod() => null; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/MemberVisibilityOrdering.cs ================================================ using System; public class ValidOrder { public int public1; public int public2; internal int internal1; internal int internal2; protected int protectedVariant1; private protected int protectedVariant2; protected internal int protectedVariant3; protected int protectedVariant4; private protected int protectedVariant5; protected internal int protectedVariant6; private int private1; private int private2; } public class AbovePrivate { private int private1; // Secondary [Private1, Private2, Private3, Private4, Private5, Private6, Private7] // ^^^^^^^^^^^^ protected int protectedVariant1; // Noncompliant [Private1] {{Move this protected Field above the private ones.}} // ^^^^^^^^^^^^^^^^^^^^^ private protected int protectedVariant2; // Noncompliant [Private2] {{Move this protected Field above the private ones.}} protected internal int protectedVariant3; // Noncompliant [Private3] {{Move this protected Field above the private ones.}} public int public1; // Noncompliant [Private4] {{Move this public Field above the private ones.}} internal int internal1; // Noncompliant [Private5] {{Move this internal Field above the private ones.}} public int public2; // Noncompliant [Private6] {{Move this public Field above the private ones.}} internal int internal2; // Noncompliant [Private7] {{Move this internal Field above the private ones.}} } public class AboveProtected { protected int protectedVariant; // Secondary [Protected1, Protected2] public int public1; // Noncompliant [Protected1] {{Move this public Field above the protected ones.}} internal int internal1; // Noncompliant [Protected2] {{Move this internal Field above the protected ones.}} } public class AboveProtectedInternal { protected internal int protectedVariant; // Secondary [ProtectedInternal1, ProtectedInternal2] public int public1; // Noncompliant [ProtectedInternal1] {{Move this public Field above the protected ones.}} internal int internal1; // Noncompliant [ProtectedInternal2] {{Move this internal Field above the protected ones.}} } public class AboveProtectedPrivate { protected private int protectedVariant; // Secondary [ProtectedPrivate1, ProtectedPrivate2] public int public1; // Noncompliant [ProtectedPrivate1] {{Move this public Field above the protected ones.}} internal int internal1; // Noncompliant [ProtectedPrivate2] {{Move this internal Field above the protected ones.}} } public abstract class CompliantClassFull { public const string Constant1 = "C1"; private const string Constant2 = "C2"; public enum Enum1 { None, All } private enum Enum2 { None, Any, All } public readonly object field1 = new(); public static readonly object field2 = new(); private readonly object field3 = new(); private static readonly object field4, field5; public abstract int AbstractMethod1(); public abstract int AbstractProperty1 { get; } protected abstract int AbstractMethod2(); protected abstract int AbstractProperty2 { get; } public delegate void SomeDelegate1(); private delegate void SomeDelegate2(); public event EventHandler SomeEvent1; private event EventHandler SomeEvent2; public object Property1 { get; } = 42; private object Property2 => 42; public object this[int index] => 42; private object this[string name] { get => 42; } public CompliantClassFull() { } private CompliantClassFull(int arg) { } ~CompliantClassFull() { } // Not interesting public void Method1() { } private void Method2() { } public class Nested1 { } // Relative order of these types is not important private struct Nested2 { } public record Nested3 { } protected record struct Nested4 { } public record Nested5 { } public struct Nested6 { } public class Nested7 { } } public abstract class AllWrong { private const string Constant1 = "C1"; // Secondary [Const] public const string Constant2 = "C2"; // Noncompliant [Const] {{Move this public Constant above the private ones.}} private enum Enum1 // Secondary [Enum] { None, All } public enum Enum2 // Noncompliant [Enum] {{Move this public Enum above the private ones.}} { None, Any, All } private readonly object field1 = new(); // Secondary [Field1, Field2] private static readonly object field2 = new(); public readonly object field3 = new(); // Noncompliant [Field1] {{Move this public Field above the private ones.}} public static readonly object field4, field5; // Noncompliant [Field2] {{Move this public Field above the private ones.}} protected abstract int AbstractMethod1(); // Secondary [Abstract1, Abstract2] protected abstract int AbstractProperty1 { get; } public abstract int AbstractMethod2(); // Noncompliant [Abstract1] {{Move this public Abstract Member above the protected ones.}} public abstract int AbstractProperty2 { get; } // Noncompliant [Abstract2] {{Move this public Abstract Member above the protected ones.}} private delegate void SomeDelegate1(); // Secondary [Delegate] public delegate void SomeDelegate2(); // Noncompliant [Delegate] {{Move this public Delegate above the private ones.}} private event EventHandler SomeEvent1; // Secondary [Event] public event EventHandler SomeEvent2; // Noncompliant [Event] {{Move this public Event above the private ones.}} private object Property1 { get; } = 42; // Secondary [Property] public object Property2 => 42; // Noncompliant [Property] {{Move this public Property above the private ones.}} private object this[int index] => 42; // Secondary [Indexer] public object this[string name] // Noncompliant [Indexer] {{Move this public Indexer above the private ones.}} { get => 42; } private AllWrong() { } // Secondary [Constructor] public AllWrong(int arg) { } // Noncompliant [Constructor] {{Move this public Constructor above the private ones.}} private void Method1() { } // Secondary [Method] public void Method2() { } // Noncompliant [Method] {{Move this public Method above the private ones.}} } public record R { private int shouldBeLast; // Secondary [InRecord] public int shouldBeFirst; // Noncompliant [InRecord] {{Move this public Field above the private ones.}} } public record struct RS { private int shouldBeLast; // Secondary [InRecordStruct] public int shouldBeFirst; // Noncompliant [InRecordStruct] {{Move this public Field above the private ones.}} } public record struct S { private int shouldBeLast; // Secondary [InStruct] public int shouldBeFirst; // Noncompliant [InStruct] {{Move this public Field above the private ones.}} } public static class ExtensionBlockCompliant { extension(string s) { public int PublicProp => 0; internal int InternalProp => 0; private int PrivateProp => 0; public void PublicMethod() { } private void PrivateMethod() { } } } public static class ExtensionBlockWrong { extension(string s) { private int PrivateProp => 0; // Secondary [ExtProp] public int PublicProp => 0; // Noncompliant [ExtProp] {{Move this public Property above the private ones.}} private void PrivateMethod() { } // Secondary [ExtMethod] public void PublicMethod() { } // Noncompliant [ExtMethod] {{Move this public Method above the private ones.}} } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/MethodExpressionBodyLine.cs ================================================  using System; public class Sample { private Exception field; public Sample() => field = new Exception(); // Noncompliant {{Move this expression body to the next line.}} // ^^^^^^^^^^^^^^^^^^^^^^^ public object MemberAccess() => field.Message; // Noncompliant {{Move this expression body to the next line.}} // ^^^^^^^^^^^^^ public object Invocation() => Expression(); // Noncompliant public object Expression() => 1 + 2; // Noncompliant ~Sample() => Console.WriteLine("Bye"); // Noncompliant public static object operator +(Sample a, Sample b) => a.field; // Noncompliant public static explicit operator string(Sample sample) => sample.field.Message; // Noncompliant // Single-token values are tolerated public object SingleName() => field; // Comments are fine public object Simple_Null() => null; public object Simple_Bool() => true; public object Simple_Int() => 42; public object Simple_Decimal() => 42.42D; public bool ComplexCompliant() => true && false || true; public void WithBody() { Console.WriteLine("This is fine"); } } public interface IBase { object FromInterface(); } public abstract class Base { public abstract object FromAbstractClass(); public virtual object FromVirtualMethod() => 42; } public class TestStub : Base, IBase { public object NewMethod() => throw new NotImplementedException(); // Noncompliant, not a mandatory stub override // This is common pattern in UTs => we ignore these specific cases public override object FromAbstractClass() => throw new NotImplementedException(); public override object FromVirtualMethod() => throw new NotSupportedException("Parameters are allowed"); public object FromInterface() => throw new NotImplementedException(); } public class UnexpectedExceptionType : IBase { public object FromInterface() => throw new ArgumentException("This is functional"); // Noncompliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/MoveMethodToDedicatedExtensionClass.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using MyString = string; static class StringExtensions { public static string ToUpperCase(this string str) { return str.ToUpper(); } public static string String(this System.String str) => str; public static string Generic(this System.String str, T other) => str + other.ToString(); public static string ToLowerCase(this MyString str) => str.ToLower(); public static short Short(this short nb) => nb; // Noncompliant {{Move this extension method to the Int16Extensions class.}} // ^^^^^ public static int Int(this int nb) => nb; // Noncompliant {{Move this extension method to the Int32Extensions class.}} // ^^^ public static long Long(this long nb) => nb; // Noncompliant {{Move this extension method to the Int64Extensions class.}} public static short Int16(this System.Int16 nb) => nb; // Noncompliant {{Move this extension method to the Int16Extensions class.}} public static int Int32(this System.Int32 nb) => nb; // Noncompliant {{Move this extension method to the Int32Extensions class.}} public static long Int64(this System.Int64 nb) => nb; // Noncompliant {{Move this extension method to the Int64Extensions class.}} public static System.Int128 Int128(this System.Int128 nb) => nb; // Noncompliant {{Move this extension method to the Int128Extensions class.}} public static void ValueTuple(this (int, int) tuple) { } // Noncompliant {{Move this extension method to the ValueTupleExtensions or Int32Extensions class.}} public static void ValueTruple(this (int, int, int) truple) { } // Noncompliant {{Move this extension method to the ValueTupleExtensions or Int32Extensions class.}} public static void FunWithTuples(this (int, ExplodedNode, System.Int64) truple) { } // Noncompliant {{Move this extension method to the ValueTupleExtensions, Int32Extensions, ExplodedNodeExtensions, or Int64Extensions class.}} public static void FunWithTwoples(this (string, ExplodedNode, System.Int64) truple) { } // Compliant } class Generic { } static class GenericExtensions { public static T Generic(this T t) => t; // Noncompliant {{Move this extension method to the ObjectExtensions class.}} public static T[] Generic(this T[] t) => t; // Noncompliant {{Move this extension method to the ObjectExtensions class.}} public static T ConstrainedGeneric(this T t) where T : Generic => t; // Error@+1 [CS0450] public static T InvalidConstrainedGeneric(this T t) where T : class, Generic => t; // Noncompliant - the where clause is invalid and thus we cannot find Generic } static class IEnumerableExtensions { public static IEnumerable ToEnumerable(this T item) // Noncompliant {{Move this extension method to the ObjectExtensions class.}} { yield return item; } public static IEnumerable GenericExtension(this IEnumerable enumerable) => enumerable; public static T ComplexConstrainedGeneric(this T t) where T : class, IEnumerable => t; public static T TwoConstrainedGeneric(this T t) where T : class, IEnumerable, ICloneable => t; } static class ICloneableExtensions { public static T TwoConstrainedGeneric(this T t) where T : struct, IEnumerable, ICloneable => t; } static class NotClonableNorEnumerableExtensions { public static T TwoConstrainedGeneric(this T t) where T : class, IEnumerable, ICloneable => t; // Noncompliant {{Move this extension method to the IEnumerableExtensions or ICloneableExtensions class.}} public static T DifferentOrder(this T t) where T : class, ICloneable, IEnumerable => t; // Noncompliant {{Move this extension method to the ICloneableExtensions or IEnumerableExtensions class.}} } static class DictionaryExtensions { public static Dictionary GenericExtension(this Dictionary dictionary) => dictionary; } class NotAnExtensionClass // Error [CS1106] { public static int Int(this int nb) => nb; // Noncompliant } class IntExtensions // Error [CS1106] { public static int Int(this int nb) => nb; // Noncompliant - The class is not static } class IntExtensions // Error [CS1106] { public static int Int(this int nb) => nb; // Noncompliant - The class is not static } static class InvalidExtensions { // Error@+1 [CS1103] public static void Dynamic(this dynamic dynamic) { } // Noncompliant {{Move this extension method to the dynamicExtensions class.}} // Error@+1 [CS1103] unsafe public static void Pointer(this int* pointer) { } // Noncompliant {{Move this extension method to the ObjectExtensions class.}} } public class ExplodedNode { } static class ExplodedNodeExtensions { public static void FromIEnumerable(this IEnumerable nodes) { } // Compliant public static void FromDictionaryValue(this Dictionary map) { } // Compliant public static void GenericFromKey(this Dictionary map) { } // Compliant public static void GenericFromValue(this Dictionary map) { } // Compliant public static void Nested(this IEnumerable>> convoluted) { } // Compliant public static void OtherType(this Action action) { } // Compliant } static class SomeOtherExtensions { public static void FromIEnumerable(this IEnumerable nodes) { } // Noncompliant {{Move this extension method to the IEnumerableExtensions or ExplodedNodeExtensions class.}} public static void FromDictionaryValue(this Dictionary map) { } // Noncompliant {{Move this extension method to the DictionaryExtensions, StringExtensions, or ExplodedNodeExtensions class.}} public static void GenericFromKey(this Dictionary map) { } // Noncompliant {{Move this extension method to the DictionaryExtensions or ExplodedNodeExtensions class.}} public static void GenericFromValue(this Dictionary map) { } // Noncompliant {{Move this extension method to the DictionaryExtensions or ExplodedNodeExtensions class.}} public static void Nested(this IEnumerable>> convoluted) { } // Noncompliant {{Move this extension method to the IEnumerableExtensions, IListExtensions, IDictionaryExtensions, StringExtensions, or ExplodedNodeExtensions class.}} public static void DictionaryGenerics(this Dictionary map) { } // Noncompliant {{Move this extension method to the DictionaryExtensions class.}} public static void OtherType(this Action action) { } // Noncompliant {{Move this extension method to the ActionExtensions or ExplodedNodeExtensions class.}} } static class ListExtensions { public static void GenericList(this List items) { } // Compliant public static void NestedGeneric(this List>> convoluted) { } // Compliant } static class GenericIntermediateTypeExtensions { public static void NonGenericType(this GenericIntermediateType nodes) { } // Compliant public static void GenericType(this GenericIntermediateType generics) { } // Compliant public static void NestedGeneric(this List> generics) { } // Compliant public static void NestedNonGeneric(this List> generics) { } // Compliant public class GenericIntermediateType { } } static class NoncompliantCSharp14Extensions { extension(IEnumerable nodes) { public void InstanceNoncompliant() { } // Noncompliant {{Move this extension method to the IEnumerableExtensions or ExplodedNodeExtensions class.}} } extension(IEnumerable) { public static void StaticNoncompliant() { } // Noncompliant {{Move this extension method to the IEnumerableExtensions or ExplodedNodeExtensions class.}} } } public class Compliant { } static class CompliantExtensions { extension(IEnumerable nodes) { public void InstanceCompliant() { } // Compliant } extension(IEnumerable) { public static void StaticCompliant() { } // Compliant } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/NullPatternMatching.cs ================================================ using System; using System.Linq.Expressions; public class Sample { public void Method(object o, int a, int b, object another) { if (o == null) // Noncompliant {{Use 'is null' pattern matching.}} // ^^^^^^^^^ { } if (a > b || (a < b && o == null)) // Noncompliant // ^^^^^^^^^ { } if (o != null) // Noncompliant {{Use 'is not null' pattern matching.}} // ^^^^^^^^^ { } _ = !(o == null); // Noncompliant // ^^^^^^^^^ _ = !(o != null); // Noncompliant // ^^^^^^^^^ _ = null == o; // Noncompliant _ = null != o; // Noncompliant _ = o is null; _ = o is not null; _ = o == "null"; _ = o != "null"; _ = o == this; _ = o != this; _ = o == another; _ = o != another; _ = o == Invocation(); _ = o != Invocation(); Use(o == null); // Noncompliant while (o == null) // Noncompliant { } Use(o ==); // Error [CS1525] Invalid expression term ';' Use(== o); // Error [CS1525] Invalid expression term '==' } private object Invocation() => null; private void Use(bool b) { } public void CustomOperator() { var s = new Sample(); if(s == null) // Noncompliant { // This is always visited due to overriden operator => we don't care, it's a bad idea anyway } } public void ExpressionTree(object x) { Func func1 = () => x == null; // Noncompliant Func func2 = () => x is null; Expression> expression1 = () => x == null; Expression> expression2 = () => x != null; Expression> expression3 = () => x is null; // Error [CS8122] } public static bool operator ==(Sample a, Sample b) => b is null; public static bool operator !=(Sample a, Sample b) => b is not null; } public class IsPattern { public void Method(object value, Exception ex) { _ = value is { }; // Noncompliant {{Use 'is not null' pattern matching.}} // ^^^ _ = ex is { Message: { } }; // Noncompliant {{Use 'is not null' pattern matching.}} // ^^^ } public void Compliant(object value, string str, Exception ex) { _ = value is null; _ = value is not null; _ = value is { } renamed; _ = Value() is { } captured; _ = value is Exception; _ = value is Exception e; _ = str is { Length: 0 }; _ = ex is { Message: { } message }; } private object Value() => null; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/OperatorLocation.cs ================================================ using System.Collections.Generic; using System .Collections .Generic; using System. // Noncompliant Collections. // Noncompliant Generic; class MyClass { void Compliant(int a, int b, int c, object o, List list) { _ = a + b; _ = a + b; c += a - b; c -= a - b; c *= a - b; c /= a - b; c %= a - b; o ??= new object(); c <<= a - b; c >>= a - b; c >>>= a - b; c |= a - b; c &= a - b; c ^= a - b; o.ToString (); _ = list? [0]; _ = list! [0]; _ = list[0] .ToString(); _ = list?[0]? .ToString(); } void LogicalOperator(bool a, bool b) { _ = a && b; _ = a && // Noncompliant {{The '&&' operator should not be at the end of the line.}} // ^^ b; _ = a || // Noncompliant b; _ = a & // Noncompliant b; _ = a | // Noncompliant b; _ = a ^ // Noncompliant b; _ = a && // Noncompliant b; _ = a && // Noncompliant (b || // Noncompliant a) && // Noncompliant b; } void Ternary(bool a, bool b) { _ = a ? 1 : 2; _ = a ? 1 : // Noncompliant 2; _ = a ? // Noncompliant 1 : // Noncompliant 2; _ = a && // Noncompliant b ? // Noncompliant {{The '?' operator should not be at the end of the line.}} 1 : // Noncompliant {{The ':' operator should not be at the end of the line.}} 2; } void Coelesce(object o1, object o2) { _ = o1 ?? o2; _ = o1 ?? // Noncompliant o2; _ = o1 ?? // Noncompliant o2; _ = o1 ?? // Noncompliant o2; _ = o1? .ToString(); } void AsIs(object o) { _ = o as string; _ = o as // Noncompliant string; _ = o as // Noncompliant string; _ = o is string; _ = o is // Noncompliant string; _ = o is // Noncompliant string; } void Pattern(object o1, object o2) { _ = o1 is int or float; _ = o1 is // Noncompliant int or float; _ = o1 is int or // Noncompliant float; _ = o1 is string { Length: > 5 } and // Noncompliant {{The 'and' operator should not be at the end of the line.}} { Length: < 10 }; _ = o2 is string { Length: > 5 } and // Noncompliant { Length: < 10 }; } void Arithmetic(int a, int b, string str) { _ = a + b; _ = a + // Noncompliant b; _ = a + // Noncompliant b; _ = a - b; _ = a - // Noncompliant b; _ = a - // Noncompliant b; _ = a * b; _ = a * // Noncompliant b; _ = a * // Noncompliant b; _ = a / b; _ = a / // Noncompliant b; _ = a / // Noncompliant b; _ = a % b; _ = a % // Noncompliant b; _ = a % // Noncompliant b; _ = str + " text " + str; _ = str + // Noncompliant " text " + str; _ = str + " text " + // Noncompliant str; } void Comparison(int a, int b) { _ = a == b; _ = a == // Noncompliant b; _ = a == // Noncompliant b; _ = a != b; _ = a != // Noncompliant b; _ = a != // Noncompliant b; _ = a < b; _ = a < // Noncompliant b; _ = a < // Noncompliant b; _ = a > b; _ = a > // Noncompliant b; _ = a > // Noncompliant b; _ = a <= b; _ = a <= // Noncompliant b; _ = a <= // Noncompliant b; _ = a >= b; _ = a >= // Noncompliant b; _ = a >= // Noncompliant b; } void Shift(int a, int b) { _ = a << b; _ = a << // Noncompliant b; _ = a << // Noncompliant b; _ = a >> b; _ = a >> // Noncompliant b; _ = a >> // Noncompliant b; _ = a >>> b; _ = a >>> // Noncompliant b; _ = a >>> // Noncompliant b; } // Covered by SA1003 void Unary(int a, bool b) { _ = a++ + 1; _ = +a; _ = + a; _ = -a; _ = - a; _ = ~a; _ = ~ a; _ = ++a; _ = ++ a; _ = --a; _ = -- a; _ = a++; _ = !b; _ = ! b; _ = ^a; _ = ^ a; unsafe { _ = &a; _ = & a; int *p = &a; _ = *p; _ = * p; } } void Range() { _ = 1..2; _ = 1 ..2; _ = 1.. // Noncompliant 2; _ = 1 .. // Noncompliant 2; } void MemberAccess(object o) { _ = o.ToString(); _ = o .ToString(); _ = o. // Noncompliant ToString(); _ = o?.ToString().ToString(); _ = o? .ToString()! .ToString(); _ = o! .ToString()? .ToString(); _ = o?. // Noncompliant {{The '.' operator should not be at the end of the line.}} // ^ ToString()?. // Noncompliant ToString(); _ = o!. // Noncompliant ToString()!. // Noncompliant ToString(); o? . // Noncompliant ToString(); o! . // Noncompliant ToString(); } void QualifiedName() { System.Console.BackgroundColor = System.ConsoleColor.Yellow; System .Console .BackgroundColor = System .ConsoleColor .Yellow; System. // Noncompliant Console. // Noncompliant BackgroundColor = System. // Noncompliant ConsoleColor. // Noncompliant Yellow; } } namespace MyNamespace.With.Some.Dots { } namespace MyNamespace .With .Some .Dots { } namespace MyNamespace. // FN, Roslyn doesn't call us back in this case => we don't care With. // FN, Roslyn doesn't call us back in this case => we don't care Some. // Noncompliant Dots { } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/PropertyOrdering.cs ================================================ public class AllValid { public static int S1 { get; } public static int S2 => 42; public static int S3 { get => 42; } public int I1 { get; } public int I2 => 42; public int I3 { get => 42; } internal static int InternalS { get; } // Compliant internal int InternalI { get; } protected static int ProtectedS { get; } // Compliant protected int ProtectedI { get; } private static int PrivateS { get; } // Compliant private int PrivateI { get; } } public class SomeValid { public static int S1 => 42; public int I1 => 42; public int I2 => 42; public static int S2 => 42; // Noncompliant {{Move this static property above the public instance ones.}} // ^^ public int I3 => 42; // Compliant, there're no other private/protected/internal instance fields above internal static int InternalS { get; } protected static int ProtectedS { get; } protected internal static int ProtectedInternalS { get; } protected private static int ProtectedPrivateS { get; } private static int PrivateS { get; } } public class ProtectedInternal { protected internal int ProtectedI { get; } protected static int ProtectedS { get; } // Noncompliant {{Move this static property above the protected instance ones.}} } public class ProtectedPrivate { protected private int ProtectedI { get; } protected static int ProtectedS { get; } // Noncompliant {{Move this static property above the protected instance ones.}} } public class AllWrong { public int I1 { get; } public int I2 => 42; public int I3 { get => 42; } internal int InternalI { get; } protected int ProtectedI { get; } private int PrivateI { get; } public static int S1 { get; } // Noncompliant {{Move this static property above the public instance ones.}} public static int S2 => 42; // Noncompliant public static int S3 // Noncompliant { get => 42; } internal static int InternalS { get; } // Noncompliant {{Move this static property above the internal instance ones.}} private static int PrivateS { get; } // Noncompliant {{Move this static property above the private instance ones.}} protected static int ProtectedS { get; } // Noncompliant {{Move this static property above the protected instance ones.}} } public record R { public int I => 42; public static int S => 42; // Noncompliant } public record struct RS { public int I => 42; public static int S => 42; // Noncompliant } public struct Str { public int I => 42; public static int S => 42; // Noncompliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/ProtectedFieldsCase.cs ================================================  public class Sample { public int PublicField; private int PrivateField; internal int InternalField; file int FileField; // Error [CS0106] protected const int ConstProtectedField = 42; abstract protected readonly int AbstractReadonlyProtectedField; // Error [CS0681] protected readonly int ReadonlyProtectedField; // Noncompliant protected readonly int ReadonlyProtectedField1, ReadonlyProtectedField2, ReadonlyProtectedField3; // ^^^^^^^^^^^^^^^^^^^^^^^ Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^ Noncompliant@-1 // ^^^^^^^^^^^^^^^^^^^^^^^ Noncompliant@-2 protected int ProtectedField; // Raise by SA1306 private protected int PrivateProtectedField; // Raise by SA1306 protected static int StaticProtectedField; // Raise by SA1306 private protected static int StaticPrivateProtectedField; // Raise by SA1306 protected int MultipleProtectedField1, MultipleProtectedField2, MultipleProtectedField3; // Raise by SA1306 protected static readonly int StaticReadonlyProtectedField; // Conflict with SA1311 protected internal int ProtectedInternalField; // Conflict with SA1307 protected internal static int StaticProtectedInternalField; // Conflict with SA1307 public int publicField; private int privateField; internal int internalField; file int fileField; // Error [CS0106] protected int protectedField; protected int multipleProtectedField1, multipleProtectedField2, multipleProtectedField3; protected internal int protectedInternalField; private protected int privateProtectedField; protected static int staticProtectedField; protected readonly int readonlyProtectedField; protected static readonly int staticReadonlyProtectedField; protected internal static int staticProtectedInternalField; private protected static int staticPrivateProtectedField; } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/SeparateDeclarations.cs ================================================ using System; namespace NS { public class PrecededByParenthesis() { } // Compliant public class Adjacent { } // Noncompliant {{Add an empty line before this declaration.}} // ^^^^^^ } namespace AdjacentNS { } // Noncompliant public abstract class CompliantClassFull { private const string Constant1 = "C1"; private const string Constant2 = "C2"; public enum Enum1 { None, All } public enum Enum2 { None, All } private enum Enum3 { None, Any, All } private readonly object field1 = new(); private static readonly object field2 = new(); private readonly object field3 = new(); private readonly object field4, field5; public abstract int AbstractMethod1(); public abstract int AbstractProperty1 { get; } public abstract int AbstractMethod2(); public abstract int AbstractProperty2 { get; } delegate void SomeDelegate1(); delegate void SomeDelegate2(); public event EventHandler SomeEvent1; public event EventHandler SomeEvent2; public object Property1 { get; } = 42; public object Property2 => 42; public object Property3 => 43; public object Property4 { get => 42; } public object Property5 { get; set; } public object Property6 { get; set; } public object this[int index] => 42; public object this[bool value] => 42; public object this[string name] { get => 42; } public CompliantClassFull() { } private CompliantClassFull(int arg) { } ~CompliantClassFull() { } private void Method1() { } public void Method2() { } public static int operator +(CompliantClassFull a, CompliantClassFull b) => 42; public static int operator -(CompliantClassFull a, CompliantClassFull b) => 42; public static implicit operator int(CompliantClassFull a) => 42; public static explicit operator CompliantClassFull(int a) => null; public class Nested1 { } private struct Nested2 { } public record Nested3 { } protected record struct Nested4 { } public record Nested5 { } public struct Nested6 { } public class Nested7 { } } public abstract class AllWrong { private const string Constant1 = "C1"; private const string Constant2 = "C2"; public enum Enum1 // Noncompliant { None, All } private enum Enum2 // Noncompliant { None, Any, All } private enum Enum3 { None } // Noncompliant private readonly object field1 = new(); // Noncompliant private static readonly object field2 = new(); private readonly object field3 = new(); private readonly object field4, field5; public abstract int AbstractMethod1(); // FN public abstract int AbstractProperty1 { get; } delegate void SomeDelegate1(); // Noncompliant delegate void SomeDelegate2(); public event EventHandler SomeEvent1; // Noncompliant public event EventHandler SomeEvent2; public object Property1 { get; } = 42; // Noncompliant public object Property2 => 42; public object Property3 { get => 42; } public object this[int index] => 42; // Noncompliant public object this[bool value] => 42; public object this[string name] // Noncompliant { get => 42; } public AllWrong() { } // Noncompliant private AllWrong(int arg) { } // Noncompliant ~AllWrong() { } // Noncompliant private void Method1() { } // Noncompliant public void Method2() { } // Noncompliant public static int operator +(AllWrong a, AllWrong b) => 42; // Noncompliant public static int operator -(AllWrong a, AllWrong b) => 42; // Noncompliant public static implicit operator int(AllWrong a) => 42; // Noncompliant public static explicit operator AllWrong(int a) => null; // Noncompliant public class Nested1 { } // Noncompliant private struct Nested2 { } // Noncompliant public record Nested3 { } // Noncompliant protected record struct Nested4 { } // Noncompliant } public class Comments { public class Compliant { } // This is fine public class CompliantSingleSingleLine { } // Missplaced comment // This is not fine, but it is compliant public class CompliantTwoSeparatedSingleLine { } /* * Fine for multiline */ public class CompliantSingleMultiLine1 { } /* And multiline on a single line */ public class CompliantSingleMultiLine2 { } /* And multiline on a single line */ /* Alaso if there are multiple */ public class CompliantMultipleMultiLine { } /// /// Documentation is fine /// public class CompliantDocumentation { } /// /// This is broken, but still compliant /// public class CompliantDocumentationInterrupted { } /** * * There still should be an empty line before the documentation block * */ public class CompliantMultilineDocumentation { } public class ProblemsStartBelowThisLine { } // There still should be empty line before the comment public class SingleSingleLine { } // Noncompliant@-1 // There still should be empty line before the comment // Also if there are multiple public class MultipleSingleLine { } // Noncompliant@-2 /* * Fine for multiline */ public class SingleMultiLine1 { } // Noncompliant@-3 /* And multiline on a single line */ public class SingleMultiLine2 { } // Noncompliant@-1 /* And multiline on a single line */ /* Alaso if there are multiple */ public class MultipleMultiLine { } // Noncompliant@-2 /// /// There still should be an empty line before the documentation block /// public class Documentation { } // Noncompliant@-3 /// /// There still should be an empty line before the documentation block, not inside /// public class InterruptedDocumentation { } // Noncompliant@-4 /** * * There still should be an empty line before the documentation block * */ public class MultilineDocumentation { } // Noncompliant@-5 } public class MultiLines { private int singleLineField1; private int multiLineField1 = // Noncompliant 1 + 1; private int singleLineField2; private int multiLineField2 = 1 + 1; private int singleLineField3; public int SingleLineProperty1 => 42; public int MultiLineProperty1 // Noncompliant { get => 42; } public int SingleLineProperty2 => 42; // Noncompliant public int MultiLineProperty2 { get => 42; } public int SingleLineProperty3 => 42; public event EventHandler SingleLineEvent1; public event EventHandler MultiLineEvent1 // Noncompliant { add { } remove { } } public event EventHandler SingleLineEvent2; // Noncompliant public event EventHandler MultiLineEvent2 { add { } remove { } } public event EventHandler SingleLineEvent3; public object this[int index] => 42; public object this[string name] // Noncompliant { get => 42; } public object this[bool condition] => 42; // Noncompliant public object this[double value] { get => 42; } public object this[decimal value] => 42; } public interface ISomething { void SayHello(); // All compliant int Property { get; } int DoSomething(); int MultiLineProperty { get; set; } void DoNothing(); void DoNothingAtAll(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/TernaryLine.cs ================================================  public class Sample { private bool condition; private object trueBranch, falseBranch; public void Method() { _ = condition && condition ? trueBranch : falseBranch; // ^^^^^^^^^^^^ Noncompliant {{Place branches of the multiline ternary on a separate line.}} // ^^^^^^^^^^^^^ Noncompliant@-1 {{Place branches of the multiline ternary on a separate line.}} _ = condition ? trueBranch : falseBranch; // Noncompliant // ^^^^^^^^^^^^^ _ = condition ? trueBranch // Noncompliant // ^^^^^^^^^^^^ : falseBranch; _ = condition ? trueBranch // Noncompliant .ToString() : falseBranch; _ = condition ? trueBranch : falseBranch; _ = condition ? trueBranch : falseBranch; _ = condition && condition ? trueBranch : falseBranch; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/TypeMemberOrdering.cs ================================================  using System; public class CompliantClassSmall { private const string Constant = "C"; private static readonly object field = new(); public object Property { get; } public void Method() { } } public abstract class CompliantClassFull { private const string Constant1 = "C1"; private const string Constant2 = "C2"; public enum Enum1 { None, All } private enum Enum2 { None, Any, All } private readonly object field1 = new(); private static readonly object field2 = new(); private readonly object field3 = new(); private readonly object field4, field5; public abstract int AbstractMethod1(); public abstract int AbstractProperty1 { get; } public abstract int AbstractMethod2(); public abstract int AbstractProperty2 { get; } delegate void SomeDelegate1(); delegate void SomeDelegate2(); public event EventHandler SomeEvent1; public event EventHandler SomeEvent2; public object Property1 { get; } = 42; public object Property2 => 42; public object Property3 { get => 42; } public object this[int index] => 42; public object this[string name] { get => 42; } public CompliantClassFull() { } private CompliantClassFull(int arg) { } ~CompliantClassFull() { } private void Method1() { } // Compliant, this rule doesn't care about accessibility ordering public void Method2() { } public static int operator +(CompliantClassFull a, CompliantClassFull b) => 42; public static int operator -(CompliantClassFull a, CompliantClassFull b) => 42; public static implicit operator int(CompliantClassFull a) => 42; public static explicit operator CompliantClassFull(int a) => null; public class Nested1 { } // Relative order of these types is not important private struct Nested2 { } public record Nested3 { } protected record struct Nested4 { } public record Nested5 { } public struct Nested6 { } public class Nested7 { } } public class WhenMemberShouldBeFirst { public WhenMemberShouldBeFirst() { } // Secondary [First] {{Move the declaration before this one.}} public void Method() { } private readonly object field; // Noncompliant [First] {{Move Fields before Constructors.}} public class Nested { } } public class WhenMemberShouldBeLast { private readonly object field; public class Nested { } // Secondary [Last2] {{Move the declaration before this one.}} public WhenMemberShouldBeLast() { } // Noncompliant [Last1] {{Move Constructors after Fields, before Methods.}} // Secondary@+1 [Last1] public void Method() { } // Noncompliant [Last2] {{Move Methods after Constructors, before Nested Types.}} } public class WhenMembersAreSwapped { private readonly object field; public void Method() { } // Secondary [Swapped1] {{Move the declaration before this one.}} public WhenMembersAreSwapped() { } // Noncompliant [Swapped1] {{Move Constructors after Fields, before Methods.}} public class Nested { } } public class WhenLessMembersAreSwapped { private const string constant = "C"; public void Method1() { } // Secondary [Swapped2] {{Move the declaration before this one.}} public void Method2() { } // Only this one is out of place public WhenLessMembersAreSwapped() { } // Noncompliant [Swapped2] {{Move Constructors after Constants, before Methods.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^ public class Nested { } } public class InterleavedViolations { private int field1; public int Property1 { get; set; } // Secondary [Interleaved1, Interleaved3] private int field2; // Noncompliant [Interleaved1] {{Move Fields before Properties.}} void Method1() { } // Secondary [Interleaved2, Interleaved4] public int Property2 { get; set; } // Noncompliant [Interleaved2] {{Move Properties after Fields, before Methods.}} void Method2() { } private int field3; // Noncompliant [Interleaved3] {{Move Fields before Properties.}} void Method3() { } public int Property3 { get; set; } // Noncompliant [Interleaved4] {{Move Properties after Fields, before Methods.}} } public abstract class AllWrong { public class Nested { } // Secondary [AllWrongOperator1, AllWrongOperator2, AllWrongOperator3, AllWrongOperator4] // Secondary@+1 [AllWrongDestructor] public void Method() { } // Noncompliant [AllWrongMethod1] {{Move Methods after Destructor, before Operators.}} // ^^^^^^ public void Method2() { } // Noncompliant [AllWrongMethod2] {{Move Methods after Destructor, before Operators.}} // Secondary@+1 [AllWrongMethod1, AllWrongMethod2] This is not very useful, because it's already where it should be. "After" part would be more helpful. public static int operator +(AllWrong a, AllWrong b) => 42; // Noncompliant [AllWrongOperator1] {{Move Operators after Methods, before Nested Types.}} // ^ public static int operator -(AllWrong a, AllWrong b) => 42; // Noncompliant [AllWrongOperator2] {{Move Operators after Methods, before Nested Types.}} public static implicit operator int(AllWrong a) => 42; // Noncompliant [AllWrongOperator3] {{Move Operators after Methods, before Nested Types.}} public static explicit operator AllWrong(int a) => null; // Noncompliant [AllWrongOperator4] {{Move Operators after Methods, before Nested Types.}} // ^^^^^^^^ // Secondary@+1 [AllWrongConstructor] ~AllWrong() { } // Noncompliant [AllWrongDestructor] {{Move Destructor after Constructors, before Methods.}} // ^^^^^^^^ // Secondary@+1 [AllWrongIndexer] public AllWrong() { } // Noncompliant [AllWrongConstructor] {{Move Constructors after Indexers, before Destructor.}} // ^^^^^^^^ // Secondary@+1 [AllWrongField1, AllWrongField2] public abstract int Abstract(); // Noncompliant [AllWrongAbstract] {{Move Abstract Members after Fields, before Properties.}} // ^^^^^^^^ // Secondary@+1 [AllWrongProperty] public object this[int index] => 42; // Noncompliant [AllWrongIndexer] {{Move Indexers after Properties, before Constructors.}} // ^^^^ // Secondary@+1 [AllWrongAbstract] public object Property { get; } // Noncompliant [AllWrongProperty] {{Move Properties after Abstract Members, before Indexers.}} // ^^^^^^^^ // Secondary@+1 [AllWrongEnum] private readonly object field1 = new(); // Noncompliant [AllWrongField1] {{Move Fields after Enums, before Abstract Members.}} // ^^^^^^^^^^^^^^^^^^^^^ private readonly object field2, field3; // Noncompliant [AllWrongField2] {{Move Fields after Enums, before Abstract Members.}} // ^^^^^^^^^^^^^^^^^^^^^ // Secondary@+1 [AllWrongConst] public enum Enum { None, All } // Noncompliant [AllWrongEnum] {{Move Enums after Constants, before Fields.}} // ^^^^ private const string Constant = "C", Answer = "42"; // Noncompliant [AllWrongConst] {{Move Constants before Enums.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } public record R (string Name) { public void DoNothing() { } // Secondary [R] public int field; // Noncompliant [R] {{Move Fields before Methods.}} } public struct S { public void DoNothing() { } // Secondary [S] public int field; // Noncompliant [S] {{Move Fields before Methods.}} } public record struct RS { public void DoNothing() { } // Secondary [RS] public int field; // Noncompliant [RS] {{Move Fields before Methods.}} } public interface ISomething { public void DoNothing(); // Secondary [ISomething] public int Value { get; } // Noncompliant [ISomething] {{Move Properties before Methods.}} } public class AbstractClassValid { public void Go() { } protected class NestedClassBefore(); protected abstract class NestedAbstract { } // Compliant, classes should be at the end, even when abstract protected class NestedClassAfter(); } public class AbstractClassWrong { protected abstract class Nested { } // Secondary [AbstractClass] public void Go() { } // Noncompliant [AbstractClass] } public static class ExtensionBlockCompliant { extension(string s) { public int Property => 0; public void Method() { } } } public static class ExtensionBlockWrong { extension(string s) { public void Method() { } // Secondary [ExtBlock] public int Property => 0; // Noncompliant [ExtBlock] {{Move Properties before Methods.}} } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseDifferentMember.cs ================================================ using System; using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis; internal class UseIsExtension { public void Test(IMethodSymbol methodSymbol) { _ = methodSymbol.IsExtensionMethod; // Noncompliant {{Use 'IsExtension' instead of 'IsExtensionMethod'. It also covers extension methods defined in extension blocks.}} IsExtensionMethod(); // Compliant _ = new Inner().IsExtensionMethod; // Compliant _ = methodSymbol is { IsExtensionMethod: true }; // Noncompliant _ = methodSymbol is { OriginalDefinition.IsExtensionMethod: true }; // Noncompliant // ^^^^^^^^^^^^^^^^^ _ = methodSymbol is { OriginalDefinition.IsExtension: true }; // Compliant } private void IsExtensionMethod() { throw new NotImplementedException(); } class Inner { public bool IsExtensionMethod => true; } } public static class IMethodSymbolExtensions { extension(IMethodSymbol methodSymbol) { public bool IsExtension => true; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseField.cs ================================================ public class Sample { private int field; private int Private { get; set; } // Noncompliant {{Use field instead of this private auto-property.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ protected int Protected { get; set; } // Noncompliant {{Use field instead of this protected auto-property.}} protected static int ProtectedStatic { get; set; } // Noncompliant {{Use field instead of this protected auto-property.}} private protected int PrivateProtected { get; set; } // Noncompliant {{Use field instead of this private protected auto-property.}} private int PrivateReadOnly { get; } // Noncompliant protected int ProtectedReadOnly { get; } // Noncompliant protected static int ProtectedStaticReadOnly { get; } // Noncompliant private protected int PrivateProtectedReadOnly { get; } // Noncompliant // Compliant public int Public { get; set; } internal int Internal { get; set; } internal protected int InternalProtected { get; set; } protected virtual int ProtectedVirtual { get; set; } private int WithArrow { get => field; set => field = value; } private int WithBody { get { return field; } set { field = value; } } } public abstract class Abstract { protected abstract bool IsAbstract { get; set; } } public abstract class Override : Abstract { protected override bool IsAbstract { get; set; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseInnermostRegistrationContext.cs ================================================ using System; using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.Core.AnalysisContext; class UseInnermostRegistrationContext { void SonarAnalysisContext(SonarAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => context.RegisterTreeAction(null, treeAction => { })); // Noncompliant {{Use inner-most registration context 'compilationStart' instead of 'context'.}} // ^^^^^^^ void SonarCompilationStartAnalysisContext(SonarCompilationStartAnalysisContext compilationStart) => compilationStart.RegisterTreeAction(null, tree => _ = compilationStart.Cancel); // Noncompliant {{Use inner-most registration context 'tree' instead of 'compilationStart'.}} // ^^^^^^^^^^^^^^^^ void SonarCodeBlockStartAnalysisContext(SonarCodeBlockStartAnalysisContext sonarCodeBlockStart) => sonarCodeBlockStart.RegisterCodeBlockEndAction(end => _ = sonarCodeBlockStart.CodeBlock); // Noncompliant {{Use inner-most registration context 'end' instead of 'sonarCodeBlockStart'.}} void SonarSymbolStartAnalysisContext(SonarSymbolStartAnalysisContext symbolStart) => symbolStart.RegisterSymbolEndAction(end => _ = symbolStart.Symbol); // Noncompliant {{Use inner-most registration context 'end' instead of 'symbolStart'.}} void SonarParametrizedAnalysisContext(SonarParametrizedAnalysisContext context) => context.RegisterCompilationStartAction(compilationStart => context.RegisterTreeAction(null, treeAction => { })); // Noncompliant {{Use inner-most registration context 'compilationStart' instead of 'context'.}} void NestedAndDuplicated(SonarAnalysisContext context) { context.RegisterCompilationStartAction(compilationStart => { context.RegisterCompilationStartAction(innerCompilationStart => { }); // Noncompliant _ = compilationStart.Cancel; // Compliant compilationStart.RegisterTreeAction(null, treeAction => { context.RegisterTreeAction(null, innerTreeAction => { }); // Noncompliant {{Use inner-most registration context 'treeAction' instead of 'context'.}} _ = compilationStart.Cancel; // Noncompliant _ = treeAction.Cancel; // Compliant }); compilationStart.RegisterCompilationEndAction(end => { compilationStart.RegisterCompilationEndAction(innerEnd => { }); // Noncompliant {{Use inner-most registration context 'end' instead of 'compilationStart'.}} _ = compilationStart.Cancel; // Noncompliant _ = end.Cancel; // Compliant }); compilationStart.RegisterSymbolStartAction(symbolStart => symbolStart.RegisterCodeBlockStartAction(codeBlockStart => codeBlockStart.RegisterNodeAction(node => { context.RegisterTreeAction(null, _ => { }); // Noncompliant {{Use inner-most registration context 'node' instead of 'context'.}} _ = compilationStart.Cancel; // Noncompliant {{Use inner-most registration context 'node' instead of 'compilationStart'.}} _ = symbolStart.Cancel; // Noncompliant {{Use inner-most registration context 'node' instead of 'symbolStart'.}} _ = codeBlockStart.Cancel; // Noncompliant {{Use inner-most registration context 'node' instead of 'codeBlockStart'.}} _ = node.Cancel; // Compliant })), SymbolKind.NamedType); }); } void TwoContextInOutterScope(SonarAnalysisContext context, SonarCompilationStartAnalysisContext compilationStart, int p) { context.RegisterCompilationStartAction(innerCompilationStart => { }); // Compliant _ = compilationStart.Cancel; // Compliant context.RegisterCompilationStartAction(_ => context.RegisterTreeAction(null, treeAction => { })); // Noncompliant {{Use inner-most registration context '_' instead of 'context'.}} compilationStart.RegisterTreeAction(null, _ => { context.RegisterTreeAction(null, _ => { }); // Noncompliant {{Use inner-most registration context '_' instead of 'context'.}} var c1 = compilationStart.Cancel; // Noncompliant var c2 = _.Cancel; // Compliant }); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseLinqExtensions.cs ================================================ using System.Collections.Generic; using System.Linq; public class Sample { public IEnumerable Query(IEnumerable list) => from x in list // Noncompliant {{Use IEnumerable extensions instead of the query syntax.}} where x != "test" select x.Length; public IEnumerable Extensions(IEnumerable list) => list.Where(x => x != "test").Select(x => x.Length); } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseNullInsteadOfDefault.cs ================================================ using System; using System.Collections.Generic; class UseNullInsteadOfDefault { void Method() { _ = default; // Error [CS8716] var discarded = default; // Error [CS8716] int integer = default; int x = default, y = 0, z = default(int); var integer2 = default(Int32); object objectReference = default; // Noncompliant {{Use 'null' instead of 'default' for reference types.}} // ^^^^^^^ var objectReference2 = default(object); // Noncompliant {{Use 'null' instead of 'default' for reference types.}} // ^^^^^^^^^^^^^^^ object obj1 = default, obj2 = null, obj3 = default(object); // ^^^^^^^ // ^^^^^^^^^^^^^^^@-1 IEnumerable collection = null; IEnumerable collection2 = default; // Noncompliant IEnumerable collection3 = true ? null : default; // Noncompliant collection = null; collection2 = default; // Noncompliant collection2 = true ? null : default; // Noncompliant _ = default(object) is null; // Noncompliant FP - Do we care? _ = collection is default(IEnumerable); // Noncompliant int? nullableInt = default; // Noncompliant _ = collection == default; // Noncompliant Use(null); Use(default); // Noncompliant switch (collection) { case default(IEnumerable): // Noncompliant break; default: break; } switch (integer) { case default(int): break; default: break; } } void Use(string s) { } void OptionalWithNull(string name = null) { } void OptionalWithDefault(string name = default) { } // Noncompliant void OptionalWithNull(int? name = null) { } void OptionalWithDefault(int name = default) { } void OptionalWithDefault(int? name = default) { } // Noncompliant void GenericMethod() { T t = default; var t2 = default(T); T? t3 = default; T? t4 = default(T); t = default; t = default(T); t3 = default; t3 = default(T); _ = t == default; // Error [CS8761] _ = t3 == default; // Error [CS8761] _ = t is default(T); // Error [CS0150] _ = t3 is default(T); // Error [CS0150] switch (t) { case default(T): // Error [CS0150] break; default: break; } switch (t3) { case default(T): // Error [CS0150] break; default: break; } } void GenericMethodClassConstraint() where T : class { T t = default; // Noncompliant var t2 = default(T); // Noncompliant T? t3 = default; // Noncompliant T? t4 = default(T); // Noncompliant t = default; // Noncompliant t = default(T); // Noncompliant t3 = default; // Noncompliant t3 = default(T); // Noncompliant _ = t == default; // Noncompliant _ = t3 == default; // Noncompliant _ = t3 != default; // Noncompliant _ = t is default(T); // Noncompliant _ = t3 is default(T); // Noncompliant _ = t is not default(T); // Noncompliant _ = t3 is not default(T); // Noncompliant switch (t) { case default(T): // Noncompliant break; default: break; } switch (t3) { case default(T): // Noncompliant break; default: break; } } void GenericMethodStructConstraint() where T : struct { T t = default; var t2 = default(T); T? t3 = default; // Noncompliant } void GenericMethodEnumConstraint() where T : Enum { T t = default; var t2 = default(T); T? t3 = default; // FN } void GenericMethodStructEnumConstraint() where T : struct, Enum { T t = default; var t2 = default(T); T? t3 = default; // Noncompliant T? t4 = default(T); // FN t = default; t = default(T); t3 = default; // Noncompliant t3 = default(T); _ = t == default; // Error [CS8761] _ = t3 == default; // Error [CS0019] _ = t is default(T); // Error [CS0150] _ = t3 is default(T); // Error [CS0150] switch (t) { case default(T): // Error [CS0150] break; default: break; } switch (t3) { case default(T): // Error [CS0150] break; default: break; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UsePositiveLogic.cs ================================================ public class Sample { private bool condition; public object Not(object value) { if (!condition) // Noncompliant {{Swap the branches and use positive condition.}} // ^^^^^^^^^^ { return null; } else { return value; } if (!condition) // Compliant, doesn't have 'else' { return null; } if (!condition) // Compliant, has `else if` { return null; } else if (condition) { return null; } if (!condition) // Compliant, has `else if` { return null; } else if (!condition) // Noncompliant { return null; } else { return value; } if (condition) { return value; } else { return null; } if (!condition) // Compliant, has `else if` { return null; } else if (condition) { return value; } else { return null; } } public object NotEquals(object left, object right) { if (left != right) // Noncompliant { return null; } else { return left; } if (left == right) { return left; } else { return null; } } public object NotEqualsBool(bool left, bool right) { if (left != right) // Noncompliant { return null; } else { return left; } if (left == right) { return left; } else { return null; } } public void Ternary(object value) { _ = !condition // Noncompliant ? null : value; _ = condition ? value : null; } public void NotAndChain(object value) { _ = !condition && (!condition && (!condition)) // Noncompliant ? null : value; _ = condition || condition || condition ? value : null; _ = !condition || !condition || !condition // Noncompliant ? null : value; _ = condition && condition && condition ? value : null; _ = !condition && !(condition || condition && !condition) // Noncompliant, the outer can be inverted ? null : value; _ = condition || condition || (condition && !condition) ? value : null; _ = !condition && condition && !condition // Compliant, there's at least one positive ? null : value; _ = !condition && (!condition || !condition) // Compliant ? null : value; } public void PatternMatching(object value) { _ = value is not null // Noncompliant ? value : null; _ = value is not true // Noncompliant ? value : null; _ = value is not string // Noncompliant ? value : null; _ = value is not string and not int and not bool // Noncompliant ? value : null; _ = value is not string or not int or not bool // Noncompliant ? value : null; _ = value is not string and not int or not bool // Compliant, there's 'or' in the 'and' chain ? value : null; _ = value is null ? null : value; } public void ConditionAndPatternChain_Simple(object value) { _ = !condition && value is not string and not int // Noncompliant ? value : null; _ = !condition || value is not string or not int // Noncompliant ? value : null; _ = !condition && value is not string or not int // Compliant, there's 'or' in the 'and' chain ? value : null; _ = !condition || value is not string and not int // Compliant, there's 'and' in the 'or' chain ? value : null; } public void ConditionAndPatternChain_Long(object value) { _ = !condition && !condition && value is not string and not int and not bool && !condition // Noncompliant ? value : null; _ = !condition || !condition || value is not string or not int or not bool || !condition // Noncompliant ? value : null; _ = !condition && !condition && value is not string and not int and not bool || !condition // Compliant, there's 'or' in the 'and' chain ? value : null; _ = !condition && !condition && value is not string and not int or not bool && !condition // Compliant, there's 'or' in the 'and' chain ? value : null; _ = !condition || !condition || value is not string or not int or not bool && !condition // Compliant, there's 'and' in the 'or' chain ? value : null; _ = !condition || !condition || value is not string or not int and not bool || !condition // Compliant, there's 'and' in the 'or' chain ? value : null; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseRawString.cs ================================================ public class Sample { public void Method() { // Noncompliant@+1 {{Use raw string literal for multiline strings.}} _ = @" Verbatim Multiline"; // Noncompliant@+1 _ = @""" Still Verbatim But mutiline """; // Noncompliant@+1 _ = @"Smallest just to get the EOL "; // Noncompliant@+1 _ = @$"Verbatim {42} Interpolated"; // Noncompliant@+1 _ = $@"Interpolated {42} Verbatim"; _ = """ This is fine """; _ = $""" Interpolated {42} Raw """; _ = $$$""" Interpolated {{{42}}} Raw """; } public void SingleLine() { _ = ""; _ = "Normal"; _ = @"Verbatim"; _ = @"""Still Verbatim"""; _ = """Raw"""; _ = $"Interpolated {42}"; _ = @$"Verbatim {42} Interpolated"; _ = $@"Interpolated {42} Verbatim"; _ = $"""Interpolated {42} Raw"""; _ = $$$"""Interpolated {{{42}}} Raw"""; } public void Binary() { // We don't raise on these _ = "FirstLine" + "Also first Line"; _ = "FirstLine\n" + "Second Line"; _ = "FirstLine\r" + "Second Line"; _ = "FirstLine\n\r" + "Second Line"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseRegexSafeIsMatch.cs ================================================ using System.Text.RegularExpressions; using RealRegex = System.Text.RegularExpressions.Regex; using System; namespace ThisNamespace { class UseRegexSafeIsMatchNonCompliant { private Regex regex; private RealRegex realRegex; void InstanceRegex(string content) { regex.IsMatch(content); // Noncompliant {{Use 'SafeIsMatch' instead.}} // ^^^^^^^ regex.IsMatch(content, 0); // Noncompliant regex.Matches(content); // Noncompliant {{Use 'SafeMatches' instead.}} regex.Matches(content, 0); // Noncompliant regex.Match(content); // Noncompliant {{Use 'SafeMatch' instead.}} regex.Match(content, 0); // Noncompliant realRegex.IsMatch(content); // Noncompliant realRegex.IsMatch(content, 0); // Noncompliant realRegex.Matches(content); // Noncompliant realRegex.Matches(content, 0); // Noncompliant realRegex.Match(content); // Noncompliant realRegex.Match(content, 0); // Noncompliant Regex.IsMatch(content, "pattern"); // Noncompliant {{Use 'SafeRegex.IsMatch' instead.}} // ^^^^^ Regex.IsMatch(content, "pattern", RegexOptions.None); // Noncompliant Regex.Matches(content, "pattern"); // Noncompliant {{Use 'SafeRegex.Matches' instead.}} Regex.Matches(content, "pattern", RegexOptions.None); // Noncompliant Regex.Match(content, "pattern"); // Noncompliant {{Use 'SafeRegex.Match' instead.}} Regex.Match(content, "pattern", RegexOptions.None); // Noncompliant RealRegex.IsMatch(content, "pattern"); // Noncompliant RealRegex.IsMatch(content, "pattern", RegexOptions.None); // Noncompliant RealRegex.Matches(content, "pattern"); // Noncompliant RealRegex.Matches(content, "pattern", RegexOptions.None); // Noncompliant RealRegex.Match(content, "pattern"); // Noncompliant RealRegex.Match(content, "pattern", RegexOptions.None); // Noncompliant } } class UseRegexSafeIsMatchCompliant { private class Regex { public bool IsMatch(string input) => false; public MatchCollection Matches(string input) => null; public Match Match(string input) => null; public static bool IsMatch(string input, string pattern) => false; public static MatchCollection Matches(string input, string pattern) => null; public static Match Match(string input, string pattern) => null; } private Regex regex; void InstanceRegex(string content) { regex.IsMatch(content); regex.Matches(content); regex.Match(content); Regex.IsMatch(content, "pattern"); Regex.Matches(content, "pattern"); Regex.Match(content, "pattern"); } } } namespace OtherNamespace { public static class RegexExtensions { public static bool SafeIsMatch(this Regex regex, string input) => throw new NotImplementedException(); public static Match SafeMatch(this Regex regex, string input) => throw new NotImplementedException(); public static bool SafeIsMatch(this Regex regex, string input, bool timeoutFallback) => throw new NotImplementedException(); public static MatchCollection SafeMatches(this Regex regex, string input) => throw new NotImplementedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseShortName.cs ================================================ using System.Threading; public class Sample { public void SuggestedNames(SyntaxNode node, SyntaxTree tree, SemanticModel model, CancellationToken cancel) { } public void OtherNames(SyntaxNode song, SyntaxTree wood, SemanticModel sculpture, CancellationToken nuke) { } public void LongName1(SyntaxNode syntaxNode) { } // Noncompliant {{Use short name 'node'.}} // ^^^^^^^^^^ public void LongName2(SyntaxNode prefixedSyntaxNode) { } // Noncompliant {{Use short name 'prefixedNode'.}} public void LongName3(SyntaxNode syntaxNodeCount) { } // Noncompliant {{Use short name 'nodeCount'.}} public void LongName4(SyntaxTree syntaxTree) { } // Noncompliant {{Use short name 'tree'.}} public void LongName5(SyntaxTree firstSyntaxTreeCount) { } // Noncompliant {{Use short name 'firstTreeCount'.}} public void LongName6(CancellationToken cancellationToken) { } // Noncompliant {{Use short name 'cancel'.}} private SyntaxNode node; private SyntaxNode otherNode, syntaxNode; // Noncompliant {{Use short name 'node'.}} // ^^^^^^^^^^ private SyntaxTree syntaxTree; // Noncompliant private SemanticModel semanticModel; // Noncompliant public SyntaxNode SyntaxNode { get; } // Noncompliant {{Use short name 'Node'.}} // ^^^^^^^^^^ private SyntaxToken syntaxToken; // Noncompliant {{Use short name 'token'.}} private SyntaxToken SyntaxToken { get; } // Noncompliant {{Use short name 'Token'.}} private SyntaxTrivia syntaxTrivia; // Noncompliant {{Use short name 'trivia'.}} private SyntaxTrivia SyntaxTrivia{ get; } // Noncompliant {{Use short name 'Trivia'.}} private DiagnosticDescriptor diagnosticDescriptor; // Noncompliant {{Use short name 'descriptor'.}} private DiagnosticDescriptor DiagnosticDescriptor { get; } // Noncompliant {{Use short name 'Descriptor'.}} public void TypedDeclarations() { SyntaxNode nodeNode; SyntaxNode otherNode; SyntaxNode node; SyntaxNode syntaxNode; // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename. SyntaxTree syntaxTree; // Noncompliant SemanticModel semanticModel; // Noncompliant CancellationToken cancellationToken; // Noncompliant void SyntaxNode() { } // Not in the scope (for now) } public void VarDeclarations() { var nodeNode = CreateNode(); var otherNode = CreateNode(); var node = CreateNode(); var syntaxNode = CreateNode(); // Noncompliant {{Use short name 'node'.}} If there exist 'node' and 'syntaxNode' in the same scope, both need a rename. // ^^^^^^^^^^ var syntaxTree = CreateTree(); // Noncompliant var semanticModel = CreateModel(); // Noncompliant var cancellationToken = CreateCancel(); // Noncompliant void SyntaxNode() { } // Not in the scope (for now) } public void UnexpectedType(SyntaxNode syntaxTree) // Wrong, but compliant { var semanticModel = CreateNode(); // Wrong, but compliant SemanticModel syntaxNode = null; // Wrong, but compliant } private SyntaxNode CreateNode() => null; private SyntaxTree CreateTree() => null; private SemanticModel CreateModel() => null; private CancellationToken CreateCancel() => default; private class NestedWithPublicFields { public SyntaxNode SyntaxNode; // Noncompliant {{Use short name 'Node'.}} public SyntaxTree SyntaxTree; // Noncompliant public SemanticModel SemanticModel; // Noncompliant } } public class ArrowProperty { public SyntaxNode SyntaxNode => null; // Noncompliant } public class BodyProperty { public SyntaxNode SyntaxNode // Noncompliant { get => null; set { } } } public class Methods { // It does not appy to method names public void SyntaxNode() { } public void SyntaxTree() { } public void SemanticModel() { } public void CancellationToken() { } } public abstract class Base { protected abstract void DoSomething(SyntaxNode syntaxNode); // Noncompliant {{Use short name 'node'.}} } public class Inherited : Base { protected override void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927 { } } public interface IInterface { void DoSomething(SyntaxNode syntaxNode); // Noncompliant } public class Implemented : IInterface { public void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927 { } } public partial class Partial { public partial void DoSomething(SyntaxNode syntaxNode); // Noncompliant } public partial class Partial { public partial void DoSomething(SyntaxNode syntaxNode) // Compliant so we don't contradict S927 { // Implementation } } public class SyntaxNode { } public class SyntaxToken { } public class SyntaxTree { } public class SyntaxTrivia { } public class SemanticModel { } public class DiagnosticDescriptor { } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/TestCases/UseVar.cs ================================================ using System.Collections.Generic; public class UseVar { public static Dictionary field = new(); public static List Property { get; set; } = new(); List FromMethod() => new(); List Method(bool condition, List optional = new()) // Error [CS1736] { field = new(); Property = new(); int uninitializedValueType; List uninitialized; var list = new List(); var array = new string[0]; List multiple1 = new(), multiple2 = new(); List ternary = condition ? new() : new(); IEnumerable canNotInfer = condition ? list : array; var invalidMultiple1 = new List(), invalidMultiple2 = new List(); // Error [CS0819] var nottyped = new(); // Error [CS8754] List noncompliant = new(); // Noncompliant {{Use var.}} // ^^^^^^^^^^^^ List fromInvocation = FromMethod(); // Noncompliant List listRenamed = list; // Noncompliant List propertyRenamed = UseVar.Property; // Noncompliant Dictionary filedRenamed = global::UseVar.field; // Noncompliant List redundant = new List(); // IDE0007 return new(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.CSharp.Styling.Test/packages.lock.json ================================================ { "version": 1, "dependencies": { "net10.0": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0", "Microsoft.TestPlatform.TestHost": "18.4.0" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "17.11.48", "contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ==" }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.11.48", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Composition": "9.0.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.4.0", "Newtonsoft.Json": "13.0.3" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Security.Cryptography.Pkcs": "8.0.1" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { "System.Collections.Immutable": "8.0.0" } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "Internal.SonarAnalyzer.CSharp.Styling": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.0.0, )", "SonarAnalyzer.CSharp.Core": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/IssueReporterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.AnalysisContext.Test; #pragma warning disable CS0618 // Type or member is obsolete [TestClass] public class IssueReporterTest { private readonly Version defaultVersion = new Version("4.9.2"); private readonly DummyDiagnosticReporter reporter = new(); [TestMethod] [DataRow("S1481")] [DataRow("S927")] [DataRow("S4487")] [DataRow("S2696")] [DataRow("S2259")] [DataRow("S1144")] [DataRow("S2325")] [DataRow("S1117")] [DataRow("S1481")] [DataRow("S1871")] [DataRow("S108")] public void ReportIssueCore_DesignTimeDiagnostic_ExcludedRule(string diagnosticId) { ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", true, diagnosticId); reporter.Counter.Should().Be(0); reporter.LastDiagnostic.Should().BeNull(); } [TestMethod] public void ReportIssueCore_DesignTimeDiagnostic_HasMappedPath_True() { var diagnostic = ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", hasMappedPath: true); reporter.Counter.Should().Be(1); reporter.LastDiagnostic.Should().Be(diagnostic); } [TestMethod] public void ReportIssueCore_DesignTimeDiagnostic_HasMappedPath_False() { ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", hasMappedPath: false); reporter.Counter.Should().Be(0); reporter.LastDiagnostic.Should().BeNull(); } [TestMethod] public void ReportIssueCore_DesignTimeDiagnostic_RoslynVersion_1000_0_0() { ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", hasMappedPath: true, roslynVersion: new Version(1000, 0, 0)); reporter.Counter.Should().Be(0); reporter.LastDiagnostic.Should().BeNull(); } [TestMethod] public void ReportIssueCore_DesignTimeDiagnostic_RoslynVersion_Greater_Than_Current() { Version current = new(typeof(SemanticModel).Assembly.GetName().Version.ToString()); Version greaterThanCurrent = new(current.Major, current.Minor, current.Build + 1); ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", hasMappedPath: true, roslynVersion: greaterThanCurrent); reporter.Counter.Should().Be(0); reporter.LastDiagnostic.Should().BeNull(); } [TestMethod] public void ReportIssueCore_DesignTimeDiagnostic_RoslynVersion_Less_Than_Current() { var lessthanCurrent = LessThanCurrent(); var diagnostic = ReportDiagnostic(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", hasMappedPath: true, roslynVersion: lessthanCurrent); reporter.Counter.Should().Be(1); reporter.LastDiagnostic.Should().Be(diagnostic); static Version LessThanCurrent() { var roslynVersion = new Version(typeof(SemanticModel).Assembly.GetName().Version.ToString()); return roslynVersion.Build switch { > 0 => new Version(roslynVersion.Major, roslynVersion.Minor, roslynVersion.Build - 1), _ when roslynVersion.Minor > 0 => new Version(roslynVersion.Major, roslynVersion.Minor - 1), _ => new Version(roslynVersion.Major - 1, 99, 99), }; } } [TestMethod] public void ReportIssueCore_MinimumRoslyVersion_Is_4_9_2() => Assert.AreEqual(defaultVersion, IssueReporter.GetMinimumDesignTimeRoslynVersion()); [TestMethod] [DataRow(null, false)] [DataRow("error", false)] [DataRow("error:", false)] [DataRow(" error:", true)] [DataRow(" error:", true)] [DataRow(" error", false)] [DataRow(" error :", true)] [DataRow(" ErRoR :", true)] [DataRow(" error :", true)] [DataRow(" error ", false)] [DataRow("error foo", false)] [DataRow("error foo:", false)] [DataRow(" error foo:", true)] [DataRow(" error foo :", true)] [DataRow(" error foo:", true)] [DataRow(" error foo :", true)] [DataRow(" errorfoo:", false)] [DataRow(" error foo:", true)] [DataRow(" eRrOr fOo:", true)] [DataRow(" error in foo:", false)] [DataRow(" error in foo :", false)] public void IsTextMatchingVbcErrorPattern_ReturnsExpected(string text, bool expectedResult) => IssueReporter.IsTextMatchingVbcErrorPattern(text).Should().Be(expectedResult); private Diagnostic ReportDiagnostic(string filePath, bool hasMappedPath, string diagnosticId = "id", Version roslynVersion = null) { roslynVersion ??= defaultVersion; var tree = GetTree(filePath); var diagnostic = GetDiagnostic(diagnosticId, tree, hasMappedPath); IssueReporter.SetMinimumDesignTimeRoslynVersion(roslynVersion); IssueReporter.ReportIssueCore( x => true, x => new DummyReportingContext(reporter, x, tree), diagnostic); IssueReporter.SetMinimumDesignTimeRoslynVersion(defaultVersion); return diagnostic; } private static SyntaxTree GetTree(string filePath) { var tree = Substitute.For(); tree.FilePath.Returns(filePath); var root = Substitute.For(null, null, 42); root.Language.Returns(LanguageNames.CSharp); tree.GetRoot().Returns(root); return tree; } private static Diagnostic GetDiagnostic(string diagnosticId, SyntaxTree tree, bool hasMappedPath) { var location = GetLocation(tree, hasMappedPath); var descriptor = new DiagnosticDescriptor(diagnosticId, "title", "message", "category", DiagnosticSeverity.Warning, true); return Diagnostic.Create(descriptor, location); } private static Location GetLocation(SyntaxTree tree, bool hasMappedPath) { var location = Substitute.For(); location.Kind.Returns(LocationKind.ExternalFile); location.SourceTree.Returns(tree); location.GetMappedLineSpan().Returns(GetPosition(hasMappedPath)); return location; } private static FileLinePositionSpan GetPosition(bool hasMappedPath) { // Unfortunately, this is the only way to set hasMappedPath to true for LinePositionSpan. // It's a struct, so it cannot be substituted. // And the property does not have a setter, so we have to use reflection at the constructor level. var ctor = typeof(FileLinePositionSpan).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, [typeof(string), typeof(LinePositionSpan), typeof(bool)], null); return (FileLinePositionSpan)ctor.Invoke([string.Empty, default(LinePositionSpan), hasMappedPath]); } private class DummyReportingContext(DummyDiagnosticReporter reporter, Diagnostic diagnostic, SyntaxTree tree) : ReportingContext(diagnostic, reporter.ReportDiagnostic, null, tree); private class DummyDiagnosticReporter { public int Counter { get; private set; } = 0; public Diagnostic LastDiagnostic { get; private set; } public void ReportDiagnostic(Diagnostic diagnostic) { LastDiagnostic = diagnostic; Counter++; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarCodeBlockReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarCodeBlockReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var codeBlock = SyntaxFactory.Block(); var owningSymbol = Substitute.For(); var model = Substitute.For(); var options = AnalysisScaffolding.CreateOptions(); var context = new CodeBlockAnalysisContext(codeBlock, owningSymbol, model, options, _ => { }, _ => true, cancel); var sut = new SonarCodeBlockReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Tree.Should().BeSameAs(codeBlock.SyntaxTree); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); sut.CodeBlock.Should().BeSameAs(codeBlock); sut.OwningSymbol.Should().BeSameAs(owningSymbol); sut.Model.Should().BeSameAs(model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarCodeBlockStartAnalysisContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarCodeBlockStartAnalysisContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var codeBlock = SyntaxFactory.Block(); var owningSymbol = Substitute.For(); var model = Substitute.For(); var options = AnalysisScaffolding.CreateOptions(); var context = Substitute.For>(codeBlock, owningSymbol, model, options, cancel); var sut = new SonarCodeBlockStartAnalysisContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); sut.CodeBlock.Should().BeSameAs(codeBlock); sut.OwningSymbol.Should().BeSameAs(owningSymbol); sut.Model.Should().BeSameAs(model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarCodeFixContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarCodeFixContextTest { [TestMethod] public async Task SonarCodeFixContext_Properties_ReturnRoslynCodeFixContextProperties() { var document = CreateProject().FindDocument("MyFile.cs"); var tree = await document.GetSyntaxTreeAsync(); var literal = tree.GetRoot().DescendantNodesAndSelf().OfType().Single(); var cancel = new CancellationToken(true); var diagnostic = Diagnostic.Create(new DiagnosticDescriptor("1", "title", "format", "category", DiagnosticSeverity.Hidden, false), literal.GetLocation()); var sonarCodefix = new SonarCodeFixContext(new CodeFixContext(document, diagnostic, (_, _) => { }, cancel)); sonarCodefix.Cancel.Should().Be(cancel); sonarCodefix.Document.Should().Be(document); sonarCodefix.Diagnostics.Should().Contain(diagnostic); sonarCodefix.Span.Should().Be(new TextSpan(18, 13)); } [TestMethod] public void SonarCodeFixContext_RegisterDocumentCodeFix_CodeFixRegistered() { var isActionRegistered = false; var document = CreateProject().FindDocument("MyFile.cs"); var diagnostic = Diagnostic.Create(new DiagnosticDescriptor("1", "title", "format", "category", DiagnosticSeverity.Hidden, false), Location.None); var sonarCodefix = new SonarCodeFixContext(new CodeFixContext(document, diagnostic, (_, _) => isActionRegistered = true, CancellationToken.None)); sonarCodefix.RegisterCodeFix("Title", _ => Task.FromResult(document), [diagnostic]); isActionRegistered.Should().BeTrue(); } [TestMethod] public void SonarCodeFixContext_RegisterSolutionCodeFix_CodeFixRegistered() { var isActionRegistered = false; var document = CreateProject().FindDocument("MyFile.cs"); var diagnostic = Diagnostic.Create(new DiagnosticDescriptor("1", "title", "format", "category", DiagnosticSeverity.Hidden, false), Location.None); var sonarCodefix = new SonarCodeFixContext(new CodeFixContext(document, diagnostic, (_, _) => isActionRegistered = true, CancellationToken.None)); sonarCodefix.RegisterCodeFix("Title", _ => Task.FromResult(new AdhocWorkspace().CurrentSolution), [diagnostic]); isActionRegistered.Should().BeTrue(); } private static ProjectBuilder CreateProject() => SolutionBuilder.Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(@"Console.WriteLine(""Hello World"")", "MyFile.cs"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarCompilationReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarCompilationReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var compilation = TestCompiler.CompileCS("// Nothing to see here").Model.Compilation; var options = AnalysisScaffolding.CreateOptions(); var context = new CompilationAnalysisContext(compilation, options, _ => { }, _ => true, cancel); var sut = new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Compilation.Should().BeSameAs(compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarCompilationStartAnalysisContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarCompilationStartAnalysisContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var compilation = TestCompiler.CompileCS("// Nothing to see here").Model.Compilation; var options = AnalysisScaffolding.CreateOptions(); var context = Substitute.For(compilation, options, cancel); var sut = new SonarCompilationStartAnalysisContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Compilation.Should().BeSameAs(compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarSemanticModelReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarSemanticModelReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var (tree, model) = TestCompiler.CompileCS("// Nothing to see here"); var options = AnalysisScaffolding.CreateOptions(); var context = new SemanticModelAnalysisContext(model, options, _ => { }, _ => true, cancel); var sut = new SonarSemanticModelReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Tree.Should().BeSameAs(tree); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Model.Should().BeSameAs(model); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } [TestMethod] public void RegistrationIsExecuted_SonarAnalysisContext_CS() => new VerifierBuilder().AddAnalyzer(() => new TestAnalyzerCS((context, g) => context.RegisterSemanticModelAction(g, c => c.ReportIssue(TestAnalyzer.Rule, c.Tree.GetRoot().GetFirstToken())))) .AddSnippet(""" using System; // Noncompliant """) .Verify(); [TestMethod] public void RegistrationIsNotExecuted_SonarAnalysisContext_TestScope_CS() => new VerifierBuilder() .AddTestReference() .AddAnalyzer(() => new TestAnalyzerCS((context, g) => context.RegisterSemanticModelAction(g, c => Assert.Fail("Registration should not happen")))) .AddSnippet(""" using System; """) .VerifyNoIssues(); [TestMethod] public void RegistrationIsNotExecuted_SonarAnalysisContext_GeneratedCode_CS() => new VerifierBuilder() .AddAnalyzer(() => new TestAnalyzerCS((context, g) => context.RegisterSemanticModelAction(g, c => Assert.Fail("Action should not be called")))) .AddSnippet(""" // using System; """) .VerifyNoIssues(); [TestMethod] public void RegistrationIsExecuted_SonarAnalysisContext_VB() => new VerifierBuilder().AddAnalyzer(() => new TestAnalyzerVB((context, g) => context.RegisterSemanticModelAction(g, c => c.ReportIssue(TestAnalyzer.Rule, c.Tree.GetRoot().GetFirstToken())))) .AddSnippet(""" Imports System ' Noncompliant """) .Verify(); [TestMethod] [CombinatorialData] public void RegistrationInAllFilesIsExecuted_SonarCompilationStartAnalysisContext_CS( [CombinatorialValues("", "// ")] string autoGenerated, [CombinatorialValues(true, false)] bool testScope) { var verifier = new VerifierBuilder().AddAnalyzer(() => new TestAnalyzerCS((context, _) => context.RegisterCompilationStartAction(start => start.RegisterSemanticModelActionInAllFiles(c => { if (c.Tree.GetRoot().GetFirstToken() is { RawKind: not (int)CS.SyntaxKind.None } token) { c.ReportIssue(TestAnalyzer.Rule, token); } })))) .AddSnippet($$""" {{autoGenerated}} using System; // Noncompliant """); if (testScope) { verifier.AddTestReference().VerifyNoIssues(); } else { verifier.Verify(); } } [TestMethod] public void RegistrationIsNotExecuted_SonarCompilationStartAnalysisContext_TestScope_CS() => new VerifierBuilder() .AddTestReference() .AddAnalyzer(() => new TestAnalyzerCS((context, generatedCodeRecognizer) => context.RegisterCompilationStartAction(start => start.RegisterSemanticModelAction(generatedCodeRecognizer, c => Assert.Fail("Registration should not be done"))))) .AddSnippet(""" using System; """) .VerifyNoIssues(); [TestMethod] public void RegistrationIsNotExecuted_SonarCompilationStartAnalysisContext_GeneratedCode_CS() => new VerifierBuilder().AddAnalyzer(() => new TestAnalyzerCS((context, generatedCodeRecognizer) => context.RegisterCompilationStartAction(start => start.RegisterSemanticModelAction(generatedCodeRecognizer, c => Assert.Fail("Action should not be called"))))) .AddSnippet(""" // using System; """) .VerifyNoIssues(); [TestMethod] public void RegistrationIsExecuted_SonarCompilationStartAnalysisContext_VB() => new VerifierBuilder().AddAnalyzer(() => new TestAnalyzerVB((context, _) => context.RegisterCompilationStartAction(start => start.RegisterSemanticModelActionInAllFiles(c => { if (c.Tree.GetRoot().GetFirstToken() is { RawKind: not (int)VB.SyntaxKind.None } token) { c.ReportIssue(TestAnalyzer.Rule, token); } })))) .AddSnippet(""" Imports System ' Noncompliant """) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarSymbolReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarSymbolReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var (tree, model) = TestCompiler.CompileCS("public class Sample { }"); var options = AnalysisScaffolding.CreateOptions(); var symbol = model.GetDeclaredSymbol(tree.Single()); var context = new SymbolAnalysisContext(symbol, model.Compilation, options, _ => { }, _ => true, cancel); var sut = new SonarSymbolReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); sut.Symbol.Should().BeSameAs(symbol); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarSyntaxNodeReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarSyntaxNodeReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var (tree, model) = TestCompiler.CompileCS("// Nothing to see here"); var node = tree.GetRoot(); var options = AnalysisScaffolding.CreateOptions(); var containingSymbol = Substitute.For(); var context = new SyntaxNodeAnalysisContext(node, containingSymbol, model, options, _ => { }, _ => true, cancel); var sut = new SonarSyntaxNodeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.Tree.Should().BeSameAs(tree); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); sut.Node.Should().BeSameAs(node); sut.Model.Should().BeSameAs(model); sut.ContainingSymbol.Should().BeSameAs(containingSymbol); } #if NET // .NET Fx shows the message box directly, the exception cannot be caught [TestMethod] [DataRow(true)] // Purpose of this is to make sure that the scaffolding works successfully end-to-end [DataRow(false)] public void ReportIssue_TreeNotInCompilation_DoNotReport(bool reportOnCorrectTree) { var analysisContext = AnalysisScaffolding.CreateSonarAnalysisContext(); var (tree, model) = TestCompiler.CompileCS("// Nothing to see here"); var nodeFromCorrectCompilation = tree.GetRoot(); var nodeFromAnotherCompilation = TestCompiler.CompileCS("// This is another Compilation with another Tree").Tree.GetRoot(); var rule = AnalysisScaffolding.CreateDescriptorMain(); var node = tree.GetRoot(); var wasReported = false; var context = new SyntaxNodeAnalysisContext(node, model, AnalysisScaffolding.CreateOptions(), _ => wasReported = true, _ => true, default); var sut = new SonarSyntaxNodeReportingContext(analysisContext, context); try { sut.ReportIssue(rule, reportOnCorrectTree ? nodeFromCorrectCompilation : nodeFromAnotherCompilation); } catch (Exception ex) // Can't catch internal DebugAssertException { if (reportOnCorrectTree) { throw; // This should not happen => fail the test } else { ex.GetType().Name.Should().BeOneOf("AssertionException", "DebugAssertException"); ex.Message.Should().Contain("Primary location should be part of the compilation. An AD0001 is raised if this is not the case."); } } wasReported.Should().Be(reportOnCorrectTree); } #endif [TestMethod] [DataRow("class")] #if NET [DataRow("record")] #endif public void IsRedundantPrimaryConstructorBaseTypeContext_ReturnsTrueForTypeDeclaration(string type) { // For Roslyn < 4.9.2, the node action is called either twice with different ContainingSymbol. var snippet = $$$""" public {{{type}}} Base(int i); public {{{type}}} Derived(int i) : Base(i); // This is the node that is asserted // ^^^^^^^ {{IsRedundantPrimaryConstructorBaseTypeContext is False, ContainingSymbol is Method Derived.Derived(int)}} """; new VerifierBuilder() .AddAnalyzer(() => new TestAnalyzer([SyntaxKindEx.PrimaryConstructorBaseType], c => $"IsRedundantPrimaryConstructorBaseTypeContext is {c.IsRedundantPrimaryConstructorBaseTypeContext()}, ContainingSymbol is {c.ContainingSymbol.Kind} {c.ContainingSymbol.ToDisplayString()}")) .WithOptions(LanguageOptions.FromCSharp12) .AddSnippet(snippet) .Verify(); } [DiagnosticAnalyzer(LanguageNames.CSharp)] private sealed class TestAnalyzer : SonarDiagnosticAnalyzer { private readonly SyntaxKind[] syntaxKinds; private readonly Func message; public DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, new RuleDescriptor("Test", "Test", "BUG", "BLOCKER", "READY", SourceScope.All, true, "Test"), "{0}", true, false, false); public override ImmutableArray SupportedDiagnostics => [Rule]; public TestAnalyzer(SyntaxKind[] syntaxKinds, Func message) { this.syntaxKinds = syntaxKinds; this.message = message; } protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeAction(TestGeneratedCodeRecognizer.Instance, c => c.ReportIssue(Rule, c.Node, message(c)), syntaxKinds); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/AnalysisContext/SonarSyntaxTreeReportingContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.AnalysisContext.Test; [TestClass] public class SonarSyntaxTreeReportingContextTest { [TestMethod] public void Properties_ArePropagated() { var cancel = new CancellationToken(true); var (tree, model) = TestCompiler.CompileCS("// Nothing to see here"); var options = AnalysisScaffolding.CreateOptions(); var context = new SyntaxTreeAnalysisContext(tree, options, _ => { }, _ => true, cancel); var sut = new SonarSyntaxTreeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context, model.Compilation); sut.Tree.Should().BeSameAs(tree); sut.Compilation.Should().BeSameAs(model.Compilation); sut.Options.Should().BeSameAs(options); sut.Cancel.Should().Be(cancel); } [TestMethod] public void ReportIssue_IgnoreSecondaryLocationOutsideCompilation() { Diagnostic lastDiagnostic = null; var (tree, model) = TestCompiler.CompileCS("using System;"); var secondaryTree = TestCompiler.CompileCS("namespace Nothing;").Tree; // This tree is not in the analyzed compilation var context = new SyntaxTreeAnalysisContext(tree, AnalysisScaffolding.CreateOptions(), x => lastDiagnostic = x, _ => true, default); var sut = new SonarSyntaxTreeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context, model.Compilation); var rule = AnalysisScaffolding.CreateDescriptor("Sxxxx", DiagnosticDescriptorFactory.MainSourceScopeTag); sut.ReportIssue(rule, tree.GetRoot(), [new SecondaryLocation(tree.GetRoot().GetLocation(), null)]); lastDiagnostic.Should().NotBeNull(); lastDiagnostic.Id.Should().Be("Sxxxx"); lastDiagnostic.AdditionalLocations.Should().ContainSingle("This secondary location is in compilation"); sut.ReportIssue(rule, tree.GetRoot(), [new SecondaryLocation(secondaryTree.GetRoot().GetLocation(), null)]); lastDiagnostic.Should().NotBeNull(); lastDiagnostic.Id.Should().Be("Sxxxx"); lastDiagnostic.AdditionalLocations.Should().BeEmpty("This secondary location is outside the compilation"); } [TestMethod] public void ReportIssue_NullArguments_Throws() { var compilation = TestCompiler.CompileCS("// Nothing to see here").Model.Compilation; var context = new SyntaxTreeAnalysisContext(compilation.SyntaxTrees.First(), new AnalyzerOptions([]), _ => { }, _ => true, CancellationToken.None); var sut = new SonarSyntaxTreeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context, compilation); var rule = AnalysisScaffolding.CreateDescriptor("Sxxxx", DiagnosticDescriptorFactory.MainSourceScopeTag); sut.Invoking(x => x.ReportIssue(null, primaryLocation: null, secondaryLocations: [])).Should().Throw().And.ParamName.Should().Be("rule"); sut.Invoking(x => x.ReportIssue(rule, primaryLocation: null, secondaryLocations: null)).Should().NotThrow(); sut.Invoking(x => x.ReportIssue(rule, primaryLocation: null, secondaryLocations: [], properties: null)).Should().NotThrow(); } [TestMethod] public void ReportIssue_PropertiesAndSecondaryLocations_Combine() { Diagnostic lastDiagnostic = null; var (tree, model) = TestCompiler.CompileCS("using System;"); var context = new SyntaxTreeAnalysisContext(tree, AnalysisScaffolding.CreateOptions(), x => lastDiagnostic = x, _ => true, default); var sut = new SonarSyntaxTreeReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context, model.Compilation); var rule = AnalysisScaffolding.CreateDescriptor("Sxxxx", DiagnosticDescriptorFactory.MainSourceScopeTag); var secondaryLocation = new SecondaryLocation(tree.GetRoot().GetLocation(), "secondary"); sut.ReportIssue(rule, tree.GetRoot().GetLocation(), [secondaryLocation], properties: new[] { "custom property"}.ToImmutableDictionary(x => x)); lastDiagnostic.Should().NotBeNull(); lastDiagnostic.Id.Should().Be("Sxxxx"); lastDiagnostic.Properties.Should().HaveCount(2) .And.Contain(new KeyValuePair("custom property", "custom property")).And.Contain(new KeyValuePair("0", "secondary")); lastDiagnostic.AdditionalLocations.Should().ContainSingle("secondary"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Analyzers/DiagnosticDescriptorFactoryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Analyzers; [TestClass] public class DiagnosticDescriptorFactoryTest { [TestMethod] public void GetUtilityDescriptor_Should_Contain_NotConfigurable_CustomTag() { var result = DiagnosticDescriptorFactory.CreateUtility("Sxxx", "Title"); #if DEBUG result.CustomTags.Should().NotContain(WellKnownDiagnosticTags.NotConfigurable); #else result.CustomTags.Should().Contain(WellKnownDiagnosticTags.NotConfigurable); #endif } [TestMethod] public void Create_ConfiguresProperties_CS() { var result = DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, CreateRuleDescriptor(SourceScope.Main, true), "Sxxxx Message", null, false, false); result.Id.Should().Be("Sxxxx"); result.Title.ToString().Should().Be("Sxxxx Title"); result.MessageFormat.ToString().Should().Be("Sxxxx Message"); result.Category.Should().Be("Major Bug"); result.DefaultSeverity.Should().Be(DiagnosticSeverity.Warning); result.IsEnabledByDefault.Should().BeTrue(); result.Description.ToString().Should().Be("Sxxxx Description"); result.HelpLinkUri.Should().Be(string.Empty); result.CustomTags.Should().BeEquivalentTo(LanguageNames.CSharp, DiagnosticDescriptorFactory.MainSourceScopeTag, DiagnosticDescriptorFactory.SonarWayTag); } [TestMethod] public void Create_ConfiguresProperties_VB() { var result = DiagnosticDescriptorFactory.Create(AnalyzerLanguage.VisualBasic, CreateRuleDescriptor(SourceScope.Main, true), "Sxxxx Message", null, false, false); result.Id.Should().Be("Sxxxx"); result.Title.ToString().Should().Be("Sxxxx Title"); result.MessageFormat.ToString().Should().Be("Sxxxx Message"); result.Category.Should().Be("Major Bug"); result.DefaultSeverity.Should().Be(DiagnosticSeverity.Warning); result.IsEnabledByDefault.Should().BeTrue(); result.Description.ToString().Should().Be("Sxxxx Description"); result.HelpLinkUri.Should().Be(string.Empty); result.CustomTags.Should().BeEquivalentTo(LanguageNames.VisualBasic, DiagnosticDescriptorFactory.MainSourceScopeTag, DiagnosticDescriptorFactory.SonarWayTag); } [TestMethod] public void Create_FadeOutCode_HasUnnecessaryTag_HasInfoSeverity() { var result = DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, CreateRuleDescriptor(SourceScope.Main, true), "Sxxxx Message", null, true, false); result.DefaultSeverity.Should().Be(DiagnosticSeverity.Info); result.CustomTags.Should().Contain(WellKnownDiagnosticTags.Unnecessary); } [TestMethod] [CombinatorialData] public void Create_CompilationEndDiagnostic([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string language, [CombinatorialValues(true, false)] bool isCompilationEnd) { var result = DiagnosticDescriptorFactory.Create(AnalyzerLanguage.FromName(language), CreateRuleDescriptor(SourceScope.Main, true), "Sxxxx Message", null, false, isCompilationEnd); if (isCompilationEnd) { result.CustomTags.Should().Contain(DiagnosticDescriptorFactory.CompilationEnd); } else { result.CustomTags.Should().NotContain(DiagnosticDescriptorFactory.CompilationEnd); } } [TestMethod] public void Create_HasCorrectSonarWayTag() { CreateTags(true).Should().Contain(DiagnosticDescriptorFactory.SonarWayTag); CreateTags(false).Should().NotContain(DiagnosticDescriptorFactory.SonarWayTag); static IEnumerable CreateTags(bool sonarWay) => DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, CreateRuleDescriptor(SourceScope.Main, sonarWay), "Sxxxx Message", null, false, false).CustomTags; } [TestMethod] public void Create_HasCorrectScopeTags() { CreateTags(SourceScope.Main).Should().Contain(DiagnosticDescriptorFactory.MainSourceScopeTag).And.NotContain(DiagnosticDescriptorFactory.TestSourceScopeTag); CreateTags(SourceScope.Tests).Should().Contain(DiagnosticDescriptorFactory.TestSourceScopeTag).And.NotContain(DiagnosticDescriptorFactory.MainSourceScopeTag); CreateTags(SourceScope.All).Should().Contain(DiagnosticDescriptorFactory.MainSourceScopeTag, DiagnosticDescriptorFactory.TestSourceScopeTag); static IEnumerable CreateTags(SourceScope scope) => DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, CreateRuleDescriptor(scope, true), "Sxxxx Message", null, false, false).CustomTags; } [TestMethod] public void Create_UnexpectedType_Throws() { var rule = new RuleDescriptor("Sxxxx", string.Empty, "Lorem Ipsum", string.Empty, string.Empty, SourceScope.Main, true, string.Empty); var f = () => DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, rule, string.Empty, null, false, false); f.Should().Throw().WithMessage("Unexpected Type value: Lorem Ipsum"); } [TestMethod] [DataRow("Minor", "BUG", "Minor Bug")] [DataRow("Major", "BUG", "Major Bug")] [DataRow("Major", "CODE_SMELL", "Major Code Smell")] [DataRow("Major", "VULNERABILITY", "Major Vulnerability")] [DataRow("Major", "SECURITY_HOTSPOT", "Major Security Hotspot")] [DataRow("Critical", "BUG", "Critical Bug")] [DataRow("Blocker", "BUG", "Blocker Bug")] [DataRow("Whatever Xxx", "BUG", "Whatever Xxx Bug")] public void Create_ComputesCategory(string severity, string type, string expected) { var rule = new RuleDescriptor("Sxxxx", string.Empty, type, severity, string.Empty, SourceScope.Main, true, string.Empty); DiagnosticDescriptorFactory.Create(AnalyzerLanguage.CSharp, rule, "Sxxxx Message", null, false, false).Category.Should().Be(expected); } private static RuleDescriptor CreateRuleDescriptor(SourceScope scope, bool sonarWay) => new("Sxxxx", "Sxxxx Title", "BUG", "Major", string.Empty, scope, sonarWay, "Sxxxx Description"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/AnalyzerAdditionalFileTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class AnalyzerAdditionalFileTest { public TestContext TestContext { get; set; } [TestMethod] public void AnalyzerAdditionalFile_GetText() { var path = TestFiles.WriteFile(TestContext, "AdditionalFile.txt", "some sample content"); var additionalFile = new AnalyzerAdditionalFile(path); var content = additionalFile.GetText(); content.ToString().Should().Be("some sample content"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/AnalyzerConfigurationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Text; using static SonarAnalyzer.Core.Common.AnalyzerConfiguration; namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class AnalyzerConfigurationTest { private const string FirstSonarLintFilePath = @"bar\SonarLint.xml"; private const string FirstSonarLintFileContent = "fake SonarLintXml content 1"; private const string FirstRuleId = "S0000"; private const string SecondSonarLintFilePath = @"qix\SonarLint.xml"; private const string SecondSonarLintFileContent = "fake SonarLintXml content 2"; private const string SecondRuleId = "S9999"; private IRuleLoader ruleLoader; [TestInitialize] public void Initialize() { ruleLoader = Substitute.For(); ruleLoader.GetEnabledRules(FirstSonarLintFileContent).Returns(new HashSet { FirstRuleId }); ruleLoader.GetEnabledRules(SecondSonarLintFileContent).Returns(new HashSet { SecondRuleId }); } [TestMethod] public void AlwaysEnabled_WhenNotInitialized_ReturnsTrue() => AlwaysEnabled.IsEnabled("S101").Should().BeTrue(); [TestMethod] public void AlwaysEnabled_AnyValue_ReturnsTrue() { AlwaysEnabled.IsEnabled(null).Should().BeTrue(); AlwaysEnabled.IsEnabled(string.Empty).Should().BeTrue(); AlwaysEnabled.IsEnabled("foo").Should().BeTrue(); } [TestMethod] public void ForceSonarCfg_DisabledByDefault() { AlwaysEnabled.ForceSonarCfg.Should().BeFalse(); new HotspotConfiguration(ruleLoader).ForceSonarCfg.Should().BeFalse(); } [TestMethod] public void ForceSonarCfg_DisabledByDefault_ExistExceptionalConfig() { AlwaysEnabled.ForceSonarCfg.Should().BeFalse(); new HotspotConfiguration(ruleLoader).ForceSonarCfg.Should().BeFalse(); AlwaysEnabledWithSonarCfg.ForceSonarCfg.Should().BeTrue(); } [TestMethod] public void AlwaysEnabled_IgnoresInitialize() { var sut = AlwaysEnabled; sut.Initialize(null); sut.IsEnabled(FirstRuleId).Should().BeTrue(); } [TestMethod] public void HotspotConfiguration_WhenInitializeIsCalledWithDifferentSonarLintPaths_UpdatesEnabledRules() { var sut = new HotspotConfiguration(ruleLoader); // act Initialize(sut, FirstSonarLintFilePath, FirstSonarLintFileContent); // assert sut.IsEnabled(FirstRuleId).Should().BeTrue(); sut.IsEnabled(SecondRuleId).Should().BeFalse(); // act Initialize(sut, SecondSonarLintFilePath, SecondSonarLintFileContent); // assert sut.IsEnabled(FirstRuleId).Should().BeFalse(); sut.IsEnabled(SecondRuleId).Should().BeTrue(); ruleLoader.Received(1).GetEnabledRules(FirstSonarLintFileContent); ruleLoader.Received(1).GetEnabledRules(SecondSonarLintFileContent); } [TestMethod] public void HotspotConfiguration_WhenInitializedTwiceWithTheSameFile_DoesNotUpdateEnabledRules() { var sut = new HotspotConfiguration(ruleLoader); // act Initialize(sut, FirstSonarLintFilePath, FirstSonarLintFileContent); Initialize(sut, FirstSonarLintFilePath, FirstSonarLintFileContent); // assert ruleLoader.Received(1).GetEnabledRules(Arg.Any()); ruleLoader.Received(1).GetEnabledRules(FirstSonarLintFileContent); } [TestMethod] public void HotspotConfiguration_WhenInitializeIsSecondTimeWithNonSonarLint_DoesNotUpdateEnabledRules() { var sut = new HotspotConfiguration(ruleLoader); // act Initialize(sut, FirstSonarLintFilePath, FirstSonarLintFileContent); Initialize(sut, "Foo.xml", "fake SonarLintXml content"); // assert ruleLoader.Received(1).GetEnabledRules(Arg.Any()); ruleLoader.Received(1).GetEnabledRules(FirstSonarLintFileContent); } [TestMethod] public void HotspotConfiguration_WhenIsEnabledWithoutInitialized_ThrowException() { var sut = new HotspotConfiguration(ruleLoader); sut.Invoking(x => x.IsEnabled(string.Empty)).Should().Throw().WithMessage("Call Initialize() before calling IsEnabled()."); } [TestMethod] public void HotspotConfiguration_WhenIsInitializedWithNull_ThrowsException() { var sut = new HotspotConfiguration(ruleLoader); sut.Invoking(x => x.Initialize(null)).Should().Throw(); } [TestMethod] public void HotspotConfiguration_GivenDifferentFileName_WillNotFinishInitialization() { var sut = new HotspotConfiguration(ruleLoader); Initialize(sut, "FooBarSonarLint.xml", "fake SonarLintXml content"); ruleLoader.DidNotReceive().GetEnabledRules(Arg.Any()); } private static void Initialize(HotspotConfiguration sut, string path, string content) => sut.Initialize(new AnalyzerOptions(GetAdditionalFiles(path, content))); private static ImmutableArray GetAdditionalFiles(string path, string content) { var additionalText = Substitute.For(); additionalText.Path.Returns(path); additionalText.GetText(default).Returns(SourceText.From(content)); return [additionalText]; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/AnalyzerLanguageTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class AnalyzerLanguageTest { [TestMethod] public void ToString_ReturnsLanguageName() { AnalyzerLanguage.CSharp.ToString().Should().Be("C#"); AnalyzerLanguage.VisualBasic.ToString().Should().Be("Visual Basic"); } [TestMethod] [DataRow("File.cs")] [DataRow("File.Cs")] [DataRow("File.CS")] [DataRow("File.razor")] [DataRow(@"C:\Project\File.cs")] [DataRow(@"/c/Project/File.cs")] [DataRow(@"/c/Project/File.razor")] public void FromPath_CS(string path) => AnalyzerLanguage.FromPath(path).Should().Be(AnalyzerLanguage.CSharp); [TestMethod] [DataRow("File.vb")] [DataRow("File.Vb")] [DataRow("File.VB")] [DataRow(@"C:\Project\File.vb")] [DataRow(@"/c/Project/File.vb")] public void FromPath_VB(string path) => AnalyzerLanguage.FromPath(path).Should().Be(AnalyzerLanguage.VisualBasic); [TestMethod] [DataRow("File.txt", ".txt")] [DataRow("", "")] [DataRow(null, "")] public void FromPath_UnexpectedThrows(string path, string message) { var action = () => AnalyzerLanguage.FromPath(path); action.Should().Throw().WithMessage($"Unsupported file extension: {message}"); } [TestMethod] public void FromName() { AnalyzerLanguage.FromName(LanguageNames.CSharp).Should().Be(AnalyzerLanguage.CSharp); AnalyzerLanguage.FromName(LanguageNames.VisualBasic).Should().Be(AnalyzerLanguage.VisualBasic); } [TestMethod] public void FromName_UnexpectedThrows() => ((Func)(() => AnalyzerLanguage.FromName(LanguageNames.FSharp))).Should().Throw().WithMessage("Unsupported language name: F#"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/BidirectionalDictionaryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class BidirectionalDictionaryTest { private BidirectionalDictionary sut; [TestInitialize] public void Initialize() => sut = new BidirectionalDictionary(); [TestMethod] public void AddKeyA_AlreadyExists_Throws() { sut.Add(1, 2); sut.Invoking(x => x.Add(1, 3)).Should().Throw().WithMessage("An element with the same key already exists in the BidirectionalDictionary."); } [TestMethod] public void AddKeyB_AlreadyExists_Throws() { sut.Add(1, 2); sut.Invoking(x => x.Add(3, 2)).Should().Throw().WithMessage("An element with the same key already exists in the BidirectionalDictionary."); } [TestMethod] public void Keys_ReturnsCorrectKeys() { sut.Add(1, 11); sut.Add(2, 12); sut.Add(3, 13); sut.Add(4, 14); sut.Add(5, 15); sut.AKeys.Should().BeEquivalentTo(new[] {1, 2, 3, 4, 5}); sut.BKeys.Should().BeEquivalentTo(new[] {11, 12, 13, 14, 15}); } [TestMethod] public void GetByA_ElementExists_ReturnsCorrectElement() { sut.Add(1, 2); sut.GetByA(1).Should().Be(2); } [TestMethod] public void GetByB_ElementExists_ReturnsCorrectElement() { sut.Add(1, 2); sut.GetByB(2).Should().Be(1); } [TestMethod] public void GetByA_ElementDoesNotExist_ThrowsException() => sut.Invoking(x => x.GetByA(1)).Should().Throw(); [TestMethod] public void GetByB_ElementDoesNotExist_ThrowsException() => sut.Invoking(x => x.GetByB(1)).Should().Throw(); [TestMethod] public void ContainsKeyByA_ElementExists_ReturnsTrue() { sut.Add(4, 2); sut.ContainsKeyByA(4).Should().BeTrue(); } [TestMethod] public void ContainsKeyByB_ElementExists_ReturnsTrue() { sut.Add(4, 2); sut.ContainsKeyByB(2).Should().BeTrue(); } [TestMethod] public void ContainsKeyByA_ElementDoesNotExist_ReturnsFalse() { sut.Add(2, 3); sut.ContainsKeyByA(1).Should().BeFalse(); } [TestMethod] public void ContainsKeyByB_ElementDoesNotExist_ReturnsFalse() { sut.Add(2, 3); sut.ContainsKeyByB(1).Should().BeFalse(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/DisjointSetsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class DisjointSetsTest { private static readonly string[] FirstSixPositiveInts = Enumerable.Range(1, 6).Select(x => x.ToString()).ToArray(); [TestMethod] public void FindRootAndUnion_AreConsistent() { var sets = new DisjointSets(FirstSixPositiveInts); foreach (var element in FirstSixPositiveInts) { sets.FindRoot(element).Should().Be(element); } sets.Union("1", "1"); sets.FindRoot("1").Should().Be("1"); // Reflexivity sets.Union("1", "2"); sets.FindRoot("1").Should().Be(sets.FindRoot("2")); // Correctness sets.Union("1", "2"); sets.FindRoot("1").Should().Be(sets.FindRoot("2")); // Idempotency sets.Union("2", "1"); sets.FindRoot("1").Should().Be(sets.FindRoot("2")); // Symmetry sets.FindRoot("3").Should().Be("3"); sets.Union("2", "3"); sets.FindRoot("2").Should().Be(sets.FindRoot("3")); sets.FindRoot("1").Should().Be(sets.FindRoot("3")); // Transitivity sets.Union("3", "4"); sets.FindRoot("1").Should().Be(sets.FindRoot("4")); // Double transitivity sets.Union("4", "1"); sets.FindRoot("4").Should().Be(sets.FindRoot("1")); // Idempotency after transitivity } [TestMethod] public void GetAllSetsAndUnion_AreConsistent() { var sets = new DisjointSets(FirstSixPositiveInts); AssertSets([["1"], ["2"], ["3"], ["4"], ["5"], ["6"]], sets); // Initial state sets.Union("1", "2"); AssertSets([["1", "2"], ["3"], ["4"], ["5"], ["6"]], sets); // Correctness sets.Union("1", "2"); AssertSets([["1", "2"], ["3"], ["4"], ["5"], ["6"]], sets); // Idempotency sets.Union("2", "3"); AssertSets([["1", "2", "3"], ["4"], ["5"], ["6"]], sets); // Transitivity sets.Union("1", "3"); AssertSets([["1", "2", "3"], ["4"], ["5"], ["6"]], sets); // Idempotency after transitivity sets.Union("4", "5"); AssertSets([["1", "2", "3"], ["4", "5"], ["6"]], sets); // Separated trees sets.Union("3", "4"); AssertSets([["1", "2", "3", "4", "5"], ["6"]], sets); // Merged trees } [TestMethod] public void GetAllSetsAndUnion_OfNestedTrees() { var sets = new DisjointSets(FirstSixPositiveInts); sets.Union("1", "2"); sets.Union("3", "4"); sets.Union("5", "6"); AssertSets([["1", "2"], ["3", "4"], ["5", "6"]], sets); // Merge of 1-height trees sets.Union("2", "3"); AssertSets([["1", "2", "3", "4"], ["5", "6"]], sets); // Merge of 2-height trees sets.Union("4", "5"); AssertSets([["1", "2", "3", "4", "5", "6"]], sets); // Merge of 1-height tree and 2-height tree } [TestMethod] public void GetAllSets_ReturnsSortedSets() { var sets = new DisjointSets(["3", "2", "1"]); AssertSets([["1"], ["2"], ["3"]], sets); sets.Union("3", "1"); AssertSets([["1", "3"], ["2"]], sets); } private static void AssertSets(List> expected, DisjointSets sets) => sets.GetAllSets().Should().BeEquivalentTo(expected, x => x.WithStrictOrdering()); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/FixAllProviders/DocumentBasedFixAllProviderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; namespace SonarAnalyzer.Core.Test.Common.FixAllProviders; [TestClass] public class DocumentBasedFixAllProviderTest { [TestMethod] [DataRow(FixAllScope.Document, "Fix all 'SDummy' in 'MyFile1.cs'")] [DataRow(FixAllScope.Project, "Fix all 'SDummy' in 'project0'")] [DataRow(FixAllScope.Solution, "Fix all 'SDummy' in Solution")] public async Task GetFixAsync_ForSupportedScope_HasCorrectTitle(FixAllScope scope, string expectedTitle) { var codeFix = new DummyCodeFixCS(); var document = CreateProject().FindDocument("MyFile1.cs"); var fixAllContext = new FixAllContext(document, codeFix, scope, "Dummy Action", codeFix.FixableDiagnosticIds, new FixAllDiagnosticProvider(null), default); var result = await SonarAnalyzer.Core.Common.DocumentBasedFixAllProvider.Instance.GetFixAsync(fixAllContext); result.Title.Should().Be(expectedTitle); } [TestMethod] public async Task GetFixAsync_ForUnsupportedScope_ReturnsNull() { var codeFix = new DummyCodeFixCS(); var document = CreateProject().FindDocument("MyFile1.cs"); var fixAllContext = new FixAllContext(document, codeFix, FixAllScope.Custom, "Dummy Action", codeFix.FixableDiagnosticIds, new FixAllDiagnosticProvider(null), default); var result = await SonarAnalyzer.Core.Common.DocumentBasedFixAllProvider.Instance.GetFixAsync(fixAllContext); result.Should().BeNull(); } [TestMethod] public async Task GetFixAsync_ForDocument_OnlyTheDocumentChanged() { var codeFix = new DummyCodeFixCS(); var project = CreateProject(); var document1Before = project.FindDocument("MyFile1.cs"); var document2Before = project.FindDocument("MyFile2.cs"); var compilation = project.GetCompilation(); var diagnostics = DiagnosticVerifier.AnalyzerDiagnostics(compilation, new DummyAnalyzerCS(), CompilationErrorBehavior.Ignore).ToImmutableArray(); var fixAllContext = new FixAllContext(document1Before, codeFix, FixAllScope.Document, "Dummy Action", codeFix.FixableDiagnosticIds, new FixAllDiagnosticProvider(diagnostics), default); var result = await SonarAnalyzer.Core.Common.DocumentBasedFixAllProvider.Instance.GetFixAsync(fixAllContext); var executedOperation = await result.GetOperationsAsync(default); var document1After = executedOperation.OfType().Single().ChangedSolution.GetDocument(document1Before.Id); var document2After = executedOperation.OfType().Single().ChangedSolution.GetDocument(document2Before.Id); var document1BeforeRoot = await document1Before.GetSyntaxRootAsync(); var document1BeforeContent = document1BeforeRoot.GetText().ToString(); var document1AfterRoot = await document1After.GetSyntaxRootAsync(); var document1AfterContent = document1AfterRoot.GetText().ToString(); document1BeforeContent.Should().NotBe(document1AfterContent); var document2BeforeRoot = await document2Before.GetSyntaxRootAsync(); var document2BeforeContent = document2BeforeRoot.GetText().ToString(); var document2AfterRoot = await document2After.GetSyntaxRootAsync(); var document2AfterContent = document2AfterRoot.GetText().ToString(); document2BeforeContent.Should().Be(document2AfterContent); } [TestMethod] [DataRow(FixAllScope.Project)] [DataRow(FixAllScope.Solution)] public async Task GetFixAsync_ForProjectAndSolution_AllFilesAreFixed(FixAllScope scope) { var codeFix = new DummyCodeFixCS(); var project = CreateProject(); var document1Before = project.FindDocument("MyFile1.cs"); var document2Before = project.FindDocument("MyFile2.cs"); var compilation = project.GetCompilation(); var diagnostics = DiagnosticVerifier.AnalyzerDiagnostics(compilation, new DummyAnalyzerCS(), CompilationErrorBehavior.Ignore).ToImmutableArray(); var fixAllContext = new FixAllContext(project.Project, codeFix, scope, "Dummy Action", codeFix.FixableDiagnosticIds, new FixAllDiagnosticProvider(diagnostics), default); var result = await SonarAnalyzer.Core.Common.DocumentBasedFixAllProvider.Instance.GetFixAsync(fixAllContext); var executedOperation = await result.GetOperationsAsync(default); var document1After = executedOperation.OfType().Single().ChangedSolution.GetDocument(document1Before.Id); var document2After = executedOperation.OfType().Single().ChangedSolution.GetDocument(document2Before.Id); var document1BeforeRoot = await document1Before.GetSyntaxRootAsync(); var document1BeforeContent = document1BeforeRoot.GetText().ToString(); var document1AfterRoot = await document1After.GetSyntaxRootAsync(); var document1AfterContent = document1AfterRoot.GetText().ToString(); document1BeforeContent.Should().NotBe(document1AfterContent); var document2BeforeRoot = await document2Before.GetSyntaxRootAsync(); var document2BeforeContent = document2BeforeRoot.GetText().ToString(); var document2AfterRoot = await document2After.GetSyntaxRootAsync(); var document2AfterContent = document2AfterRoot.GetText().ToString(); document2BeforeContent.Should().NotBe(document2AfterContent); } private static ProjectBuilder CreateProject() => SolutionBuilder.Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(@"public class C1 { public void M1() { int number1 = 1 } };", "MyFile1.cs") .AddSnippet(@"public class C2 { public void M2() { int number2 = 2 } };", "MyFile2.cs"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/HashCodeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #pragma warning disable CA1825 // Avoid zero-length array allocations using HashCode = SonarAnalyzer.Core.Common.HashCode; namespace SonarAnalyzer.Core.Common.Test; [TestClass] public class HashCodeTest { [TestMethod] [DataRow(null)] [DataRow("Lorem Ipsum")] public void Combine_ProducesDifferentResults(string input) { var hash2 = HashCode.Combine(input, input); var hash3 = HashCode.Combine(input, input, input); var hash4 = HashCode.Combine(input, input, input, input); var hash5 = HashCode.Combine(input, input, input, input, input); hash2.Should().NotBe(0); hash3.Should().NotBe(0).And.NotBe(hash2); hash4.Should().NotBe(0).And.NotBe(hash2).And.NotBe(hash3); hash5.Should().NotBe(0).And.NotBe(hash2).And.NotBe(hash3).And.NotBe(hash4); } [TestMethod] public void DictionaryContentHash_StableForImmutableDictionary() { var numbers = Enumerable.Range(1, 1000); var dict1 = numbers.ToImmutableDictionary(x => x, x => x); var dict2 = numbers.OrderByDescending(x => x).ToImmutableDictionary(x => x, x => x); var hashCode1 = HashCode.DictionaryContentHash(dict1); var hashCode2 = HashCode.DictionaryContentHash(dict2); hashCode1.Should().Be(hashCode2); } [TestMethod] public void EnumerableUnorderedContentHash_Empty() { var ints = new int[0]; var strings = new string[0]; HashCode.EnumerableUnorderedContentHash(ints).Should().Be(HashCode.EnumerableUnorderedContentHash(new int[0])); HashCode.EnumerableUnorderedContentHash(strings).Should().Be(HashCode.EnumerableUnorderedContentHash(strings)); HashCode.EnumerableUnorderedContentHash(ints).Should().Be(HashCode.EnumerableUnorderedContentHash(strings)); } [TestMethod] public void EnumerableUnorderedContentHash_Order() { var ints1 = new[] { 0, 1, 2 }; var ints2 = new[] { 2, 1, 0 }; var ints3 = new[] { 0, 1, 8 }; HashCode.EnumerableUnorderedContentHash(ints1).Should().Be(HashCode.EnumerableUnorderedContentHash(ints2)).And.NotBe(0); HashCode.EnumerableUnorderedContentHash(ints1).Should().NotBe(HashCode.EnumerableUnorderedContentHash(ints3)); } [TestMethod] public void EnumerableUnorderedContentHash_DifferentLength() { var ints1 = new[] { 0, 1, 2 }; var ints2 = new[] { 0, 1, 2, 3 }; HashCode.EnumerableUnorderedContentHash(ints1).Should().NotBe(HashCode.EnumerableUnorderedContentHash(ints2)); } [TestMethod] public void EnumerableOrderedContentHash_Empty() { var ints = new int[0]; var strings = new string[0]; HashCode.EnumerableOrderedContentHash(ints).Should().Be(HashCode.EnumerableOrderedContentHash(new int[0])); HashCode.EnumerableOrderedContentHash(strings).Should().Be(HashCode.EnumerableOrderedContentHash(strings)); HashCode.EnumerableOrderedContentHash(ints).Should().Be(HashCode.EnumerableOrderedContentHash(strings)); } [TestMethod] public void EnumerableOrderedContentHash_Order() { var ints1 = new[] { 0, 1, 2 }; var ints2 = new[] { 0, 1, 2 }; var ints3 = new[] { 2, 1, 0 }; var ints4 = new[] { 0, 1, 8 }; HashCode.EnumerableOrderedContentHash(ints1).Should().Be(HashCode.EnumerableOrderedContentHash(ints2)).And.NotBe(0); HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints3)); HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints4)); } [TestMethod] public void EnumerableOrderedContentHash_DifferentLength() { var ints1 = new[] { 0, 1, 2 }; var ints2 = new[] { 0, 1, 2, 3 }; HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints2)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/MultiValueDictionaryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class MultiValueDictionaryTest { [TestMethod] public void MultiValueDictionary_Add() { var mvd = new MultiValueDictionary { { 5, 42 }, { 5, 42 }, { 42, 42 } }; mvd.Keys.Should().HaveCount(2); mvd[5].Should().HaveCount(2); } [TestMethod] public void MultiValueDictionary_Add_Set() { var mvd = MultiValueDictionary.Create>(); mvd.Add(5, 42); mvd.Add(5, 42); mvd.Add(42, 42); mvd.Keys.Should().HaveCount(2); mvd[5].Should().ContainSingle(); } [TestMethod] public void MultiValueDictionary_AddRange() { var mvd = new MultiValueDictionary(); mvd.AddRangeWithKey(5, new[] { 42, 42 }); mvd[5].Should().HaveCount(2); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/NaturalLanguageDetectorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class NaturalLanguageDetectorTest { [TestMethod] [DataRow(null, 0)] [DataRow("Hello02139710238712987", 1.3262863)] [DataRow("This is an english text!", 4.6352161)] [DataRow("Hello", 4.7598878)] [DataRow("Hello hello hello hello", 4.7598878)] [DataRow("Hleol", 2.0215728)] [DataRow("Hleol hleol", 2.0215728)] [DataRow("Hleol Hello hleol", 2.9343445)] [DataRow("Hleol Incomprehensibility hleol", 3.5209606)] [DataRow("Incomprehensibility ", 4.3978577)] [DataRow("slrwaxquavy", 0.783135)] [DataRow("SlRwAxQuAvY", 0.5729079)] [DataRow("012345678", 1)] [DataRow("images/blob/50281d86d6ed5c61975971150adf", 1.1821769)] [DataRow("js/commit/8863b9d04c722b278fa93c5d66ad1e", 0.9126614)] [DataRow("net/core/builder/e426a9ae7167c5807b173d5", 1.9399531)] [DataRow("net/more/builder/3ad489866f41084fa4f3307", 1.9014789)] [DataRow("project/commit/c5acf965067478784b54e2d24", 1.2177787)] [DataRow("/var/lib/openshift/51122e382d5271c5ca000", 1.3230153)] [DataRow("examples/commit/16ad89c4172c259f15bce56e", 1.6869377)] [DataRow("examples/commit/8e1d746900f5411e9700fea0", 1.48724)] [DataRow("examples/commit/c95b6a84b6fd1efc832a46cd", 1.503256)] [DataRow("examples/commit/d6f6ef7457d99e31990fa64b", 1.4204883)] [DataRow("examples/commit/ea15f07ce79366a08fee5b60", 1.8357153)] [DataRow("cn/res/chinapostplan/structure/181041269", 3.494024)] [DataRow("com/istio/proxy/blob/bcdc1684df0839a6125", 1.5356048)] [DataRow("com/kriskowal/q/blob/b0fa72980717dc202ff", 1.3069352)] [DataRow("com/ph/logstash/de2ba3f964ae7039b7b74a4a", 1.4612998)] [DataRow("default/src/test/java/org/xwiki/componen", 2.6909549)] [DataRow("search_my_organization-example.json", 3.6890879)] [DataRow("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", 3.5768316)] [DataRow("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", 4.2315959)] [DataRow("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 1.2038558)] [DataRow("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", 1.2038558)] [DataRow("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", 1.2252754)] [DataRow("0123456789ABCDEFGHIJKLMNOPQRSTUV", 1.2310129)] [DataRow("abcdefghijklmnopqrstuvwxyz", 1.1127479)] [DataRow("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1.1127479)] [DataRow("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT", 3.2985092)] [DataRow("org.apache.tomcat.websocket.WS_AUTHENTICATION_PASSWORD", 4.061177)] public void CalculateHumanLanguageScore(string input, double expectedScore) => NaturalLanguageDetector.HumanLanguageScore(input).Should().BeApproximately(expectedScore, 0.01); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/RuleLoaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml; namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class RuleLoaderTest { [TestMethod] public void GivenNonXmlFile_RuleLoader_Throws() { var sut = new RuleLoader(); sut.Invoking(x => x.GetEnabledRules("not xml")).Should().Throw(); } [TestMethod] public void GivenSonarLintXml_RuleLoader_LoadsActiveRules() { const string content = """ S1067 """; CollectionAssert.AreEqual(new RuleLoader().GetEnabledRules(content).ToArray(), new[] {"S1067"}); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/ShannonEntropyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class ShannonEntropyTest { [TestMethod] [DataRow(null, 0)] [DataRow("", 0)] [DataRow("a", 0)] [DataRow("aa", 0)] [DataRow("aA", 1)] [DataRow("abc", 1.58)] [DataRow("aabc", 1.5)] [DataRow("0000000000000000000000000000000000000000", 0)] [DataRow("0000000000000000000011111111111111111111", 1)] [DataRow("0000011111222223333344444555556666677777", 3)] [DataRow("squ_764ba4e9ccba193c84369792691b5500d999aadd", 3.964)] [DataRow("qAhEMdXy/MPwEuDlhh7O0AFBuzGvNy7AxpL3sX3q", 4.684183)] public void Calculate_Entropy(string input, double expectedEntropy) => ShannonEntropy.Calculate(input).Should().BeApproximately(expectedEntropy, 0.01); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Common/UnexpectedLanguageExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Common; [TestClass] public class UnexpectedLanguageExceptionTest { [TestMethod] public void Message_String_Ctor() => new UnexpectedLanguageException("F#").Message.Should().Be("Unexpected language: F#"); [TestMethod] public void Message_CS() => new UnexpectedLanguageException(AnalyzerLanguage.CSharp).Message.Should().Be("Unexpected language: C#"); [TestMethod] public void Message_VB() => new UnexpectedLanguageException(AnalyzerLanguage.VisualBasic).Message.Should().Be("Unexpected language: Visual Basic"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/AnalysisConfigReaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class AnalysisConfigReaderTest { public TestContext TestContext { get; set; } [TestMethod] public void MissingFile_ReturnsEmpty() { var sut = new AnalysisConfigReader("ThisFileDoesNotExist.xml"); sut.UnchangedFiles().Should().BeEmpty(); sut.IsCloud.Should().BeFalse(); } [TestMethod] [DataRow(@"")] [DataRow(@"")] [DataRow(@"")] [DataRow(@"")] // No Id attribute public void UnexpectedXml_Throws(string xml) { var path = TestFiles.WriteFile(TestContext, "SonarQubeAnalysisConfig.xml", xml); ((Func)(() => new AnalysisConfigReader(path))).Should().Throw().WithMessage($"File '{path}' could not be parsed."); } [TestMethod] [DataRow(@"")] [DataRow(@"")] [DataRow(@"")] [DataRow(@"")] [DataRow(@"")] public void MissingContent(string xml) { var path = TestFiles.WriteFile(TestContext, "SonarQubeAnalysisConfig.xml", xml); var sut = new AnalysisConfigReader(path); sut.UnchangedFiles().Should().BeEmpty(); sut.IsCloud.Should().BeFalse(); } [TestMethod] public void UnchangedFiles_NotPresent_ReturnsEmpty() { var path = AnalysisScaffolding.CreateAnalysisConfig(TestContext, "SomeOtherProperty", "This is not UnchangedFilesPath"); var sut = new AnalysisConfigReader(path); sut.UnchangedFiles().Should().BeEmpty(); } [TestMethod] public void UnchangedFiles_Empty_ReturnsEmpty() { var path = AnalysisScaffolding.CreateAnalysisConfig(TestContext, []); var sut = new AnalysisConfigReader(path); sut.UnchangedFiles().Should().BeEmpty(); } [TestMethod] public void UnchangedFiles_Present_ReturnsContent() { var path = AnalysisScaffolding.CreateAnalysisConfig(TestContext, [@"C:\File1.cs", @"C:\File2.cs", "Any other string"]); var sut = new AnalysisConfigReader(path); sut.UnchangedFiles().Should().BeEquivalentTo(@"C:\File1.cs", @"C:\File2.cs", "Any other string"); } [TestMethod] [DataRow("8.0.0.82356", true)] // Actual SQ-C version [DataRow("8.0.0.29455", true)] // While this is SQ-S 8.0 and not cloud, we expect true, because this analyzer will never be backported there [DataRow("8.0.0.99999", true)] [DataRow("2026.1.0.1234", false)] [DataRow("whatever", false)] [DataRow("", false)] public void IsSonarCloud(string version, bool expected) { var path = AnalysisScaffolding.CreateAnalysisConfig(TestContext, "key", "value", version); var sut = new AnalysisConfigReader(path); sut.IsCloud.Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/FilesToAnalyzeProviderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class FilesToAnalyzeProviderTest { private const string MixedSlashesWebConfigPath1 = @"C:\Projects/DummyProj/wEB.config"; private const string MixedSlashesWebConfigPath2 = @"C:\Projects/DummyProj/Views\Web.confiG"; private const string FilesToAnalyzePath = @"TestResources\FilesToAnalyze\FilesToAnalyze.txt"; private const string InvalidFilesToAnalyzePath = @"TestResources\FilesToAnalyze\FilesToAnalyzeWithInvalidValues.txt"; [TestMethod] public void FileNameWithMixedCapitalizationAndMixedSlashes_FindFilesWithFileName_ReturnsAllWebConfigFiles() { var sut = new FilesToAnalyzeProvider(FilesToAnalyzePath); var results = sut.FindFiles("Web.config", false); results.Should().BeEquivalentTo(new[] { MixedSlashesWebConfigPath1, MixedSlashesWebConfigPath2 }); } [TestMethod] public void FileNameWithMixedCapitalizationAndMixedSlashes_FindFilesWithRegex_ReturnsAllWebConfigFiles() { var fileNamePattern = new Regex(@"[\\\/]web\.config$", RegexOptions.IgnoreCase); var sut = new FilesToAnalyzeProvider(FilesToAnalyzePath); var results = sut.FindFiles(fileNamePattern, false); results.Should().BeEquivalentTo(new[] { MixedSlashesWebConfigPath1, MixedSlashesWebConfigPath2 }); } [TestMethod] public void FileWithInvalidValues_FindFilesWithFileName_ReturnsValidValue() { var sut = new FilesToAnalyzeProvider(InvalidFilesToAnalyzePath); var results = sut.FindFiles("123", false); results.Should().ContainSingle(); results.Should().Contain("123"); } [TestMethod] public void FileWithInvalidValues_FindFilesWithRegex_ReturnsValidValue() { var fileNamePattern = new Regex("web\\.config$", RegexOptions.IgnoreCase); var sut = new FilesToAnalyzeProvider(InvalidFilesToAnalyzePath); var results = sut.FindFiles(fileNamePattern, false); results.Should().BeEquivalentTo(new[] { MixedSlashesWebConfigPath2, @"C:\Projects\Controllers:web.config", @"C:web.config", @"C:\Projects\Controllers/web.config" }); } [TestMethod] public void EmptyFile_FindFiles_ReturnsEmptyEnumerable() { var sut = new FilesToAnalyzeProvider(@"TestResources\FilesToAnalyze\EmptyFilesToAnalyze.txt"); var results = sut.FindFiles(new Regex(".*"), false); results.Should().BeEmpty(); } [TestMethod] [DataRow("")] [DataRow(null)] [DataRow("invalidPath")] [DataRow(@"TestResources\FilesToAnalyze\NonExistingFile.txt")] public void InvalidPath_FindFiles_ReturnsEmptyEnumerable(string filePath) { var sut = new FilesToAnalyzeProvider(filePath); var results = sut.FindFiles(new Regex(".*"), false); results.Should().BeEmpty(); } [TestMethod] public void FileWithValidValues_FindFilesRequestingAnyFile_AllValuesFromTheFileAreReturned() { var sut = new FilesToAnalyzeProvider(FilesToAnalyzePath); var results = sut.FindFiles(new Regex(".*"), false); results.Should().BeEquivalentTo(new[] { MixedSlashesWebConfigPath1, MixedSlashesWebConfigPath2, @"C:\Projects\DummyProj\Views\Global.asax", @"C:\Projects\DummyProj\Csharp\Controllers\HomeController.cs", @"C:\Projects\DummyProj\VisualBasic\Controllers\HomeController.vb", @"C:\Projects/DummyProj/Views\Web.confiGuration" }); } [TestMethod] public void UnableToOpenFile_FindFiles_ReturnsEmptyEnumerable() { using (Stream iStream = File.Open(FilesToAnalyzePath, FileMode.Open, FileAccess.Read, FileShare.None)) { var sut = new FilesToAnalyzeProvider(FilesToAnalyzePath); var results = sut.FindFiles(new Regex(".*")); results.Should().BeEmpty(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/ProjectConfigReaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class ProjectConfigReaderTest { public TestContext TestContext { get; set; } [TestMethod] public void AllPropertiesAreSet() { var sut = CreateProjectConfigReader(@"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"); // this will fail if a new property is added to the class and no test case for it is added foreach (var property in sut.GetType().GetProperties().Where(x => x.Name != "AnalysisConfig")) { var value = property.GetValue(sut)?.ToString(); value.Should().NotBeNullOrEmpty($"property '{property.Name}' should have value"); } } [TestMethod] public void WhenAllValuesAreSet_LoadsExpectedValues() { var sut = CreateProjectConfigReader(@"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"); sut.AnalysisConfigPath.Should().Be(@"c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml"); sut.ProjectPath.Should().Be(@"C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj"); sut.FilesToAnalyzePath.Should().Be(@"c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt"); sut.OutPath.Should().Be(@"C:\foo\bar\.sonarqube\out\0"); sut.ProjectType.Should().Be(ProjectType.Product); sut.TargetFramework.Should().Be("netcoreapp3.1"); } [TestMethod] [DataRow("Path_MixedSeparators", @"c:\foo\bar\.sonarqube\conf/0/FilesToAnalyze.txt")] [DataRow("Path_Unix", @"/home/user/.sonarqube/conf/0/FilesToAnalyze.txt")] [DataRow("Path_Windows", @"c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt")] public void WithVariousPathFormats_ReturnsValueAsIs(string project, string expectedFilesToAnalyzePath) => CreateProjectConfigReader($@"TestResources\SonarProjectConfig\{project}\SonarProjectConfig.xml").FilesToAnalyzePath.Should().Be(expectedFilesToAnalyzePath); [TestMethod] public void WhenHasMissingFilesToAnalyzePath_ReturnsCorrectValue() { var sut = CreateProjectConfigReader(@"TestResources\SonarProjectConfig\MissingFilesToAnalyzePath\SonarProjectConfig.xml"); sut.AnalysisConfigPath.Should().Be(@"c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml"); sut.ProjectPath.Should().Be(@"C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj"); sut.OutPath.Should().Be(@"C:\foo\bar\.sonarqube\out\0"); sut.ProjectType.Should().Be(ProjectType.Product); sut.TargetFramework.Should().Be("netcoreapp3.1"); } [TestMethod] public void WhenHasUnexpectedProjectType_FallsBackToProduct() => CreateProjectConfigReader(@"TestResources\SonarProjectConfig\UnexpectedProjectTypeValue\SonarProjectConfig.xml").ProjectType.Should().Be(ProjectType.Product); [TestMethod] [DataRow("MissingAnalysisConfigPath")] [DataRow("MissingOutPath")] [DataRow("MissingProjectPath")] [DataRow("MissingProjectType")] [DataRow("MissingTargetFramework")] public void WhenHasMissingValues_FilesToAnalyzePath_ReturnsCorrectValue(string folder) => CreateProjectConfigReader($@"TestResources\SonarProjectConfig\{folder}\SonarProjectConfig.xml").FilesToAnalyzePath.Should().Be(@"c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt"); [TestMethod] [DataRow("Invalid_DifferentClassName")] [DataRow("Invalid_DifferentNamespace")] [DataRow("Invalid_Xml")] public void WhenInvalid_FilesToReturnPath_ThrowsException(string folder) => Assert.Throws(() => CreateProjectConfigReader($@"TestResources\SonarProjectConfig\{folder}\SonarProjectConfig.xml")) .Message.Should().Be("sonarProjectConfig could not be parsed."); [TestMethod] public void FilesToAnalyze_LoadsFileFromConfig() { var config = SourceText.From(""" TestResources\FilesToAnalyze\FilesToAnalyze.txt """); var files = new ProjectConfigReader(config).FilesToAnalyze.FindFiles("web.config", false); files.Should().BeEquivalentTo(@"C:\Projects/DummyProj/wEB.config", @"C:\Projects/DummyProj/Views\Web.confiG"); } [TestMethod] public void AnalysisConfig_LoadsConfigFromDisk() { var path = AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext); // With AnalysisConfigPath that exists var sut = CreateProjectConfigReader(path); sut.AnalysisConfig.Should().NotBeNull(); } private static ProjectConfigReader CreateProjectConfigReader(string relativePath) => new(SourceText.From(File.ReadAllText(relativePath))); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/ProjectTypeCacheTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class ProjectTypeCacheTest { [TestMethod] public void TestReference_ShouldBeSynchronized() { // Purpose of this test is to remind us, that we need to synchronize this list with sonar-scanner-msbuild and sonar-security. var synchronizedSortedReferences = new[] { "dotMemory.Unit", "Microsoft.VisualStudio.TestPlatform.TestFramework", "Microsoft.VisualStudio.QualityTools.UnitTestFramework", "MSTest.TestFramework", "Machine.Specifications", "nunit.framework", "nunitlite", "TechTalk.SpecFlow", "xunit", "xunit.core", "xunit.v3.core", "FluentAssertions", "Shouldly", "FakeItEasy", "Moq", "NSubstitute", "Rhino.Mocks", "Telerik.JustMock" }; ProjectTypeCache.TestAssemblyNames.OrderBy(x => x).Should().BeEquivalentTo(synchronizedSortedReferences); } [TestMethod] public void IsTest_ReturnsTrueForTestFrameworks() { IsTest(NuGetMetadataReference.JetBrainsDotMemoryUnit(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.MSTestTestFrameworkV1).Should().BeTrue(); IsTest(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.MicrosoftVisualStudioQualityToolsUnitTestFramework).Should().BeTrue(); IsTest(NuGetMetadataReference.MachineSpecifications(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.NUnitLite(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.SpecFlow(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.XunitFrameworkV1).Should().BeTrue(); IsTest(NuGetMetadataReference.XunitFramework(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.XunitFrameworkV3(TestConstants.NuGetLatestVersion)).Should().BeTrue(); // Assertion IsTest(NuGetMetadataReference.FluentAssertions(NugetPackageVersions.FluentAssertionsVersions.Ver7)).Should().BeTrue(); IsTest(NuGetMetadataReference.Shouldly(TestConstants.NuGetLatestVersion)).Should().BeTrue(); // Mock IsTest(NuGetMetadataReference.FakeItEasy(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.JustMock(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.Moq(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.NSubstitute(TestConstants.NuGetLatestVersion)).Should().BeTrue(); IsTest(NuGetMetadataReference.RhinoMocks(TestConstants.NuGetLatestVersion)).Should().BeTrue(); } [TestMethod] public void IsTest_ReturnsFalse() { IsTest(null).Should().BeFalse(); // Any non-test reference IsTest(NuGetMetadataReference.SystemValueTuple(TestConstants.NuGetLatestVersion)).Should().BeFalse(); } [TestMethod] public void IsTest_Compilation() { IsTest(NuGetMetadataReference.MSTestTestFrameworkV1).Should().BeTrue(); IsTest(null).Should().BeFalse(); } private static bool IsTest(IEnumerable additionalReferences) => CreateSemanticModel(additionalReferences).Compilation.IsTest(); private static SemanticModel CreateSemanticModel(IEnumerable additionalReferences) => new SnippetCompiler("// Nothing to see here", additionalReferences).Model; } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/SonarLintXmlReaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class SonarLintXmlReaderTest { [TestMethod] [DataRow(LanguageNames.CSharp, "cs")] [DataRow(LanguageNames.VisualBasic, "vbnet")] public void SonarLintXmlReader_WhenAllValuesAreSet_ExpectedValues(string language, string xmlLanguageName) { var sut = CreateSonarLintXmlReader(@$"TestResources\SonarLintXml\All_Properties_{xmlLanguageName}\SonarLint.xml"); sut.IgnoreHeaderComments(language).Should().BeTrue(); sut.AnalyzeGeneratedCode(language).Should().BeFalse(); sut.AnalyzeRazorCode(language).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); AssertArrayContent(sut.TestExclusions, nameof(sut.TestExclusions)); AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); sut.ParametrizedRules.Should().HaveCount(2); var rule = sut.ParametrizedRules.First(x => x.Key.Equals("S2342")); rule.Parameters[0].Key.Should().Be("format"); rule.Parameters[0].Value.Should().Be("^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); rule.Parameters[1].Key.Should().Be("flagsAttributeFormat"); rule.Parameters[1].Value.Should().Be("^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); static void AssertArrayContent(string[] array, string folder) { array.Should().HaveCount(2); array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); } } [TestMethod] public void SonarLintXmlReader_PartiallyMissingProperties_ExpectedAndDefaultValues() { var sut = CreateSonarLintXmlReader(@"TestResources\SonarLintXml\Partially_missing_properties\SonarLint.xml"); sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); sut.AnalyzeRazorCode(LanguageNames.CSharp).Should().BeTrue(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } [TestMethod] public void SonarLintXmlReader_PropertiesCSharpTrueVBNetFalse_ExpectedValues() { var sut = CreateSonarLintXmlReader(@"TestResources\SonarLintXml\PropertiesCSharpTrueVbnetFalse\SonarLint.xml"); sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeTrue(); sut.IgnoreHeaderComments(LanguageNames.VisualBasic).Should().BeFalse(); sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeTrue(); sut.AnalyzeGeneratedCode(LanguageNames.VisualBasic).Should().BeFalse(); sut.AnalyzeRazorCode(LanguageNames.CSharp).Should().BeTrue(); sut.AnalyzeRazorCode(LanguageNames.VisualBasic).Should().BeFalse(); } [TestMethod] public void SonarLintXmlReader_DuplicatedProperties_DoesNotFail() => ((Action)(() => CreateSonarLintXmlReader(@"TestResources\SonarLintXml\Duplicated_Properties\SonarLint.xml"))).Should().NotThrow(); [TestMethod] [DataRow("")] [DataRow("this is not an xml")] [DataRow(@"")] public void SonarLintXmlReader_WithMalformedXml_DefaultBehaviour(string sonarLintXmlContent) => CheckSonarLintXmlReaderDefaultValues(new SonarLintXmlReader(SourceText.From(sonarLintXmlContent))); [TestMethod] public void SonarLintXmlReader_MissingProperties_DefaultBehaviour() => CheckSonarLintXmlReaderDefaultValues(CreateSonarLintXmlReader(@"TestResources\SonarLintXml\Missing_properties\SonarLint.xml")); [TestMethod] public void SonarLintXmlReader_WithIncorrectValueType_DefaultBehaviour() => CheckSonarLintXmlReaderDefaultValues(CreateSonarLintXmlReader(@"TestResources\SonarLintXml\Incorrect_value_type\SonarLint.xml")); [TestMethod] public void SonarLintXmlReader_CheckEmpty_DefaultBehaviour() => CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader.Empty); [TestMethod] public void SonarLintXmlReader_LanguageDoesNotExist_Throws() { var sut = CreateSonarLintXmlReader(@"TestResources\SonarLintXml\All_Properties_cs\SonarLint.xml"); sut.Invoking(x => x.IgnoreHeaderComments(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); sut.Invoking(x => x.AnalyzeGeneratedCode(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); sut.Invoking(x => x.AnalyzeRazorCode(LanguageNames.FSharp)).Should().Throw().WithMessage("Unexpected language: F#"); } private static void CheckSonarLintXmlReaderDefaultValues(SonarLintXmlReader sut) { sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.AnalyzeRazorCode(LanguageNames.CSharp).Should().BeTrue(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); sut.ParametrizedRules.Should().NotBeNull().And.HaveCount(0); } private static void AssertArrayContent(string[] array, string folder) { array.Should().HaveCount(2); array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); } private static SonarLintXmlReader CreateSonarLintXmlReader(string relativePath) => new(SourceText.From(File.ReadAllText(relativePath))); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Configuration/SonarLintXmlTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using System.Xml.Serialization; namespace SonarAnalyzer.Core.Configuration.Test; [TestClass] public class SonarLintXmlTest { [TestMethod] public void SonarLintXml_DeserializeFile_ExpectedValues() { var deserializer = new XmlSerializer(typeof(SonarLintXml)); using TextReader textReader = new StreamReader(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"); var sonarLintXml = (SonarLintXml)deserializer.Deserialize(textReader); AssertSettings(sonarLintXml.Settings); AssertRules(sonarLintXml.Rules); } private static void AssertSettings(List settings) { settings.Should().HaveCount(11); AssertKeyValuePair(settings[0], "sonar.cs.analyzeRazorCode", "false"); AssertKeyValuePair(settings[1], "sonar.cs.ignoreHeaderComments", "true"); AssertKeyValuePair(settings[2], "sonar.cs.analyzeGeneratedCode", "false"); AssertKeyValuePair(settings[3], "sonar.cs.file.suffixes", ".cs"); AssertKeyValuePair(settings[4], "sonar.cs.roslyn.ignoreIssues", "false"); AssertKeyValuePair(settings[5], "sonar.exclusions", "Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/*"); AssertKeyValuePair(settings[6], "sonar.inclusions", "Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/*"); AssertKeyValuePair(settings[7], "sonar.global.exclusions", "Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/*"); AssertKeyValuePair(settings[8], "sonar.test.exclusions", "Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/*"); AssertKeyValuePair(settings[9], "sonar.test.inclusions", "Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/*"); AssertKeyValuePair(settings[10], "sonar.global.test.exclusions", "Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/*"); } private static void AssertRules(List rules) { rules.Should().HaveCount(4); rules.Where(x => x.Parameters.Any()).Should().HaveCount(2); rules[0].Key.Should().BeEquivalentTo("S2225"); rules[0].Parameters.Should().BeEmpty(); rules[1].Key.Should().BeEquivalentTo("S2342"); rules[1].Parameters.Should().HaveCount(2); AssertKeyValuePair(rules[1].Parameters[0], "format", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$"); AssertKeyValuePair(rules[1].Parameters[1], "flagsAttributeFormat", "^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$"); rules[2].Key.Should().BeEquivalentTo("S2346"); rules[2].Parameters.Should().BeEmpty(); rules[3].Key.Should().BeEquivalentTo("S1067"); rules[3].Parameters.Should().HaveCount(1); AssertKeyValuePair(rules[3].Parameters[0], "max", "1"); } private static void AssertKeyValuePair(SonarLintXmlKeyValuePair pair, string expectedKey, string expectedValue) { pair.Key.Should().BeEquivalentTo(expectedKey); pair.Value.Should().BeEquivalentTo(expectedValue); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/BasicBlockExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Extensions; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class BasicBlockExtensionsTest { [TestMethod] public void IsEnclosedIn_ReturnsTrueForLocalLifeTime() { const string code = @" public class Sample { public void Method() { var t = true || true; } }"; var cfg = TestCompiler.CompileCfgCS(code); var localLifetimeRegion = cfg.Root.NestedRegions.Single(); var block = cfg.Blocks[localLifetimeRegion.FirstBlockOrdinal]; block.IsEnclosedIn(ControlFlowRegionKind.LocalLifetime).Should().BeTrue(); } [TestMethod] public void IsEnclosedIn_IgnoresLocalLifeTimeForOtherKinds() { const string code = @" public class Sample { public void Method() { try { DoSomething(); var t = true || true; // This causes LocalLivetimeRegion to be generated } catch { } } public void DoSomething() { } }"; var cfg = TestCompiler.CompileCfgCS(code); var block = cfg.Blocks[2]; block.EnclosingRegion.Kind.Should().Be(ControlFlowRegionKind.LocalLifetime); block.EnclosingRegion.EnclosingRegion.Kind.Should().Be(ControlFlowRegionKind.Try); block.EnclosingRegion.EnclosingRegion.EnclosingRegion.Kind.Should().Be(ControlFlowRegionKind.TryAndCatch); block.IsEnclosedIn(ControlFlowRegionKind.Try).Should().BeTrue(); block.IsEnclosedIn(ControlFlowRegionKind.Catch).Should().BeFalse(); block.IsEnclosedIn(ControlFlowRegionKind.TryAndCatch).Should().BeFalse(); // Because it's enclosed in Try block.IsEnclosedIn(ControlFlowRegionKind.LocalLifetime).Should().BeTrue(); // When asking for it, it should return } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/CompilationExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class CompilationExtensionsTest { private const string Snippet = """ class Sample { int _field; string Property { get; } void MethodWithParameters(int arg1) { } void MethodWithParameters(int arg1, string arg2) { } } """; [TestMethod] [DataRow("NonExistingType", "NonExistingMember", false)] [DataRow("Sample", "NonExistingMember", false)] [DataRow("Sample", "_field", true)] [DataRow("Sample", "Property", true)] [DataRow("Sample", "MethodWithParameters", true)] public void IsMemberAvailable_WithoutTypeCheck(string typeName, string memberName, bool expectedResult) { var (_, semanticModel) = TestCompiler.Compile(Snippet, false, AnalyzerLanguage.CSharp); semanticModel.Compilation.IsMemberAvailable(new(typeName), memberName) .Should().Be(expectedResult); } [TestMethod] public void IsMemberAvailable_WithPredicate() { var (_, semanticModel) = TestCompiler.Compile(Snippet, false, AnalyzerLanguage.CSharp); semanticModel.Compilation.IsMemberAvailable(new("Sample"), "_field", x => KnownType.System_Int32.Matches(x.Type)) .Should().BeTrue(); semanticModel.Compilation.IsMemberAvailable(new("Sample"), "Property", x => x is { IsReadOnly: true }) .Should().BeTrue(); semanticModel.Compilation.IsMemberAvailable(new("Sample"), "MethodWithParameters", x => x is { Parameters.Length: 2 }) .Should().BeTrue(); } [TestMethod] public void ReferencesAny_ShouldBeTrue() { Check(NuGetMetadataReference.NLog(), KnownAssembly.NLog); Check(NuGetMetadataReference.NLog().Concat(NuGetMetadataReference.NHibernate()), KnownAssembly.NLog); Check(NuGetMetadataReference.NLog(), KnownAssembly.NLog, KnownAssembly.NSubstitute); static void Check(IEnumerable compilationReferences, params KnownAssembly[] checkedAssemblies) => ReferencesAny_ShouldBe(true, compilationReferences, checkedAssemblies); } [TestMethod] public void ReferencesAny_ShouldBeFalse() { Check(Enumerable.Empty(), KnownAssembly.NLog); Check(Enumerable.Empty(), KnownAssembly.NLog, KnownAssembly.NSubstitute); static void Check(IEnumerable compilationReferences, params KnownAssembly[] checkedAssemblies) => ReferencesAny_ShouldBe(false, compilationReferences, checkedAssemblies); } [TestMethod] public void ReferencesAny_ShouldThrow() { var (_, model) = TestCompiler.Compile(string.Empty, false, AnalyzerLanguage.CSharp); ((Func)(() => model.Compilation.ReferencesAny())) .Should() .ThrowExactly() .WithMessage("Assemblies argument needs to be non-empty"); } private static void ReferencesAny_ShouldBe(bool expected, IEnumerable compilationReferences, params KnownAssembly[] checkedAssemblies) { var (_, model) = TestCompiler.Compile(string.Empty, false, AnalyzerLanguage.CSharp, compilationReferences.ToArray()); model.Compilation.ReferencesAny(checkedAssemblies).Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/ControlFlowGraphExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CFG.Extensions; using SonarAnalyzer.CFG.Roslyn; namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class ControlFlowGraphExtensionsTest { [TestMethod] public void FindLocalFunctionCfgInScope_ThrowForUnexpectedSymbol() { const string code = @" public class Sample { public void Method() { } }"; var (tree, semanticModel) = TestCompiler.CompileCS(code); var method = tree.Single(); var symbol = semanticModel.GetDeclaredSymbol(method) as IMethodSymbol; var cfg = ControlFlowGraph.Create(method, semanticModel, default); Action a = () => cfg.FindLocalFunctionCfgInScope(symbol, default); a.Should().Throw().WithMessage("Specified argument was out of the range of valid values.*Parameter*localFunction*"); } [TestMethod] public void FindLocalFunctionCfgInScope_ThrowForNullSymbol() { const string code = @" public class Sample { public void Method() { } }"; var cfg = TestCompiler.CompileCfgCS(code); Action a = () => cfg.FindLocalFunctionCfgInScope(null, default); a.Should().Throw().WithMessage("Specified argument was out of the range of valid values.*Parameter*localFunction*"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/DictionaryExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class DictionaryExtensionsTest { [TestMethod] public void DictionaryEquals_Different() { var empty = new Dictionary(); var original = new Dictionary { { "a", "a" }, { "b", "b" } }; var differentKeys = new Dictionary { { "a", "a" }, { "c", "c" } }; var differentValues = new Dictionary { { "a", "a" }, { "b", "xxxx" } }; DictionaryExtensions.DictionaryEquals(null, empty).Should().BeFalse(); DictionaryExtensions.DictionaryEquals(empty, null).Should().BeFalse(); original.DictionaryEquals(empty).Should().BeFalse(); original.DictionaryEquals(differentKeys).Should().BeFalse(); original.DictionaryEquals(differentValues).Should().BeFalse(); } [TestMethod] public void DictionaryEquals_SameContent() { var dict1 = new Dictionary { { "a", "a" }, { "b", "b" } }; var dict2 = new Dictionary { { "a", "a" }, { "b", "b" } }; dict1.DictionaryEquals(dict1).Should().BeTrue(); dict1.DictionaryEquals(dict2).Should().BeTrue(); } [TestMethod] public void DictionaryEquals_SameContent_DifferentOrdering() { var numbers = Enumerable.Range(1, 1000); var dict1 = numbers.ToDictionary(x => x, x => x); var dict2 = numbers.OrderByDescending(x => x).ToDictionary(x => x, x => x); dict1.DictionaryEquals(dict1).Should().BeTrue(); dict1.DictionaryEquals(dict2).Should().BeTrue(); dict2.DictionaryEquals(dict1).Should().BeTrue(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/IEnumerableExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; #pragma warning disable SA1122 // Use string.Empty for empty strings namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class IEnumerableExtensionsTest { [TestMethod] public void TestAreEqual_01() { var c1 = new List { 1, 2, 3 }; var c2 = new List { "1", "2", "3" }; c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeTrue(); } [TestMethod] public void TestAreEqual_02() { var c1 = new List { 1, 2 }; var c2 = new List { "1", "2", "3" }; c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeFalse(); } [TestMethod] public void TestAreEqual_03() { var c1 = new List { 1, 2, 3 }; var c2 = new List { "1", "2" }; c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeFalse(); } [TestMethod] public void TestAreEqual_04() { var c1 = new List(); var c2 = new List { "1", "2" }; c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeFalse(); } [TestMethod] public void TestAreEqual_05() { var c1 = new List { 1 }; var c2 = new List(); c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeFalse(); } [TestMethod] public void TestAreEqual_06() { var c1 = new List(); var c2 = new List(); c1.Equals(c2, (e1, e2) => e1.ToString() == e2).Should().BeTrue(); } [TestMethod] public void JoinStr_T_String() { var lst = new[] { Tuple.Create(1, "a"), Tuple.Create(2, "bb"), Tuple.Create(3, "ccc") }; lst.JoinStr(null, x => x.Item2).Should().Be("abbccc"); lst.JoinStr(", ", x => x.Item2).Should().Be("a, bb, ccc"); lst.JoinStr(" ", x => x.Item2 + "!").Should().Be("a! bb! ccc!"); lst.JoinStr("; ", x => x.Item1 + ":" + x.Item2).Should().Be("1:a; 2:bb; 3:ccc"); } [TestMethod] public void JoinStr_T_Int() { var lst = new[] { Tuple.Create(1, "a"), Tuple.Create(2, "bb"), Tuple.Create(3, "ccc") }; lst.JoinStr(", ", x => x.Item1).Should().Be("1, 2, 3"); lst.JoinStr(null, x => x.Item1 + 10).Should().Be("111213"); } [TestMethod] public void JoinStr_String() { Array.Empty().JoinStr(", ").Should().Be(""); new[] { "a" }.JoinStr(", ").Should().Be("a"); new[] { "a", "bb", "ccc" }.JoinStr(", ").Should().Be("a, bb, ccc"); new[] { "a", "bb", "ccc" }.JoinStr(null).Should().Be("abbccc"); } [TestMethod] public void JoinStr_Int() { Array.Empty().JoinStr(", ").Should().Be(""); new[] { 1 }.JoinStr(", ").Should().Be("1"); new[] { 1, 22, 333 }.JoinStr(", ").Should().Be("1, 22, 333"); new[] { 1, 22, 333 }.JoinStr(null).Should().Be("122333"); } [TestMethod] public void JoinNonEmpty() { Array.Empty().JoinNonEmpty(", ").Should().Be(""); new[] { "a" }.JoinNonEmpty(", ").Should().Be("a"); new[] { "a", "bb", "ccc" }.JoinNonEmpty(", ").Should().Be("a, bb, ccc"); new[] { "a", "bb", "ccc" }.JoinNonEmpty(null).Should().Be("abbccc"); new[] { "a", "bb", "ccc" }.JoinNonEmpty("").Should().Be("abbccc"); new[] { null, "a", "b" }.JoinNonEmpty(".").Should().Be("a.b"); new[] { "a", null, "b" }.JoinNonEmpty(".").Should().Be("a.b"); new[] { "a", "b", null }.JoinNonEmpty(".").Should().Be("a.b"); new string[] { null, null, null }.JoinNonEmpty(".").Should().Be(""); new string[] { "", "", "" }.JoinNonEmpty(".").Should().Be(""); new string[] { "", "\t", " " }.JoinNonEmpty(".").Should().Be(""); new string[] { "a", "\t", "b" }.JoinNonEmpty(".").Should().Be("a.b"); } [TestMethod] public void WhereNotNull_Class() { var instance = new object(); Array.Empty().WhereNotNull().Should().BeEmpty(); new object[] { null, null, null }.WhereNotNull().Should().BeEmpty(); new object[] { 1, "a", instance }.WhereNotNull().Should().BeEquivalentTo(new object[] { 1, "a", instance }); new object[] { 1, "a", null }.WhereNotNull().Should().BeEquivalentTo(new object[] { 1, "a" }); } [TestMethod] public void WhereNotNull_NullableStruct() { Array.Empty().WhereNotNull().Should().BeEmpty(); new StructType?[] { null, null, null }.WhereNotNull().Should().BeEmpty(); new StructType?[] { new StructType(1), new StructType(2), new StructType(3) } .WhereNotNull().Should().BeEquivalentTo(new object[] { new StructType(1), new StructType(2), new StructType(3) }); new StructType?[] { new StructType(1), new StructType(2), null }.WhereNotNull().Should().BeEquivalentTo(new object[] { new StructType(1), new StructType(2) }); } [TestMethod] public void JoinAndNull() => ((object[])null).JoinAnd().Should().Be(string.Empty); [TestMethod] [DataRow("")] // empty collection [DataRow("", null)] [DataRow("", "")] [DataRow("", "", "")] [DataRow("", "", " ", "\t", null)] [DataRow("a", "a")] [DataRow("a and b", "a", "b")] [DataRow("a and b", "a", null, "b")] [DataRow("a, b, and c", "a", "b", "c")] [DataRow("a, b, and c", "a", null, "b", "", "c")] public void JoinAndStrings(string expected, params string[] collection) => collection.JoinAnd().Should().Be(expected); [TestMethod] [DataRow("")] // Empty collection [DataRow("0", 0)] [DataRow("0 and 1", 0, 1)] [DataRow("0, 1, and 2", 0, 1, 2)] [DataRow("1000", 1000)] public void JoinAndInts(string expected, params int[] collection) => collection.JoinAnd().Should().Be(expected); [TestMethod] [DataRow("")] // Empty collection [DataRow("08/30/2022 12:29:11", "2022-08-30T12:29:11")] [DataRow("08/30/2022 12:29:11 and 12/24/2022 16:00:00", "2022-08-30T12:29:11", "2022-12-24T16:00:00")] [DataRow("08/30/2022 12:29:11, 12/24/2022 16:00:00, and 12/31/2022 00:00:00", "2022-08-30T12:29:11", "2022-12-24T16:00:00", "2022-12-31T00:00:00")] public void JoinAndDateTime(string expected, params string[] collection) { using var scope = new CurrentCultureScope(); collection.Select(x => DateTime.Parse(x)).JoinAnd().Should().Be(expected); } [TestMethod] public void JoinAndMixedClasses() { var collection = new Exception[] { new IndexOutOfRangeException("IndexOutOfRangeMessage"), new InvalidOperationException("OperationMessage"), null, new NotSupportedException("NotSupportedMessage"), }; var result = collection.JoinAnd(); result.Should().Be("System.IndexOutOfRangeException: IndexOutOfRangeMessage, System.InvalidOperationException: OperationMessage, and System.NotSupportedException: NotSupportedMessage"); } [TestMethod] public void ToSecondary_NullMessage() { var code = "public class C {}"; List locations = [ Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(0, 6)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(7, 12)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)) ]; var secondaryLocations = locations.ToSecondary(null); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().BeNull()); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondary_MessageArgs(string[] messageArgs) { var code = "public class C {}"; List locations = [ Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(0, 6)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(7, 12)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)) ]; var secondaryLocations = locations.ToSecondary("Message", messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be("Message")); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondary_MessageFormat(string format, params string[] messageArgs) { var code = "public class C {}"; List locations = [ Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(0, 6)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(7, 12)), Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)) ]; var secondaryLocations = locations.ToSecondary(format, messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be(string.Format(format, messageArgs))); } [TestMethod] public void ToSecondarySecondary_SyntaxNode_NullMessage() { List nodes = [ TestCompiler.NodeBetweenMarkersCS("$$public$$ class C {}").Node, TestCompiler.NodeBetweenMarkersCS("public $$class$$ C {}").Node, TestCompiler.NodeBetweenMarkersCS("public class $$C$$ {}").Node, ]; var secondaryLocations = nodes.ToSecondaryLocations(null); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().BeNull()); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondarySecondary_SyntaxNode_MessageArgs(string[] messageArgs) { List nodes = [ TestCompiler.NodeBetweenMarkersCS("$$public$$ class C {}").Node, TestCompiler.NodeBetweenMarkersCS("public $$class$$ C {}").Node, TestCompiler.NodeBetweenMarkersCS("public class $$C$$ {}").Node, ]; var secondaryLocations = nodes.ToSecondaryLocations("Message", messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be("Message")); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondarySecondary_SyntaxNode_MessageFormat(string format, params string[] messageArgs) { List nodes = [ TestCompiler.NodeBetweenMarkersCS("$$public$$ class C {}").Node, TestCompiler.NodeBetweenMarkersCS("public $$class$$ C {}").Node, TestCompiler.NodeBetweenMarkersCS("public class $$C$$ {}").Node, ]; var secondaryLocations = nodes.ToSecondaryLocations(format, messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be(string.Format(format, messageArgs))); } [TestMethod] public void ToSecondarySecondary_SyntaxToken_NullMessage() { List nodes = [ TestCompiler.TokenBetweenMarkersCS("$$public$$ class C {}").Token, TestCompiler.TokenBetweenMarkersCS("public $$class$$ C {}").Token, TestCompiler.TokenBetweenMarkersCS("public class $$C$$ {}").Token, ]; var secondaryLocations = nodes.ToSecondaryLocations(null); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().BeNull()); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondarySecondary_SyntaxToken_MessageArgs(string[] messageArgs) { List nodes = [ TestCompiler.TokenBetweenMarkersCS("$$public$$ class C {}").Token, TestCompiler.TokenBetweenMarkersCS("public $$class$$ C {}").Token, TestCompiler.TokenBetweenMarkersCS("public class $$C$$ {}").Token, ]; var secondaryLocations = nodes.ToSecondaryLocations("Message", messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be("Message")); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondarySecondary_SyntaxToken_MessageFormat(string format, params string[] messageArgs) { List nodes = [ TestCompiler.TokenBetweenMarkersCS("$$public$$ class C {}").Token, TestCompiler.TokenBetweenMarkersCS("public $$class$$ C {}").Token, TestCompiler.TokenBetweenMarkersCS("public class $$C$$ {}").Token, ]; var secondaryLocations = nodes.ToSecondaryLocations(format, messageArgs); secondaryLocations.Should().NotBeEmpty() .And.HaveCount(3) .And.AllSatisfy(x => x.Message.Should().Be(string.Format(format, messageArgs))); } private struct StructType { private readonly int count; public StructType(int count) { this.count = count; } } } #pragma warning restore SA1122 // Use string.Empty for empty strings ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/IXmlLineInfoExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class IXmlLineInfoExtensionsTest { [TestMethod] public void Element_Simple() { var doc = XDocument.Parse("", LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var tag = doc.Elements().First(); var location = tag.CreateLocation("Test", tag.Name, tag); location.Should().Be(Location.Create("Test", new TextSpan(0, 1), new LinePositionSpan(new LinePosition(0, 1), new LinePosition(0, 2)))); } [TestMethod] // https://sonarsource.atlassian.net/browse/USER-292 // How to fix: https://sonarsource.atlassian.net/browse/USER-292?focusedCommentId=763449 public void Element_WithNamespace() { var doc = XDocument.Parse(""" xxx """, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var passwordTag = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "Password"); var location = passwordTag.CreateLocation("Test", passwordTag.Name, passwordTag); location.Should().Be(Location.Create("Test", new TextSpan(3, 13), new LinePositionSpan(new LinePosition(3, 5), new LinePosition(3, 18)))); } [TestMethod] public void Element_WithDefaultNamespace() { var doc = XDocument.Parse(""" xxx """, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var passwordTag = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "Password"); var location = passwordTag.CreateLocation("Test", passwordTag.Name, passwordTag); location.Should().Be(Location.Create("Test", new TextSpan(3, 8), new LinePositionSpan(new LinePosition(3, 5), new LinePosition(3, 13)))); } [TestMethod] public void Attribute_WithNamespace() { var doc = XDocument.Parse(""" """, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var securityTag = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "Security"); var passwordAttribute = securityTag.Attributes().First(x => x.Name.LocalName == "Password"); var location = passwordAttribute.CreateLocation("Test", passwordAttribute.Name, passwordAttribute.Parent); location.Should().Be(Location.Create("Test", new TextSpan(3, 13), new LinePositionSpan(new LinePosition(3, 4), new LinePosition(3, 17)))); } [TestMethod] public void Element_WithNestedNamespaces() { var doc = XDocument.Parse(""" """, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var t2 = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "T2"); var t0005 = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "T0005"); var t2Location = t2.CreateLocation("Test", t2.Name, t2); var t0005Location = t0005.CreateLocation("Test", t0005.Name, t0005); // "c0005".Length + "t0005".Length + ":".Length == 11 t0005Location.Should().Be(Location.Create("Test", new TextSpan(3, 11), new LinePositionSpan(new LinePosition(3, 9), new LinePosition(3, 20)))); // "c2".Length + "t2".Length + ":".Length == 5 t2Location.Should().Be(Location.Create("Test", new TextSpan(4, 5), new LinePositionSpan(new LinePosition(4, 13), new LinePosition(4, 18)))); } [TestMethod] public void Element_WithRedefinition() { var doc = XDocument.Parse(""" """, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); var nestedTag = doc.DescendantNodes().OfType().First(x => x.Name.LocalName == "Nested"); var nestedLocation = nestedTag.CreateLocation("Test", nestedTag.Name, nestedTag); nestedLocation.Should().Be(Location.Create("Test", new TextSpan(3, 14), new LinePositionSpan(new LinePosition(3, 9), new LinePosition(3, 23)))); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/RegexExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class RegexExtensionsTest { // https://stackoverflow.com/questions/3403512/regex-match-take-a-very-long-time-to-execute // Regular expression with catastrophic backtracking to ensure timeout exception when a small timeout is set private const string TimeoutPattern = @"^((?[a-zA-Z]):\\)*((?[a-zA-Z0-9_]+(([a-zA-Z0-9_\s_\-\.]*[a-zA-Z0-9_]+)|([a-zA-Z0-9_]+)))\\)*(?([a-zA-Z0-9_]+(([a-zA-Z0-9_\s_\-\.]*[a-zA-Z0-9_]+)|([a-zA-Z0-9_]+))\.(?[a-zA-Z0-9]{1,6})$))"; private const string MatchingPath = @"C:\Users\username\AppData\Local\Temp\00af5451-626f-40db-af1d-89d376dc5ef6\SomeFile.csproj"; [TestMethod] [DataRow(true)] [DataRow(false)] public void SafeIsMatch_Timeout_Fallback(bool timeoutFallback) { var regex = new Regex(TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(1)); regex.SafeIsMatch(MatchingPath, timeoutFallback).Should().Be(timeoutFallback); } [TestMethod] [DataRow(MatchingPath, 1, false)] [DataRow(MatchingPath, 1_000_000, true)] [DataRow("äöü", 1, false)] [DataRow("äöü", 1_000_000, false)] public void SafeMatch_Timeout(string input, long timeoutTicks, bool matchSucceed) { var regex = new Regex(TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(timeoutTicks)); regex.SafeMatch(input).Success.Should().Be(matchSucceed); } [TestMethod] [DataRow(MatchingPath, 1, 0)] [DataRow(MatchingPath, 1_000_000, 1)] [DataRow("äöü", 1, 0)] [DataRow("äöü", 1_000_000, 0)] public void SafeMatches_Timeout(string input, long timeoutTicks, int matchCount) { var regex = new Regex(TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(timeoutTicks)); var actual = regex.SafeMatches(input); actual.Count.Should().Be(matchCount); if (matchCount > 0) { var access = () => actual[0]; access.Should().NotThrow().Which.Index.Should().Be(0); } } [TestMethod] [DataRow(MatchingPath, 1, MatchingPath)] [DataRow(MatchingPath, 1_000_000, "Replaced")] [DataRow("äöü", 1, "äöü")] [DataRow("äöü", 1_000_000, "äöü")] public void SafeReplace_Timeout(string input, long timeoutTicks, string expected) { var regex = new Regex(TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(timeoutTicks)); var actual = regex.SafeReplace(input, "Replaced"); actual.Should().Be(expected); } [TestMethod] [DataRow(MatchingPath, 1, false)] [DataRow(MatchingPath, 1_000_000, true)] [DataRow("äöü", 1, false)] [DataRow("äöü", 1_000_000, false)] public void SafeRegex_IsMatch_Timeout(string input, long timeoutTicks, bool isMatch) { var actual = SafeRegex.IsMatch(input, TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(timeoutTicks)); actual.Should().Be(isMatch); } [TestMethod] [DataRow(false)] [DataRow(true)] public void SafeRegex_IsMatch_TimeoutFallback(bool fallback) { var actual = SafeRegex.IsMatch(MatchingPath, TimeoutPattern, RegexOptions.None, TimeSpan.FromTicks(1), fallback); actual.Should().Be(fallback); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Extensions/XAttributeExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Xml.Linq; namespace SonarAnalyzer.Core.Test.Extensions; [TestClass] public class XAttributeExtensionsTest { [TestMethod] public void CreateLocation_WithNoLineInfo_ReturnsNull() { var sut = new XAttribute(XName.Get("name"), "A"); sut.CreateLocation(string.Empty).Should().BeNull(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Json/JsonNodeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Json.Test; [TestClass] public class JsonNodeTest { [TestMethod] public void BehavesAsValue() { var sut = new JsonNode(LinePosition.Zero, LinePosition.Zero, 42); sut.Kind.Should().Be(Kind.Value); sut.Value.Should().Be(42); sut.Invoking(x => x.Count).Should().Throw(); sut.Invoking(x => x[0]).Should().Throw(); sut.Invoking(x => x["Key"]).Should().Throw(); sut.Invoking(x => x.Keys).Should().Throw(); sut.Invoking(x => x.Add(sut)).Should().Throw(); sut.Invoking(x => x.Add("Key", sut)).Should().Throw(); sut.Invoking(x => x.ContainsKey("Key")).Should().Throw(); sut.Invoking(x => ((IEnumerable)x).GetEnumerator()).Should().Throw(); } [TestMethod] public void UnsupportedKinds() { Func action = () => new JsonNode(LinePosition.Zero, Kind.Value); action.Should().Throw(); action = () => new JsonNode(LinePosition.Zero, Kind.Unknown); action.Should().Throw(); } [TestMethod] public void BehavesAsList() { var a = new JsonNode(LinePosition.Zero, LinePosition.Zero, "a"); var b = new JsonNode(LinePosition.Zero, LinePosition.Zero, "b"); var sut = new JsonNode(LinePosition.Zero, Kind.List); sut.Add(a); sut.Add(b); sut.Kind.Should().Be(Kind.List); sut.Should().HaveCount(2); ((object)sut[0]).Should().Be(a); ((object)sut[1]).Should().Be(b); var cnt = 0; foreach (var item in sut) { new[] { a, b }.Should().Contain(item); cnt++; } cnt.Should().Be(2); sut.Invoking(x => x.Value).Should().Throw(); sut.Invoking(x => x["Key"]).Should().Throw(); sut.Invoking(x => x.Keys).Should().Throw(); sut.Invoking(x => x.Add("Key", sut)).Should().Throw(); sut.Invoking(x => x.ContainsKey("Key")).Should().Throw(); } [TestMethod] public void BehavesAsDictionary() { var a = new JsonNode(LinePosition.Zero, LinePosition.Zero, "a"); var b = new JsonNode(LinePosition.Zero, LinePosition.Zero, "b"); var sut = new JsonNode(LinePosition.Zero, Kind.Object); sut.Add("KeyA", a); sut.Add("KeyB", b); sut.Kind.Should().Be(Kind.Object); sut.Count.Should().Be(2); ((object)sut["KeyA"]).Should().Be(a); ((object)sut["KeyB"]).Should().Be(b); sut.Keys.Should().BeEquivalentTo("KeyA", "KeyB"); sut.ContainsKey("KeyA").Should().BeTrue(); sut.ContainsKey("KeyB").Should().BeTrue(); sut.ContainsKey("KeyC").Should().BeFalse(); sut.Invoking(x => x.Value).Should().Throw(); sut.Invoking(x => x[0]).Should().Throw(); sut.Invoking(x => x.Add(sut)).Should().Throw(); sut.Invoking(x => ((IEnumerable)x).GetEnumerator()).Should().Throw(); } [TestMethod] public void UpdateEnd() { var start = new LinePosition(1, 42); var end = new LinePosition(2, 10); var sut = new JsonNode(start, Kind.List); sut.Start.Should().Be(start); sut.End.Should().Be(LinePosition.Zero); sut.UpdateEnd(end); sut.End.Should().Be(end); sut.Invoking(x => x.UpdateEnd(end)).Should().Throw(); } // Light-weight way to test that string could be parsed. Precise tests could be found in SyntaxAnalyzerTest.cs [TestMethod] public void ParsedFromString() { var sut = JsonNode.FromString(@"[""a"",""b""]"); sut.Kind.Should().Be(Kind.List); sut.Should().HaveCount(2); sut[0].Kind.Should().Be(Kind.Value); sut[1].Kind.Should().Be(Kind.Value); sut[0].Value.Should().Be("a"); sut[1].Value.Should().Be("b"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Json/JsonSerializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace SonarAnalyzer.Core.Json.Test; [TestClass] public class JsonSerializerTest { [TestMethod] public void Serialize_UnsupportedType() => FluentActions.Invoking(() => JsonSerializer.Serialize(new { Value = new StringBuilder() })).Should().Throw().WithMessage("Unexpected type: StringBuilder"); [TestMethod] public void Serialize_BasicTypes() { var stringObjectMap = new Dictionary { { "StringKey", "String value"}, { "BoolKey", true}, { "IntKey", 42} }; var stringStringMap = ImmutableSortedDictionary.Empty.Add("Key A", "Value A").Add("Key B", "Value B"); var value = new TestData("Name", true, false, null, ["a", "b", "c"], StringComparison.CurrentCulture, stringObjectMap.ToArray(), stringStringMap.ToArray()); var result = JsonSerializer.Serialize(value); result.ToUnixLineEndings().Should().Be(""" { "stringValue": "Name", "trueValue": true, "falseValue": false, "nullString": null, "stringArray": ["a", "b", "c"], "enumValue": "CurrentCulture", "stringObjectMap": [ { "key": "StringKey", "value": "String value" }, { "key": "BoolKey", "value": true }, { "key": "IntKey", "value": 42 } ], "stringStringMap": [ { "key": "Key A", "value": "Value A" }, { "key": "Key B", "value": "Value B" } ] } """); // And it also deserializes correctly var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new JsonStringEnumConverter(), new PrimitiveObjectConverter() } }; System.Text.Json.JsonSerializer.Deserialize(result, options).Should().BeEquivalentTo(value); } [TestMethod] public void Serialize_Encoding() => JsonSerializer.Serialize(new { Value = "Start \\ \" \n \r \t \b \f End" }).ToUnixLineEndings().Should().Be(""" { "value": "Start \\ \" \n \r \t \b \f End" } """); [TestMethod] public void Serialize_ObjectWithIndexer() => JsonSerializer.Serialize(new List { 42, 43 }).ToUnixLineEndings().Should().Be(""" { "capacity": 4, "count": 2 } """); [DataRow((sbyte)42)] // sbyte [DataRow((byte)42)] // byte [DataRow((short)42)] // short [DataRow((ushort)42)] // ushort [DataRow(42)] // int [DataRow(42U)] // uint [DataRow(42L)] // long [DataRow(42UL)] // ulong [TestMethod] public void Serialize_IntegerTypes(object value) => JsonSerializer.Serialize(new { Value = value }).ToUnixLineEndings().Should().Be(""" { "value": 42 } """); [DataRow(42.42)] // double [DataRow(42.42f)] // float [TestMethod] public void Serialize_FloatingPointTypes(object value) { var newCulture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone(); newCulture.NumberFormat.NumberDecimalSeparator = ","; using var scope = new CurrentCultureScope(newCulture); value.ToString().Should().Be("42,42"); JsonSerializer.Serialize(new { Value = value }).ToUnixLineEndings().Should().Be(""" { "value": 42.42 } """); } [TestMethod] // decimal cannot be in DataRow public void Serialize_DecimalType() { var newCulture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone(); newCulture.NumberFormat.NumberDecimalSeparator = ","; using var scope = new CurrentCultureScope(newCulture); 42.42m.ToString().Should().Be("42,42"); JsonSerializer.Serialize(new { Value = 42.42m }).ToUnixLineEndings().Should().Be(""" { "value": 42.42 } """); } private sealed record TestData(string StringValue, bool TrueValue, bool FalseValue, string NullString, string[] StringArray, StringComparison EnumValue, KeyValuePair[] StringObjectMap, KeyValuePair[] StringStringMap) { public TestData() : this(null, true, false, null, null, StringComparison.Ordinal, null, null) { } } private sealed class PrimitiveObjectConverter : JsonConverter { // This makes KeyValuePair to deserialize to actual value. The default behavior is that object holds general JsonElement instead. public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch { JsonTokenType.True => true, JsonTokenType.False => false, JsonTokenType.Null => null, JsonTokenType.String => reader.GetString(), JsonTokenType.Number => reader.GetDouble(), _ => throw new InvalidOperationException("Unexpected token type: " + reader.TokenType) }; public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => throw new NotSupportedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Json/JsonWalkerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Json.Test; [TestClass] public class JsonWalkerTest { [TestMethod] public void VisitsAllNodes() { const string json = @" { ""OuterKey"": ""OuterValue"", ""OuterBool"": true, ""OuterNull"": null, ""NestedArray"": [ ""Array1"", [""Array2-Nested1"", null, ""Array2-Nested2"", { ""InnerKey"": ""Array2-NestedObject"" }], ""Array3"" ] }"; var sut = new JsonWalkerCollector(); sut.Visit(JsonNode.FromString(json)); sut.VisitedKeys.Should().BeEquivalentTo("OuterKey", "OuterBool", "OuterNull", "NestedArray", "InnerKey"); sut.VisitedValues.Should().BeEquivalentTo(new object[] { "OuterValue", true, null, "Array1", "Array2-Nested1", null, "Array2-Nested2", "Array2-NestedObject", "Array3" }); } [TestMethod] [DataRow("[]")] [DataRow("{}")] public void VisitsAtomicJson_VisitsEmpty(string json) { var sut = new JsonWalkerCollector(); sut.Visit(JsonNode.FromString(json)); sut.VisitedKeys.Should().BeEmpty(); sut.VisitedValues.Should().BeEmpty(); } private class JsonWalkerCollector : JsonWalker { public readonly List VisitedKeys = new(); public readonly List VisitedValues = new(); protected override void VisitObject(string key, JsonNode value) { VisitedKeys.Add(key); base.VisitObject(key, value); } protected override void VisitValue(JsonNode node) { VisitedValues.Add(node.Value); base.VisitValue(node); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Json/LexicalAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Json.Test; [TestClass] public class LexicalAnalyzerTest { [TestMethod] public void IgnoresWhiteSpace() { var sut = new LexicalAnalyzer(" \t\n\r [ \n \r ] \r\n"); sut.NextSymbol().Should().Be(Symbol.OpenSquareBracket); sut.NextSymbol().Should().Be(Symbol.CloseSquareBracket); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] public void SupportsSingleLineComments() { var sut = new LexicalAnalyzer(" // [ ]\t\n\r [ //{}\n \r ] //{{}}\r\n"); sut.NextSymbol().Should().Be(Symbol.OpenSquareBracket); sut.NextSymbol().Should().Be(Symbol.CloseSquareBracket); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] public void SupportsMultiLineComments() { var sut = new LexicalAnalyzer(" /* [ ]\t\n\r */ [ /* foo bar \n baz [] */ \n \r ] /*{{}}*/\r\n"); sut.NextSymbol().Should().Be(Symbol.OpenSquareBracket); sut.NextSymbol().Should().Be(Symbol.CloseSquareBracket); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] public void ReadSpecialCharacters() { var sut = new LexicalAnalyzer("{{[[,,]]}}::"); sut.NextSymbol().Should().Be(Symbol.OpenCurlyBracket); sut.NextSymbol().Should().Be(Symbol.OpenCurlyBracket); sut.NextSymbol().Should().Be(Symbol.OpenSquareBracket); sut.NextSymbol().Should().Be(Symbol.OpenSquareBracket); sut.NextSymbol().Should().Be(Symbol.Comma); sut.NextSymbol().Should().Be(Symbol.Comma); sut.NextSymbol().Should().Be(Symbol.CloseSquareBracket); sut.NextSymbol().Should().Be(Symbol.CloseSquareBracket); sut.NextSymbol().Should().Be(Symbol.CloseCurlyBracket); sut.NextSymbol().Should().Be(Symbol.CloseCurlyBracket); sut.NextSymbol().Should().Be(Symbol.Colon); sut.NextSymbol().Should().Be(Symbol.Colon); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow("0")] [DataRow("000")] [DataRow("-0")] [DataRow("-1")] [DataRow("-42")] [DataRow("-42424242")] [DataRow("1")] [DataRow("2")] [DataRow("3")] [DataRow("4")] [DataRow("5")] [DataRow("6")] [DataRow("7")] [DataRow("8")] [DataRow("9")] [DataRow("42")] [DataRow("42424242")] [DataRow("1234567890")] [DataRow("9223372036854775807")] [DataRow("-9223372036854775807")] public void ReadNumber_Integers_ParseToDouble(string source) { var sut = new LexicalAnalyzer(source); sut.NextSymbol().Should().Be(Symbol.Value); sut.Value.Should().BeOfType().And.Be(double.Parse(source)); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow("0.0")] [DataRow("000.000")] [DataRow("111.111")] [DataRow("424242.5555555")] public void ReadNumber_Decimal(string source) { var expected = decimal.Parse(source, CultureInfo.InvariantCulture); var sut = new LexicalAnalyzer(source); sut.NextSymbol().Should().Be(Symbol.Value); sut.Value.Should().BeOfType().And.Be(expected); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow("0e0", 0.0)] [DataRow("1e1", 10.0)] [DataRow("42e0", 42.0)] [DataRow("42e-1", 4.2)] [DataRow("42E-1", 4.2)] [DataRow("42e1", 420.0)] [DataRow("42e+1", 420.0)] [DataRow("42E+1", 420.0)] [DataRow("8e8", 800_000_000)] [DataRow("-42e1", -420.0)] [DataRow("-42e-1", -4.2)] [DataRow("-42e+1", -420.0)] [DataRow("4.2e1", 42.0)] [DataRow("44.22e2", 4422.0)] public void ReadNumber_Double_ParseToDouble(string source, double expected) { var sut = new LexicalAnalyzer(source); sut.NextSymbol().Should().Be(Symbol.Value); sut.Value.Should().BeOfType().And.Be(expected); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow(" \"\" ", "")] [DataRow(" \"Lorem Ipsum\" ", "Lorem Ipsum")] [DataRow(" /*\"Lorem Ipsum\"*/ \"dolor sit amet\" ", "dolor sit amet")] [DataRow(" \"Lorem /**/ Ipsum\" ", "Lorem /**/ Ipsum")] [DataRow(" \"Lorem // Ipsum\" ", "Lorem // Ipsum")] [DataRow(" \"Quote\\\"Quote\" ", "Quote\"Quote")] [DataRow(" \"Slash\\/ Backslash\\\\\" ", "Slash/ Backslash\\")] [DataRow(" \"Special B\\b F\\f N\\n R\\r T\\t\" ", "Special B\b F\f N\n R\r T\t")] [DataRow(" \"Unicode\u0158\u0159\" ", "UnicodeŘř")] [DataRow(@"""\u0159""", "ř")] public void ReadString(string source, string expected) { var sut = new LexicalAnalyzer(source); sut.NextSymbol().Should().Be(Symbol.Value); sut.Value.Should().BeOfType().And.Be(expected); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow("null", null)] [DataRow("true", true)] [DataRow("false", false)] public void ReadKeyword(string source, object expected) { var sut = new LexicalAnalyzer(source); sut.NextSymbol().Should().Be(Symbol.Value); sut.Value.Should().Be(expected); sut.NextSymbol().Should().Be(Symbol.EndOfInput); } [TestMethod] [DataRow(".", "Unexpected character '.' at line 1 position 1")] [DataRow("tx", "Unexpected character 'x'. Keyword 'true' was expected at line 1 position 1")] [DataRow(@"""\u", @"Unexpected EOI, \uXXXX escape expected at line 1 position 1")] [DataRow(@"""\u12", @"Unexpected EOI, \uXXXX escape expected at line 1 position 1")] [DataRow(@"""\u12345", @"Unexpected EOI at line 1 position 1")] [DataRow(@"""\x", @"Unexpected escape sequence \x at line 1 position 1")] [DataRow(@"""\", @"Unexpected EOI at line 1 position 1")] [DataRow("0-", "Unexpected Number format: Unexpected '-' at line 1 position 1")] [DataRow("-.", "Unexpected Number format: Unexpected '.' at line 1 position 1")] [DataRow("0..", "Unexpected Number format: Unexpected '.' at line 1 position 1")] [DataRow("0.0.", "Unexpected Number format: Unexpected '.' at line 1 position 1")] [DataRow("0e0.0", "Unexpected Number format: Unexpected '.' at line 1 position 1")] [DataRow("0+", "Unexpected Number format at line 1 position 1")] [DataRow("0.0+", "Unexpected Number format at line 1 position 1")] [DataRow("0e0+0", "Unexpected Number format at line 1 position 1")] [DataRow("0e", "Unexpected Number exponent format: at line 1 position 1")] [DataRow("0e-", "Unexpected Number exponent format: - at line 1 position 1")] [DataRow("/*", "Unexpected EOI at line 1 position 1")] [DataRow(" /* * /", "Unexpected EOI at line 1 position 1")] [DataRow(" /* *", "Unexpected EOI at line 1 position 1")] [DataRow("/*/", "Unexpected EOI at line 1 position 1")] [DataRow(" */", "Unexpected character '*' at line 1 position 2")] [DataRow(" /0", "Unexpected character '*' at line 1 position 2")] [DataRow(" /", "Unexpected character '*' at line 1 position 2")] [DataRow("#", "Unexpected character '*' at line 1 position 1")] [DataRow("$", "Unexpected character '*' at line 1 position 1")] [DataRow("%", "Unexpected character '*' at line 1 position 1")] [DataRow("&", "Unexpected character '*' at line 1 position 1")] [DataRow("'", "Unexpected character '*' at line 1 position 1")] [DataRow("(", "Unexpected character '*' at line 1 position 1")] [DataRow(")", "Unexpected character '*' at line 1 position 1")] [DataRow("*", "Unexpected character '*' at line 1 position 1")] [DataRow("+", "Unexpected character '*' at line 1 position 1")] [DataRow(".", "Unexpected character '*' at line 1 position 1")] [DataRow("/", "Unexpected character '*' at line 1 position 1")] public void InvalidInput_ThrowsJsonException(string source, string expectedMessage) { var sut = new LexicalAnalyzer(source); sut.Invoking(x => x.NextSymbol()).Should().Throw().WithMessage(expectedMessage); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Json/SyntaxAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Linq.Expressions; using SonarAnalyzer.Core.Json.Parsing; namespace SonarAnalyzer.Core.Json.Test; [TestClass] public class SyntaxAnalyzerTest { [TestMethod] [DataRow("[null]", null)] [DataRow("[true]", true)] [DataRow("[false]", false)] [DataRow("[42]", 42)] [DataRow("[42.42]", 42.42)] [DataRow("[\"Lorem Ipsum\"]", "Lorem Ipsum")] public void StandaloneValue(string source, object expected) { var sut = new SyntaxAnalyzer(source); var ret = sut.Parse(); ret.Kind.Should().Be(Kind.List); ret.Should().ContainSingle(); var value = ret.Single(); value.Kind.Should().Be(Kind.Value); value.Value.Should().Be(expected); } [TestMethod] [DataRow("{}")] [DataRow("{ }")] [DataRow(" { \t\n\r } ")] public void EmptyObject(string source) { var sut = new SyntaxAnalyzer(source); var ret = sut.Parse(); ret.Kind.Should().Be(Kind.Object); ret.Count.Should().Be(0); } [TestMethod] [DataRow("[]")] [DataRow("[ ]")] [DataRow(" [ \t\n\r ] ")] public void EmptyList(string source) { var sut = new SyntaxAnalyzer(source); var ret = sut.Parse(); ret.Kind.Should().Be(Kind.List); ret.Should().BeEmpty(); } [TestMethod] public void ParseObject() { const string json = @" { ""a"": ""aaa"", ""b"": 42, ""c"": true, ""d"": null }"; var sut = new SyntaxAnalyzer(json); var ret = sut.Parse(); ret.Kind.Should().Be(Kind.Object); ret.ContainsKey("a").Should().BeTrue(); ret.ContainsKey("b").Should().BeTrue(); ret.ContainsKey("c").Should().BeTrue(); ret.ContainsKey("d").Should().BeTrue(); ret["a"].Value.Should().Be("aaa"); ret["b"].Value.Should().Be(42); ret["c"].Value.Should().Be(true); ret["d"].Value.Should().Be(null); } [TestMethod] public void ParseList() { const string json = @"[""aaa"", 42, true, null]"; var sut = new SyntaxAnalyzer(json); var ret = sut.Parse(); ret.Kind.Should().Be(Kind.List); ret.Select(x => x.Value).Should().ContainInOrder("aaa", 42, true, null); } [TestMethod] public void ParseNested() { const string json = @" { ""a"": [""aaa"", ""bbb"", ""ccc"", { ""x"": true}], ""b"": 42, ""c"": {""1"": 111, ""2"": ""222"", ""list"": [42, 43, 44]} }"; var sut = new SyntaxAnalyzer(json); var root = sut.Parse(); root.Kind.Should().Be(Kind.Object); root.ContainsKey("a").Should().BeTrue(); root.ContainsKey("b").Should().BeTrue(); root.ContainsKey("c").Should().BeTrue(); root.ContainsKey("d").Should().BeFalse(); var a = root["a"]; a.Kind.Should().Be(Kind.List); a.Where(x => x.Kind == Kind.Value).Select(x => x.Value).Should().ContainInOrder(new[] { "aaa", "bbb", "ccc" }); var objectInList = a.Single(x => x.Kind == Kind.Object); objectInList.ContainsKey("x").Should().BeTrue(); objectInList["x"].Value.Should().Be(true); root["b"].Value.Should().Be(42); var c = root["c"]; c.Kind.Should().Be(Kind.Object); c.ContainsKey("1").Should().BeTrue(); c.ContainsKey("2").Should().BeTrue(); c.ContainsKey("list").Should().BeTrue(); c["1"].Kind.Should().Be(Kind.Value); c["1"].Value.Should().Be(111); c["2"].Kind.Should().Be(Kind.Value); c["2"].Value.Should().Be("222"); c["list"].Kind.Should().Be(Kind.List); c["list"].Select(x => x.Value).Should().ContainInOrder(42, 43, 44); } [TestMethod] [DataRow("true", "{ or [ expected, but Value found at line 1 position 1")] [DataRow(@"{ ""key"",", ": expected, but Comma found at line 1 position 8")] [DataRow("{,", "String Value expected, but Comma found at line 1 position 2")] [DataRow("[0 0", "] expected, but Value found at line 1 position 4")] [DataRow("[ ,", "{, [ or Value (true, false, null, String, Number) expected, but Comma found at line 1 position 3")] [DataRow(@"{ ""key"" : ""value"" ", "} expected, but EndOfInput found at line 1 position 11")] public void InvalidSyntax_Throws(string source, string expectedMessage) { var sut = new SyntaxAnalyzer(source); sut.Invoking(x => x.Parse()).Should().Throw().WithMessage(expectedMessage); } [TestMethod] public void Location() { const string json = @"{ 'a': [ 'aaa', 'bbb', 'ccc', { 'x': true} ], 'b': 42, 'c': { '1': 111, '2': '222', 'list': [42, 43, 44] }, 'd': [] }"; var sut = new SyntaxAnalyzer(json.Replace('\'', '"')); // Avoid "" escaping to preserve correct indexes in this editor var root = sut.Parse(); AssertLocation(() => root, 0, 0, 15, 1); AssertLocation(() => root["a"], 1, 9, 6, 12); AssertLocation(() => root["b"], 7, 9, 7, 11); AssertLocation(() => root["c"], 8, 9, 12, 10); AssertLocation(() => root["d"], 14, 0, 14, 2); var array = root["a"]; AssertLocation(() => array[0], 2, 12, 2, 17); AssertLocation(() => array[1], 3, 12, 3, 17); AssertLocation(() => array[2], 4, 12, 4, 17); AssertLocation(() => array[3], 5, 12, 5, 24); AssertLocation(() => array[3]["x"], 5, 19, 5, 23); } [TestMethod] public void Location_EndOfLines() { const string json = "[0,\n1,\r2,\r\n3,\u20284,\u20295]"; var sut = new SyntaxAnalyzer(json); var root = sut.Parse(); AssertLocation(() => root, 0, 0, 5, 2); AssertLocation(() => root[0], 0, 1, 0, 2); AssertLocation(() => root[1], 1, 0, 1, 1); AssertLocation(() => root[2], 2, 0, 2, 1); AssertLocation(() => root[3], 3, 0, 3, 1); AssertLocation(() => root[4], 4, 0, 4, 1); AssertLocation(() => root[5], 5, 0, 5, 1); } private static void AssertLocation(Expression> expression, int startLine, int startCharacter, int endLine, int endCharacter) { var node = expression.Compile()(); node.Start.Line.Should().Be(startLine, expression.ToString()); node.Start.Character.Should().Be(startCharacter, expression.ToString()); node.End.Line.Should().Be(endLine, expression.ToString()); node.End.Character.Should().Be(endCharacter, expression.ToString()); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/RegularExpressions/RegexContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text.RegularExpressions; namespace SonarAnalyzer.Core.RegularExpressions.Test; [TestClass] public class RegexContextTest { [TestMethod] [DataRow("[A", RegexOptions.None)] #if NET [DataRow(@"^([0-9]{2})(? new RegexContext(null, pattern, null, options).ParseError.Should().NotBeNull(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/RegularExpressions/WildcardPatternMatcherTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.Core.RegularExpressions.Test; [TestClass] public class WildcardPatternMatcherTest { /// /// Based on https://github.com/SonarSource/sonar-plugin-api/blob/master/plugin-api/src/test/java/org/sonar/api/utils/WildcardPatternTest.java. /// [TestMethod] [DataRow("Foo", "Foo", true)] [DataRow("foo", "FOO", false)] [DataRow("Foo", "Foot", false)] [DataRow("Foo", "Bar", false)] [DataRow("org/T?st.cs", "org/Test.cs", true)] [DataRow("org/T?st.cs", "org/Tost.cs", true)] [DataRow("org/T?st.cs", "org/Teeest.cs", false)] [DataRow("org/*.cs", "org/Foo.cs", true)] [DataRow("org/*.cs", "org/Bar.cs", true)] [DataRow("org/**", "org/Foo.cs", true)] [DataRow("org/**", "org/foo/bar.jsp", true)] [DataRow("org/**/Test.cs", "org/Test.cs", true)] [DataRow("org/**/Test.cs", "org/foo/Test.cs", true)] [DataRow("org/**/Test.cs", "org/foo/bar/Test.cs", true)] [DataRow("org/**/*.cs", "org/Foo.cs", true)] [DataRow("org/**/*.cs", "org/foo/Bar.cs", true)] [DataRow("org/**/*.cs", "org/foo/bar/Baz.cs", true)] [DataRow("o?/**/*.cs", "org/test.cs", false)] [DataRow("o?/**/*.cs", "o/test.cs", false)] [DataRow("o?/**/*.cs", "og/test.cs", true)] [DataRow("o?/**/*.cs", "og/foo/bar/test.cs", true)] [DataRow("o?/**/*.cs", "og/foo/bar/test.c", false)] [DataRow("org/sonar/**", "org/sonar/commons/Foo", true)] [DataRow("org/sonar/**", "org/sonar/Foo.cs", true)] [DataRow("xxx/org/sonar/**", "org/sonar/Foo", false)] [DataRow("org/sonar/**/**", "org/sonar/commons/Foo", true)] [DataRow("org/sonar/**/**", "org/sonar/commons/sub/Foo.cs", true)] [DataRow("org/sonar/**/Foo", "org/sonar/commons/sub/Foo", true)] [DataRow("org/sonar/**/Foo", "org/sonar/Foo", true)] [DataRow("*/foo/*", "org/foo/Bar", true)] [DataRow("*/foo/*", "foo/Bar", false)] [DataRow("*/foo/*", "foo", false)] [DataRow("*/foo/*", "org/foo/bar/Hello", false)] [DataRow("hell?", "hell", false)] [DataRow("hell?", "hello", true)] [DataRow("hell?", "helloworld", false)] [DataRow("**/Reader", "java/io/Reader", true)] [DataRow("**/Reader", "org/sonar/channel/CodeReader", false)] [DataRow("**", "java/io/Reader", true)] [DataRow("**/app/**", "com/app/Utils", true)] [DataRow("**/app/**", "com/application/MyService", false)] [DataRow("**/*$*", "foo/bar", false)] [DataRow("**/*$*", "foo/bar$baz", true)] [DataRow("a+", "aa", false)] [DataRow("a+", "a+", true)] [DataRow("[ab]", "a", false)] [DataRow("[ab]", "[ab]", true)] [DataRow("\\n", "\n", false)] [DataRow("foo\\bar", "foo/bar", true)] [DataRow("/foo", "foo", true)] [DataRow("\\foo", "foo", true)] [DataRow("foo\\bar", "foo\\bar", true)] [DataRow("foo/bar", "foo\\bar", true)] [DataRow("foo\\bar/baz", "foo\\bar\\baz", true)] [DataRow("*cshtml.g.cs", "hello_cshtml.g.cs", true)] // Compile time cshtml auto-generated files [DataRow("*razor.g.cs", "hello_razor.g.cs", true)] // Compile time cshtml auto-generated files [DataRow("**\\*cshtml*g.cs", "C:\\Something\\SomeFile.cshtml.-6NXeWT5Akt4vxdz.ide.g.cs", true)] // Design time cshtml auto-generated files [DataRow("**/*cshtml*g.cs", "C:\\Something\\SomeFile.cshtml.-6NXeWT5Akt4vxdz.ide.g.cs", true)] [DataRow("**/*razor*ide.g.cs", "C:\\Something\\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs", true)] // Design time razor auto-generated files public void IsMatch_MatchesPatternsAsExpected(string pattern, string input, bool expectedResult) { // The test cases are copied from the plugin-api and the directory separators need replacing as Roslyn will not give us the paths with '/'. input = input.Replace("/", Path.DirectorySeparatorChar.ToString()); WildcardPatternMatcher.IsMatch(pattern, input, false).Should().Be(expectedResult); } [TestMethod] [DataRow("")] [DataRow(" ")] [DataRow("/")] [DataRow("\\")] public void IsMatch_InvalidPattern_ReturnsFalse(string pattern) => WildcardPatternMatcher.IsMatch(pattern, "foo", false).Should().BeFalse(); [TestMethod] [DataRow(null, "foo")] [DataRow("foo", null)] public void IsMatch_InputParametersArenull_DoesNotThrow(string pattern, string input) => WildcardPatternMatcher.IsMatch(pattern, input, false).Should().BeFalse(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/IMethodSymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class IMethodSymbolExtensionsTest { internal const string TestInput = """ using System.Linq; namespace NS { public static class Helper { public static void ToVoid(this int self){} } public class Class { public static void TestMethod() { new int[] { 0, 1, 2 }.Any(); Enumerable.Any(new int[] { 0, 1, 2 }); new int[] { 0, 1, 2 }.Clone(); new int[] { 0, 1, 2 }.Cast(); 1.ToVoid(); } } } """; private SemanticModel model; private List statements; public TestContext TestContext { get; set; } [TestInitialize] public void Compile() { var compilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).AddSnippet(TestInput).GetCompilation(); var tree = compilation.SyntaxTrees.First(); model = compilation.GetSemanticModel(tree); statements = tree.GetRoot(TestContext.CancellationToken).DescendantNodes().OfType().First(x => x.Identifier.ValueText == "TestMethod") .Body.DescendantNodes().OfType().ToList(); } [TestMethod] public void Symbol_IsExtensionOnIEnumerable() { MethodSymbolForIndex(3).IsExtensionOn(KnownType.System_Collections_IEnumerable) .Should().BeTrue(); MethodSymbolForIndex(2).IsExtensionOn(KnownType.System_Collections_IEnumerable) .Should().BeFalse(); MethodSymbolForIndex(1).IsExtensionOn(KnownType.System_Collections_IEnumerable) .Should().BeFalse(); } [TestMethod] public void Symbol_IsExtensionOnGenericIEnumerable() { MethodSymbolForIndex(0).IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) .Should().BeTrue(); MethodSymbolForIndex(1).IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) .Should().BeTrue(); MethodSymbolForIndex(2).IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) .Should().BeFalse(); MethodSymbolForIndex(3).IsExtensionOn(KnownType.System_Collections_Generic_IEnumerable_T) .Should().BeFalse(); } [TestMethod] public void Symbol_IsExtensionOnInt() { MethodSymbolForIndex(4).IsExtensionOn(KnownType.System_Int32) .Should().BeTrue(); MethodSymbolForIndex(2).IsExtensionOn(KnownType.System_Int32) .Should().BeFalse(); } [TestMethod] public void IsAnyAttributeInOverridingChain_WhenMethodSymbolIsNull_ReturnsFalse() => ((IMethodSymbol)null).IsAnyAttributeInOverridingChain().Should().BeFalse(); [TestMethod] [DataRow(MethodKind.AnonymousFunction, "method")] [DataRow(MethodKind.BuiltinOperator, "operator")] [DataRow(MethodKind.Constructor, "constructor")] [DataRow(MethodKind.Conversion, "operator")] [DataRow(MethodKind.DeclareMethod, "method")] [DataRow(MethodKind.DelegateInvoke, "method")] [DataRow(MethodKind.Destructor, "destructor")] [DataRow(MethodKind.EventAdd, "method")] [DataRow(MethodKind.EventRaise, "method")] [DataRow(MethodKind.EventRemove, "method")] [DataRow(MethodKind.ExplicitInterfaceImplementation, "method")] [DataRow(MethodKind.FunctionPointerSignature, "method")] [DataRow(MethodKind.LambdaMethod, "method")] [DataRow(MethodKind.LocalFunction, "local function")] [DataRow(MethodKind.Ordinary, "method")] [DataRow(MethodKind.PropertyGet, "getter")] [DataRow(MethodKind.PropertySet, "setter")] [DataRow(MethodKind.ReducedExtension, "method")] [DataRow(MethodKind.SharedConstructor, "constructor")] [DataRow(MethodKind.StaticConstructor, "constructor")] [DataRow(MethodKind.UserDefinedOperator, "operator")] public void GetClassification_Method(MethodKind methodKind, string expected) { var symbol = Substitute.For(); symbol.Kind.Returns(SymbolKind.Method); symbol.MethodKind.Returns(methodKind); symbol.GetClassification().Should().Be(expected); } [TestMethod] [DataRow("BaseClass ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedClosedGeneric ", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedNoOverrides", "VirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedClosedGeneric ", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedNoOverrides", "GenericVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "NonVirtualMethod ")] [DataRow("DerivedClosedGeneric ", "NonVirtualMethod ")] [DataRow("DerivedNoOverrides", "NonVirtualMethod ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "GenericNonVirtualMethod")] [DataRow("DerivedClosedGeneric ", "GenericNonVirtualMethod")] [DataRow("DerivedNoOverrides", "GenericNonVirtualMethod", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] public void GetAttributesWithInherited_MethodSymbol(string className, string methodName, params string[] expectedAttributes) { className = className.TrimEnd(); methodName = methodName.TrimEnd(); var code = $$""" using System; [AttributeUsage(AttributeTargets.All, Inherited = true)] public class InheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] public class NotInheritedAttribute : Attribute { } public class DerivedInheritedAttribute: InheritedAttribute { } public class DerivedNotInheritedAttribute: NotInheritedAttribute { } public class UnannotatedAttribute : Attribute { } public class BaseClass { [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public virtual void VirtualMethod() { } [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public void NonVirtualMethod() { } [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public void GenericNonVirtualMethod() { } [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public virtual void GenericVirtualMethod() { } } public class DerivedOpenGeneric: BaseClass { public override void VirtualMethod() { } public new void NonVirtualMethod() { } public new void GenericNonVirtualMethod() { } public override void GenericVirtualMethod() { } } public class DerivedClosedGeneric: BaseClass { public override void VirtualMethod() { } public new void NonVirtualMethod() { } public new void GenericNonVirtualMethod() { } public override void GenericVirtualMethod() { } } public class DerivedNoOverrides: BaseClass { } public class Program { public static void Main() { new {{className}}().{{methodName}}(); } } """; var compiler = new SnippetCompiler(code); var invocationExpression = compiler.Nodes().Should().ContainSingle().Subject; var method = compiler.Symbol(invocationExpression); var actual = method.GetAttributesWithInherited().Select(x => x.AttributeClass.Name).ToList(); actual.Should().BeEquivalentTo(expectedAttributes); // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var type = compiler.EmitAssembly().GetType(className.Replace("", "`1"), throwOnError: true); var methodInfo = type.GetMethod(methodName.Replace("", string.Empty)); methodInfo.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } [TestMethod] [DataRow("3.0.20105.1")] [DataRow(TestConstants.NuGetLatestVersion)] public void IsControllerActionMethod_PublicControllerMethods_AreEntryPoints(string aspNetMvcVersion) { const string code = """ public abstract class Foo : System.Web.Mvc.Controller { public Foo() { } public void PublicFoo() { } protected void ProtectedFoo() { } internal void InternalFoo() { } private void PrivateFoo() { } public static void StaticFoo() { } public virtual void VirtualFoo() { } public abstract void AbstractFoo(); public void InFoo(in string arg) { } public void OutFoo(out string arg) { arg = null; } public void RefFoo(ref string arg) { } public void ReadonlyRefFoo(ref readonly string arg) { } public void GenericFoo(T arg) { } private class Bar : System.Web.Mvc.Controller { public void InnerFoo() { } } [System.Web.Mvc.NonActionAttribute] public void PublicNonAction() { } } """; var compilation = new SnippetCompiler(code, NuGetMetadataReference.MicrosoftAspNetMvc(aspNetMvcVersion)); compilation.TypeByMetadataName("Foo").Constructors[0].IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.PublicFoo").IsControllerActionMethod().Should().BeTrue(); compilation.MethodSymbol("Foo.ProtectedFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.InternalFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.PrivateFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.StaticFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.VirtualFoo").IsControllerActionMethod().Should().BeTrue(); compilation.MethodSymbol("Foo.AbstractFoo").IsControllerActionMethod().Should().BeTrue(); compilation.MethodSymbol("Foo.InFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.OutFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.ReadonlyRefFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.RefFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.GenericFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.InnerFoo").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.PublicNonAction").IsControllerActionMethod().Should().BeFalse(); } [TestMethod] [DataRow("3.0.20105.1")] [DataRow(TestConstants.NuGetLatestVersion)] public void IsControllerActionMethod_ControllerMethods_AreEntryPoints(string aspNetMvcVersion) { const string code = """ public class Foo : System.Web.Mvc.Controller { public void PublicFoo() { } [System.Web.Mvc.NonActionAttribute] public void PublicNonAction() { } } public class Controller { public void PublicBar() { } } public class MyController : Controller { public void PublicDiz() { } } """; var compilation = new SnippetCompiler(code, NuGetMetadataReference.MicrosoftAspNetMvc(aspNetMvcVersion)); compilation.MethodSymbol("Foo.PublicFoo").IsControllerActionMethod().Should().BeTrue(); compilation.MethodSymbol("Controller.PublicBar").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("MyController.PublicDiz").IsControllerActionMethod().Should().BeFalse(); compilation.MethodSymbol("Foo.PublicNonAction").IsControllerActionMethod().Should().BeFalse(); } [TestMethod] [DataRow("2.1.3")] [DataRow(TestConstants.NuGetLatestVersion)] public void IsControllerActionMethod_MethodsInClassesWithControllerAttribute_AreEntryPoints(string aspNetMvcVersion) { const string code = """ [Microsoft.AspNetCore.Mvc.ControllerAttribute] public class Foo { public void PublicFoo() { } [Microsoft.AspNetCore.Mvc.NonActionAttribute] public void PublicNonAction() { } } """; var compilation = new SnippetCompiler(code, MetadataReferenceFacade.NetStandard.Union(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetMvcVersion))); compilation.MethodSymbol("Foo.PublicFoo").IsControllerActionMethod().Should().BeTrue(); compilation.MethodSymbol("Foo.PublicNonAction").IsControllerActionMethod().Should().BeFalse(); } [TestMethod] [DataRow("2.1.3")] [DataRow(TestConstants.NuGetLatestVersion)] public void IsControllerActionMethod_MethodsInClassesWithNonControllerAttribute_AreNotEntryPoints(string aspNetMvcVersion) { const string code = """ [Microsoft.AspNetCore.Mvc.NonControllerAttribute] public class Foo : Microsoft.AspNetCore.Mvc.ControllerBase { public void PublicFoo() { } } """; var compilation = new SnippetCompiler(code, MetadataReferenceFacade.NetStandard.Union(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetMvcVersion))); compilation.MethodSymbol("Foo.PublicFoo").IsControllerActionMethod().Should().BeFalse(); } [TestMethod] [DataRow("2.1.3")] [DataRow(TestConstants.NuGetLatestVersion)] public void IsControllerActionMethod_ConstructorsInClasses_AreNotEntryPoints(string aspNetMvcVersion) { const string code = """ [Microsoft.AspNetCore.Mvc.ControllerAttribute] public class Foo : Microsoft.AspNetCore.Mvc.ControllerBase { public Foo() { } } """; var compilation = new SnippetCompiler(code, MetadataReferenceFacade.NetStandard.Union(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetMvcVersion))); compilation.TypeByMetadataName("Foo").Constructors[0].IsControllerActionMethod().Should().BeFalse(); } [TestMethod] public void IsImplementingInterfaceMember_VBRenamedExplicitImplementation() { var compilation = new SnippetCompiler(""" Public Class Foo Implements System.IEquatable(Of Foo) Public Function MyEquals(other As Foo) As Boolean Implements System.IEquatable(Of Foo).Equals Return True End Function End Class """, false, AnalyzerLanguage.VisualBasic); var methodSymbol = compilation.DeclaredSymbols().Single(x => x.Name == "MyEquals"); methodSymbol.IsImplementingInterfaceMember(KnownType.System_IEquatable_T, nameof(IEquatable<>.Equals)).Should().BeTrue(); } [TestMethod] [DataRow("List", "Clear()", "Clear", "System.Collections.Generic.ICollection", "T")] [DataRow("List", "Clear()", "Clear", "System.Collections.IList")] [DataRow("List", "RemoveAt(1)", "RemoveAt", "System.Collections.Generic.IList", "T")] [DataRow("List", "RemoveAt(1)", "RemoveAt", "System.Collections.IList")] [DataRow("ObservableCollection", "RemoveAt(1)", "RemoveAt", "System.Collections.Generic.IList", "T")] [DataRow("ObservableCollection", "RemoveAt(1)", "RemoveAt", "System.Collections.IList")] [DataRow("Derived1", "ToString(string.Empty, null)", "ToString", "System.IFormattable")] [DataRow("Derived2", "ToString(string.Empty, null)", "ToString", "System.IFormattable")] [DataRow("Derived3", "ToString(string.Empty, null)", "ToString", "System.IFormattable")] [DataRow("Derived4", "ToString(string.Empty, null)", "ToString", "System.IFormattable")] public void IsImplementingInterfaceMember_Methods(string declaration, string invocation, string methodName, string interfaceType, params string[] genericParameter) { var code = $$""" using System; using System.Collections.Generic; using System.Collections.ObjectModel; public class Test { public void Method({{declaration}} instance) { instance.{{invocation}}; } } public class Base { public virtual string ToString(string format, IFormatProvider formatProvider) => ""; } public class Derived1: Base, IFormattable { public override string ToString(string format, IFormatProvider formatProvider) => ""; } public class Derived2: Derived1 { public override string ToString(string format, IFormatProvider formatProvider) => ""; } public class Derived3: Derived2 { } public class Derived4: Derived3 { public override string ToString(string format, IFormatProvider formatProvider) => ""; } """; var compilation = new SnippetCompiler(code); var invocationSyntax = compilation.Tree.GetRoot(TestContext.CancellationToken).DescendantNodesAndSelf().OfType().First(); var methodSymbol = compilation.Model.GetSymbolInfo(invocationSyntax, TestContext.CancellationToken).Symbol as IMethodSymbol; methodSymbol.IsImplementingInterfaceMember(new KnownType(interfaceType, genericParameter), methodName).Should().BeTrue(); } [TestMethod] public void IsExtension_ExtensionMethod_ReturnsTrue_CS() => new SnippetCompiler(""" public static class Extensions { public static void ExtensionMethod(this string s) { } extension(string s) { public int ExtensionMemberMethod() => 42; } extension(string) { public static int StaticExtensionMemberMethod() => 42; } } """).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeTrue()); [TestMethod] public void IsExtension_RegularMethod_ReturnsFalse_CS() => new SnippetCompiler(""" public class Sample { public void RegularMethod() { } public static void StaticRegularMethod() { } } """).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeFalse()); [TestMethod] public void IsExtension_ExtensionMethod_ReturnsTrue_VB() => new SnippetCompiler(""" Module Extensions Public Sub ExtensionMethod(s As String) End Sub End Module """, false, AnalyzerLanguage.VisualBasic).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeTrue()); [TestMethod] public void IsExtension_RegularMethod_ReturnsFalse_VB() => new SnippetCompiler(""" Public Class Sample Public Sub RegularMethod() End Sub Public Shared Sub SharedRegularMethod() End Sub End Class """, false, AnalyzerLanguage.VisualBasic).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeFalse()); private IMethodSymbol MethodSymbolForIndex(int index) { var statement = (ExpressionStatementSyntax)statements[index]; var methodSymbol = model.GetSymbolInfo(statement.Expression, TestContext.CancellationToken).Symbol as IMethodSymbol; return methodSymbol; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/INamedTypeSymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class INamedTypeSymbolExtensionsTest { [TestMethod] public void GetAllNamedTypesForNamedType_WhenSymbolIsNull_ReturnsEmpty() => ((INamedTypeSymbol)null).GetAllNamedTypes().Should().BeEmpty(); [TestMethod] [DataRow(TypeKind.Array, "array")] [DataRow(TypeKind.Class, "class")] [DataRow(TypeKind.Delegate, "delegate")] [DataRow(TypeKind.Dynamic, "dynamic")] [DataRow(TypeKind.Enum, "enum")] [DataRow(TypeKind.Error, "error")] [DataRow(TypeKind.FunctionPointer, "function pointer")] [DataRow(TypeKind.Interface, "interface")] [DataRow(TypeKind.Module, "module")] [DataRow(TypeKind.Pointer, "pointer")] [DataRow(TypeKind.Struct, "struct")] [DataRow(TypeKind.Structure, "struct")] [DataRow(TypeKind.Submission, "submission")] [DataRow(TypeKind.TypeParameter, "type parameter")] [DataRow(TypeKind.Unknown, "unknown")] public void GetClassification_NamedTypes(TypeKind typeKind, string expected) { var symbol = Substitute.For(); symbol.Kind.Returns(SymbolKind.NamedType); symbol.TypeKind.Returns(typeKind); symbol.IsRecord.Returns(false); symbol.GetClassification().Should().Be(expected); } [TestMethod] public void GetClassification_NamedType_Unknown() { var symbol = Substitute.For(); symbol.Kind.Returns(SymbolKind.NamedType); symbol.TypeKind.Returns((TypeKind)255); #if DEBUG new Action(() => symbol.GetClassification()).Should().Throw(); #else symbol.GetClassification().Should().Be("type"); #endif } [TestMethod] [DataRow(TypeKind.Class, "record")] [DataRow(TypeKind.Struct, "record struct")] public void GetClassification_Record(TypeKind typeKind, string expected) { var symbol = Substitute.For(); symbol.Kind.Returns(SymbolKind.NamedType); symbol.TypeKind.Returns(typeKind); symbol.IsRecord.Returns(true); symbol.GetClassification().Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/INamespaceSymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class INamespaceSymbolExtensionsTest { [TestMethod] [DataRow("System", "System")] [DataRow("System.Collections.Generic", "System.Collections.Generic")] // Odd cases but nothing that needs a fix: [DataRow("System.Collections.Generic", "System..Collections..Generic")] [DataRow("System.Collections.Generic", ".System.Collections.Generic.")] public void Is_ValidNameSpaces(string code, string test) { var snippet = $""" using {code}; """; var (tree, model) = TestCompiler.CompileCS(snippet); var name = tree.GetRoot().DescendantNodes().OfType().Single().Name; var symbol = model.GetSymbolInfo(name).Symbol; var ns = symbol.Should().BeAssignableTo().Subject; ns.Is(test).Should().BeTrue(); } [TestMethod] [DataRow("", true)] [DataRow("System", false)] public void Is_Global(string test, bool expected) { var snippet = """ using System; """; var (tree, model) = TestCompiler.CompileCS(snippet); var name = tree.GetRoot().DescendantNodes().OfType().Single().Name; var symbol = model.GetSymbolInfo(name).Symbol; var ns = symbol.Should().BeAssignableTo().Subject; var globalNs = ns.ContainingNamespace; globalNs.IsGlobalNamespace.Should().BeTrue(); globalNs.Is(test).Should().Be(expected); } [TestMethod] [DataRow("System", "Microsoft")] [DataRow("System", "System.Collections")] [DataRow("System.Collections", "System")] [DataRow("System.Collections", "Collections")] [DataRow("System.Collections", "System.Collections.Generic")] [DataRow("System.Collections", "Collections.Generic")] [DataRow("System.Collections.Generic", "System.Collections")] [DataRow("System.Collections.Generic", "Generic")] [DataRow("System.Collections.Generic", "")] [DataRow("System.Collections.Generic", "global::System.Collections.Generic")] [DataRow("System.Collections.Generic", "global.System.Collections.Generic")] public void Is_InvalidNameSpaces(string code, string test) { var snippet = $""" using {code}; """; var (tree, model) = TestCompiler.CompileCS(snippet); var name = tree.GetRoot().DescendantNodes().OfType().Single().Name; var symbol = model.GetSymbolInfo(name).Symbol; var ns = symbol.Should().BeAssignableTo().Subject; ns.Is(test).Should().BeFalse(); } [TestMethod] public void Is_ThrowsArgumentNullExceptionForName() { var snippet = """ using System; """; var (tree, model) = TestCompiler.CompileCS(snippet); var name = tree.GetRoot().DescendantNodes().OfType().Single().Name; var symbol = model.GetSymbolInfo(name).Symbol; var ns = symbol.Should().BeAssignableTo().Subject; var action = () => ns.Is(null); action.Should().Throw().WithMessage("*name*"); } [TestMethod] [DataRow("")] [DataRow("System")] [DataRow("System.Collection")] public void Is_ReturnsFalseForNullSymbol(string nameSpace) { INamespaceSymbol ns = null; ns.Is(nameSpace).Should().BeFalse(); } [TestMethod] public void GetAllNamedTypesForNamespace_WhenSymbolIsNull_ReturnsEmpty() => ((INamespaceSymbol)null).GetAllNamedTypes().Should().BeEmpty(); [TestMethod] public void Symbol_GetSelfAndBaseTypes() { var snippet = new SnippetCompiler(""" public class Base { } public class Derived : Base { } """); var objectType = snippet.TypeByMetadataName("System.Object"); var baseTypes = objectType.GetSelfAndBaseTypes().ToList(); baseTypes.Should().ContainSingle(); baseTypes.Should().HaveElementAt(0, objectType); var derivedType = snippet.DeclaredSymbol("Derived"); baseTypes = derivedType.GetSelfAndBaseTypes().ToList(); baseTypes.Should().HaveCount(3); baseTypes.Should().HaveElementAt(0, derivedType); baseTypes.Should().HaveElementAt(1, snippet.DeclaredSymbol("Base")); baseTypes.Should().HaveElementAt(2, objectType); } [TestMethod] public void Symbol_GetAllNamedTypes_Type() { var snippet = new SnippetCompiler(""" public class Outer { public class Nested { public class NestedMore { } } } """); var typeSymbol = snippet.DeclaredSymbol("Outer"); typeSymbol.GetAllNamedTypes().Should().HaveCount(3); } [TestMethod] public void Symbol_GetAllNamedTypes_Namespace() { var snippet = new SnippetCompiler(""" namespace NS { public class Base { public class Nested { public class NestedMore { } } } public class Derived : Base { } public interface IInterface { } } """); var nsSymbol = snippet.NamespaceSymbol("NS"); nsSymbol.GetAllNamedTypes().Should().HaveCount(5); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/IParameterSymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class IParameterSymbolExtensionsTest { [TestMethod] public void IsType_Null() => IParameterSymbolExtensions.IsType(null, KnownType.System_Boolean).Should().BeFalse(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/IPropertySymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class IPropertySymbolExtensionsTest { [TestMethod] public void IsExtension_ExtensionProperty_ReturnsTrue() => new SnippetCompiler(""" public static class Extensions { extension(string s) { public int Property { get => 42; set { } } public int GetterOnly => 42; public int SetterOnly { set { } } } extension(string) { public static int StaticProperty { get => 42; set { } } public static int StaticGetterOnly => 42; public static int StaticSetterOnly { set { } } } } """).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeTrue()); [TestMethod] public void IsExtension_RegularProperty_ReturnsFalse() => new SnippetCompiler(""" public class Sample { public int Property { get; set; } public int GetterOnly => 42; public int SetterOnly { set { } } public static int StaticProperty { get; set; } public static int StaticGetterOnly => 42; public static int StaticSetterOnly { set { } } } """).DeclaredSymbols().Should().AllSatisfy(x => x.IsExtension.Should().BeFalse()); [TestMethod] public void IsAnyAttributeInOverridingChain_WhenPropertySymbolIsNull_ReturnsFalse() => IPropertySymbolExtensions.IsAnyAttributeInOverridingChain(null).Should().BeFalse(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/ISymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using ISymbolExtensionsCommon = SonarAnalyzer.Core.Semantics.Extensions.ISymbolExtensions; namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class ISymbolExtensionsTest { private const string TestInput = """ public interface IInterface { int Property2 { get; set; } void Method3(); } public interface IOtherInterface { void Method3(); } public abstract class Base { public virtual void Method1() { } protected virtual void Method2() { } public abstract int Property { get; set; } public void Method4(){} } public class Derived1 : Base { public override int Property { get; set; } private int PrivateProperty { get; set; } private protected int PrivateProtectedProperty { get; set; } protected int ProtectedProperty { get; set; } protected internal int ProtectedInternalProperty { get; set; } internal int InternalProperty { get; set; } } public abstract class Derived2 : Base, IInterface { public override int Property { get; set; } public int Property2 { get; set; } public virtual void Method3(){} public abstract void Method5(); } public class Derived3: Derived2, IInterface, IOtherInterface { public override void Method3(){} public override void Method5() {} } public class TwoInterfaces: IInterface, IOtherInterface { public int Property2 { get; set; } public void Method3(){} } """; private SnippetCompiler testSnippet; [TestInitialize] public void Compile() => testSnippet = new SnippetCompiler(TestInput); [TestMethod] public void IsInType_Null_KnownType() => ISymbolExtensionsCommon.IsInType(null, KnownType.System_Boolean).Should().BeFalse(); [TestMethod] public void IsInType_Null_TypeSymbol() => ISymbolExtensionsCommon.IsInType(null, (ITypeSymbol)null).Should().BeFalse(); [TestMethod] public void IsInType_Null_ArrayOfTypeSymbols() => ISymbolExtensionsCommon.IsInType(null, []).Should().BeFalse(); [TestMethod] [DataRow("{ get; set; }")] [DataRow("{ get; }")] [DataRow("{ get; } = string.Empty;")] [DataRow("{ get; set; } = string.Empty;")] #if NET [DataRow("{ get; init; }")] #endif public void IsAutoProperty_AutoProperty_CS(string getterSetter) { var code = $$""" public class Sample { public string SymbolMember {{getterSetter}} } """; CreateSymbol(code, AnalyzerLanguage.CSharp).IsAutoProperty().Should().BeTrue(); } [TestMethod] public void IsAutoProperty_AutoProperty_VB() { const string code = """ Public Class Sample Public Property SymbolMember As String End Class """; CreateSymbol(code, AnalyzerLanguage.VisualBasic).IsAutoProperty().Should().BeTrue(); } [TestMethod] public void IsAutoProperty_ExplicitProperty_CS() { const string code = """ public class Sample { private string _SymbolMember; // Try to confuse the method with auto-like implementation public string SymbolMember { get => _SymbolMember; set { _SymbolMember = value; } } } """; CreateSymbol(code, AnalyzerLanguage.CSharp).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_ExplicitProperty_VB() { const string code = """ Public Class Sample Private _SymbolMember As String ' Try to confuse the method with auto-like implementation Public Property SymbolMember As String Get Return _SymbolMember End Get Set(value As String) _SymbolMember = value End Set End Property End Class """; CreateSymbol(code, AnalyzerLanguage.VisualBasic).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_NonpropertySymbol_CS() { const string code = """ public class Sample { public void SymbolMember() { } } """; CreateSymbol(code, AnalyzerLanguage.CSharp).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void IsAutoProperty_NonpropertySymbol_VB() { const string code = """ Public Class Sample Public Sub SymbolMember() End Sub End Class """; CreateSymbol(code, AnalyzerLanguage.VisualBasic).IsAutoProperty().Should().BeFalse(); } [TestMethod] public void Symbol_IsPublicApi() { testSnippet.MethodSymbol("Base.Method1").IsPubliclyAccessible().Should().BeTrue(); testSnippet.MethodSymbol("Base.Method2").IsPubliclyAccessible().Should().BeTrue(); testSnippet.PropertySymbol("Base.Property").IsPubliclyAccessible().Should().BeTrue(); testSnippet.PropertySymbol("IInterface.Property2").IsPubliclyAccessible().Should().BeTrue(); testSnippet.PropertySymbol("Derived1.PrivateProperty").IsPubliclyAccessible().Should().BeFalse(); testSnippet.PropertySymbol("Derived1.PrivateProtectedProperty").IsPubliclyAccessible().Should().BeFalse(); testSnippet.PropertySymbol("Derived1.ProtectedProperty").IsPubliclyAccessible().Should().BeTrue(); testSnippet.PropertySymbol("Derived1.ProtectedInternalProperty").IsPubliclyAccessible().Should().BeTrue(); testSnippet.PropertySymbol("Derived1.InternalProperty").IsPubliclyAccessible().Should().BeFalse(); } [TestMethod] public void Symbol_InterfaceMembersOrMemberOverride() { testSnippet.MethodSymbol("Base.Method1").InterfaceMembers().Should().BeEmpty(); testSnippet.MethodSymbol("Base.Method1").GetOverriddenMember().Should().BeNull(); testSnippet.PropertySymbol("Derived2.Property").GetOverriddenMember().Should().NotBeNull(); testSnippet.PropertySymbol("Derived2.Property2").InterfaceMembers().Should().ContainSingle(); testSnippet.MethodSymbol("Derived2.Method3").InterfaceMembers().Should().ContainSingle().Which.Should().Be(testSnippet.MethodSymbol("IInterface.Method3")); testSnippet.MethodSymbol("TwoInterfaces.Method3").InterfaceMembers().Should().BeEquivalentTo([ testSnippet.MethodSymbol("IInterface.Method3"), testSnippet.MethodSymbol("IOtherInterface.Method3")]); testSnippet.MethodSymbol("Derived3.Method3").InterfaceMembers().Should().BeEquivalentTo([ testSnippet.MethodSymbol("IInterface.Method3"), testSnippet.MethodSymbol("IOtherInterface.Method3")]); } [TestMethod] [CombinatorialData] public void Symbol_InterfaceMembers_CrossAssemblyNullableContext( [CombinatorialValues("disable", "enable")] string nullableExternal, [CombinatorialValues("disable", "enable")] string nullableInternal) { // Tests that InterfaceMembers() correctly identifies implementations regardless of nullable context. // See https://github.com/SonarSource/sonar-dotnet-enterprise/pull/1732 for details. var returnAnnotation = nullableExternal == "enable" ? "?" : string.Empty; var paramAnnotation = nullableInternal == "enable" ? "?" : string.Empty; var interfaceCode = $$""" #nullable {{nullableExternal}} public interface IExternalInterface { string{{returnAnnotation}} Execute(string parameter); } """; var implementationCode = $$""" #nullable {{nullableInternal}} public class Implementation : IExternalInterface { public string Execute(string{{paramAnnotation}} parameter) => ""; } """; var parseOptions = new CSharpParseOptions(LanguageVersion.Latest); var interfaceMetadata = new SnippetCompiler(interfaceCode, ignoreErrors: false, AnalyzerLanguage.CSharp, parseOptions: parseOptions).Compilation.ToMetadataReference(); var implCompilation = new SnippetCompiler(implementationCode, ignoreErrors: false, AnalyzerLanguage.CSharp, [interfaceMetadata], parseOptions: parseOptions).Compilation; var method = implCompilation.GetTypeByMetadataName("Implementation").Should().BeAssignableTo() .Which.GetMembers("Execute").Should().ContainSingle() .Which.Should().BeAssignableTo() .Subject; var interfaceMethod = implCompilation.GetTypeByMetadataName("IExternalInterface").Should().BeAssignableTo() .Which.GetMembers("Execute").Should().ContainSingle() .Which.Should().BeAssignableTo() .Subject; method.InterfaceMembers().Should().ContainSingle().Which.Should().Be(interfaceMethod); } [TestMethod] public void Symbol_GetOverriddenMember() { var actualOverriddenMethod = testSnippet.MethodSymbol("Base.Method1").GetOverriddenMember(); actualOverriddenMethod.Should().BeNull(); var expectedOverriddenProperty = testSnippet.PropertySymbol("Base.Property"); var propertySymbol = testSnippet.PropertySymbol("Derived2.Property"); var actualOverriddenProperty = propertySymbol.GetOverriddenMember(); actualOverriddenProperty.Should().NotBeNull(); actualOverriddenProperty.Should().Be(expectedOverriddenProperty); testSnippet.MethodSymbol("Derived3.Method3").GetOverriddenMember().Should().Be(testSnippet.MethodSymbol("Derived2.Method3")); testSnippet.MethodSymbol("Derived3.Method5").GetOverriddenMember().Should().Be(testSnippet.MethodSymbol("Derived2.Method5")); } [TestMethod] public void Symbol_IsChangeable() { testSnippet.MethodSymbol("Base.Method1").IsChangeable().Should().BeFalse(); testSnippet.MethodSymbol("Base.Method4").IsChangeable().Should().BeTrue(); testSnippet.MethodSymbol("Derived2.Method5").IsChangeable().Should().BeFalse(); testSnippet.MethodSymbol("Derived2.Method3").IsChangeable().Should().BeFalse(); } [TestMethod] public void AnyAttributeDerivesFrom_WhenSymbolIsNull_ReturnsFalse() => ISymbolExtensionsCommon.AnyAttributeDerivesFrom(null, KnownType.Void).Should().BeFalse(); [TestMethod] public void AnyAttributeDerivesFromAny_WhenSymbolIsNull_ReturnsFalse() => ISymbolExtensionsCommon.AnyAttributeDerivesFromAny(null, [KnownType.Void]).Should().BeFalse(); [TestMethod] public void GetAttributesForKnownType_WhenSymbolIsNull_ReturnsEmpty() => ISymbolExtensionsCommon.GetAttributes(null, KnownType.Void).Should().BeEmpty(); [TestMethod] public void GetAttributesForKnownTypes_WhenSymbolIsNull_ReturnsEmpty() => ISymbolExtensionsCommon.GetAttributes(null, [KnownType.Void]).Should().BeEmpty(); [TestMethod] public void GetParameters_WhenSymbolIsNotMethodOrProperty_ReturnsEmpty() { var symbol = Substitute.For(); symbol.Kind.Returns(SymbolKind.Alias); symbol.GetParameters().Should().BeEmpty(); } [TestMethod] public void InterfaceMembers_WhenSymbolIsNull_ReturnsEmpty() => ((ISymbol)null).InterfaceMembers().Should().BeEmpty(); [TestMethod] public void GetOverriddenMember_WhenSymbolIsNull_ReturnsNull() => ((ISymbol)null).GetOverriddenMember().Should().BeNull(); [TestMethod] public void GetEffectiveAccessibility_WhenSymbolIsNull_ReturnsNotApplicable() => ISymbolExtensionsCommon.GetEffectiveAccessibility(null).Should().Be(Accessibility.NotApplicable); [TestMethod] [DataRow(SymbolKind.Alias, "alias")] [DataRow(SymbolKind.ArrayType, "array")] [DataRow(SymbolKind.Assembly, "assembly")] [DataRow(SymbolKind.Discard, "discard")] [DataRow(SymbolKind.DynamicType, "dynamic")] [DataRow(SymbolKind.ErrorType, "error")] [DataRow(SymbolKind.Event, "event")] [DataRow(SymbolKind.Field, "field")] [DataRow(SymbolKind.FunctionPointerType, "function pointer")] [DataRow(SymbolKind.Label, "label")] [DataRow(SymbolKind.Local, "local")] [DataRow(SymbolKind.Namespace, "namespace")] [DataRow(SymbolKind.NetModule, "netmodule")] [DataRow(SymbolKind.Parameter, "parameter")] [DataRow(SymbolKind.PointerType, "pointer")] [DataRow(SymbolKind.Preprocessing, "preprocessing")] [DataRow(SymbolKind.Property, "property")] [DataRow(SymbolKind.RangeVariable, "range variable")] [DataRow(SymbolKind.TypeParameter, "type parameter")] public void GetClassification_SimpleKinds(SymbolKind symbolKind, string expected) { var symbol = Substitute.For(); symbol.Kind.Returns(symbolKind); symbol.GetClassification().Should().Be(expected); } [TestMethod] public void GetClassification_UnknowKind() { var symbol = Substitute.For(); symbol.Kind.Returns((SymbolKind)999); #if DEBUG new Action(() => symbol.GetClassification()).Should().Throw(); #else ISymbolExtensionsCommon.GetClassification(symbol).Should().Be("symbol"); #endif } [TestMethod] public void AllPartialParts_MethodSymbol_NonPartialMethod() { const string code = """ public partial class Sample { partial void SymbolMember(); } """; var symbol = CreateSymbol(code, AnalyzerLanguage.CSharp); var methodSymbol = symbol as IMethodSymbol; var result = symbol.AllPartialParts().ToList(); result.Should().ContainSingle().And.Subject.Should().Contain(methodSymbol); } [TestMethod] public void AllPartialParts_MethodSymbol_PartialMethodSameClass() { const string code = """ public partial class Sample { partial void SymbolMember(); partial void SymbolMember() { } } """; var symbols = CreateSymbols(code, AnalyzerLanguage.CSharp, x => x is MethodDeclarationSyntax); var declarationSymbol = symbols[0] as IMethodSymbol; var declarationResult = declarationSymbol.AllPartialParts().ToList(); declarationResult.Should().HaveCount(2).And.Contain([declarationSymbol, declarationSymbol.PartialImplementationPart]); var implementationSymbol = symbols[1] as IMethodSymbol; var implementationResult = implementationSymbol.AllPartialParts().ToList(); implementationResult.Should().HaveCount(2).And.Contain([implementationSymbol, implementationSymbol.PartialDefinitionPart]); } [TestMethod] public void AllPartialParts_MethodSymbol_PartialMethodDifferentClass() { const string code = """ public partial class Sample { partial void SymbolMember(); } public partial class Sample { partial void SymbolMember() { } } """; var symbols = CreateSymbols(code, AnalyzerLanguage.CSharp, x => x is MethodDeclarationSyntax); var declarationSymbol = symbols[0] as IMethodSymbol; var declarationResult = declarationSymbol.AllPartialParts().ToList(); declarationResult.Should().HaveCount(2).And.Contain([declarationSymbol, declarationSymbol.PartialImplementationPart]); var implementationSymbol = symbols[1] as IMethodSymbol; var implementationResult = implementationSymbol.AllPartialParts().ToList(); implementationResult.Should().HaveCount(2).And.Contain([implementationSymbol, implementationSymbol.PartialDefinitionPart]); } [TestMethod] public void AllPartialParts_PropertySymbol_PartialPropertySameClass() { const string code = """ public partial class Sample { public partial int SymbolMember { get; set; } public partial int SymbolMember { get => 0; set { } } } """; var symbols = CreateSymbols(code, AnalyzerLanguage.CSharp, x => x is PropertyDeclarationSyntax); var declarationSymbol = symbols[0] as IPropertySymbol; var declarationResult = declarationSymbol.AllPartialParts().ToList(); declarationResult.Should().HaveCount(2).And.Contain([declarationSymbol, declarationSymbol.PartialImplementationPart]); var implementationSymbol = symbols[1] as IPropertySymbol; var implementationResult = implementationSymbol.AllPartialParts().ToList(); implementationResult.Should().HaveCount(2).And.Contain([implementationSymbol, implementationSymbol.PartialDefinitionPart]); } [TestMethod] public void AllPartialParts_PropertySymbol_PartialPropertyDifferentClass() { const string code = """ public partial class Sample { public partial int SymbolMember { get; set; } } public partial class Sample { public partial int SymbolMember { get => 0; set { } } } """; var symbols = CreateSymbols(code, AnalyzerLanguage.CSharp, x => x is PropertyDeclarationSyntax); var declarationSymbol = symbols[0] as IPropertySymbol; var declarationResult = declarationSymbol.AllPartialParts().ToList(); declarationResult.Should().HaveCount(2).And.Contain([declarationSymbol, declarationSymbol.PartialImplementationPart]); var implementationSymbol = symbols[1] as IPropertySymbol; var implementationResult = implementationSymbol.AllPartialParts().ToList(); implementationResult.Should().HaveCount(2).And.Contain([implementationSymbol, implementationSymbol.PartialDefinitionPart]); } [TestMethod] public void AllPartialParts_OtherSymbol() { var result = Substitute.For().AllPartialParts().ToList(); result.Should().ContainSingle(); } private static ISymbol CreateSymbol(string snippet, AnalyzerLanguage language, ParseOptions parseOptions = null) { var (tree, semanticModel) = TestCompiler.Compile(snippet, false, language, parseOptions: parseOptions); var node = tree.GetRoot().DescendantNodes().Last(x => x.ToString().Contains(" SymbolMember")); return semanticModel.GetDeclaredSymbol(node); } private static List CreateSymbols(string snippet, AnalyzerLanguage language, Func additionalFilter = null) { var (tree, semanticModel) = TestCompiler.Compile(snippet, false, language); var nodes = tree.GetRoot().DescendantNodes().Where(x => x.ToString().Contains("SymbolMember") && (additionalFilter?.Invoke(x) ?? true)).ToList(); return nodes.Select(x => semanticModel.GetDeclaredSymbol(x)).ToList(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/Extensions/ITypeSymbolExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.Core.Test.Semantics.Extensions; [TestClass] public class ITypeSymbolExtensionsTest { private const string TestInput = """ namespace NS { using System; using PropertyBag = System.Collections.Generic.Dictionary; public abstract class Base { public class Nested { public class NestedMore { } } } public class Derived1 : Base { } public class Derived2 : Base, IInterface { } public interface IInterface { } } """; private ClassDeclarationSyntax baseClassDeclaration; private ClassDeclarationSyntax derivedClassDeclaration1; private ClassDeclarationSyntax derivedClassDeclaration2; private SyntaxNode root; private SemanticModel model; [TestInitialize] public void Compile() { var snippet = new SnippetCompiler(TestInput); root = snippet.Tree.GetRoot(); model = snippet.Model; baseClassDeclaration = root.DescendantNodes().OfType().First(x => x.Identifier.ValueText == "Base"); derivedClassDeclaration1 = root.DescendantNodes().OfType().First(x => x.Identifier.ValueText == "Derived1"); derivedClassDeclaration2 = root.DescendantNodes().OfType().First(x => x.Identifier.ValueText == "Derived2"); } [TestMethod] public void IsAny_Null() => ((ITypeSymbol)null).IsAny(KnownType.System_Boolean).Should().BeFalse(); [TestMethod] public void ImplementsAny_Null() => ((ITypeSymbol)null).ImplementsAny([KnownType.System_Boolean]).Should().BeFalse(); [TestMethod] public void Type_DerivesOrImplementsAny() { var baseType = new KnownType("NS.Base"); var interfaceType = new KnownType("NS.IInterface"); var derived1Type = model.GetDeclaredSymbol(derivedClassDeclaration1) as INamedTypeSymbol; var derived2Type = model.GetDeclaredSymbol(derivedClassDeclaration2) as INamedTypeSymbol; derived2Type.DerivesOrImplements(interfaceType).Should().BeTrue(); derived1Type.DerivesOrImplements(interfaceType).Should().BeFalse(); var baseTypes = ImmutableArray.Create(interfaceType, baseType); derived1Type.DerivesOrImplementsAny(baseTypes).Should().BeTrue(); } [TestMethod] public void Type_Is() { var baseKnownType = new KnownType("NS.Base"); var baseKnownTypes = ImmutableArray.Create(baseKnownType); var baseType = model.GetDeclaredSymbol(baseClassDeclaration) as INamedTypeSymbol; baseType.Is(baseKnownType).Should().BeTrue(); baseType.IsAny(baseKnownTypes).Should().BeTrue(); } [TestMethod] public void Type_GetSymbolType_Alias() { var aliasUsing = root.DescendantNodesAndSelf().OfType().FirstOrDefault(x => x.Alias is not null); var symbol = model.GetDeclaredSymbol(aliasUsing); var type = symbol.GetSymbolType(); symbol.ToString().Should().Be("PropertyBag"); type.ToString().Should().Be("System.Collections.Generic.Dictionary"); } [TestMethod] [DataRow("System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable", true)] [DataRow("System.Collections.Generic.IEnumerable", "System.IDisposable", false)] [DataRow("System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable", true)] [DataRow("System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable", false)] [DataRow("System.Collections.Generic.IEnumerable", "System.IDisposable", false)] [DataRow("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", true)] [DataRow("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", false)] [DataRow("System.Collections.Generic.List", "System.IDisposable", false)] [DataRow("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", true)] [DataRow("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", true)] [DataRow("System.Collections.Generic.List", "System.IDisposable", false)] public void Type_DerivesOrImplements_Type(string typeSymbolName, string typeName, bool expected) { var compilation = TestCompiler.CompileCS(""" using System.Collections.Generic; public class IntList : List, IEnumerable { IEnumerator IEnumerable.GetEnumerator() => null; } """).Model.Compilation; var allTypes = compilation.GlobalNamespace.GetAllNamedTypes().ToList(); var intList = allTypes.Single(x => x.Name == "IntList"); allTypes.Add(intList.BaseType); allTypes.AddRange(intList.AllInterfaces); var typeSymbol = allTypes.Single(x => x.ToString() == typeSymbolName); var type = allTypes.Single(x => x.ToString() == typeName); typeSymbol.DerivesOrImplements(type).Should().Be(expected); } [TestMethod] [DataRow("Open`3", "Open`3", true)] [DataRow("Half2`2", "Open`3", true)] [DataRow("Half1`1", "Open`3", true)] [DataRow("Closed", "Open`3", true)] [DataRow("Half2`2", "Half2`2", true)] [DataRow("Half1`1", "Half2`2", true)] [DataRow("Closed", "Half2`2", true)] [DataRow("Half1`1", "Half1`1", true)] [DataRow("Closed", "Half1`1", true)] [DataRow("Closed", "Closed", true)] [DataRow("Half2`2", "Closed", false)] [DataRow("Half1`1", "Closed", false)] [DataRow("Half2`2", "Half1`1", false)] public void Type_DerivesOrImplements_HalfClosed(string typeName, string derivesFromName, bool expected) { var compilation = TestCompiler.CompileCS(""" public class Open { } public class Half2: Open { } public class Half1: Half2 { } public class Closed: Half1 { } """).Model.Compilation; var type = compilation.GetTypeByMetadataName(typeName); var derivesFrom = compilation.GetTypeByMetadataName(derivesFromName); type.DerivesOrImplements(derivesFrom).Should().Be(expected); } [TestMethod] [DataRow("int")] [DataRow("System.Int32")] [DataRow("int?")] [DataRow("System.Nullable")] [DataRow("CustomStruct")] [DataRow("CustomRefStruct")] [DataRow("RecordStruct")] public void IsStruct_Simple_True(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" struct CustomStruct { } record struct RecordStruct { } ref struct CustomRefStruct { } ref struct Test { {{type}} field; } """); fieldSymbol.Type.IsStruct().Should().BeTrue(); } [TestMethod] [DataRow("object")] [DataRow("System.IComparable")] public void IsStruct_Simple_False(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" class Test { {{type}} field; } """); fieldSymbol.Type.IsStruct().Should().BeFalse(); } [TestMethod] [DataRow("struct")] [DataRow("unmanaged")] public void IsStruct_Generic(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: {{typeConstraint}} { T field; } """); fieldSymbol.Type.IsStruct().Should().BeTrue(); } [TestMethod] [DataRow("T")] [DataRow("T?")] [DataRow("Nullable")] public void IsStruct_Generic_Nullable(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: struct { {{type}} field; } """); fieldSymbol.Type.IsStruct().Should().BeTrue(); } [TestMethod] [DataRow("")] [DataRow("where T: new()")] [DataRow("where T: class")] [DataRow("where T: class, new()")] [DataRow("where T: Exception")] [DataRow("where T: Enum")] [DataRow("where T: Enum, IComparable")] [DataRow("where T: Enum, new()")] [DataRow("where T: Enum, IComparable, new()")] [DataRow("where T: notnull")] public void IsStruct_False_Generic(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test {{typeConstraint}} { T field; } """); fieldSymbol.Type.IsStruct().Should().BeFalse(); } [TestMethod] [DataRow("")] // Unbounded (can be reference type or value type) [DataRow("where T: new()")] // Unbounded [DataRow("where T: notnull")] // Unbounded [DataRow("where T: class")] [DataRow("where T: class?")] [DataRow("where T: class, new()")] [DataRow("where T: Exception")] [DataRow("where T: Exception?")] [DataRow("where T: IComparable")] [DataRow("where T: IComparable?")] [DataRow("where T: Delegate")] [DataRow("where T: Delegate?")] public void IsStruct_False_Generic_NullableReferenceType(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" #nullable enable using System; class Test {{typeConstraint}} { T? field; } """); fieldSymbol.Type.IsStruct().Should().BeFalse(); } [TestMethod] public void IsStruct_False_Generic_Derived() { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where U: T { U field; } """); fieldSymbol.Type.IsStruct().Should().BeFalse(); } [TestMethod] public void IsStruct_SelfRefrencingStruct() { var (tree, model) = TestCompiler.CompileCS(""" interface Interface where T: struct, Interface { } struct Impl: Interface { } // For demonstration how an implementation can look like class Test { static void Method(Interface parameter) where T: struct, Interface { } } """); var parameter = tree.GetRoot().DescendantNodes().OfType().First(); var parameterSymbol = (IParameterSymbol)model.GetDeclaredSymbol(parameter); parameterSymbol.Type.IsStruct().Should().BeFalse(); // parameter must be a struct, but even the compiler doesn't recognizes this } [TestMethod] [DataRow("int")] [DataRow("System.Int32")] [DataRow("CustomStruct")] [DataRow("CustomRefStruct")] [DataRow("RecordStruct")] public void IsNonNullableValueType_Simple_True(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" struct CustomStruct { } record struct RecordStruct { } ref struct CustomRefStruct { } ref struct Test { {{type}} field; } """); fieldSymbol.Type.IsNonNullableValueType().Should().BeTrue(); } [TestMethod] [DataRow("object")] [DataRow("System.IComparable")] [DataRow("int?")] [DataRow("System.Nullable")] public void IsNonNullableValueType_Simple_False(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" class Test { {{type}} field; } """); fieldSymbol.Type.IsNonNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("struct")] [DataRow("unmanaged")] public void IsNonNullableValueType_Generic(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: {{typeConstraint}} { T field; } """); fieldSymbol.Type.IsNonNullableValueType().Should().BeTrue(); } [TestMethod] [DataRow("T?")] [DataRow("Nullable")] public void IsNonNullableValueType_Generic_ConstraintStruct_Nullable(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: struct { {{type}} field; } """); fieldSymbol.Type.IsNonNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("")] // Unbounded (can be reference type or value type) [DataRow("where T: new()")] // Unbounded [DataRow("where T: notnull")] // Unbounded [DataRow("where T: struct")] [DataRow("where T: unmanaged")] [DataRow("where T: Enum")] [DataRow("where T: struct, Enum")] [DataRow("where T: struct, Enum, IComparable")] [DataRow("where T: class")] [DataRow("where T: class?")] [DataRow("where T: class, new()")] [DataRow("where T: Exception")] [DataRow("where T: Exception?")] [DataRow("where T: IComparable")] [DataRow("where T: IComparable?")] [DataRow("where T: Delegate")] [DataRow("where T: Delegate?")] public void IsNonNullableValueType_Generic_Constraint_Nullable(string constraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" #nullable enable using System; class Test {{constraint}} { T? field; } """); fieldSymbol.Type.IsNonNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("int?")] [DataRow("System.Nullable")] public void IsNullableValueType_Simple_True(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" class Test { {{type}} field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeTrue(); } [TestMethod] [DataRow("int")] [DataRow("System.Int32")] [DataRow("CustomStruct")] [DataRow("CustomRefStruct")] [DataRow("RecordStruct")] [DataRow("object")] [DataRow("System.IComparable")] public void IsNullableValueType_Simple_False(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" struct CustomStruct { } record struct RecordStruct { } ref struct CustomRefStruct { } ref struct Test { {{type}} field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("T?")] [DataRow("Nullable")] [DataRow("CustomStruct?")] [DataRow("RecordStruct?")] public void IsNullableValueType_Generic_ConstraintStruct_Nullable(string type) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; struct CustomStruct { } record struct RecordStruct { } class Test where T: struct { {{type}} field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeTrue(); } [TestMethod] [DataRow("struct")] [DataRow("unmanaged")] [DataRow("struct, Enum")] [DataRow("struct, Enum, IComparable")] public void IsNullableValueType_Generic_Constraint_Nullable(string constraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: {{constraint}} { T? field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeTrue(); } [TestMethod] [DataRow("struct")] [DataRow("unmanaged")] public void IsNullableValueType_Generic_ConstraintStruct_NonNullable(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" using System; class Test where T: {{typeConstraint}} { T field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("")] // Unbounded (can be reference type or value type) [DataRow("where T: new()")] // Unbounded [DataRow("where T: notnull")] // Unbounded [DataRow("where T: class")] [DataRow("where T: class?")] [DataRow("where T: class, new()")] [DataRow("where T: Enum")] [DataRow("where T: Exception")] [DataRow("where T: Exception?")] [DataRow("where T: IComparable")] [DataRow("where T: IComparable?")] [DataRow("where T: Delegate")] [DataRow("where T: Delegate?")] public void IsNullableValueType_False_Generic_NullableReferenceType(string typeConstraint) { var fieldSymbol = FirstFieldSymbolFromCode($$""" #nullable enable using System; class Test {{typeConstraint}} { T? field; } """); fieldSymbol.Type.IsNullableValueType().Should().BeFalse(); } [TestMethod] [DataRow("", "AttributeTargets")] [DataRow("where T: Enum", "T")] [DataRow("where T: struct, Enum", "T")] [DataRow("where T: Enum", "T?")] // With "#nullable enable" "?" means either nullable value type or nullable reference type (unbound generic) and T? is an Enum public void IsEnum_True(string typeConstraint, string fieldType) { var fieldSymbol = FirstFieldSymbolFromCode($$""" #nullable enable using System; class Test {{typeConstraint}} { {{fieldType}} field; } """); fieldSymbol.Type.IsEnum().Should().BeTrue(); } [TestMethod] [DataRow("", "int")] [DataRow("", "object")] [DataRow("", "IComparable")] [DataRow("where T: struct, Enum", "T?")] // Here ? means nullable value type because of the additional struct constraint and T? is an Nullable [DataRow("where T: struct, Enum", "Nullable")] public void IsEnum_False(string typeConstraint, string fieldType) { var fieldSymbol = FirstFieldSymbolFromCode($$""" #nullable enable using System; class Test {{typeConstraint}} { {{fieldType}} field; } """); fieldSymbol.Type.IsEnum().Should().BeFalse(); } [TestMethod] public void GetSelfAndBaseTypes_WhenSymbolIsNull_ReturnsEmpty() => ((ITypeSymbol)null).GetSelfAndBaseTypes().Should().BeEmpty(); [TestMethod] [DataRow("BaseClass ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute", "NotInheritedAttribute")] [DataRow("DerivedOpenGeneric", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("DerivedClosedGeneric ", "InheritedAttribute", "DerivedInheritedAttribute", "DerivedNotInheritedAttribute", "UnannotatedAttribute")] [DataRow("Implement ")] public void GetAttributesWithInherited_TypeSymbol(string className, params string[] expectedAttributes) { className = className.TrimEnd(); var code = $$""" using System; [AttributeUsage(AttributeTargets.All, Inherited = true)] public class InheritedAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, Inherited = false)] public class NotInheritedAttribute : Attribute { } public class DerivedInheritedAttribute: InheritedAttribute { } public class DerivedNotInheritedAttribute: NotInheritedAttribute { } public class UnannotatedAttribute : Attribute { } [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public class BaseClass { } [Inherited] [DerivedInherited] [NotInherited] [DerivedNotInherited] [Unannotated] public interface IInterface { } public class DerivedOpenGeneric: BaseClass { } public class DerivedClosedGeneric: BaseClass { } public class Implement: IInterface { } public class Program { public static void Main() { new {{className}}(); } } """; var compiler = new SnippetCompiler(code); var objectCreation = compiler.Nodes().Should().ContainSingle().Subject; if (compiler.Symbol(objectCreation) is { MethodKind: MethodKind.Constructor, ReceiverType: { } receiver }) { var actual = receiver.GetAttributesWithInherited().Select(x => x.AttributeClass.Name).ToList(); actual.Should().BeEquivalentTo(expectedAttributes); } else { Assert.Fail("Constructor could not be found."); } // GetAttributesWithInherited should behave like MemberInfo.GetCustomAttributes from runtime reflection: var type = compiler.EmitAssembly().GetType(className.Replace("", "`1"), throwOnError: true); type.GetCustomAttributes(inherit: true).Select(x => x.GetType().Name).Should().BeEquivalentTo(expectedAttributes); } private static IFieldSymbol FirstFieldSymbolFromCode(string code) { var (tree, model) = TestCompiler.CompileCS(code); var field = tree.GetRoot().DescendantNodes().OfType().First(); var fieldSymbol = (IFieldSymbol)model.GetDeclaredSymbol(field.Declaration.Variables[0]); return fieldSymbol; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/KnownAssemblyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static SonarAnalyzer.Core.Semantics.KnownAssembly; using static SonarAnalyzer.Core.Semantics.KnownAssembly.Predicates; namespace SonarAnalyzer.Core.Test.Semantics; [TestClass] public class KnownAssemblyTest { [TestMethod] public void KnownReference_ThrowsWhenPredicateNull_1() { var sut = () => new KnownAssembly(default(Func)); sut.Should().Throw().Which.ParamName.Should().Be("predicate"); } [TestMethod] public void KnownReference_ThrowsWhenPredicateNull_2() { var sut = () => new KnownAssembly(default(Func, bool>)); sut.Should().Throw().Which.ParamName.Should().Be("predicate"); } [TestMethod] public void KnownReference_ThrowsWhenPredicateNull_Params() { var sut = () => new KnownAssembly(_ => true, null, _ => true); sut.Should().Throw().Which.ParamName.Should().Be("predicate"); } [TestMethod] [DataRow("Test", true)] [DataRow("test", true)] [DataRow("TEST", true)] [DataRow("MyTest", false)] [DataRow("TestMy", false)] [DataRow("MyTestMy", false)] [DataRow("MyTESTMy", false)] [DataRow("Without", false)] public void NameIs_Test(string name, bool expected) { var sut = new KnownAssembly(NameIs("Test")); var identity = new AssemblyIdentity(name); var compilation = CompilationWithReferenceTo(identity); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("Test", true)] [DataRow("test", true)] [DataRow("TEST", true)] [DataRow("MyTest", true)] [DataRow("TestMy", true)] [DataRow("MyTestMy", true)] [DataRow("MyTESTMy", true)] [DataRow("Without", false)] public void NameContains_Test(string name, bool expected) { var sut = new KnownAssembly(Contains("Test")); var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("Test", true)] [DataRow("test", true)] [DataRow("TEST", true)] [DataRow("MyTest", false)] [DataRow("TestMy", true)] [DataRow("MyTestMy", false)] [DataRow("MyTESTMy", false)] [DataRow("Without", false)] public void NameStartsWith_Test(string name, bool expected) { var sut = new KnownAssembly(StartsWith("Test")); var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("Test", true)] [DataRow("test", true)] [DataRow("TEST", true)] [DataRow("MyTest", true)] [DataRow("TestMy", false)] [DataRow("MyTestMy", false)] [DataRow("MyTESTMy", false)] [DataRow("Without", false)] public void NameEndsWith_Test(string name, bool expected) { var sut = new KnownAssembly(EndsWith("Test")); var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("1.0.0.0", false)] [DataRow("1.9.9.99", false)] [DataRow("2.0.0.0", true)] [DataRow("2.0.0.1", true)] [DataRow("2.1.0.0", true)] [DataRow("3.1.0.0", true)] public void Version_GreaterOrEqual_2_0(string version, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); var sut = new KnownAssembly(VersionGreaterOrEqual(new Version(2, 0))); sut.IsReferencedBy(compilation).Should().Be(expected); sut = new KnownAssembly(VersionGreaterOrEqual("2.0")); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("1.0.0.0", true)] [DataRow("1.9.9.99", true)] [DataRow("2.0.0.0", false)] [DataRow("2.0.0.1", false)] [DataRow("2.1.0.0", false)] [DataRow("3.1.0.0", false)] public void Version_LowerThen_2_0(string version, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); var sut = new KnownAssembly(VersionLowerThen(new Version(2, 0))); sut.IsReferencedBy(compilation).Should().Be(expected); sut = new KnownAssembly(VersionLowerThen("2.0")); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("1.0.0.0", false)] [DataRow("1.9.9.99", false)] [DataRow("2.0.0.0", true)] [DataRow("2.0.0.1", true)] [DataRow("2.1.0.0", true)] [DataRow("3.1.0.0", true)] [DataRow("3.4.9.99", true)] [DataRow("3.5.0.0", true)] [DataRow("3.5.0.1", false)] [DataRow("10.0.0.0", false)] public void Version_Between_2_0_and_3_5(string version, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); var sut = new KnownAssembly(VersionBetween(new Version(2, 0, 0, 0), new Version(3, 5, 0, 0))); sut.IsReferencedBy(compilation).Should().Be(expected); sut = new KnownAssembly(VersionBetween("2.0.0.0", "3.5.0.0")); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("c5b62af9de6d7244", true)] [DataRow("C5B62AF9DE6D7244", true)] [DataRow("c5-b6-2a-f9-de-6d-72-44", true)] [DataRow( "002400000480000094000000060200000024000052534131000400000100010081b4345a022cc0f4b42bdc795a5a7a1623c1e58dc2246645d751ad41ba98f2749dc5c4e0da3a9e09febcb2cd5b088a0f" + "041f8ac24b20e736d8ae523061733782f9c4cd75b44f17a63714aced0b29a59cd1ce58d8e10ccdb6012c7098c39871043b7241ac4ab9f6b34f183db716082cd57c1ff648135bece256357ba735e67dc6", true)] [DataRow("AA-68-91-16-d3-a4-ae-33", false)] public void PublicKeyTokenIs_c5b62af9de6d7244(string publicKeyToken, bool expected) { var identity = new AssemblyIdentity("assemblyName", publicKeyOrToken: ImmutableArray.Create( 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x81, 0xb4, 0x34, 0x5a, 0x02, 0x2c, 0xc0, 0xf4, 0xb4, 0x2b, 0xdc, 0x79, 0x5a, 0x5a, 0x7a, 0x16, 0x23, 0xc1, 0xe5, 0x8d, 0xc2, 0x24, 0x66, 0x45, 0xd7, 0x51, 0xad, 0x41, 0xba, 0x98, 0xf2, 0x74, 0x9d, 0xc5, 0xc4, 0xe0, 0xda, 0x3a, 0x9e, 0x09, 0xfe, 0xbc, 0xb2, 0xcd, 0x5b, 0x08, 0x8a, 0x0f, 0x04, 0x1f, 0x8a, 0xc2, 0x4b, 0x20, 0xe7, 0x36, 0xd8, 0xae, 0x52, 0x30, 0x61, 0x73, 0x37, 0x82, 0xf9, 0xc4, 0xcd, 0x75, 0xb4, 0x4f, 0x17, 0xa6, 0x37, 0x14, 0xac, 0xed, 0x0b, 0x29, 0xa5, 0x9c, 0xd1, 0xce, 0x58, 0xd8, 0xe1, 0x0c, 0xcd, 0xb6, 0x01, 0x2c, 0x70, 0x98, 0xc3, 0x98, 0x71, 0x04, 0x3b, 0x72, 0x41, 0xac, 0x4a, 0xb9, 0xf6, 0xb3, 0x4f, 0x18, 0x3d, 0xb7, 0x16, 0x08, 0x2c, 0xd5, 0x7c, 0x1f, 0xf6, 0x48, 0x13, 0x5b, 0xec, 0xe2, 0x56, 0x35, 0x7b, 0xa7, 0x35, 0xe6, 0x7d, 0xc6), hasPublicKey: true); var compilation = CompilationWithReferenceTo(identity); var sut = new KnownAssembly(PublicKeyTokenIs(publicKeyToken)); sut.IsReferencedBy(compilation).Should().Be(expected); sut = new KnownAssembly(OptionalPublicKeyTokenIs(publicKeyToken)); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] public void PublicKeyTokenIs_FailsWhenKeyIsMissing() { var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", hasPublicKey: false)); var sut = new KnownAssembly(PublicKeyTokenIs("c5b62af9de6d7244")); sut.IsReferencedBy(compilation).Should().BeFalse(); } [TestMethod] public void OptionalPublicKeyTokenIs_SucceedsWhenKeyIsMissing() { var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", hasPublicKey: false)); var sut = new KnownAssembly(OptionalPublicKeyTokenIs("c5b62af9de6d7244")); sut.IsReferencedBy(compilation).Should().BeTrue(); } [TestMethod] [DataRow("Test", "1.0.0.0", false)] [DataRow("Test", "1.9.9.99", false)] [DataRow("TestMy", "2.0.0.0", true)] [DataRow("MyTest", "2.0.0.0", false)] [DataRow("TestMy", "3.5.0.0", true)] [DataRow("TestMy", "3.5.0.1", false)] [DataRow("Test", "10.0.0.0", false)] public void Combinator_NameStartWith_Test_And_Version_Between_2_0_And_3_5(string name, string version, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name, new Version(version))); var sut = new KnownAssembly(StartsWith("Test").And(VersionBetween(new Version(2, 0, 0, 0), new Version(3, 5, 0, 0)))); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("Start", true)] [DataRow("End", true)] [DataRow("StartOrEnd", true)] [DataRow("StartTest", true)] [DataRow("TestEnd", true)] [DataRow("EndStart", false)] [DataRow("EndSomething", false)] [DataRow("SomethingStart", false)] public void Combinator_StartsWith_Start_Or_EndsWith_End_1(string name, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); var sut = new KnownAssembly(StartsWith("Start"), EndsWith("End")); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] [DataRow("Start", true)] [DataRow("End", true)] [DataRow("StartOrEnd", true)] [DataRow("StartTest", true)] [DataRow("TestEnd", true)] [DataRow("EndStart", false)] [DataRow("EndSomething", false)] [DataRow("SomethingStart", false)] public void Combinator_StartsWith_Start_Or_EndsWith_End_2(string name, bool expected) { var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); var sut = new KnownAssembly(StartsWith("Start").Or(EndsWith("End"))); sut.IsReferencedBy(compilation).Should().Be(expected); } [TestMethod] public void CompilationShouldNotReferenceAssemblies() { var compilation = TestCompiler.CompileCS("// Empty file").Model.Compilation; compilation.References(XUnit_Assert).Should().BeFalse(); compilation.References(MSTest).Should().BeFalse(); compilation.References(NFluent).Should().BeFalse(); compilation.References(KnownAssembly.FluentAssertions).Should().BeFalse(); compilation.References(KnownAssembly.NSubstitute).Should().BeFalse(); compilation.References(MicrosoftExtensionsLoggingAbstractions).Should().BeFalse(); compilation.References(Serilog).Should().BeFalse(); compilation.References(NLog).Should().BeFalse(); compilation.References(Log4Net).Should().BeFalse(); compilation.References(CommonLoggingCore).Should().BeFalse(); compilation.References(CastleCore).Should().BeFalse(); } [TestMethod] public void XUnitAssert_2_4() => CompilationShouldReference(NuGetMetadataReference.XunitFramework("2.4.2"), XUnit_Assert); [TestMethod] public void XUnitAssert_1_9() => CompilationShouldReference(NuGetMetadataReference.XunitFrameworkV1, XUnit_Assert); [TestMethod] public void XUnitAssert_3_0() => CompilationShouldReference(NuGetMetadataReference.XunitFrameworkV3(), XUnit_Assert); [TestMethod] public void MSTest_V1() => CompilationShouldReference(NuGetMetadataReference.MSTestTestFrameworkV1, MSTest); [TestMethod] public void MSTest_V2() => CompilationShouldReference(NuGetMetadataReference.MSTestTestFramework("3.0.2"), MSTest); [TestMethod] public void MSTest_MicrosoftVisualStudioQualityToolsUnitTestFramework() => CompilationShouldReference(NuGetMetadataReference.MicrosoftVisualStudioQualityToolsUnitTestFramework, MSTest); [TestMethod] public void FluentAssertions_6_10() => CompilationShouldReference(NuGetMetadataReference.FluentAssertions("6.10.0"), KnownAssembly.FluentAssertions); [TestMethod] public void NFluent_2_8() => CompilationShouldReference(NuGetMetadataReference.NFluent("2.8.0"), NFluent); [TestMethod] public void NFluent_1_0() => // 1.0.0 has no publicKeyToken CompilationShouldReference(NuGetMetadataReference.NFluent("1.0.0"), NFluent); [TestMethod] public void NSubstitute_5_0() => CompilationShouldReference(NuGetMetadataReference.NSubstitute("5.0.0"), KnownAssembly.NSubstitute); [TestMethod] public void MicrosoftExtensionsLoggingAbstractions_Latest() => CompilationShouldReference(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions(), MicrosoftExtensionsLoggingAbstractions); [TestMethod] public void Serilog_Latest() => CompilationShouldReference(NuGetMetadataReference.Serilog(TestConstants.NuGetLatestVersion), Serilog); [TestMethod] public void NLog_Latest() => CompilationShouldReference(NuGetMetadataReference.NLog(), NLog); [TestMethod] public void Log4net_1_2_10() => CompilationShouldReference(NuGetMetadataReference.Log4Net("1.2.10", "1.0"), Log4Net); [TestMethod] public void Log4net_2_0_8() => CompilationShouldReference(NuGetMetadataReference.Log4Net("2.0.8", "net45-full"), Log4Net); [TestMethod] public void CommonLoggingCore_Latest() => CompilationShouldReference(NuGetMetadataReference.CommonLoggingCore(), CommonLoggingCore); [TestMethod] public void CastleCore_Latest() => CompilationShouldReference(NuGetMetadataReference.CastleCore(), CastleCore); private static void CompilationShouldReference(IEnumerable references, KnownAssembly expectedAssembly) => TestCompiler.CompileCS("// Empty file", references.ToArray()).Model.Compilation.References(expectedAssembly).Should().BeTrue(); private static Compilation CompilationWithReferenceTo(AssemblyIdentity identity) { var compilation = Substitute.For("compilationName", ImmutableArray.Empty, new Dictionary(), false, null, null); compilation.ReferencedAssemblyNames.Returns([identity]); return compilation; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/KnownMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Semantics; [TestClass] public class KnownMethodsTest { [TestMethod] public void IsMainMethod_Null_ShouldBeFalse() => KnownMethods.IsMainMethod(null).Should().BeFalse(); [TestMethod] public void IsObjectEquals_Null_ShouldBeFalse() => KnownMethods.IsObjectEquals(null).Should().BeFalse(); [TestMethod] public void IsStaticObjectEquals_Null_ShouldBeFalse() => KnownMethods.IsStaticObjectEquals(null).Should().BeFalse(); [TestMethod] public void IsObjectGetHashCode_Null_ShouldBeFalse() => KnownMethods.IsObjectGetHashCode(null).Should().BeFalse(); [TestMethod] public void IsObjectToString_Null_ShouldBeFalse() => KnownMethods.IsObjectToString(null).Should().BeFalse(); [TestMethod] public void IsIAsyncDisposableDisposeAsync_Null_ShouldBeFalse() => KnownMethods.IsIAsyncDisposableDisposeAsync(null).Should().BeFalse(); [TestMethod] public void IsGetObjectData_Null_ShouldBeFalse() => KnownMethods.IsGetObjectData(null).Should().BeFalse(); [TestMethod] public void IsSerializationConstructor_Null_ShouldBeFalse() => KnownMethods.IsSerializationConstructor(null).Should().BeFalse(); [TestMethod] public void IsArrayClone_Null_ShouldBeFalse() => KnownMethods.IsArrayClone(null).Should().BeFalse(); [TestMethod] public void IsRecordPrintMembers_Null_ShouldBeFalse() => KnownMethods.IsRecordPrintMembers(null).Should().BeFalse(); [TestMethod] public void IsGcSuppressFinalize_Null_ShouldBeFalse() => KnownMethods.IsGcSuppressFinalize(null).Should().BeFalse(); [TestMethod] public void IsDebugAssert_Null_ShouldBeFalse() => KnownMethods.IsDebugAssert(null).Should().BeFalse(); [TestMethod] public void IsDiagnosticDebugMethod_Null_ShouldBeFalse() => KnownMethods.IsDiagnosticDebugMethod(null).Should().BeFalse(); [TestMethod] public void IsOperatorBinaryPlus_Null_ShouldBeFalse() => KnownMethods.IsOperatorBinaryPlus(null).Should().BeFalse(); [TestMethod] public void IsOperatorBinaryMinus_Null_ShouldBeFalse() => KnownMethods.IsOperatorBinaryMinus(null).Should().BeFalse(); [TestMethod] public void IsOperatorBinaryMultiply_Null_ShouldBeFalse() => KnownMethods.IsOperatorBinaryMultiply(null).Should().BeFalse(); [TestMethod] public void IsOperatorBinaryDivide_Null_ShouldBeFalse() => KnownMethods.IsOperatorBinaryDivide(null).Should().BeFalse(); [TestMethod] public void IsOperatorBinaryModulus_Null_ShouldBeFalse() => KnownMethods.IsOperatorBinaryModulus(null).Should().BeFalse(); [TestMethod] public void IsOperatorEquals_Null_ShouldBeFalse() => KnownMethods.IsOperatorEquals(null).Should().BeFalse(); [TestMethod] public void IsOperatorNotEquals_Null_ShouldBeFalse() => KnownMethods.IsOperatorNotEquals(null).Should().BeFalse(); [TestMethod] public void IsConsoleWriteLine_Null_ShouldBeFalse() => KnownMethods.IsConsoleWriteLine(null).Should().BeFalse(); [TestMethod] public void IsConsoleWrite_Null_ShouldBeFalse() => KnownMethods.IsConsoleWrite(null).Should().BeFalse(); [TestMethod] public void IsListAddRange_Null_ShouldBeFalse() => KnownMethods.IsListAddRange(null).Should().BeFalse(); [TestMethod] public void IsEventHandler_Null_ShouldBeFalse() => KnownMethods.IsEventHandler(null).Should().BeFalse(); [TestMethod] public void IsEnumerableConcat_Null_ShouldBeFalse() => KnownMethods.IsEnumerableConcat(null).Should().BeFalse(); [TestMethod] public void Symbol_IsProbablyEventHandler() { var snippet = new SnippetCompiler(""" public class Sample { public void Method() { } public void EventHandler(object o, System.EventArgs args){} } """); snippet.MethodSymbol("Sample.Method").IsEventHandler().Should().BeFalse(); snippet.MethodSymbol("Sample.EventHandler").IsEventHandler().Should().BeTrue(); } [TestMethod] public void Symbol_IsProbablyEventHandler_ResolveEventHandler() { var snippet = new SnippetCompiler(""" using System; using System.Reflection; public class AssemblyLoad { public AssemblyLoad() { AppDomain.CurrentDomain.AssemblyResolve += LoadAnyVersion; } Assembly LoadAnyVersion(object sender, ResolveEventArgs args) => null; } """); snippet.MethodSymbol("AssemblyLoad.LoadAnyVersion").IsEventHandler().Should().BeTrue(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/KnownTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = Microsoft.CodeAnalysis.CSharp.Syntax; using VB = Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace SonarAnalyzer.Core.Test.Semantics; [TestClass] public class KnownTypeTest { [TestMethod] public void Matches_TypeSymbolIsNull_ThrowsArgumentNullException() => new KnownType("typeName").Invoking(x => x.Matches(null)).Should().Throw(); [TestMethod] public void Constructor_InvalidName_Throws() => ((Func)(() => new KnownType("Invalid.Order+Of.Separators"))).Should().Throw(); [TestMethod] [DataRow("System.Action", true, "System.Action", false)] [DataRow("System.DateTime", false, "System.Action", false)] [DataRow("System.Collections.ArrayList", true, "System.Collections.ArrayList", false)] [DataRow("System.Threading.Timer", false, "System.Timers.Timer", false)] [DataRow("string", true, "System.String", false)] [DataRow("byte", true, "System.Byte", false)] [DataRow("string[]", true, "System.String", true)] [DataRow("System.String[]", true, "System.String", true)] [DataRow("byte[]", true, "System.Byte", true)] [DataRow("System.Byte[]", true, "System.Byte", true)] [DataRow("System.Exception[]", false, "Exceptions.Exception", true)] [DataRow("System.Action[]", false, "System.Action", false)] [DataRow("System.Action", false, "System.Action", false)] [DataRow("System.Action", true, "System.Action", false, "T")] [DataRow("System.Action", false, "System.Action", true, "T2")] [DataRow("System.Action", true, "System.Action", false, "T1", "T2")] [DataRow("System.Action", false, "System.Action", true, "T1", "T2")] [DataRow("System.Action", false, "System.Action", false, "T2", "T1")] [DataRow("System.Action", false, "System.Action", true, "T1")] [DataRow("System.Action", false, "System.Action", false, "T")] [DataRow("System.Action", false, "System.Action", true, "T")] [DataRow("OuterNamespace.InnerNamespace.TheType", true, "OuterNamespace.InnerNamespace.TheType", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce", false, "OuterNamespace.InnerNamespace.TheType", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce", true, "OuterNamespace.InnerNamespace.TheType+NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce.NestedTwice", false, "OuterNamespace.InnerNamespace.TheType+NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce.NestedTwice", true, "OuterNamespace.InnerNamespace.TheType+NestedOnce+NestedTwice", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce", false, "NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType", false, "OuterNamespace.InnerNamespace.TheType+NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedGeneric", true, "OuterNamespace.InnerNamespace.TheType+NestedGeneric", false, "T")] [DataRow("OuterNamespace.InnerNamespace.GenericType.Nested", true, "OuterNamespace.InnerNamespace.GenericType+Nested", false)] [DataRow("OuterNamespace.InnerNamespace.GenericType", true, "OuterNamespace.InnerNamespace.GenericType", false, "TOuter")] [DataRow("OuterNamespace.InnerNamespace.GenericType.NestedGeneric", true, "OuterNamespace.InnerNamespace.GenericType+NestedGeneric", false, "TInner")] // Only the most-inner Generic type is relevant [DataRow("OuterNamespace.InnerNamespace.GenericType.NestedGeneric.NestedTwice", true, "OuterNamespace.InnerNamespace.GenericType+NestedGeneric+NestedTwice", false)] public void Matches_TypeSymbol_CS(string symbolName, bool expectedMatch, string fullTypeName, bool isArray, params string[] genericParameters) => new KnownType(fullTypeName, genericParameters) { IsArray = isArray } .Matches(GetSymbol_CS(symbolName)) .Should().Be(expectedMatch); [TestMethod] [DataRow("System.Collections.Generic.IDictionary(Of TKey, TValue)", false, "System.Collections.Generic.IDictionary", false)] [DataRow("System.Collections.Generic.IDictionary(Of TKey, TValue)", false, "System.Collections.Generic.IDictionary", false, "TKey")] [DataRow("System.Collections.Generic.IDictionary(Of TKey, TValue)", true, "System.Collections.Generic.IDictionary", false, "TKey", "TValue")] [DataRow("System.Collections.Generic.IDictionary(Of TKey, TValue)", false, "System.Collections.Generic.IDictionary", false, "TValue", "TKey")] [DataRow("String()", true, "System.String", true)] [DataRow("String()", false, "System.Byte", true)] [DataRow("OuterNamespace.InnerNamespace.TheType", true, "OuterNamespace.InnerNamespace.TheType", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce", false, "OuterNamespace.InnerNamespace.TheType", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce", true, "OuterNamespace.InnerNamespace.TheType+NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce.NestedTwice", false, "OuterNamespace.InnerNamespace.TheType+NestedOnce", false)] [DataRow("OuterNamespace.InnerNamespace.TheType.NestedOnce.NestedTwice", true, "OuterNamespace.InnerNamespace.TheType+NestedOnce+NestedTwice", false)] public void Matches_TypeSymbol_VB(string symbolName, bool expectedMatch, string fullTypeName, bool isArray, params string[] genericParameters) => new KnownType(fullTypeName, genericParameters) { IsArray = isArray } .Matches(GetSymbol_VB(symbolName)) .Should().Be(expectedMatch); [TestMethod] [DataRow("System.String", "System.String", false)] [DataRow("System.String[]", "System.String", true)] [DataRow("System.Action", "System.Action", false, "T")] [DataRow("System.Action", "System.Action", false, "T1", "T2")] [DataRow("System.Action[]", "System.Action", true, "T1", "T2")] public void DebuggerDisplay(string expectedResult, string fullTypeName, bool isArray, params string[] genericParameters) => new KnownType(fullTypeName, genericParameters) { IsArray = isArray }.DebuggerDisplay.Should().Be(expectedResult); private static ITypeSymbol GetSymbol_CS(string type) { var (tree, model) = TestCompiler.CompileCS($$""" namespace Exceptions { public class Exception { } } public class Test { public {{type}} Value; } namespace OuterNamespace { namespace InnerNamespace { public class TheType { public class NestedOnce { public class NestedTwice { } } public class NestedGeneric { } } public class GenericType { public class Nested { } public class NestedGeneric { public class NestedTwice { } } } } } """); var expression = tree.Single(); return model.GetDeclaredSymbol(expression).GetSymbolType(); } private static ITypeSymbol GetSymbol_VB(string type) { var (tree, model) = TestCompiler.CompileVB($""" Public Class Test(Of TKey, TValue) : Public Value As {type} : End Class Namespace OuterNamespace Namespace InnerNamespace Public Class TheType Public Class NestedOnce Public Class NestedTwice End Class End Class End Class End Namespace End Namespace """); var expression = tree.Single(); return model.GetDeclaredSymbol(expression).GetSymbolType(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Semantics/NetFrameworkVersionProviderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; namespace SonarAnalyzer.Core.Test.Semantics; [TestClass] public class NetFrameworkVersionProviderTest { [TestMethod] public void NetFrameworkVersionProvider_WithNullCompilation_ReturnsUnknown() { var versionProvider = new NetFrameworkVersionProvider(); versionProvider.Version(null).Should().Be(NetFrameworkVersion.Unknown); } [TestMethod] [DataRow("3.5", NetFrameworkVersion.Probably35)] [DataRow("4.0_no_IO", NetFrameworkVersion.Between4And451)] [DataRow("4.0_with_IO", NetFrameworkVersion.Between4And451)] [DataRow("4.8", NetFrameworkVersion.After452)] public void Version_MockedFramework(string name, NetFrameworkVersion expected) { var compilation = CreateRawCompilation(MetadataReference.CreateFromFile(Path.Combine(Paths.TestsRoot, "FrameworkMocks", "lib", name, "mscorlib.dll"))); var versionProvider = new NetFrameworkVersionProvider(); versionProvider.Version(compilation).Should().Be(expected); } [TestMethod] public void NetFrameworkVersionProvider_NoReference() { var compilation = CreateRawCompilation(); var versionProvider = new NetFrameworkVersionProvider(); versionProvider.Version(compilation).Should().Be(NetFrameworkVersion.Unknown); } private static Compilation CreateRawCompilation(params MetadataReference[] references) { var solution = new AdhocWorkspace().CurrentSolution; var project = solution.AddProject("Test", "Test", LanguageNames.CSharp); var projectBuilder = ProjectBuilder.FromProject(project); return projectBuilder.AddReferences(references).GetCompilation(); // Do not add default references from SnippetCompiler } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/SonarAnalyzer.Core.Test.csproj ================================================  net10.0 false PreserveNewest ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Extensions/ComparisonKindExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Utilities; namespace SonarAnalyzer.Core.Syntax.Extensions.Test; [TestClass] public class ComparisonKindExtensionsTest { [TestMethod] [DataRow(ComparisonKind.Equals, "==")] [DataRow(ComparisonKind.NotEquals, "!=")] [DataRow(ComparisonKind.LessThan, "<")] [DataRow(ComparisonKind.LessThanOrEqual, "<=")] [DataRow(ComparisonKind.GreaterThan, ">")] [DataRow(ComparisonKind.GreaterThanOrEqual, ">=")] public void ToDisplayString_CSharp(ComparisonKind kind, string expected) => kind.ToDisplayString(AnalyzerLanguage.CSharp).Should().Be(expected); [TestMethod] [DataRow(ComparisonKind.Equals, "=")] [DataRow(ComparisonKind.NotEquals, "<>")] [DataRow(ComparisonKind.LessThan, "<")] [DataRow(ComparisonKind.LessThanOrEqual, "<=")] [DataRow(ComparisonKind.GreaterThan, ">")] [DataRow(ComparisonKind.GreaterThanOrEqual, ">=")] public void ToDisplayString_VisualBasic(ComparisonKind kind, string expected) => kind.ToDisplayString(AnalyzerLanguage.VisualBasic).Should().Be(expected); [TestMethod] public void ToDisplayString_None_CSharp_Throws() => ComparisonKind.None.Invoking(x => x.ToDisplayString(AnalyzerLanguage.CSharp)).Should().Throw(); [TestMethod] public void ToDisplayString_None_VisualBasic_Throws() => ComparisonKind.None.Invoking(x => x.ToDisplayString(AnalyzerLanguage.VisualBasic)).Should().Throw(); [TestMethod] public void ToDisplayString_UnsupportedLanguage_Throws() => ComparisonKind.Equals.Invoking(x => x.ToDisplayString(null)).Should().Throw(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Extensions/CountComparisonResultExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Utilities; namespace SonarAnalyzer.Core.Syntax.Extensions.Test; [TestClass] public class CountComparisonResultExtensionsTest { [TestMethod] [DataRow(ComparisonKind.Equals, -1, CountComparisonResult.AlwaysFalse)] [DataRow(ComparisonKind.Equals, +0, CountComparisonResult.Empty)] [DataRow(ComparisonKind.Equals, +1, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.Equals, +9, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.NotEquals, -1, CountComparisonResult.AlwaysTrue)] [DataRow(ComparisonKind.NotEquals, +0, CountComparisonResult.NotEmpty)] [DataRow(ComparisonKind.NotEquals, +1, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.NotEquals, +9, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.GreaterThan, -1, CountComparisonResult.AlwaysTrue)] [DataRow(ComparisonKind.GreaterThan, +0, CountComparisonResult.NotEmpty)] [DataRow(ComparisonKind.GreaterThan, +1, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.GreaterThan, +9, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.LessThan, -1, CountComparisonResult.AlwaysFalse)] [DataRow(ComparisonKind.LessThan, +0, CountComparisonResult.AlwaysFalse)] [DataRow(ComparisonKind.LessThan, +1, CountComparisonResult.Empty)] [DataRow(ComparisonKind.LessThan, +2, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.GreaterThanOrEqual, -1, CountComparisonResult.AlwaysTrue)] [DataRow(ComparisonKind.GreaterThanOrEqual, +0, CountComparisonResult.AlwaysTrue)] [DataRow(ComparisonKind.GreaterThanOrEqual, +1, CountComparisonResult.NotEmpty)] [DataRow(ComparisonKind.GreaterThanOrEqual, +2, CountComparisonResult.SizeDepedendent)] [DataRow(ComparisonKind.LessThanOrEqual, -9, CountComparisonResult.AlwaysFalse)] [DataRow(ComparisonKind.LessThanOrEqual, -1, CountComparisonResult.AlwaysFalse)] [DataRow(ComparisonKind.LessThanOrEqual, +0, CountComparisonResult.Empty)] [DataRow(ComparisonKind.LessThanOrEqual, +1, CountComparisonResult.SizeDepedendent)] public void Compare(ComparisonKind comparison, int count, CountComparisonResult expected) => comparison.Compare(count).Should().Be(expected); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Extensions/LocationExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; namespace SonarAnalyzer.Core.Syntax.Extensions.Test; [TestClass] public class LocationExtensionsTest { [TestMethod] public void EnsureMappedLocation_NonGeneratedLocation_ShouldBeSame() { var code = """ using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } } """; var location = Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(50, 75)); var result = location.EnsureMappedLocation(); result.Should().BeSameAs(location); } [TestMethod] public void EnsureMappedLocation_LocationNull_ShouldReturnNull() { Location location = null; var result = location.EnsureMappedLocation(); result.Should().BeNull(); } [TestMethod] public void EnsureMappedLocation_GeneratedLocation_ShouldTargetOriginal() { var code = """ using System; namespace HelloWorld { class Program { static void Main(string[] args) { #line (1, 5) - (1, 20) 30 "Original.razor" Console.WriteLine("Hello, World!"); } } } """; var location = Location.Create(CSharpSyntaxTree.ParseText(code).WithFilePath("Program.razor.g.cs"), new TextSpan(code.IndexOf("\"Hello, World!\""), 15)); var result = location.EnsureMappedLocation(); result.Should().NotBeSameAs(location); result.GetLineSpan().Path.Should().Be("Original.razor"); result.GetLineSpan().Span.Start.Line.Should().Be(0); result.GetLineSpan().Span.Start.Character.Should().Be(4); result.GetLineSpan().Span.End.Line.Should().Be(0); result.GetLineSpan().Span.End.Character.Should().Be(19); } [TestMethod] public void ToSecondary_NullMessage() { var code = "public class C {}"; var location = Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)); var secondaryLocation = location.ToSecondary(null); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(location); secondaryLocation.Message.Should().BeNull(); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondary_MessageArgs(string[] messageArgs) { var code = "public class C {}"; var location = Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)); var secondaryLocation = location.ToSecondary("Message", messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(location); secondaryLocation.Message.Should().Be("Message"); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondary_MessageFormat(string format, params string[] messageArgs) { var code = "public class C {}"; var location = Location.Create(CSharpSyntaxTree.ParseText(code), TextSpan.FromBounds(13, 14)); var secondaryLocation = location.ToSecondary(format, messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(location); secondaryLocation.Message.Should().Be(string.Format(format, messageArgs)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Extensions/SyntaxNodeExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.Core.Syntax.Extensions.Test; [TestClass] public class SyntaxNodeExtensionsTest { [TestMethod] public void LineNumberToReport() { const string code = """ namespace Test { class Sample { } } """; var method = CSharpSyntaxTree.ParseText(code).Single(); method.LineNumberToReport().Should().Be(3); method.GetLocation().GetLineSpan().StartLinePosition.LineNumberToReport().Should().Be(3); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Extensions/SyntaxTokenExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Extensions.Test; [TestClass] public class SyntaxTokenExtensionsTest { [TestMethod] public void ToSecondaryLocation_NullMessage() { var code = "public class $$C$$ {}"; var token = TestCompiler.TokenBetweenMarkersCS(code).Token; var secondaryLocation = token.ToSecondaryLocation(null); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(token.GetLocation()); secondaryLocation.Message.Should().BeNull(); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondaryLocation_MessageArgs(string[] messageArgs) { var code = "public class $$C$$ {}"; var token = TestCompiler.TokenBetweenMarkersCS(code).Token; var secondaryLocation = token.ToSecondaryLocation("Message", messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(token.GetLocation()); secondaryLocation.Message.Should().Be("Message"); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondaryLocation_MessageFormat(string format, params string[] messageArgs) { var code = "public class $$C$$ {}"; var token = TestCompiler.TokenBetweenMarkersCS(code).Token; var secondaryLocation = token.ToSecondaryLocation(format, messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(token.GetLocation()); secondaryLocation.Message.Should().Be(string.Format(format, messageArgs)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Utilities/GeneratedCodeRecognizerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities.Test; [TestClass] public class GeneratedCodeRecognizerTest { [TestMethod] [DataRow(null)] [DataRow("")] public void IsGenerated_WithNullOrEmptyPathAndNullRoot_ReturnsFalse(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); new TestRecognizer().IsGenerated(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_razor.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_razor.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_RAZOR.g.cS")] public void IsGenerated_GeneratedFiles_ReturnsTrue(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); new TestRecognizer().IsGenerated(tree).Should().BeTrue(); } [TestMethod] public void IsGenerated_NonGeneratedPath_ReturnsTrue() { var tree = Substitute.For(); tree.FilePath.Returns(@"C:\SonarSource\SomeFile.cs"); new TestRecognizer().IsGenerated(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile_razor.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_RAZOR.g.cS")] [DataRow(@"C:\SonarSource\SomeFile_razor.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs")] [DataRow(@"SomeFile_razor.ide.g.cs")] [DataRow(@"SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs")] public void IsRazorGeneratedFile_RazorGeneratedFiles_Razor_ReturnsTrue(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); GeneratedCodeRecognizer.IsRazorGeneratedFile(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsRazor(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsCshtml(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile_cshtml.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_CSHTML.g.cs")] [DataRow(@"SomeFile_cshtml.g.cs")] [DataRow(@"SomeFile_cshtml.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile.cshtml.-6NXeWT5Akt4vxdz.ide.g.cs")] [DataRow(@"SomeFile.cshtml.-6NXeWT5Akt4vxdz.ide.g.cs")] public void IsRazorGeneratedFile_RazorGeneratedFiles_Cshtml_ReturnsTrue(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); GeneratedCodeRecognizer.IsRazorGeneratedFile(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsCshtml(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsRazor(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_razor.g.cs.randomEnding")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.g.cs.randomEnding")] [DataRow(@"C:\SonarSource\SomeFile_razor.g.ß")] [DataRow(@"SomeFile.__virtual.cs")] [DataRow(@"SomeFile.razor__virtual.cs")] [DataRow(@"virtualcsharp-razor:///c:/dev/Project/Views/Home/Index.razor__viRtUaL.cs")] [DataRow(@"virtualcsharp-razor:///c:/dev/Project/Views/Home/Index.cshtml__VIRTUAL.cs")] [DataRow(@"SomeFile.php.-ABC.ide.g.cs")] public void IsRazorGeneratedFile_NonRazorGeneratedFiles_ReturnsFalse(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); GeneratedCodeRecognizer.IsRazorGeneratedFile(tree).Should().BeFalse(); GeneratedCodeRecognizer.IsRazor(tree).Should().BeFalse(); GeneratedCodeRecognizer.IsCshtml(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile_razor.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_RAZOR.g.cS")] [DataRow(@"SomeFile_RAZOR.g.cS")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_CSHTML.g.cS")] [DataRow(@"SomeFile_csHTML.g.cS")] public void IsBuildTimeRazorGeneratedFile_ReturnsTrue(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); GeneratedCodeRecognizer.IsBuildTimeRazorGeneratedFile(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsDesignTimeRazorGeneratedFile(tree).Should().BeFalse(); } [TestMethod] [DataRow(@"C:\SonarSource\SomeFile_razor.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs")] [DataRow(@"SomeFile_razor.ide.g.cs")] [DataRow(@"SomeFile.razor.-6NXeWT5Akt4vxdz.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile_cshtml.ide.g.cs")] [DataRow(@"C:\SonarSource\SomeFile.csHTml.-6NXeWT5Akt4vxdz.ide.g.cs")] [DataRow(@"SomeFile_cshtml.ide.g.cs")] [DataRow(@"SomeFile.CSHTmL.-6NXeWT5Akt4vxdz.ide.g.cs")] public void IsDesignTimeRazorGeneratedFile_ReturnsTrue(string path) { var tree = Substitute.For(); tree.FilePath.Returns(path); GeneratedCodeRecognizer.IsDesignTimeRazorGeneratedFile(tree).Should().BeTrue(); GeneratedCodeRecognizer.IsBuildTimeRazorGeneratedFile(tree).Should().BeFalse(); } [TestMethod] public void IsRazorGeneratedFile_NullSyntaxTree_ReturnsFalse() => GeneratedCodeRecognizer.IsRazorGeneratedFile(null).Should().BeFalse(); [TestMethod] public void IsBuildTimeRazorGeneratedFile_NullSyntaxTree_ReturnsFalse() => GeneratedCodeRecognizer.IsBuildTimeRazorGeneratedFile(null).Should().BeFalse(); [TestMethod] public void IsDesignTimeRazorGeneratedFile_NullSyntaxTree_ReturnsFalse() => GeneratedCodeRecognizer.IsDesignTimeRazorGeneratedFile(null).Should().BeFalse(); #pragma warning disable T0016 // Internal Styling Rule T0016 private class TestRecognizer : GeneratedCodeRecognizer { protected override string GetAttributeName(SyntaxNode node) => string.Empty; protected override bool IsTriviaComment(SyntaxTrivia trivia) => false; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/Syntax/Utilities/VisualIndentComparerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Core.Syntax.Utilities.Test; [TestClass] public class VisualIndentComparerTest { [TestMethod] public void TestVisualIndent_NonTabsOnly() { VisualIndentComparer.IsSecondIndentLonger("", "").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("", "@").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("123", "AB").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("123", "ABC").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("123", "ABCD").Should().Be(true); } [TestMethod] public void TestVisualIndent_TabsOnly() { VisualIndentComparer.IsSecondIndentLonger("\t\t\t", "\t\t").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("\t\t\t", "\t").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("\t", "\t\t").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("\t", "\t").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("", "\t").Should().Be(true); } [TestMethod] public void TestVisualIndent_Mix_ResultIsCertain() { // More tabs and same chars -> certain of outcome VisualIndentComparer.IsSecondIndentLonger("\t\t123", "\tABCD").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("\tABCD", "\t\t123").Should().Be(true); // More tabs and more chars -> certain of outcome VisualIndentComparer.IsSecondIndentLonger("\t\t123", "\t12").Should().Be(false); VisualIndentComparer.IsSecondIndentLonger("\t12", "\t\t123").Should().Be(true); } [TestMethod] public void TestVisualIndent_Mix_ResultIsUncertain() { // More tabs but fewer characters -> depends on tab spacing // -> error on the side of caution and return false VisualIndentComparer.IsSecondIndentLonger("\t\t", " ").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger(" ", "\t\t").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("\t\t\t", "\t\t12").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("\t\t12", "\t\t\t").Should().Be(true); // Example ITs: sources\Nancy\src\Nancy\Json\JsonDeserializer.cs #389,390, same pattern in #620,621 // " } else if (... )" // " buffer.Append (ch);" VisualIndentComparer.IsSecondIndentLonger("\t\t\t} ", "\t\t\t\t").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("\t\t\t\t", "\t\t\t} ").Should().Be(true); // Example ITs: "sources\Nancy\src\Nancy\TinyIoc\TinyIoC.cs, #1448,1450 // " if (!registrationType.IsAssignableFrom(type))" // "#endif" <-- not part of syntax tree // " throw new ArgumentException(String.Format("types: The type {0} is not assignable from {1}", registrationType.FullName, type.FullName));" VisualIndentComparer.IsSecondIndentLonger(" ", "\t\t\t\t\t").Should().Be(true); VisualIndentComparer.IsSecondIndentLonger("\t\t\t\t", " ").Should().Be(true); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/FilesToAnalyze/EmptyFilesToAnalyze.txt ================================================  ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/FilesToAnalyze/FilesToAnalyze.txt ================================================ C:\Projects/DummyProj/wEB.config C:\Projects/DummyProj/Views\Web.confiG C:\Projects\DummyProj\Views\Global.asax C:\Projects\DummyProj\Csharp\Controllers\HomeController.cs C:\Projects\DummyProj\VisualBasic\Controllers\HomeController.vb C:\Projects/DummyProj/Views\Web.confiGuration ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/FilesToAnalyze/FilesToAnalyzeWithInvalidValues.txt ================================================ foo 123 C:\Projects\Controllers:web.config C:web.config C:\Projects\Controllers/web.config C:\Projects/DummyProj/Views\Web.confiG ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/All_properties_cs/SonarLint.xml ================================================ sonar.cs.analyzeRazorCode false sonar.cs.ignoreHeaderComments true sonar.cs.analyzeGeneratedCode false sonar.cs.file.suffixes .cs sonar.cs.roslyn.ignoreIssues false sonar.exclusions Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* sonar.inclusions Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* sonar.global.exclusions Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* sonar.test.exclusions Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* sonar.test.inclusions Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* sonar.global.test.exclusions Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* S2225 S2342 format ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ flagsAttributeFormat ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ S2346 S1067 max 1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/All_properties_vbnet/SonarLint.xml ================================================ sonar.vbnet.ignoreHeaderComments true sonar.vbnet.analyzeGeneratedCode false sonar.vbnet.file.suffixes .vb sonar.vbnet.roslyn.ignoreIssues false sonar.exclusions Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* sonar.inclusions Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* sonar.global.exclusions Fake/GlobalExclusions/**/*,Fake/GlobalExclusions/Second*/**/* sonar.test.exclusions Fake/TestExclusions/**/*,Fake/TestExclusions/Second*/**/* sonar.test.inclusions Fake/TestInclusions/**/*,Fake/TestInclusions/Second*/**/* sonar.global.test.exclusions Fake/GlobalTestExclusions/**/*,Fake/GlobalTestExclusions/Second*/**/* S2225 S2342 format ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ flagsAttributeFormat ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ S2115 S3776 threshold 15 propertyThreshold 3 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/Duplicated_Properties/SonarLint.xml ================================================ sonar.cs.ignoreHeaderComments true sonar.cs.ignoreHeaderComments true sonar.cs.analyzeGeneratedCode true sonar.cs.analyzeGeneratedCode false sonar.exclusions Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* sonar.exclusions Fake/Inclusions/**/* ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/Incorrect_value_type/SonarLint.xml ================================================ sonar.cs.ignoreHeaderComments abc sonar.cs.analyzeGeneratedCode null sonar.cs.roslyn.ignoreIssues ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/Missing_properties/SonarLint.xml ================================================ sonar.nothing nothing ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/Partially_missing_properties/SonarLint.xml ================================================ sonar.nothing nothing sonar.cs.analyzeGeneratedCode true sonar.exclusions Fake/Exclusions/**/*,Fake/Exclusions/Second*/**/* sonar.inclusions Fake/Inclusions/**/*,Fake/Inclusions/Second*/**/* ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarLintXml/PropertiesCSharpTrueVbnetFalse/SonarLint.xml ================================================  sonar.cs.ignoreHeaderComments true sonar.vbnet.ignoreHeaderComments false sonar.cs.analyzeGeneratedCode true sonar.cs.analyzeRazorCode true sonar.vbnet.analyzeGeneratedCode false ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Invalid_DifferentClassName/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Invalid_DifferentNamespace/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Invalid_Xml/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C: ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingAnalysisConfigPath/SonarProjectConfig.xml ================================================  C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingFilesToAnalyzePath/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingOutPath/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingProjectPath/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingProjectType/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/MissingTargetFramework/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Path_MixedSeparators/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube/conf/SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp/AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf/0/FilesToAnalyze.txt C:\foo\bar\.sonarqube/out/0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Path_Unix/SonarProjectConfig.xml ================================================  /home/user/.sonarqube/conf/SonarQubeAnalysisConfig.xml /home/user/AwesomeBankWeb.CSharp.csproj /home/user/.sonarqube/conf/0/FilesToAnalyze.txt /home/user/.sonarqube/out/0 Test net5 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/Path_Windows/SonarProjectConfig.xml ================================================  c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml C:\foo\bar\AwesomeBankWeb.CSharp\AwesomeBankWeb.CSharp.csproj c:\foo\bar\.sonarqube\conf\0\FilesToAnalyze.txt C:\foo\bar\.sonarqube\out\0 Product netcoreapp3.1 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/TestResources/SonarProjectConfig/UnexpectedProjectTypeValue/SonarProjectConfig.xml ================================================  /home/user/.sonarqube/conf/SonarQubeAnalysisConfig.xml /home/user/AwesomeBankWeb.CSharp.csproj /home/user/.sonarqube/conf/0/FilesToAnalyze.txt /home/user/.sonarqube/out/0 FOO net5 ================================================ FILE: analyzers/tests/SonarAnalyzer.Core.Test/packages.lock.json ================================================ { "version": 1, "dependencies": { "net10.0": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0", "Microsoft.TestPlatform.TestHost": "18.4.0" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "17.11.48", "contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ==" }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.11.48", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Composition": "9.0.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.4.0", "Newtonsoft.Json": "13.0.3" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Security.Cryptography.Pkcs": "8.0.1" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { "System.Collections.Immutable": "8.0.0" } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/DotMemorySetupTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using JetBrains.dotMemoryUnit; namespace SonarAnalyzer.Memory.Test; [TestClass] public class DotMemorySetupTest { [TestMethod] [DotMemoryUnit(FailIfRunWithoutSupport = true)] public void EnsureDotMemoryUTsAreExecutedWithTheRightRunner() { try { dotMemory.Check(x => { }); } catch (DotMemoryUnitException ex) when (ex.Message.StartsWith("The test was run without the support for dotMemory Unit.")) { if (TestEnvironment.IsAzureDevOpsContext) { Assert.Fail("Memory tests are not executed correctly. Make sure you run the memory tests with the appropiate test runner. See https://www.jetbrains.com/help/dotmemory-unit/Using_dotMemory_Unit_Standalone_Runner.html for details."); } else { Assert.Inconclusive("The tests in this assembly need to be executed with the dotMemory tools. See 'RunMemoryTest.ps1' for details."); } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/MSTestSettings.cs ================================================ using JetBrains.dotMemoryUnit; [assembly: DoNotParallelize] [assembly: DotMemoryUnit(FailIfRunWithoutSupport = false)] ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/Registrations/SonarSyntaxNodeReportingContextMemoryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * This program is free software; you can redistribute it and/or * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Text; using JetBrains.dotMemoryUnit; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.Core.AnalysisContext; namespace SonarAnalyzer.Memory.Test.Registrations; [TestClass] public class SonarSyntaxNodeReportingContextMemoryTest { public TestContext TestContext { get; set; } [TestMethod] public void SonarSyntaxNodeReportingContextAllocationsPerNodeKind() { const int namespaceCount = 20_000; var compilation = CreateCompilationWithNamespaceDeclarations(namespaceCount); var keepAlive = new ConcurrentStack(); var withAnalyzer = compilation.WithAnalyzers([new TestAnalyzerCS((c, g) => c.RegisterNodeAction( g, context => { keepAlive.Push(context); context.ReportIssue(TestAnalyzer.Rule, context.Node); }, SyntaxKind.NamespaceDeclaration))]); var result = withAnalyzer.GetAllDiagnosticsAsync().GetAwaiter().GetResult(); keepAlive.Should().HaveCount(namespaceCount); dotMemory.Check(x => { PrintObjectStatistics(x.GroupByType()); var contextCount = x.GetObjects(where => where.Type.Is()).ObjectsCount; contextCount.Should().Be(0); // SonarSyntaxNodeReportingContext is a struct and should never be boxed }); result.Should().HaveCount(namespaceCount); GC.KeepAlive(keepAlive); } [TestMethod] [DotMemoryUnit(CollectAllocations = true)] public void SonarSyntaxNodeReportingContextOverallAllocations() { const int namespaceCount = 20_000; var warmupCompilation = CSharpCompilation.Create(assemblyName: null, [CreateNamespaceDeclarations(1)], options: new(OutputKind.DynamicallyLinkedLibrary)); ImmutableArray analyzers = [new TestAnalyzerCS((c, g) => c.RegisterNodeAction( g, context => { new MarkerObject().Reset(); // Make sure we don't get optimized away by inlining context.ReportIssue(TestAnalyzer.Rule, context.Node); }, SyntaxKind.NamespaceDeclaration))]; var withAnalyzer = warmupCompilation.WithAnalyzers(analyzers); // Warmup withAnalyzer.GetAllDiagnosticsAsync().GetAwaiter().GetResult(); var compilation = warmupCompilation.RemoveAllSyntaxTrees().AddSyntaxTrees(CreateNamespaceDeclarations(namespaceCount)); withAnalyzer = compilation.WithAnalyzers(analyzers); var checkpoint = dotMemory.Check(); var result = withAnalyzer.GetAllDiagnosticsAsync().GetAwaiter().GetResult(); dotMemory.Check(x => { var traffic = x.GetTrafficFrom(checkpoint).GroupByType(); PrintObjectStatistics(traffic); var allocated = new { AllocatedMemoryInfo = new { ObjectsCount = namespaceCount }, CollectedMemoryInfo = new { ObjectsCount = namespaceCount } }; var allocatedAndCollected = new { AllocatedMemoryInfo = new { ObjectsCount = namespaceCount }, CollectedMemoryInfo = new { ObjectsCount = 0 } }; traffic.Should().ContainEquivalentOf(new { Type = typeof(MarkerObject) }).Which.Should().BeEquivalentTo(allocated); traffic.Should().ContainEquivalentOf(new { Type = typeof(SonarSyntaxNodeReportingContext) }).Which.Should().BeEquivalentTo( allocated, because: "ReportIssue captures and boxes SonarSyntaxNodeReportingContext. This is not the hotpath, though and therefore okay."); traffic.Should().ContainEquivalentOf(new { Type = typeof(SyntaxNodeAnalysisContext) }).Which.Should().BeEquivalentTo(allocated, because: "Boxed by ReportIssue"); traffic.Should().ContainEquivalentOf(new { Type = typeof(NamespaceDeclarationSyntax) }).Which.Should().BeEquivalentTo(allocatedAndCollected); traffic.Should().ContainEquivalentOf(new { Type = typeof(ReportingContext) }).Which.Should().BeEquivalentTo(allocated); var stringAllocations = traffic.Should().ContainEquivalentOf(new { Type = typeof(string) }).Subject; stringAllocations.AllocatedMemoryInfo.ObjectsCount.Should().BeLessThan(100); stringAllocations.AllocatedMemoryInfo.SizeInBytes.Should().BeLessThan(5_000); }); result.Should().HaveCount(namespaceCount); GC.KeepAlive(result); GC.KeepAlive(withAnalyzer); } [TestMethod] [DotMemoryUnit(CollectAllocations = true)] public void SonarSyntaxNodeReportingContextOverallAllocationsWithoutReporting() { const int namespaceCount = 20_000; var compilation = CSharpCompilation.Create(assemblyName: null, [CreateNamespaceDeclarations(namespaceCount)], options: new(OutputKind.DynamicallyLinkedLibrary)); var withAnalyzer = compilation.WithAnalyzers([new TestAnalyzerCS((c, g) => c.RegisterNodeAction(g, _ => new MarkerObject().Reset(), SyntaxKind.NamespaceDeclaration))]); var checkpoint = dotMemory.Check(); var result = withAnalyzer.GetAllDiagnosticsAsync().GetAwaiter().GetResult(); dotMemory.Check(x => { var traffic = x.GetTrafficFrom(checkpoint).GroupByType(); PrintObjectStatistics(traffic); traffic.Should().ContainEquivalentOf(new { Type = typeof(MarkerObject) }).Which.Should().BeEquivalentTo(new { AllocatedMemoryInfo = new { ObjectsCount = namespaceCount } }); traffic.Should().NotContainEquivalentOf(new { Type = typeof(SonarSyntaxNodeReportingContext) }, because: "The action invocation is allocation free"); traffic.Should().NotContainEquivalentOf(new { Type = typeof(SyntaxNodeAnalysisContext) }, because: "The action invocation is allocation free"); traffic.Where(x => x.Type.Namespace.StartsWith(nameof(SonarAnalyzer)) && x.Type != typeof(MarkerObject)).Should().NotBeEmpty().And.AllSatisfy(x => x.AllocatedMemoryInfo.ObjectsCount.Should().BeLessThan(50)); var stringAllocations = traffic.Should().ContainEquivalentOf(new { Type = typeof(string) }).Subject; stringAllocations.AllocatedMemoryInfo.ObjectsCount.Should().BeLessThan(1_000); stringAllocations.AllocatedMemoryInfo.SizeInBytes.Should().BeLessThan(60_000); }); result.Should().HaveCount(0); GC.KeepAlive(result); GC.KeepAlive(withAnalyzer); } private void PrintObjectStatistics(IEnumerable typeTrafficInfos) => TestContext.WriteLine(typeTrafficInfos.OrderByDescending(x => x.AllocatedMemoryInfo.SizeInBytes).Aggregate( new StringBuilder(), (builder, x) => builder.AppendLine(x.ToString()), x => x.ToString())); private void PrintObjectStatistics(IEnumerable typeMemoryInfos) => TestContext.WriteLine(typeMemoryInfos.OrderByDescending(x => x.SizeInBytes).Aggregate( new StringBuilder(), (builder, x) => builder.AppendLine(x.ToString()), x => x.ToString())); private static CSharpCompilation CreateCompilationWithNamespaceDeclarations(int namespaceCount) => CSharpCompilation.Create(assemblyName: null, [CreateNamespaceDeclarations(namespaceCount)], options: new(OutputKind.DynamicallyLinkedLibrary)); private static SyntaxTree CreateNamespaceDeclarations(int namespaceCount) => CSharpSyntaxTree.ParseText(Enumerable.Range(0, namespaceCount).Aggregate( new StringBuilder(), (x, i) => x.AppendLine($$"""namespace A{{i}} { }"""), x => x.ToString())); } public class MarkerObject { public int Value { get; set; } = 1; [MethodImpl(MethodImplOptions.NoInlining)] public void Reset() => Value = 0; } ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/RunMemoryTest.ps1 ================================================ $dotNet = (get-command dotnet.exe).Path & $dotNet build -c Release $dotMemoryUnit = dir $env:USERPROFILE\.nuget\packages\jetbrains.dotmemoryunit\*\lib\tools\dotMemoryUnit.exe | sort -Property {$_.VersionInfo.FileVersion} | select -First 1 try { Push-Location $PSScriptRoot & $dotMemoryUnit "$dotNet" --propagate-exit-code --no-updates -- test "bin/Release/net9.0/SonarAnalyzer.Memory.Test.dll" --logger "trx;logfilename=net9.trx" @args & $dotMemoryUnit "$dotNet" --propagate-exit-code --no-updates -- test "bin/Release/net48/SonarAnalyzer.Memory.Test.dll" --logger "trx;logfilename=net48.trx" @args } finally { Pop-Location } ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/SonarAnalyzer.Memory.Test.csproj ================================================  net48;net9.0 false true true global,common global,csharp global,vbnet ================================================ FILE: analyzers/tests/SonarAnalyzer.Memory.Test/packages.lock.json ================================================ { "version": 1, "dependencies": { ".NETFramework,Version=v4.8": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "JetBrains.dotMemoryUnit": { "type": "Direct", "requested": "[3.2.20220510, )", "resolved": "3.2.20220510", "contentHash": "jhzqT6zXFN0S6zxNXKmiEoNIqAEtlbsteQEhDA8TkTFfQpE/noktqU1HXfJ4P2PPczW48aFW9h69nHY8pSbnXw==" }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0" } }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", "resolved": "1.0.3", "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", "dependencies": { "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1", "System.Threading.Tasks.Extensions": "4.5.4" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==" }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==" }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "owmu2Cr3IQ8yQiBleBHlGk8dSQ12oaF2e7TpzwJKEl4m84kkZJjEY1n33L67Y3zM5jPOjmmbdHjbfiL0RqcMRQ==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "18.0.2", "contentHash": "sOSb+0J4G/jCBW/YqmRuL0eOMXgfw1KQLdC9TkbvfA5xs7uNm+PBQXJCOzSJGXtZcZrtXozcwxPmUiRUbmd7FA==", "dependencies": { "System.Collections.Immutable": "9.0.0", "System.Diagnostics.DiagnosticSource": "9.0.0", "System.Memory": "4.6.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Json": "9.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.Build.Framework": "18.0.2", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.IO.Redist": "6.1.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Buffers": "4.6.0", "System.Collections.Immutable": "9.0.0", "System.Composition": "9.0.0", "System.Diagnostics.DiagnosticSource": "9.0.0", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Reflection.Metadata": "9.0.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0", "System.Text.Encoding.CodePages": "8.0.0", "System.Text.Encodings.Web": "9.0.0", "System.Text.Json": "9.0.0", "System.Threading.Channels": "8.0.0", "System.Threading.Tasks.Extensions": "4.6.0", "System.ValueTuple": "4.5.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "System.Threading.Tasks.Extensions": "4.5.4" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "System.Threading.Tasks.Extensions": "4.5.4" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "System.Diagnostics.DiagnosticSource": "9.0.0", "System.ValueTuple": "4.5.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "System.Buffers": "4.5.1", "System.Diagnostics.DiagnosticSource": "9.0.0", "System.Memory": "4.5.5" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "System.ValueTuple": "4.5.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Microsoft.IO.Redist": { "type": "Transitive", "resolved": "6.1.0", "contentHash": "pTYqyiu9nLeCXROGjKnnYTH9v3yQNgXj3t4v7fOWwh9dgSBIwZbiSi8V76hryG2CgTjUFU+xu8BXPQ122CwAJg==", "dependencies": { "System.Buffers": "4.6.0", "System.Memory": "4.6.0" } }, "Microsoft.NETFramework.ReferenceAssemblies.net48": { "type": "Transitive", "resolved": "1.0.3", "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1", "System.Diagnostics.DiagnosticSource": "6.0.0" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.3.0", "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==", "dependencies": { "Microsoft.IO.Redist": "6.0.1", "System.Memory": "4.5.5", "System.Threading.Tasks.Extensions": "4.5.4" } }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1", "System.Threading.Tasks.Extensions": "4.3.0" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1", "System.Collections.Immutable": "8.0.0" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Text.Json": "8.0.5" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1", "System.Text.Json": "8.0.5" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Buffers": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "ddppcFpnbohLWdYKr/ZeLZHmmI+DXFgZ3Snq+/E7SwcdW4UnvxmaugkwGywvGVWkHPGCSZjCP+MLzu23AL5SDw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.IO.Pipelines": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "eA3cinogwaNB4jdjQHOP3Z3EuyiDII7MT35jgtnsA4vkn0LUrrSHsU0nzHTzFzmaFYeKV7MYyMxOocFzsBHpTw==", "dependencies": { "System.Buffers": "4.5.1", "System.Memory": "4.5.5", "System.Threading.Tasks.Extensions": "4.5.4" } }, "System.Memory": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "OEkbBQoklHngJ8UD8ez2AERSk2g+/qpAaSWWCBFbpH727HxDq5ydVkuncBaKcKfwRqXGWx64dS6G1SUScMsitg==", "dependencies": { "System.Buffers": "4.6.0", "System.Numerics.Vectors": "4.6.0", "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "t+SoieZsRuEyiw/J+qXUbolyO219tKQQI0+2/YI+Qv7YdGValA6WiuokrNKqjrTNsy5ABWU11bdKOzUdheteXg==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", "dependencies": { "System.Collections.Immutable": "9.0.0", "System.Memory": "4.5.5" } }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.1.0", "contentHash": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg==" }, "System.Text.Encoding.CodePages": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "e2hMgAErLbKyUUwt18qSBf9T5Y+SFAL3ZedM8fLupkVj8Rj2PZ9oxQ37XX2LF8fTO1wNIxvKpihD7Of7D/NxZw==", "dependencies": { "System.Buffers": "4.5.1", "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "System.Text.Json": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "System.Buffers": "4.5.1", "System.IO.Pipelines": "9.0.0", "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0", "System.Text.Encodings.Web": "9.0.0", "System.Threading.Tasks.Extensions": "4.5.4", "System.ValueTuple": "4.5.0" } }, "System.Threading.Channels": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } }, "System.Threading.Tasks.Extensions": { "type": "Transitive", "resolved": "4.6.0", "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.1.0" } }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "SonarAnalyzer.CSharp.Core": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } }, "sonaranalyzer.visualbasic": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[1.3.2, )", "SonarAnalyzer.Core": "[10.26.0, )", "SonarAnalyzer.VisualBasic.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.visualbasic.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[1.3.2, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } }, "net9.0": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "JetBrains.dotMemoryUnit": { "type": "Direct", "requested": "[3.2.20220510, )", "resolved": "3.2.20220510", "contentHash": "jhzqT6zXFN0S6zxNXKmiEoNIqAEtlbsteQEhDA8TkTFfQpE/noktqU1HXfJ4P2PPczW48aFW9h69nHY8pSbnXw==" }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0", "Microsoft.TestPlatform.TestHost": "18.4.0" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "17.11.48", "contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ==" }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.11.48", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Composition": "9.0.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.4.0", "Newtonsoft.Json": "13.0.3" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Security.Cryptography.Pkcs": "8.0.1" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { "System.Collections.Immutable": "8.0.0" } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "SonarAnalyzer.CSharp.Core": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.csharp.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } }, "sonaranalyzer.visualbasic": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[1.3.2, )", "SonarAnalyzer.Core": "[10.26.0, )", "SonarAnalyzer.VisualBasic.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.visualbasic.core": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[1.3.2, )", "SonarAnalyzer.CFG": "[10.26.0, )", "SonarAnalyzer.Core": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/FactoryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using VerifyMSTest; using VerifyTests; namespace SonarAnalyzer.ShimLayer.Generator.Test; [TestClass] [UsesVerify] public partial class FactoryTest { [TestMethod] public async Task SnapshotAsync() => // If this test fails in CI, execute it locally to update the snapshots and push the changes. await Verifier.Verify(Factory.CreateAllFiles().Select(x => new Target("cs", x.Content, x.Name))) .UseDirectory("Snapshots") .AutoVerify(includeBuildServer: false) .UseFileName("Snap"); [TestMethod] public async Task Run() => await VerifyChecks.Run(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Model/ModelBuilderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; namespace SonarAnalyzer.ShimLayer.Generator.Model.Test; [TestClass] public class ModelBuilderTest { [TestMethod] public void Build_NestedTypes() { var type = typeof(IOperation.OperationList); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType(); } [TestMethod] public void Build_GenericTypes() { var type = typeof(IEnumerable); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType(); } [TestMethod] public void Build_Delegates() { var type = typeof(SyntaxReceiverCreator); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType(); } [TestMethod] public void Build_Enums_NoBaseline() { var type = typeof(NamespaceKind); var members = type.GetMembers(); var model = ModelBuilder.Build([new TypeDescriptor(type, members)], []); model[type].Should().BeOfType() .Which.Fields.Select(x => x.Name).Should().BeEquivalentTo([ "Assembly", "Compilation", "Module"]); // Make sure we've sent other members that are not in the result members.Should().ContainSingle(x => x is FieldInfo && x.Name == "value__"); // IsSpecialName members.Should().ContainSingle(x => x is MethodInfo && x.Name == "HasFlag"); // IsSpecialName } [TestMethod] public void Build_Enums_WithBaseline() { var type = typeof(NamespaceKind); var assembly = type.GetMember("Assembly").Single(); var compilation = type.GetMember("Compilation").Single(); var module = type.GetMember("Module").Single(); var model = ModelBuilder.Build([new(type, [assembly, compilation, module])], [new(type, [assembly])]); model[type].Should().BeOfType() .Which.Fields.Select(x => x.Name).Should().BeEquivalentTo([ "Compilation", "Module"]); } [TestMethod] public void Build_SyntaxNode_Itself() { var type = typeof(SyntaxNode); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType(); } [TestMethod] public void Build_SyntaxNode_RecordDeclaration() { var type = typeof(RecordDeclarationSyntax); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], [ new(typeof(TypeDeclarationSyntax), []), new(typeof(BaseTypeDeclarationSyntax), []), new(typeof(MemberDeclarationSyntax), []), new(typeof(CSharpSyntaxNode), []), new(typeof(SyntaxNode), [])]); model[type].Should().BeOfType().Which.BaseType.FullName.Should().Be(typeof(TypeDeclarationSyntax).FullName); } [TestMethod] public void Build_SyntaxNode_NoCommonBaseType() { var type = typeof(RecordDeclarationSyntax); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType().Which.BaseType.Should().Be(); } [TestMethod] public void Build_SyntaxNode_ExtensionBlockDeclarationSyntax_EndToEnd() { using var typeLoader = new TypeLoader(); var typeDescriptor = typeLoader.LoadLatest().Single(x => x.Type.Name == nameof(ExtensionBlockDeclarationSyntax)); var model = ModelBuilder.Build([typeDescriptor], typeLoader.LoadBaseline()); var strategy = model[typeDescriptor.Type].Should().BeOfType().Which; strategy.BaseType.FullName.Should().Be(typeof(TypeDeclarationSyntax).FullName); strategy.Members.Should().HaveCount(150); strategy.Members.Should().ContainEquivalentOf(new MemberDescriptor(typeDescriptor.Type.GetMember(nameof(ExtensionBlockDeclarationSyntax.Modifiers)).Should().ContainSingle().Subject, true)); strategy.Members.Should().ContainEquivalentOf(new MemberDescriptor(typeDescriptor.Type.GetMember(nameof(ExtensionBlockDeclarationSyntax.ParameterList)).Should().ContainSingle().Subject, false)); } [TestMethod] public void Build_IOperation() { using var typeLoader = new TypeLoader(); var type = typeLoader.LoadLatest().Single(x => x.Type.Name == nameof(IOperation)); var model = ModelBuilder.Build([type], []); model[type.Type].Should().BeOfType() .Which.Members.Select(x => x.Member.ToString()).Should().BeEquivalentTo([ "System.Void Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor)", "TResult Accept[TArgument,TResult](Microsoft.CodeAnalysis.Operations.OperationVisitor`2[TArgument,TResult], TArgument)", "Microsoft.CodeAnalysis.IOperation Parent", "Microsoft.CodeAnalysis.OperationKind Kind", "Microsoft.CodeAnalysis.SyntaxNode Syntax", "Microsoft.CodeAnalysis.ITypeSymbol Type", "Microsoft.CodeAnalysis.Optional`1[System.Object] ConstantValue", "System.Collections.Generic.IEnumerable`1[Microsoft.CodeAnalysis.IOperation] Children", "Microsoft.CodeAnalysis.IOperation+OperationList ChildOperations", "System.String Language", "System.Boolean IsImplicit", "Microsoft.CodeAnalysis.SemanticModel SemanticModel"]); } [TestMethod] public void Build_IInvocationOperation() { using var typeLoader = new TypeLoader(); var type = typeLoader.LoadLatest().Single(x => x.Type.Name == nameof(IInvocationOperation)); var model = ModelBuilder.Build([type], []); model[type.Type].Should().BeOfType() .Which.Members.Select(x => x.Member.ToString()).Should().BeEquivalentTo([ "Microsoft.CodeAnalysis.IMethodSymbol TargetMethod", "Microsoft.CodeAnalysis.ITypeSymbol ConstrainedToType", "Microsoft.CodeAnalysis.IOperation Instance", "System.Boolean IsVirtual", "System.Collections.Immutable.ImmutableArray`1[Microsoft.CodeAnalysis.Operations.IArgumentOperation] Arguments", "System.Void Accept(Microsoft.CodeAnalysis.Operations.OperationVisitor)", "TResult Accept[TArgument,TResult](Microsoft.CodeAnalysis.Operations.OperationVisitor`2[TArgument,TResult], TArgument)", "Microsoft.CodeAnalysis.IOperation Parent", "Microsoft.CodeAnalysis.OperationKind Kind", "Microsoft.CodeAnalysis.SyntaxNode Syntax", "Microsoft.CodeAnalysis.ITypeSymbol Type", "Microsoft.CodeAnalysis.Optional`1[System.Object] ConstantValue", "System.Collections.Generic.IEnumerable`1[Microsoft.CodeAnalysis.IOperation] Children", "Microsoft.CodeAnalysis.IOperation+OperationList ChildOperations", "System.String Language", "System.Boolean IsImplicit", "Microsoft.CodeAnalysis.SemanticModel SemanticModel"]); } [TestMethod] public void Build_StaticClass() { var type = typeof(GeneratorExtensions); var model = ModelBuilder.Build([new TypeDescriptor(type, [])], []); model[type].Should().BeOfType(); // ToDo: This will change later, likely to StaticClassStrategy } [TestMethod] public void Build_NoChangeStrategy() { var type = typeof(NamespaceKind); var members = type.GetMembers(); var model = ModelBuilder.Build( [new(type, members)], [new(type, members.OrderByDescending(x => x.ToString()).ToArray())]); model[type].Should().BeOfType(); } [TestMethod] public void Build_NoChangeStrategy_DifferentMembers() { var type = typeof(SyntaxToken); var members = type.GetMembers(); var model = ModelBuilder.Build( [new(type, members)], [new(type, [])]); // Fallback for types that do have a baseline (can be used), but do not have a dedicated strategy model[type].Should().BeOfType(); } [TestMethod] public void CreateMembers_NoBaseline() { var type = typeof(SyntaxNode); var parent = type.GetMember("Parent").Single(); var ancestors = type.GetMember("Ancestors").Single(); var model = ModelBuilder.Build([new TypeDescriptor(type, [parent, ancestors])], []); model[type].Should().BeOfType() .Which.Members.Should().BeEquivalentTo([ new MemberDescriptor(parent, false), new MemberDescriptor(ancestors, false)]); } [TestMethod] public void CreateMembers_WithBaseline() { using var typeLoader = new TypeLoader(); var baseline = typeLoader.LoadBaseline().Single(x => x.Type.Name == nameof(SyntaxNode)); var latest = typeLoader.LoadLatest().Single(x => x.Type.Name == nameof(SyntaxNode)); var model = ModelBuilder.Build([latest], [baseline]); model[latest.Type].Should().BeOfType() .Which.Members.Select(x => x.ToString()).Should().BeEquivalentTo([ "System.Boolean IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode)", "System.Boolean ContainsDirective(System.Int32)", "TNode FirstAncestorOrSelf[TNode,TArg](System.Func`3[TNode,TArg,System.Boolean], TArg, System.Boolean)"]); } [TestMethod] public void CreateMembers_SkippedMembers() { var type = typeof(SyntaxNode); var membersToSkip = new[] { type.GetMember("get_SyntaxTree").First(), type.GetMember("GetType").First(), type.GetMember("Equals").First(), type.GetMember("GetHashCode").First(), type.GetMember("ToString").First() }; var model = ModelBuilder.Build([new(type, membersToSkip)], []); model[type].Should().BeOfType().Which.Members.Should().BeEmpty(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Model/TypeLoaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Model.Test; [TestClass] public class TypeLoaderTest { private const string NewTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax"; // This type is not part of Roslyn 1.3.2 [TestMethod] public void LoadBaseline_AllAssemblies() { using var typeLoader = new TypeLoader(); typeLoader.LoadBaseline().Select(x => x.Type).Should() .ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.CSharp.CSharpCompilation") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.Compilation") .And.NotContain(x => x.FullName == NewTypeName) .And.HaveCount(555); } [TestMethod] public void LoadLatest_AllAssemblies() { using var typeLoader = new TypeLoader(); typeLoader.LoadLatest().Select(x => x.Type).Should() .ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.CSharp.CSharpCompilation") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.Compilation") .And.ContainSingle(x => x.FullName == NewTypeName) .And.HaveCountGreaterThan(750); } [TestMethod] public void Load_AllTypes() { using var typeLoader = new TypeLoader(); typeLoader.LoadLatest().Select(x => x.Type).Should() .ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.Compilation", "it should return classes") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.SymbolInfo", "it should return structs") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.SyntaxList`1", "it should return generic types") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.IOperation+OperationList", "it should return nested types") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.OutputKind", "it should return enums") .And.ContainSingle(x => x.FullName == "Microsoft.CodeAnalysis.IMethodSymbol", "it should return interfaces") .And.NotContain(x => !x.IsEnum && !x.IsGenericType && !x.IsInterface && !x.IsNested && !x.IsClass && !x.IsValueType, "there should be no unexpected types"); } [TestMethod] public void Load_InheritedMembers() { using var typeLoader = new TypeLoader(); typeLoader.LoadLatest().Single(x => x.Type.FullName == "Microsoft.CodeAnalysis.CSharp.CSharpCompilation").Members.Should() .ContainSingle(x => x.DeclaringType.Name == "Compilation" && x.ToString() == "Microsoft.CodeAnalysis.Compilation RemoveAllSyntaxTrees()", "it's in the base type") .And.ContainSingle(x => x.DeclaringType.Name == "CSharpCompilation" && x.ToString() == "Microsoft.CodeAnalysis.CSharp.CSharpCompilation RemoveAllSyntaxTrees()", "it shadows") .And.ContainSingle(x => x.DeclaringType.Name == "Compilation" && x.ToString() == "System.Boolean ContainsSyntaxTree(Microsoft.CodeAnalysis.SyntaxTree)", "it's in the base type") .And.ContainSingle(x => x.DeclaringType.Name == "CSharpCompilation" && x.ToString() == "Microsoft.CodeAnalysis.CSharp.CSharpCompilation RemoveAllSyntaxTrees()", "it overrides") .And.ContainSingle(x => x.DeclaringType.Name == "Object" && x.ToString() == "System.Int32 GetHashCode()", "it should contain all members down to System.Object"); } [TestMethod] public void Load_ContainsInheritedInterfaces() { using var typeLoader = new TypeLoader(); typeLoader.LoadLatest().Single(x => x.Type.FullName == "Microsoft.CodeAnalysis.IMethodSymbol").Members.Should() .ContainSingle(x => x.DeclaringType.Name == "IMethodSymbol" && x.ToString() == "System.Boolean IsGenericMethod") .And.ContainSingle(x => x.DeclaringType.Name == "ISymbol" && x.ToString() == "System.String Name", "it's from the base interface"); } [TestMethod] public void Load_ExplicitInterfaces_Ignored() { const string ImplicitInterface = "IFormattable"; const string ImplicitInterfaceMember = "System.String ToString(System.String, System.IFormatProvider)"; using var typeLoader = new TypeLoader(); var node = typeLoader.LoadLatest().Single(x => x.Type.FullName == "Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode"); // Make sure that the member exists on the type (it's explicit interface implementation) node.Type.GetInterface(ImplicitInterface).Should().NotBeNull("CSharpSyntaxNode implements IFormattable") .And.Subject.GetMembers().Should().ContainSingle(x => x.ToString() == ImplicitInterfaceMember); // Then assert with that member node.Members.Should().NotContain(x => x.ToString() == ImplicitInterfaceMember); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AccessorDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class AccessorDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(AccessorDeclarationSyntax); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(AccessorDeclarationSyntax @this) { public ArrowExpressionClauseSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AliasQualifiedNameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class AliasQualifiedNameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(AliasQualifiedNameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(AliasQualifiedNameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AllowsConstraintClauseSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct AllowsConstraintClauseSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.AllowsConstraintClauseSyntax"; private static readonly Type WrappedType; private readonly TypeParameterConstraintSyntax node; static AllowsConstraintClauseSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(AllowsConstraintClauseSyntaxWrapper)); AllowsKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AllowsKeyword"); ConstraintsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Constraints)); } private AllowsConstraintClauseSyntaxWrapper(TypeParameterConstraintSyntax node) => this.node = node; public TypeParameterConstraintSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeParameterConstraintSyntax SyntaxNode => this.node; private static readonly Func AllowsKeywordAccessor; public SyntaxToken AllowsKeyword => (SyntaxToken)AllowsKeywordAccessor(this.node); private static readonly Func> ConstraintsAccessor; public SeparatedSyntaxListWrapper Constraints => ConstraintsAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator AllowsConstraintClauseSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new AllowsConstraintClauseSyntaxWrapper((TypeParameterConstraintSyntax)node); } public static implicit operator TypeParameterConstraintSyntax(AllowsConstraintClauseSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AllowsConstraintSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct AllowsConstraintSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.AllowsConstraintSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static AllowsConstraintSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(AllowsConstraintSyntaxWrapper)); } private AllowsConstraintSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator AllowsConstraintSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new AllowsConstraintSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(AllowsConstraintSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AnonymousFunctionExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class AnonymousFunctionExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(AnonymousFunctionExpressionSyntax); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); private static readonly Func BlockAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Block"); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(AnonymousFunctionExpressionSyntax @this) { public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); public BlockSyntax Block => BlockAccessor(@this); public ExpressionSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#AnonymousMethodExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class AnonymousMethodExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(AnonymousMethodExpressionSyntax); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(AnonymousMethodExpressionSyntax @this) { public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); public ExpressionSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ArgumentKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum ArgumentKind : System.Int32 { None = 0, Explicit = 1, ParamArray = 2, DefaultValue = 3, ParamCollection = 4, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ArgumentSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ArgumentSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ArgumentSyntax); private static readonly Func RefKindKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RefKindKeyword"); extension(ArgumentSyntax @this) { public SyntaxToken RefKindKeyword => (SyntaxToken)RefKindKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ArrayTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ArrayTypeSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ArrayTypeSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(ArrayTypeSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BaseExpressionColonSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct BaseExpressionColonSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static BaseExpressionColonSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(BaseExpressionColonSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); ColonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ColonToken"); } private BaseExpressionColonSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func ColonTokenAccessor; public SyntaxToken ColonToken => (SyntaxToken)ColonTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator BaseExpressionColonSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new BaseExpressionColonSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(BaseExpressionColonSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BaseMethodDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class BaseMethodDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(BaseMethodDeclarationSyntax); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(BaseMethodDeclarationSyntax @this) { public ArrowExpressionClauseSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BaseNamespaceDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct BaseNamespaceDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BaseNamespaceDeclarationSyntax"; private static readonly Type WrappedType; private readonly MemberDeclarationSyntax node; static BaseNamespaceDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(BaseNamespaceDeclarationSyntaxWrapper)); NamespaceKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NamespaceKeyword"); NameAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Name"); ExternsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Externs"); UsingsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Usings"); MembersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Members"); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); } private BaseNamespaceDeclarationSyntaxWrapper(MemberDeclarationSyntax node) => this.node = node; public MemberDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public MemberDeclarationSyntax SyntaxNode => this.node; private static readonly Func NamespaceKeywordAccessor; public SyntaxToken NamespaceKeyword => (SyntaxToken)NamespaceKeywordAccessor(this.node); private static readonly Func NameAccessor; public NameSyntax Name => NameAccessor(this.node); private static readonly Func> ExternsAccessor; public SyntaxList Externs => (SyntaxList)ExternsAccessor(this.node); private static readonly Func> UsingsAccessor; public SyntaxList Usings => (SyntaxList)UsingsAccessor(this.node); private static readonly Func> MembersAccessor; public SyntaxList Members => (SyntaxList)MembersAccessor(this.node); private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func ModifiersAccessor; public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator BaseNamespaceDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new BaseNamespaceDeclarationSyntaxWrapper((MemberDeclarationSyntax)node); } public static implicit operator MemberDeclarationSyntax(BaseNamespaceDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BaseObjectCreationExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct BaseObjectCreationExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BaseObjectCreationExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static BaseObjectCreationExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(BaseObjectCreationExpressionSyntaxWrapper)); NewKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NewKeyword"); ArgumentListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ArgumentList"); InitializerAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Initializer"); } private BaseObjectCreationExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func NewKeywordAccessor; public SyntaxToken NewKeyword => (SyntaxToken)NewKeywordAccessor(this.node); private static readonly Func ArgumentListAccessor; public ArgumentListSyntax ArgumentList => ArgumentListAccessor(this.node); private static readonly Func InitializerAccessor; public InitializerExpressionSyntax Initializer => InitializerAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator BaseObjectCreationExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new BaseObjectCreationExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(BaseObjectCreationExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BaseParameterSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct BaseParameterSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BaseParameterSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static BaseParameterSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(BaseParameterSyntaxWrapper)); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); } private BaseParameterSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func ModifiersAccessor; public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(this.node); private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator BaseParameterSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new BaseParameterSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(BaseParameterSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BasicBlockKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum BasicBlockKind : System.Int32 { Entry = 0, Exit = 1, Block = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BinaryOperatorKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum BinaryOperatorKind : System.Int32 { None = 0, Add = 1, Subtract = 2, Multiply = 3, Divide = 4, IntegerDivide = 5, Remainder = 6, Power = 7, LeftShift = 8, RightShift = 9, And = 10, Or = 11, ExclusiveOr = 12, ConditionalAnd = 13, ConditionalOr = 14, Concatenate = 15, Equals = 16, ObjectValueEquals = 17, NotEquals = 18, ObjectValueNotEquals = 19, LessThan = 20, LessThanOrEqual = 21, GreaterThanOrEqual = 22, GreaterThan = 23, Like = 24, UnsignedRightShift = 25, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BinaryPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct BinaryPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BinaryPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static BinaryPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(BinaryPatternSyntaxWrapper)); LeftAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Left"); OperatorTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OperatorToken"); RightAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Right"); } private BinaryPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func LeftAccessor; public PatternSyntaxWrapper Left => (PatternSyntaxWrapper)LeftAccessor(this.node); private static readonly Func OperatorTokenAccessor; public SyntaxToken OperatorToken => (SyntaxToken)OperatorTokenAccessor(this.node); private static readonly Func RightAccessor; public PatternSyntaxWrapper Right => (PatternSyntaxWrapper)RightAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator BinaryPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new BinaryPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(BinaryPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(BinaryPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator BinaryPatternSyntaxWrapper(PatternSyntaxWrapper down) => (BinaryPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(BinaryPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator BinaryPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (BinaryPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BlockSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class BlockSyntaxShimExtensions { private static readonly Type WrappedType = typeof(BlockSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(BlockSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BranchKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum BranchKind : System.Int32 { None = 0, Continue = 1, Break = 2, GoTo = 3, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#BreakStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class BreakStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(BreakStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(BreakStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CaseKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum CaseKind : System.Int32 { None = 0, SingleValue = 1, Relational = 2, Range = 3, Default = 4, Pattern = 5, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CasePatternSwitchLabelSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct CasePatternSwitchLabelSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax"; private static readonly Type WrappedType; private readonly SwitchLabelSyntax node; static CasePatternSwitchLabelSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(CasePatternSwitchLabelSyntaxWrapper)); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); WhenClauseAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "WhenClause"); } private CasePatternSwitchLabelSyntaxWrapper(SwitchLabelSyntax node) => this.node = node; public SwitchLabelSyntax Node => this.node; [Obsolete("Use Node instead")] public SwitchLabelSyntax SyntaxNode => this.node; public SyntaxToken Keyword => this.node.Keyword; private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); private static readonly Func WhenClauseAccessor; public WhenClauseSyntaxWrapper WhenClause => (WhenClauseSyntaxWrapper)WhenClauseAccessor(this.node); public SyntaxToken ColonToken => this.node.ColonToken; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator CasePatternSwitchLabelSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new CasePatternSwitchLabelSyntaxWrapper((SwitchLabelSyntax)node); } public static implicit operator SwitchLabelSyntax(CasePatternSwitchLabelSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CheckedStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class CheckedStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(CheckedStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(CheckedStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ClassDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ClassDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ClassDeclarationSyntax); private static readonly Func ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); extension(ClassDeclarationSyntax @this) { public ParameterListSyntax ParameterList => ParameterListAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ClassOrStructConstraintSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ClassOrStructConstraintSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ClassOrStructConstraintSyntax); private static readonly Func QuestionTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "QuestionToken"); extension(ClassOrStructConstraintSyntax @this) { public SyntaxToken QuestionToken => (SyntaxToken)QuestionTokenAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CollectionElementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct CollectionElementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.CollectionElementSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static CollectionElementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(CollectionElementSyntaxWrapper)); } private CollectionElementSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator CollectionElementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new CollectionElementSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(CollectionElementSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CollectionExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct CollectionExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static CollectionExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(CollectionExpressionSyntaxWrapper)); OpenBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBracketToken"); ElementsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Elements)); CloseBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBracketToken"); } private CollectionExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func OpenBracketTokenAccessor; public SyntaxToken OpenBracketToken => (SyntaxToken)OpenBracketTokenAccessor(this.node); private static readonly Func> ElementsAccessor; public SeparatedSyntaxListWrapper Elements => ElementsAccessor(this.node); private static readonly Func CloseBracketTokenAccessor; public SyntaxToken CloseBracketToken => (SyntaxToken)CloseBracketTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator CollectionExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new CollectionExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(CollectionExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CommonForEachStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct CommonForEachStatementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.CommonForEachStatementSyntax"; private static readonly Type WrappedType; private readonly StatementSyntax node; static CommonForEachStatementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(CommonForEachStatementSyntaxWrapper)); AwaitKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AwaitKeyword"); ForEachKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ForEachKeyword"); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); InKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "InKeyword"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); StatementAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Statement"); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); } private CommonForEachStatementSyntaxWrapper(StatementSyntax node) => this.node = node; public StatementSyntax Node => this.node; [Obsolete("Use Node instead")] public StatementSyntax SyntaxNode => this.node; private static readonly Func AwaitKeywordAccessor; public SyntaxToken AwaitKeyword => (SyntaxToken)AwaitKeywordAccessor(this.node); private static readonly Func ForEachKeywordAccessor; public SyntaxToken ForEachKeyword => (SyntaxToken)ForEachKeywordAccessor(this.node); private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func InKeywordAccessor; public SyntaxToken InKeyword => (SyntaxToken)InKeywordAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); private static readonly Func StatementAccessor; public StatementSyntax Statement => StatementAccessor(this.node); private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator CommonForEachStatementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new CommonForEachStatementSyntaxWrapper((StatementSyntax)node); } public static implicit operator StatementSyntax(CommonForEachStatementSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ConstantPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ConstantPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ConstantPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ConstantPatternSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private ConstantPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ConstantPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ConstantPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ConstantPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(ConstantPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ConstantPatternSyntaxWrapper(PatternSyntaxWrapper down) => (ConstantPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(ConstantPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ConstantPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (ConstantPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ConstructorDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ConstructorDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ConstructorDeclarationSyntax); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(ConstructorDeclarationSyntax @this) { public ArrowExpressionClauseSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ContinueStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ContinueStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ContinueStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(ContinueStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ControlFlowBranchSemantics.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum ControlFlowBranchSemantics : System.Int32 { None = 0, Regular = 1, Return = 2, StructuredExceptionHandling = 3, ProgramTermination = 4, Throw = 5, Rethrow = 6, Error = 7, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ControlFlowConditionKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum ControlFlowConditionKind : System.Int32 { None = 0, WhenFalse = 1, WhenTrue = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ControlFlowRegionKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum ControlFlowRegionKind : System.Int32 { Root = 0, LocalLifetime = 1, Try = 2, Filter = 3, Catch = 4, FilterAndHandler = 5, TryAndCatch = 6, Finally = 7, TryAndFinally = 8, StaticLocalInitializer = 9, ErroneousBody = 10, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ConversionOperatorDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ConversionOperatorDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ConversionOperatorDeclarationSyntax); private static readonly Func ExplicitInterfaceSpecifierAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExplicitInterfaceSpecifier"); private static readonly Func CheckedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CheckedKeyword"); extension(ConversionOperatorDeclarationSyntax @this) { public ExplicitInterfaceSpecifierSyntax ExplicitInterfaceSpecifier => ExplicitInterfaceSpecifierAccessor(@this); public SyntaxToken CheckedKeyword => (SyntaxToken)CheckedKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ConversionOperatorMemberCrefSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ConversionOperatorMemberCrefSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ConversionOperatorMemberCrefSyntax); private static readonly Func CheckedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CheckedKeyword"); extension(ConversionOperatorMemberCrefSyntax @this) { public SyntaxToken CheckedKeyword => (SyntaxToken)CheckedKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#CrefParameterSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class CrefParameterSyntaxShimExtensions { private static readonly Type WrappedType = typeof(CrefParameterSyntax); private static readonly Func RefKindKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RefKindKeyword"); private static readonly Func ReadOnlyKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ReadOnlyKeyword"); extension(CrefParameterSyntax @this) { public SyntaxToken RefKindKeyword => (SyntaxToken)RefKindKeywordAccessor(@this); public SyntaxToken ReadOnlyKeyword => (SyntaxToken)ReadOnlyKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DeclarationExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct DeclarationExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static DeclarationExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(DeclarationExpressionSyntaxWrapper)); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); DesignationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Designation"); } private DeclarationExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); private static readonly Func DesignationAccessor; public VariableDesignationSyntaxWrapper Designation => (VariableDesignationSyntaxWrapper)DesignationAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator DeclarationExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new DeclarationExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(DeclarationExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DeclarationPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct DeclarationPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static DeclarationPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(DeclarationPatternSyntaxWrapper)); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); DesignationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Designation"); } private DeclarationPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); private static readonly Func DesignationAccessor; public VariableDesignationSyntaxWrapper Designation => (VariableDesignationSyntaxWrapper)DesignationAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator DeclarationPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new DeclarationPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(DeclarationPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(DeclarationPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator DeclarationPatternSyntaxWrapper(PatternSyntaxWrapper down) => (DeclarationPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(DeclarationPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator DeclarationPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (DeclarationPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DefaultConstraintSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct DefaultConstraintSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.DefaultConstraintSyntax"; private static readonly Type WrappedType; private readonly TypeParameterConstraintSyntax node; static DefaultConstraintSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(DefaultConstraintSyntaxWrapper)); DefaultKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "DefaultKeyword"); } private DefaultConstraintSyntaxWrapper(TypeParameterConstraintSyntax node) => this.node = node; public TypeParameterConstraintSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeParameterConstraintSyntax SyntaxNode => this.node; private static readonly Func DefaultKeywordAccessor; public SyntaxToken DefaultKeyword => (SyntaxToken)DefaultKeywordAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator DefaultConstraintSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new DefaultConstraintSyntaxWrapper((TypeParameterConstraintSyntax)node); } public static implicit operator TypeParameterConstraintSyntax(DefaultConstraintSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DestructorDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class DestructorDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(DestructorDeclarationSyntax); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(DestructorDeclarationSyntax @this) { public ArrowExpressionClauseSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DiscardDesignationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct DiscardDesignationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.DiscardDesignationSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static DiscardDesignationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(DiscardDesignationSyntaxWrapper)); UnderscoreTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "UnderscoreToken"); } private DiscardDesignationSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func UnderscoreTokenAccessor; public SyntaxToken UnderscoreToken => (SyntaxToken)UnderscoreTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator DiscardDesignationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new DiscardDesignationSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(DiscardDesignationSyntaxWrapper wrapper) => wrapper.node; public static implicit operator VariableDesignationSyntaxWrapper(DiscardDesignationSyntaxWrapper up) => (VariableDesignationSyntaxWrapper)up.SyntaxNode; public static explicit operator DiscardDesignationSyntaxWrapper(VariableDesignationSyntaxWrapper down) => (DiscardDesignationSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DiscardPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct DiscardPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static DiscardPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(DiscardPatternSyntaxWrapper)); UnderscoreTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "UnderscoreToken"); } private DiscardPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func UnderscoreTokenAccessor; public SyntaxToken UnderscoreToken => (SyntaxToken)UnderscoreTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator DiscardPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new DiscardPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(DiscardPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(DiscardPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator DiscardPatternSyntaxWrapper(PatternSyntaxWrapper down) => (DiscardPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(DiscardPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator DiscardPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (DiscardPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#DoStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class DoStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(DoStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(DoStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#EmptyStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class EmptyStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(EmptyStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(EmptyStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#EnumMemberDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class EnumMemberDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(EnumMemberDeclarationSyntax); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); extension(EnumMemberDeclarationSyntax @this) { public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#EventDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class EventDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(EventDeclarationSyntax); private static readonly Func SemicolonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SemicolonToken"); extension(EventDeclarationSyntax @this) { public SyntaxToken SemicolonToken => (SyntaxToken)SemicolonTokenAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExpressionColonSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ExpressionColonSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ExpressionColonSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ExpressionColonSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); ColonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ColonToken"); } private ExpressionColonSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func ColonTokenAccessor; public SyntaxToken ColonToken => (SyntaxToken)ColonTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ExpressionColonSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ExpressionColonSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ExpressionColonSyntaxWrapper wrapper) => wrapper.node; public static implicit operator BaseExpressionColonSyntaxWrapper(ExpressionColonSyntaxWrapper up) => (BaseExpressionColonSyntaxWrapper)up.SyntaxNode; public static explicit operator ExpressionColonSyntaxWrapper(BaseExpressionColonSyntaxWrapper down) => (ExpressionColonSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExpressionElementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ExpressionElementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ExpressionElementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ExpressionElementSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private ExpressionElementSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ExpressionElementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ExpressionElementSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ExpressionElementSyntaxWrapper wrapper) => wrapper.node; public static implicit operator CollectionElementSyntaxWrapper(ExpressionElementSyntaxWrapper up) => (CollectionElementSyntaxWrapper)up.SyntaxNode; public static explicit operator ExpressionElementSyntaxWrapper(CollectionElementSyntaxWrapper down) => (ExpressionElementSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExpressionOrPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ExpressionOrPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionOrPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ExpressionOrPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ExpressionOrPatternSyntaxWrapper)); } private ExpressionOrPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ExpressionOrPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ExpressionOrPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ExpressionOrPatternSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExpressionStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ExpressionStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ExpressionStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(ExpressionStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExtensionBlockDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ExtensionBlockDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionBlockDeclarationSyntax"; private static readonly Type WrappedType; private readonly TypeDeclarationSyntax node; static ExtensionBlockDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ExtensionBlockDeclarationSyntaxWrapper)); ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); } private ExtensionBlockDeclarationSyntaxWrapper(TypeDeclarationSyntax node) => this.node = node; public TypeDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeDeclarationSyntax SyntaxNode => this.node; public SyntaxToken Identifier => this.node.Identifier; public BaseListSyntax BaseList => this.node.BaseList; public SyntaxList AttributeLists => this.node.AttributeLists; public SyntaxTokenList Modifiers => this.node.Modifiers; public SyntaxToken Keyword => this.node.Keyword; public TypeParameterListSyntax TypeParameterList => this.node.TypeParameterList; private static readonly Func ParameterListAccessor; public ParameterListSyntax ParameterList => ParameterListAccessor(this.node); public SyntaxList ConstraintClauses => this.node.ConstraintClauses; public SyntaxToken OpenBraceToken => this.node.OpenBraceToken; public SyntaxList Members => this.node.Members; public SyntaxToken CloseBraceToken => this.node.CloseBraceToken; public SyntaxToken SemicolonToken => this.node.SemicolonToken; public Int32 Arity => this.node.Arity; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ExtensionBlockDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ExtensionBlockDeclarationSyntaxWrapper((TypeDeclarationSyntax)node); } public static implicit operator TypeDeclarationSyntax(ExtensionBlockDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ExtensionMemberCrefSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ExtensionMemberCrefSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ExtensionMemberCrefSyntax"; private static readonly Type WrappedType; private readonly MemberCrefSyntax node; static ExtensionMemberCrefSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ExtensionMemberCrefSyntaxWrapper)); ExtensionKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExtensionKeyword"); TypeArgumentListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "TypeArgumentList"); ParametersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Parameters"); DotTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "DotToken"); MemberAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Member"); } private ExtensionMemberCrefSyntaxWrapper(MemberCrefSyntax node) => this.node = node; public MemberCrefSyntax Node => this.node; [Obsolete("Use Node instead")] public MemberCrefSyntax SyntaxNode => this.node; private static readonly Func ExtensionKeywordAccessor; public SyntaxToken ExtensionKeyword => (SyntaxToken)ExtensionKeywordAccessor(this.node); private static readonly Func TypeArgumentListAccessor; public TypeArgumentListSyntax TypeArgumentList => TypeArgumentListAccessor(this.node); private static readonly Func ParametersAccessor; public CrefParameterListSyntax Parameters => ParametersAccessor(this.node); private static readonly Func DotTokenAccessor; public SyntaxToken DotToken => (SyntaxToken)DotTokenAccessor(this.node); private static readonly Func MemberAccessor; public MemberCrefSyntax Member => MemberAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ExtensionMemberCrefSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ExtensionMemberCrefSyntaxWrapper((MemberCrefSyntax)node); } public static implicit operator MemberCrefSyntax(ExtensionMemberCrefSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FieldExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FieldExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static FieldExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FieldExpressionSyntaxWrapper)); TokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Token"); } private FieldExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func TokenAccessor; public SyntaxToken Token => (SyntaxToken)TokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FieldExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FieldExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(FieldExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FileScopedNamespaceDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FileScopedNamespaceDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FileScopedNamespaceDeclarationSyntax"; private static readonly Type WrappedType; private readonly MemberDeclarationSyntax node; static FileScopedNamespaceDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FileScopedNamespaceDeclarationSyntaxWrapper)); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); NamespaceKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NamespaceKeyword"); NameAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Name"); SemicolonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SemicolonToken"); ExternsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Externs"); UsingsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Usings"); MembersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Members"); } private FileScopedNamespaceDeclarationSyntaxWrapper(MemberDeclarationSyntax node) => this.node = node; public MemberDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public MemberDeclarationSyntax SyntaxNode => this.node; private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func ModifiersAccessor; public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(this.node); private static readonly Func NamespaceKeywordAccessor; public SyntaxToken NamespaceKeyword => (SyntaxToken)NamespaceKeywordAccessor(this.node); private static readonly Func NameAccessor; public NameSyntax Name => NameAccessor(this.node); private static readonly Func SemicolonTokenAccessor; public SyntaxToken SemicolonToken => (SyntaxToken)SemicolonTokenAccessor(this.node); private static readonly Func> ExternsAccessor; public SyntaxList Externs => (SyntaxList)ExternsAccessor(this.node); private static readonly Func> UsingsAccessor; public SyntaxList Usings => (SyntaxList)UsingsAccessor(this.node); private static readonly Func> MembersAccessor; public SyntaxList Members => (SyntaxList)MembersAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FileScopedNamespaceDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FileScopedNamespaceDeclarationSyntaxWrapper((MemberDeclarationSyntax)node); } public static implicit operator MemberDeclarationSyntax(FileScopedNamespaceDeclarationSyntaxWrapper wrapper) => wrapper.node; public static implicit operator BaseNamespaceDeclarationSyntaxWrapper(FileScopedNamespaceDeclarationSyntaxWrapper up) => (BaseNamespaceDeclarationSyntaxWrapper)up.SyntaxNode; public static explicit operator FileScopedNamespaceDeclarationSyntaxWrapper(BaseNamespaceDeclarationSyntaxWrapper down) => (FileScopedNamespaceDeclarationSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FixedStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class FixedStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(FixedStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(FixedStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ForEachStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ForEachStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ForEachStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func AwaitKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AwaitKeyword"); extension(ForEachStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxToken AwaitKeyword => (SyntaxToken)AwaitKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ForEachVariableStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ForEachVariableStatementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ForEachVariableStatementSyntax"; private static readonly Type WrappedType; private readonly StatementSyntax node; static ForEachVariableStatementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ForEachVariableStatementSyntaxWrapper)); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); AwaitKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AwaitKeyword"); ForEachKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ForEachKeyword"); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); VariableAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Variable"); InKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "InKeyword"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); StatementAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Statement"); } private ForEachVariableStatementSyntaxWrapper(StatementSyntax node) => this.node = node; public StatementSyntax Node => this.node; [Obsolete("Use Node instead")] public StatementSyntax SyntaxNode => this.node; private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func AwaitKeywordAccessor; public SyntaxToken AwaitKeyword => (SyntaxToken)AwaitKeywordAccessor(this.node); private static readonly Func ForEachKeywordAccessor; public SyntaxToken ForEachKeyword => (SyntaxToken)ForEachKeywordAccessor(this.node); private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func VariableAccessor; public ExpressionSyntax Variable => VariableAccessor(this.node); private static readonly Func InKeywordAccessor; public SyntaxToken InKeyword => (SyntaxToken)InKeywordAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); private static readonly Func StatementAccessor; public StatementSyntax Statement => StatementAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ForEachVariableStatementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ForEachVariableStatementSyntaxWrapper((StatementSyntax)node); } public static implicit operator StatementSyntax(ForEachVariableStatementSyntaxWrapper wrapper) => wrapper.node; public static implicit operator CommonForEachStatementSyntaxWrapper(ForEachVariableStatementSyntaxWrapper up) => (CommonForEachStatementSyntaxWrapper)up.SyntaxNode; public static explicit operator ForEachVariableStatementSyntaxWrapper(CommonForEachStatementSyntaxWrapper down) => (ForEachVariableStatementSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ForStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ForStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ForStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(ForStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerCallingConventionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerCallingConventionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerCallingConventionSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static FunctionPointerCallingConventionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerCallingConventionSyntaxWrapper)); ManagedOrUnmanagedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ManagedOrUnmanagedKeyword"); UnmanagedCallingConventionListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "UnmanagedCallingConventionList"); } private FunctionPointerCallingConventionSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func ManagedOrUnmanagedKeywordAccessor; public SyntaxToken ManagedOrUnmanagedKeyword => (SyntaxToken)ManagedOrUnmanagedKeywordAccessor(this.node); private static readonly Func UnmanagedCallingConventionListAccessor; public FunctionPointerUnmanagedCallingConventionListSyntaxWrapper UnmanagedCallingConventionList => (FunctionPointerUnmanagedCallingConventionListSyntaxWrapper)UnmanagedCallingConventionListAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerCallingConventionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerCallingConventionSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(FunctionPointerCallingConventionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerParameterListSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerParameterListSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerParameterListSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static FunctionPointerParameterListSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerParameterListSyntaxWrapper)); LessThanTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "LessThanToken"); ParametersAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Parameters)); GreaterThanTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "GreaterThanToken"); } private FunctionPointerParameterListSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func LessThanTokenAccessor; public SyntaxToken LessThanToken => (SyntaxToken)LessThanTokenAccessor(this.node); private static readonly Func> ParametersAccessor; public SeparatedSyntaxListWrapper Parameters => ParametersAccessor(this.node); private static readonly Func GreaterThanTokenAccessor; public SyntaxToken GreaterThanToken => (SyntaxToken)GreaterThanTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerParameterListSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerParameterListSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(FunctionPointerParameterListSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerParameterSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerParameterSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerParameterSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static FunctionPointerParameterSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerParameterSyntaxWrapper)); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); } private FunctionPointerParameterSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func ModifiersAccessor; public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(this.node); private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerParameterSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerParameterSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(FunctionPointerParameterSyntaxWrapper wrapper) => wrapper.node; public static implicit operator BaseParameterSyntaxWrapper(FunctionPointerParameterSyntaxWrapper up) => (BaseParameterSyntaxWrapper)up.SyntaxNode; public static explicit operator FunctionPointerParameterSyntaxWrapper(BaseParameterSyntaxWrapper down) => (FunctionPointerParameterSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerTypeSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerTypeSyntax"; private static readonly Type WrappedType; private readonly TypeSyntax node; static FunctionPointerTypeSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerTypeSyntaxWrapper)); DelegateKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "DelegateKeyword"); AsteriskTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AsteriskToken"); CallingConventionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CallingConvention"); ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); } private FunctionPointerTypeSyntaxWrapper(TypeSyntax node) => this.node = node; public TypeSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeSyntax SyntaxNode => this.node; private static readonly Func DelegateKeywordAccessor; public SyntaxToken DelegateKeyword => (SyntaxToken)DelegateKeywordAccessor(this.node); private static readonly Func AsteriskTokenAccessor; public SyntaxToken AsteriskToken => (SyntaxToken)AsteriskTokenAccessor(this.node); private static readonly Func CallingConventionAccessor; public FunctionPointerCallingConventionSyntaxWrapper CallingConvention => (FunctionPointerCallingConventionSyntaxWrapper)CallingConventionAccessor(this.node); private static readonly Func ParameterListAccessor; public FunctionPointerParameterListSyntaxWrapper ParameterList => (FunctionPointerParameterListSyntaxWrapper)ParameterListAccessor(this.node); public Boolean IsVar => this.node.IsVar; private static readonly Func IsUnmanagedAccessor; public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(this.node); private static readonly Func IsNotNullAccessor; public Boolean IsNotNull => (Boolean)IsNotNullAccessor(this.node); private static readonly Func IsNintAccessor; public Boolean IsNint => (Boolean)IsNintAccessor(this.node); private static readonly Func IsNuintAccessor; public Boolean IsNuint => (Boolean)IsNuintAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerTypeSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerTypeSyntaxWrapper((TypeSyntax)node); } public static implicit operator TypeSyntax(FunctionPointerTypeSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerUnmanagedCallingConventionListSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerUnmanagedCallingConventionListSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerUnmanagedCallingConventionListSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static FunctionPointerUnmanagedCallingConventionListSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerUnmanagedCallingConventionListSyntaxWrapper)); OpenBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBracketToken"); CallingConventionsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(CallingConventions)); CloseBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBracketToken"); } private FunctionPointerUnmanagedCallingConventionListSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenBracketTokenAccessor; public SyntaxToken OpenBracketToken => (SyntaxToken)OpenBracketTokenAccessor(this.node); private static readonly Func> CallingConventionsAccessor; public SeparatedSyntaxListWrapper CallingConventions => CallingConventionsAccessor(this.node); private static readonly Func CloseBracketTokenAccessor; public SyntaxToken CloseBracketToken => (SyntaxToken)CloseBracketTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerUnmanagedCallingConventionListSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerUnmanagedCallingConventionListSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(FunctionPointerUnmanagedCallingConventionListSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#FunctionPointerUnmanagedCallingConventionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct FunctionPointerUnmanagedCallingConventionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.FunctionPointerUnmanagedCallingConventionSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static FunctionPointerUnmanagedCallingConventionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(FunctionPointerUnmanagedCallingConventionSyntaxWrapper)); NameAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Name"); } private FunctionPointerUnmanagedCallingConventionSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func NameAccessor; public SyntaxToken Name => (SyntaxToken)NameAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator FunctionPointerUnmanagedCallingConventionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new FunctionPointerUnmanagedCallingConventionSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(FunctionPointerUnmanagedCallingConventionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#GeneratedKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum GeneratedKind : System.Int32 { Unknown = 0, NotGenerated = 1, MarkedGenerated = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#GenericNameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class GenericNameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(GenericNameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(GenericNameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#GlobalStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class GlobalStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(GlobalStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); extension(GlobalStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#GotoStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class GotoStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(GotoStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(GotoStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAddressOfOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAddressOfOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAnonymousFunctionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAnonymousFunctionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAnonymousObjectCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAnonymousObjectCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IArgumentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IArgumentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IArrayCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IArrayCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IArrayElementReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IArrayElementReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IArrayInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IArrayInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAttributeOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAttributeOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IAwaitOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IAwaitOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IBinaryOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IBinaryOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IBinaryPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IBinaryPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IBlockOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IBlockOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IBranchOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IBranchOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICatchClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICatchClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICaughtExceptionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICaughtExceptionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICoalesceAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICoalesceAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICoalesceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICoalesceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICollectionElementInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICollectionElementInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICollectionExpressionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICollectionExpressionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ICompoundAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ICompoundAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConditionalAccessInstanceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConditionalAccessInstanceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConditionalAccessOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConditionalAccessOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConditionalOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConditionalOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConstantPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConstantPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConstructorBodyOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConstructorBodyOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IConversionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IConversionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDeclarationExpressionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDeclarationExpressionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDeclarationPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDeclarationPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDeconstructionAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDeconstructionAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDefaultCaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDefaultCaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDefaultValueOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDefaultValueOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDelegateCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDelegateCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDiscardOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDiscardOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDiscardPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDiscardPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDynamicIndexerAccessOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDynamicIndexerAccessOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDynamicInvocationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDynamicInvocationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDynamicMemberReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDynamicMemberReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IDynamicObjectCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IDynamicObjectCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IEmptyOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IEmptyOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IEndOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IEndOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IEventAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IEventAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IEventReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IEventReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IExpressionStatementOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IExpressionStatementOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFieldInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFieldInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFieldReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFieldReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFlowAnonymousFunctionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFlowAnonymousFunctionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFlowCaptureOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFlowCaptureOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFlowCaptureReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFlowCaptureReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IForEachLoopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IForEachLoopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IForLoopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IForLoopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IForToLoopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IForToLoopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IFunctionPointerInvocationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IFunctionPointerInvocationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IImplicitIndexerReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IImplicitIndexerReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IIncrementOrDecrementOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IIncrementOrDecrementOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInlineArrayAccessOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInlineArrayAccessOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInstanceReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInstanceReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringAdditionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringAdditionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringAppendOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringAppendOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringContentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringContentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringHandlerArgumentPlaceholderOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringHandlerArgumentPlaceholderOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringHandlerCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringHandlerCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolatedStringTextOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolatedStringTextOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInterpolationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInterpolationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInvalidOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInvalidOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IInvocationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IInvocationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IIsNullOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IIsNullOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IIsPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IIsPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IIsTypeOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IIsTypeOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILabeledOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILabeledOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IListPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IListPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILiteralOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILiteralOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILocalFunctionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILocalFunctionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILocalReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILocalReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILockOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILockOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ILoopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ILoopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IMemberInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IMemberInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IMemberReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IMemberReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IMethodBodyBaseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IMethodBodyBaseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IMethodBodyOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IMethodBodyOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IMethodReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IMethodReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#INameOfOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // INameOfOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#INegatedPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // INegatedPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IObjectCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IObjectCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IObjectOrCollectionInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IObjectOrCollectionInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IOmittedArgumentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IOmittedArgumentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IParameterInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IParameterInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IParameterReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IParameterReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IParenthesizedOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IParenthesizedOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IPatternCaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IPatternCaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IPropertyInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IPropertyInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IPropertyReferenceOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IPropertyReferenceOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IPropertySubpatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IPropertySubpatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRaiseEventOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRaiseEventOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRangeCaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRangeCaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRangeOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRangeOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IReDimClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IReDimClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IReDimOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IReDimOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRecursivePatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRecursivePatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRelationalCaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRelationalCaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IRelationalPatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IRelationalPatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IReturnOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IReturnOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISimpleAssignmentOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISimpleAssignmentOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISingleValueCaseClauseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISingleValueCaseClauseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISizeOfOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISizeOfOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISlicePatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISlicePatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISpreadOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISpreadOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IStaticLocalInitializationSemaphoreOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IStaticLocalInitializationSemaphoreOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IStopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IStopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISwitchCaseOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISwitchCaseOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISwitchExpressionArmOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISwitchExpressionArmOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISwitchExpressionOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISwitchExpressionOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISwitchOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISwitchOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ISymbolInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ISymbolInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IThrowOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IThrowOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITranslatedQueryOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITranslatedQueryOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITryOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITryOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITupleBinaryOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITupleBinaryOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITupleOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITupleOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITypeOfOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITypeOfOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITypeParameterObjectCreationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITypeParameterObjectCreationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ITypePatternOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // ITypePatternOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IUnaryOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IUnaryOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IUsingDeclarationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IUsingDeclarationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IUsingOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IUsingOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IUtf8StringOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IUtf8StringOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IVariableDeclarationGroupOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IVariableDeclarationGroupOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IVariableDeclarationOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IVariableDeclarationOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IVariableDeclaratorOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IVariableDeclaratorOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IVariableInitializerOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IVariableInitializerOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IWhileLoopOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IWhileLoopOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IWithOperation.g.cs.verified.cs ================================================ namespace SonarAnalyzer.ShimLayer; // IWithOperation ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IdentifierNameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class IdentifierNameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(IdentifierNameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(IdentifierNameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IfStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class IfStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(IfStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(IfStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IgnoredDirectiveTriviaSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct IgnoredDirectiveTriviaSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.IgnoredDirectiveTriviaSyntax"; private static readonly Type WrappedType; private readonly DirectiveTriviaSyntax node; static IgnoredDirectiveTriviaSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(IgnoredDirectiveTriviaSyntaxWrapper)); ColonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ColonToken"); ContentAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Content"); } private IgnoredDirectiveTriviaSyntaxWrapper(DirectiveTriviaSyntax node) => this.node = node; public DirectiveTriviaSyntax Node => this.node; [Obsolete("Use Node instead")] public DirectiveTriviaSyntax SyntaxNode => this.node; public SyntaxToken HashToken => this.node.HashToken; private static readonly Func ColonTokenAccessor; public SyntaxToken ColonToken => (SyntaxToken)ColonTokenAccessor(this.node); private static readonly Func ContentAccessor; public SyntaxToken Content => (SyntaxToken)ContentAccessor(this.node); public SyntaxToken EndOfDirectiveToken => this.node.EndOfDirectiveToken; public Boolean IsActive => this.node.IsActive; public SyntaxToken DirectiveNameToken => this.node.DirectiveNameToken; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator IgnoredDirectiveTriviaSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new IgnoredDirectiveTriviaSyntaxWrapper((DirectiveTriviaSyntax)node); } public static implicit operator DirectiveTriviaSyntax(IgnoredDirectiveTriviaSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ImplicitObjectCreationExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ImplicitObjectCreationExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitObjectCreationExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static ImplicitObjectCreationExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ImplicitObjectCreationExpressionSyntaxWrapper)); NewKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NewKeyword"); ArgumentListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ArgumentList"); InitializerAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Initializer"); } private ImplicitObjectCreationExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func NewKeywordAccessor; public SyntaxToken NewKeyword => (SyntaxToken)NewKeywordAccessor(this.node); private static readonly Func ArgumentListAccessor; public ArgumentListSyntax ArgumentList => ArgumentListAccessor(this.node); private static readonly Func InitializerAccessor; public InitializerExpressionSyntax Initializer => InitializerAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ImplicitObjectCreationExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ImplicitObjectCreationExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(ImplicitObjectCreationExpressionSyntaxWrapper wrapper) => wrapper.node; public static implicit operator BaseObjectCreationExpressionSyntaxWrapper(ImplicitObjectCreationExpressionSyntaxWrapper up) => (BaseObjectCreationExpressionSyntaxWrapper)up.SyntaxNode; public static explicit operator ImplicitObjectCreationExpressionSyntaxWrapper(BaseObjectCreationExpressionSyntaxWrapper down) => (ImplicitObjectCreationExpressionSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ImplicitStackAllocArrayCreationExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ImplicitStackAllocArrayCreationExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ImplicitStackAllocArrayCreationExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static ImplicitStackAllocArrayCreationExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ImplicitStackAllocArrayCreationExpressionSyntaxWrapper)); StackAllocKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "StackAllocKeyword"); OpenBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBracketToken"); CloseBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBracketToken"); InitializerAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Initializer"); } private ImplicitStackAllocArrayCreationExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func StackAllocKeywordAccessor; public SyntaxToken StackAllocKeyword => (SyntaxToken)StackAllocKeywordAccessor(this.node); private static readonly Func OpenBracketTokenAccessor; public SyntaxToken OpenBracketToken => (SyntaxToken)OpenBracketTokenAccessor(this.node); private static readonly Func CloseBracketTokenAccessor; public SyntaxToken CloseBracketToken => (SyntaxToken)CloseBracketTokenAccessor(this.node); private static readonly Func InitializerAccessor; public InitializerExpressionSyntax Initializer => InitializerAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ImplicitStackAllocArrayCreationExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ImplicitStackAllocArrayCreationExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(ImplicitStackAllocArrayCreationExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IncrementalGeneratorOutputKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; [System.FlagsAttribute] public enum IncrementalGeneratorOutputKind : System.Int32 { None = 0, Source = 1, PostInit = 2, Implementation = 4, Host = 8, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IncrementalStepRunReason.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum IncrementalStepRunReason : System.Int32 { New = 0, Modified = 1, Unchanged = 2, Cached = 3, Removed = 4, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#InstanceReferenceKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum InstanceReferenceKind : System.Int32 { ContainingTypeInstance = 0, ImplicitReceiver = 1, PatternInput = 2, InterpolatedStringHandler = 3, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#InstrumentationKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum InstrumentationKind : System.Int32 { None = 0, TestCoverage = 1, StackOverflowProbing = 2, ModuleCancellation = 3, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#InterfaceDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class InterfaceDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(InterfaceDeclarationSyntax); private static readonly Func ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); extension(InterfaceDeclarationSyntax @this) { public ParameterListSyntax ParameterList => ParameterListAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#InterpolatedStringArgumentPlaceholderKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum InterpolatedStringArgumentPlaceholderKind : System.Int32 { CallsiteArgument = 0, CallsiteReceiver = 1, TrailingValidityArgument = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#IsPatternExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct IsPatternExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static IsPatternExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(IsPatternExpressionSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); IsKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsKeyword"); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); } private IsPatternExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func IsKeywordAccessor; public SyntaxToken IsKeyword => (SyntaxToken)IsKeywordAccessor(this.node); private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator IsPatternExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new IsPatternExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(IsPatternExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LabeledStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class LabeledStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(LabeledStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(LabeledStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LambdaExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class LambdaExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(LambdaExpressionSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); private static readonly Func BlockAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Block"); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(LambdaExpressionSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); public BlockSyntax Block => BlockAccessor(@this); public ExpressionSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LanguageVersion.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class LanguageVersionEx { public const LanguageVersion CSharp7 = (LanguageVersion)7; public const LanguageVersion CSharp7_1 = (LanguageVersion)701; public const LanguageVersion CSharp7_2 = (LanguageVersion)702; public const LanguageVersion CSharp7_3 = (LanguageVersion)703; public const LanguageVersion CSharp8 = (LanguageVersion)800; public const LanguageVersion CSharp9 = (LanguageVersion)900; public const LanguageVersion CSharp10 = (LanguageVersion)1000; public const LanguageVersion CSharp11 = (LanguageVersion)1100; public const LanguageVersion CSharp12 = (LanguageVersion)1200; public const LanguageVersion CSharp13 = (LanguageVersion)1300; public const LanguageVersion CSharp14 = (LanguageVersion)1400; public const LanguageVersion LatestMajor = (LanguageVersion)2147483645; public const LanguageVersion Preview = (LanguageVersion)2147483646; public const LanguageVersion Latest = (LanguageVersion)2147483647; public const LanguageVersion Default = (LanguageVersion)0; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LineDirectivePositionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct LineDirectivePositionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.LineDirectivePositionSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static LineDirectivePositionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(LineDirectivePositionSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); LineAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Line"); CommaTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CommaToken"); CharacterAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Character"); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); } private LineDirectivePositionSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func LineAccessor; public SyntaxToken Line => (SyntaxToken)LineAccessor(this.node); private static readonly Func CommaTokenAccessor; public SyntaxToken CommaToken => (SyntaxToken)CommaTokenAccessor(this.node); private static readonly Func CharacterAccessor; public SyntaxToken Character => (SyntaxToken)CharacterAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator LineDirectivePositionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new LineDirectivePositionSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(LineDirectivePositionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LineOrSpanDirectiveTriviaSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct LineOrSpanDirectiveTriviaSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.LineOrSpanDirectiveTriviaSyntax"; private static readonly Type WrappedType; private readonly DirectiveTriviaSyntax node; static LineOrSpanDirectiveTriviaSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(LineOrSpanDirectiveTriviaSyntaxWrapper)); LineKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "LineKeyword"); FileAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "File"); } private LineOrSpanDirectiveTriviaSyntaxWrapper(DirectiveTriviaSyntax node) => this.node = node; public DirectiveTriviaSyntax Node => this.node; [Obsolete("Use Node instead")] public DirectiveTriviaSyntax SyntaxNode => this.node; private static readonly Func LineKeywordAccessor; public SyntaxToken LineKeyword => (SyntaxToken)LineKeywordAccessor(this.node); private static readonly Func FileAccessor; public SyntaxToken File => (SyntaxToken)FileAccessor(this.node); public SyntaxToken DirectiveNameToken => this.node.DirectiveNameToken; public SyntaxToken HashToken => this.node.HashToken; public SyntaxToken EndOfDirectiveToken => this.node.EndOfDirectiveToken; public Boolean IsActive => this.node.IsActive; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator LineOrSpanDirectiveTriviaSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new LineOrSpanDirectiveTriviaSyntaxWrapper((DirectiveTriviaSyntax)node); } public static implicit operator DirectiveTriviaSyntax(LineOrSpanDirectiveTriviaSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LineSpanDirectiveTriviaSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct LineSpanDirectiveTriviaSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.LineSpanDirectiveTriviaSyntax"; private static readonly Type WrappedType; private readonly DirectiveTriviaSyntax node; static LineSpanDirectiveTriviaSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(LineSpanDirectiveTriviaSyntaxWrapper)); LineKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "LineKeyword"); StartAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Start"); MinusTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "MinusToken"); EndAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "End"); CharacterOffsetAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CharacterOffset"); FileAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "File"); } private LineSpanDirectiveTriviaSyntaxWrapper(DirectiveTriviaSyntax node) => this.node = node; public DirectiveTriviaSyntax Node => this.node; [Obsolete("Use Node instead")] public DirectiveTriviaSyntax SyntaxNode => this.node; public SyntaxToken HashToken => this.node.HashToken; private static readonly Func LineKeywordAccessor; public SyntaxToken LineKeyword => (SyntaxToken)LineKeywordAccessor(this.node); private static readonly Func StartAccessor; public LineDirectivePositionSyntaxWrapper Start => (LineDirectivePositionSyntaxWrapper)StartAccessor(this.node); private static readonly Func MinusTokenAccessor; public SyntaxToken MinusToken => (SyntaxToken)MinusTokenAccessor(this.node); private static readonly Func EndAccessor; public LineDirectivePositionSyntaxWrapper End => (LineDirectivePositionSyntaxWrapper)EndAccessor(this.node); private static readonly Func CharacterOffsetAccessor; public SyntaxToken CharacterOffset => (SyntaxToken)CharacterOffsetAccessor(this.node); private static readonly Func FileAccessor; public SyntaxToken File => (SyntaxToken)FileAccessor(this.node); public SyntaxToken EndOfDirectiveToken => this.node.EndOfDirectiveToken; public Boolean IsActive => this.node.IsActive; public SyntaxToken DirectiveNameToken => this.node.DirectiveNameToken; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator LineSpanDirectiveTriviaSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new LineSpanDirectiveTriviaSyntaxWrapper((DirectiveTriviaSyntax)node); } public static implicit operator DirectiveTriviaSyntax(LineSpanDirectiveTriviaSyntaxWrapper wrapper) => wrapper.node; public static implicit operator LineOrSpanDirectiveTriviaSyntaxWrapper(LineSpanDirectiveTriviaSyntaxWrapper up) => (LineOrSpanDirectiveTriviaSyntaxWrapper)up.SyntaxNode; public static explicit operator LineSpanDirectiveTriviaSyntaxWrapper(LineOrSpanDirectiveTriviaSyntaxWrapper down) => (LineSpanDirectiveTriviaSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ListPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ListPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ListPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ListPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ListPatternSyntaxWrapper)); OpenBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBracketToken"); PatternsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Patterns)); CloseBracketTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBracketToken"); DesignationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Designation"); } private ListPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenBracketTokenAccessor; public SyntaxToken OpenBracketToken => (SyntaxToken)OpenBracketTokenAccessor(this.node); private static readonly Func> PatternsAccessor; public SeparatedSyntaxListWrapper Patterns => PatternsAccessor(this.node); private static readonly Func CloseBracketTokenAccessor; public SyntaxToken CloseBracketToken => (SyntaxToken)CloseBracketTokenAccessor(this.node); private static readonly Func DesignationAccessor; public VariableDesignationSyntaxWrapper Designation => (VariableDesignationSyntaxWrapper)DesignationAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ListPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ListPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ListPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(ListPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ListPatternSyntaxWrapper(PatternSyntaxWrapper down) => (ListPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(ListPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ListPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (ListPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LocalDeclarationStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class LocalDeclarationStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(LocalDeclarationStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func AwaitKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AwaitKeyword"); private static readonly Func UsingKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "UsingKeyword"); extension(LocalDeclarationStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxToken AwaitKeyword => (SyntaxToken)AwaitKeywordAccessor(@this); public SyntaxToken UsingKeyword => (SyntaxToken)UsingKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LocalFunctionStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct LocalFunctionStatementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.LocalFunctionStatementSyntax"; private static readonly Type WrappedType; private readonly StatementSyntax node; static LocalFunctionStatementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(LocalFunctionStatementSyntaxWrapper)); AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); ReturnTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ReturnType"); IdentifierAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Identifier"); TypeParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "TypeParameterList"); ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); ConstraintClausesAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "ConstraintClauses"); BodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Body"); ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); SemicolonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SemicolonToken"); } private LocalFunctionStatementSyntaxWrapper(StatementSyntax node) => this.node = node; public StatementSyntax Node => this.node; [Obsolete("Use Node instead")] public StatementSyntax SyntaxNode => this.node; private static readonly Func> AttributeListsAccessor; public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(this.node); private static readonly Func ModifiersAccessor; public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(this.node); private static readonly Func ReturnTypeAccessor; public TypeSyntax ReturnType => ReturnTypeAccessor(this.node); private static readonly Func IdentifierAccessor; public SyntaxToken Identifier => (SyntaxToken)IdentifierAccessor(this.node); private static readonly Func TypeParameterListAccessor; public TypeParameterListSyntax TypeParameterList => TypeParameterListAccessor(this.node); private static readonly Func ParameterListAccessor; public ParameterListSyntax ParameterList => ParameterListAccessor(this.node); private static readonly Func> ConstraintClausesAccessor; public SyntaxList ConstraintClauses => (SyntaxList)ConstraintClausesAccessor(this.node); private static readonly Func BodyAccessor; public BlockSyntax Body => BodyAccessor(this.node); private static readonly Func ExpressionBodyAccessor; public ArrowExpressionClauseSyntax ExpressionBody => ExpressionBodyAccessor(this.node); private static readonly Func SemicolonTokenAccessor; public SyntaxToken SemicolonToken => (SyntaxToken)SemicolonTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator LocalFunctionStatementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new LocalFunctionStatementSyntaxWrapper((StatementSyntax)node); } public static implicit operator StatementSyntax(LocalFunctionStatementSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LockStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class LockStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(LockStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(LockStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#LoopKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum LoopKind : System.Int32 { None = 0, While = 1, For = 2, ForTo = 3, ForEach = 4, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#MemberDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class MemberDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(MemberDeclarationSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); extension(MemberDeclarationSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#MetadataImportOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum MetadataImportOptions : System.Byte { Public = 0, Internal = 1, All = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#MethodKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class MethodKindEx { public const MethodKind LocalFunction = (MethodKind)17; public const MethodKind FunctionPointerSignature = (MethodKind)18; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NameColonSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class NameColonSyntaxShimExtensions { private static readonly Type WrappedType = typeof(NameColonSyntax); private static readonly Func ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); extension(NameColonSyntax @this) { public ExpressionSyntax Expression => ExpressionAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class NameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(NameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(NameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NamespaceDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class NamespaceDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(NamespaceDeclarationSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); extension(NamespaceDeclarationSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableAnnotation.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum NullableAnnotation : System.Byte { None = 0, NotAnnotated = 1, Annotated = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableContext.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; [System.FlagsAttribute] public enum NullableContext : System.Int32 { Disabled = 0, WarningsEnabled = 1, AnnotationsEnabled = 2, Enabled = 3, WarningsContextInherited = 4, AnnotationsContextInherited = 8, ContextInherited = 12, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableContextOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; [System.FlagsAttribute] public enum NullableContextOptions : System.Int32 { Disable = 0, Warnings = 1, Annotations = 2, Enable = 3, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableDirectiveTriviaSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct NullableDirectiveTriviaSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.NullableDirectiveTriviaSyntax"; private static readonly Type WrappedType; private readonly DirectiveTriviaSyntax node; static NullableDirectiveTriviaSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(NullableDirectiveTriviaSyntaxWrapper)); NullableKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NullableKeyword"); SettingTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SettingToken"); TargetTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "TargetToken"); } private NullableDirectiveTriviaSyntaxWrapper(DirectiveTriviaSyntax node) => this.node = node; public DirectiveTriviaSyntax Node => this.node; [Obsolete("Use Node instead")] public DirectiveTriviaSyntax SyntaxNode => this.node; public SyntaxToken HashToken => this.node.HashToken; private static readonly Func NullableKeywordAccessor; public SyntaxToken NullableKeyword => (SyntaxToken)NullableKeywordAccessor(this.node); private static readonly Func SettingTokenAccessor; public SyntaxToken SettingToken => (SyntaxToken)SettingTokenAccessor(this.node); private static readonly Func TargetTokenAccessor; public SyntaxToken TargetToken => (SyntaxToken)TargetTokenAccessor(this.node); public SyntaxToken EndOfDirectiveToken => this.node.EndOfDirectiveToken; public Boolean IsActive => this.node.IsActive; public SyntaxToken DirectiveNameToken => this.node.DirectiveNameToken; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator NullableDirectiveTriviaSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new NullableDirectiveTriviaSyntaxWrapper((DirectiveTriviaSyntax)node); } public static implicit operator DirectiveTriviaSyntax(NullableDirectiveTriviaSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableFlowState.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum NullableFlowState : System.Byte { None = 0, NotNull = 1, MaybeNull = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#NullableTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class NullableTypeSyntaxShimExtensions { private static readonly Type WrappedType = typeof(NullableTypeSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(NullableTypeSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#OmittedTypeArgumentSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class OmittedTypeArgumentSyntaxShimExtensions { private static readonly Type WrappedType = typeof(OmittedTypeArgumentSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(OmittedTypeArgumentSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#OperationKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class OperationKindEx { public const OperationKind None = (OperationKind)0; public const OperationKind Invalid = (OperationKind)1; public const OperationKind Block = (OperationKind)2; public const OperationKind VariableDeclarationGroup = (OperationKind)3; public const OperationKind Switch = (OperationKind)4; public const OperationKind Loop = (OperationKind)5; public const OperationKind Labeled = (OperationKind)6; public const OperationKind Branch = (OperationKind)7; public const OperationKind Empty = (OperationKind)8; public const OperationKind Return = (OperationKind)9; public const OperationKind YieldBreak = (OperationKind)10; public const OperationKind Lock = (OperationKind)11; public const OperationKind Try = (OperationKind)12; public const OperationKind Using = (OperationKind)13; public const OperationKind YieldReturn = (OperationKind)14; public const OperationKind ExpressionStatement = (OperationKind)15; public const OperationKind LocalFunction = (OperationKind)16; public const OperationKind Stop = (OperationKind)17; public const OperationKind End = (OperationKind)18; public const OperationKind RaiseEvent = (OperationKind)19; public const OperationKind Literal = (OperationKind)20; public const OperationKind Conversion = (OperationKind)21; public const OperationKind Invocation = (OperationKind)22; public const OperationKind ArrayElementReference = (OperationKind)23; public const OperationKind LocalReference = (OperationKind)24; public const OperationKind ParameterReference = (OperationKind)25; public const OperationKind FieldReference = (OperationKind)26; public const OperationKind MethodReference = (OperationKind)27; public const OperationKind PropertyReference = (OperationKind)28; public const OperationKind EventReference = (OperationKind)30; public const OperationKind Unary = (OperationKind)31; public const OperationKind UnaryOperator = (OperationKind)31; public const OperationKind Binary = (OperationKind)32; public const OperationKind BinaryOperator = (OperationKind)32; public const OperationKind Conditional = (OperationKind)33; public const OperationKind Coalesce = (OperationKind)34; public const OperationKind AnonymousFunction = (OperationKind)35; public const OperationKind ObjectCreation = (OperationKind)36; public const OperationKind TypeParameterObjectCreation = (OperationKind)37; public const OperationKind ArrayCreation = (OperationKind)38; public const OperationKind InstanceReference = (OperationKind)39; public const OperationKind IsType = (OperationKind)40; public const OperationKind Await = (OperationKind)41; public const OperationKind SimpleAssignment = (OperationKind)42; public const OperationKind CompoundAssignment = (OperationKind)43; public const OperationKind Parenthesized = (OperationKind)44; public const OperationKind EventAssignment = (OperationKind)45; public const OperationKind ConditionalAccess = (OperationKind)46; public const OperationKind ConditionalAccessInstance = (OperationKind)47; public const OperationKind InterpolatedString = (OperationKind)48; public const OperationKind AnonymousObjectCreation = (OperationKind)49; public const OperationKind ObjectOrCollectionInitializer = (OperationKind)50; public const OperationKind MemberInitializer = (OperationKind)51; public const OperationKind CollectionElementInitializer = (OperationKind)52; public const OperationKind NameOf = (OperationKind)53; public const OperationKind Tuple = (OperationKind)54; public const OperationKind DynamicObjectCreation = (OperationKind)55; public const OperationKind DynamicMemberReference = (OperationKind)56; public const OperationKind DynamicInvocation = (OperationKind)57; public const OperationKind DynamicIndexerAccess = (OperationKind)58; public const OperationKind TranslatedQuery = (OperationKind)59; public const OperationKind DelegateCreation = (OperationKind)60; public const OperationKind DefaultValue = (OperationKind)61; public const OperationKind TypeOf = (OperationKind)62; public const OperationKind SizeOf = (OperationKind)63; public const OperationKind AddressOf = (OperationKind)64; public const OperationKind IsPattern = (OperationKind)65; public const OperationKind Increment = (OperationKind)66; public const OperationKind Throw = (OperationKind)67; public const OperationKind Decrement = (OperationKind)68; public const OperationKind DeconstructionAssignment = (OperationKind)69; public const OperationKind DeclarationExpression = (OperationKind)70; public const OperationKind OmittedArgument = (OperationKind)71; public const OperationKind FieldInitializer = (OperationKind)72; public const OperationKind VariableInitializer = (OperationKind)73; public const OperationKind PropertyInitializer = (OperationKind)74; public const OperationKind ParameterInitializer = (OperationKind)75; public const OperationKind ArrayInitializer = (OperationKind)76; public const OperationKind VariableDeclarator = (OperationKind)77; public const OperationKind VariableDeclaration = (OperationKind)78; public const OperationKind Argument = (OperationKind)79; public const OperationKind CatchClause = (OperationKind)80; public const OperationKind SwitchCase = (OperationKind)81; public const OperationKind CaseClause = (OperationKind)82; public const OperationKind InterpolatedStringText = (OperationKind)83; public const OperationKind Interpolation = (OperationKind)84; public const OperationKind ConstantPattern = (OperationKind)85; public const OperationKind DeclarationPattern = (OperationKind)86; public const OperationKind TupleBinary = (OperationKind)87; public const OperationKind TupleBinaryOperator = (OperationKind)87; public const OperationKind MethodBody = (OperationKind)88; public const OperationKind MethodBodyOperation = (OperationKind)88; public const OperationKind ConstructorBody = (OperationKind)89; public const OperationKind ConstructorBodyOperation = (OperationKind)89; public const OperationKind Discard = (OperationKind)90; public const OperationKind FlowCapture = (OperationKind)91; public const OperationKind FlowCaptureReference = (OperationKind)92; public const OperationKind IsNull = (OperationKind)93; public const OperationKind CaughtException = (OperationKind)94; public const OperationKind StaticLocalInitializationSemaphore = (OperationKind)95; public const OperationKind FlowAnonymousFunction = (OperationKind)96; public const OperationKind CoalesceAssignment = (OperationKind)97; public const OperationKind Range = (OperationKind)99; public const OperationKind ReDim = (OperationKind)101; public const OperationKind ReDimClause = (OperationKind)102; public const OperationKind RecursivePattern = (OperationKind)103; public const OperationKind DiscardPattern = (OperationKind)104; public const OperationKind SwitchExpression = (OperationKind)105; public const OperationKind SwitchExpressionArm = (OperationKind)106; public const OperationKind PropertySubpattern = (OperationKind)107; public const OperationKind UsingDeclaration = (OperationKind)108; public const OperationKind NegatedPattern = (OperationKind)109; public const OperationKind BinaryPattern = (OperationKind)110; public const OperationKind TypePattern = (OperationKind)111; public const OperationKind RelationalPattern = (OperationKind)112; public const OperationKind With = (OperationKind)113; public const OperationKind InterpolatedStringHandlerCreation = (OperationKind)114; public const OperationKind InterpolatedStringAddition = (OperationKind)115; public const OperationKind InterpolatedStringAppendLiteral = (OperationKind)116; public const OperationKind InterpolatedStringAppendFormatted = (OperationKind)117; public const OperationKind InterpolatedStringAppendInvalid = (OperationKind)118; public const OperationKind InterpolatedStringHandlerArgumentPlaceholder = (OperationKind)119; public const OperationKind FunctionPointerInvocation = (OperationKind)120; public const OperationKind ListPattern = (OperationKind)121; public const OperationKind SlicePattern = (OperationKind)122; public const OperationKind ImplicitIndexerReference = (OperationKind)123; public const OperationKind Utf8String = (OperationKind)124; public const OperationKind Attribute = (OperationKind)125; public const OperationKind InlineArrayAccess = (OperationKind)126; public const OperationKind CollectionExpression = (OperationKind)127; public const OperationKind Spread = (OperationKind)128; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#OperatorDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class OperatorDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(OperatorDeclarationSyntax); private static readonly Func ExplicitInterfaceSpecifierAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExplicitInterfaceSpecifier"); private static readonly Func CheckedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CheckedKeyword"); extension(OperatorDeclarationSyntax @this) { public ExplicitInterfaceSpecifierSyntax ExplicitInterfaceSpecifier => ExplicitInterfaceSpecifierAccessor(@this); public SyntaxToken CheckedKeyword => (SyntaxToken)CheckedKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#OperatorMemberCrefSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class OperatorMemberCrefSyntaxShimExtensions { private static readonly Type WrappedType = typeof(OperatorMemberCrefSyntax); private static readonly Func CheckedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CheckedKeyword"); extension(OperatorMemberCrefSyntax @this) { public SyntaxToken CheckedKeyword => (SyntaxToken)CheckedKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ParenthesizedLambdaExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ParenthesizedLambdaExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ParenthesizedLambdaExpressionSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); private static readonly Func ReturnTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ReturnType"); private static readonly Func BlockAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Block"); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(ParenthesizedLambdaExpressionSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); public TypeSyntax ReturnType => ReturnTypeAccessor(@this); public BlockSyntax Block => BlockAccessor(@this); public ExpressionSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ParenthesizedPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ParenthesizedPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ParenthesizedPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ParenthesizedPatternSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); } private ParenthesizedPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ParenthesizedPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ParenthesizedPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ParenthesizedPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(ParenthesizedPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ParenthesizedPatternSyntaxWrapper(PatternSyntaxWrapper down) => (ParenthesizedPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(ParenthesizedPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ParenthesizedPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (ParenthesizedPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ParenthesizedVariableDesignationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ParenthesizedVariableDesignationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedVariableDesignationSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ParenthesizedVariableDesignationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ParenthesizedVariableDesignationSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); VariablesAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Variables)); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); } private ParenthesizedVariableDesignationSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func> VariablesAccessor; public SeparatedSyntaxListWrapper Variables => VariablesAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ParenthesizedVariableDesignationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ParenthesizedVariableDesignationSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ParenthesizedVariableDesignationSyntaxWrapper wrapper) => wrapper.node; public static implicit operator VariableDesignationSyntaxWrapper(ParenthesizedVariableDesignationSyntaxWrapper up) => (VariableDesignationSyntaxWrapper)up.SyntaxNode; public static explicit operator ParenthesizedVariableDesignationSyntaxWrapper(VariableDesignationSyntaxWrapper down) => (ParenthesizedVariableDesignationSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct PatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static PatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(PatternSyntaxWrapper)); } private PatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator PatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new PatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(PatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator ExpressionOrPatternSyntaxWrapper(PatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator PatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (PatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#Platform.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class PlatformEx { public const Platform Arm64 = (Platform)6; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PointerTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class PointerTypeSyntaxShimExtensions { private static readonly Type WrappedType = typeof(PointerTypeSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(PointerTypeSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PositionalPatternClauseSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct PositionalPatternClauseSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.PositionalPatternClauseSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static PositionalPatternClauseSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(PositionalPatternClauseSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); SubpatternsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Subpatterns)); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); } private PositionalPatternClauseSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func> SubpatternsAccessor; public SeparatedSyntaxListWrapper Subpatterns => SubpatternsAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator PositionalPatternClauseSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new PositionalPatternClauseSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(PositionalPatternClauseSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PredefinedTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class PredefinedTypeSyntaxShimExtensions { private static readonly Type WrappedType = typeof(PredefinedTypeSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(PredefinedTypeSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PrimaryConstructorBaseTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct PrimaryConstructorBaseTypeSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.PrimaryConstructorBaseTypeSyntax"; private static readonly Type WrappedType; private readonly BaseTypeSyntax node; static PrimaryConstructorBaseTypeSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(PrimaryConstructorBaseTypeSyntaxWrapper)); ArgumentListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ArgumentList"); } private PrimaryConstructorBaseTypeSyntaxWrapper(BaseTypeSyntax node) => this.node = node; public BaseTypeSyntax Node => this.node; [Obsolete("Use Node instead")] public BaseTypeSyntax SyntaxNode => this.node; public TypeSyntax Type => this.node.Type; private static readonly Func ArgumentListAccessor; public ArgumentListSyntax ArgumentList => ArgumentListAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator PrimaryConstructorBaseTypeSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new PrimaryConstructorBaseTypeSyntaxWrapper((BaseTypeSyntax)node); } public static implicit operator BaseTypeSyntax(PrimaryConstructorBaseTypeSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#PropertyPatternClauseSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct PropertyPatternClauseSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternClauseSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static PropertyPatternClauseSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(PropertyPatternClauseSyntaxWrapper)); OpenBraceTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBraceToken"); SubpatternsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Subpatterns)); CloseBraceTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBraceToken"); } private PropertyPatternClauseSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OpenBraceTokenAccessor; public SyntaxToken OpenBraceToken => (SyntaxToken)OpenBraceTokenAccessor(this.node); private static readonly Func> SubpatternsAccessor; public SeparatedSyntaxListWrapper Subpatterns => SubpatternsAccessor(this.node); private static readonly Func CloseBraceTokenAccessor; public SyntaxToken CloseBraceToken => (SyntaxToken)CloseBraceTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator PropertyPatternClauseSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new PropertyPatternClauseSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(PropertyPatternClauseSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#QualifiedNameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class QualifiedNameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(QualifiedNameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(QualifiedNameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RangeExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RangeExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RangeExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static RangeExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RangeExpressionSyntaxWrapper)); LeftOperandAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "LeftOperand"); OperatorTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OperatorToken"); RightOperandAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RightOperand"); } private RangeExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func LeftOperandAccessor; public ExpressionSyntax LeftOperand => LeftOperandAccessor(this.node); private static readonly Func OperatorTokenAccessor; public SyntaxToken OperatorToken => (SyntaxToken)OperatorTokenAccessor(this.node); private static readonly Func RightOperandAccessor; public ExpressionSyntax RightOperand => RightOperandAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RangeExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RangeExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(RangeExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RecordDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RecordDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax"; private static readonly Type WrappedType; private readonly TypeDeclarationSyntax node; static RecordDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RecordDeclarationSyntaxWrapper)); ClassOrStructKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ClassOrStructKeyword"); ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); } private RecordDeclarationSyntaxWrapper(TypeDeclarationSyntax node) => this.node = node; public TypeDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeDeclarationSyntax SyntaxNode => this.node; public SyntaxList AttributeLists => this.node.AttributeLists; public SyntaxTokenList Modifiers => this.node.Modifiers; public SyntaxToken Keyword => this.node.Keyword; private static readonly Func ClassOrStructKeywordAccessor; public SyntaxToken ClassOrStructKeyword => (SyntaxToken)ClassOrStructKeywordAccessor(this.node); public SyntaxToken Identifier => this.node.Identifier; public TypeParameterListSyntax TypeParameterList => this.node.TypeParameterList; private static readonly Func ParameterListAccessor; public ParameterListSyntax ParameterList => ParameterListAccessor(this.node); public BaseListSyntax BaseList => this.node.BaseList; public SyntaxList ConstraintClauses => this.node.ConstraintClauses; public SyntaxToken OpenBraceToken => this.node.OpenBraceToken; public SyntaxList Members => this.node.Members; public SyntaxToken CloseBraceToken => this.node.CloseBraceToken; public SyntaxToken SemicolonToken => this.node.SemicolonToken; public Int32 Arity => this.node.Arity; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RecordDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RecordDeclarationSyntaxWrapper((TypeDeclarationSyntax)node); } public static implicit operator TypeDeclarationSyntax(RecordDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RecursivePatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RecursivePatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RecursivePatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static RecursivePatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RecursivePatternSyntaxWrapper)); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); PositionalPatternClauseAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "PositionalPatternClause"); PropertyPatternClauseAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "PropertyPatternClause"); DesignationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Designation"); } private RecursivePatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); private static readonly Func PositionalPatternClauseAccessor; public PositionalPatternClauseSyntaxWrapper PositionalPatternClause => (PositionalPatternClauseSyntaxWrapper)PositionalPatternClauseAccessor(this.node); private static readonly Func PropertyPatternClauseAccessor; public PropertyPatternClauseSyntaxWrapper PropertyPatternClause => (PropertyPatternClauseSyntaxWrapper)PropertyPatternClauseAccessor(this.node); private static readonly Func DesignationAccessor; public VariableDesignationSyntaxWrapper Designation => (VariableDesignationSyntaxWrapper)DesignationAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RecursivePatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RecursivePatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(RecursivePatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(RecursivePatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator RecursivePatternSyntaxWrapper(PatternSyntaxWrapper down) => (RecursivePatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(RecursivePatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator RecursivePatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (RecursivePatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RefExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RefExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RefExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static RefExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RefExpressionSyntaxWrapper)); RefKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RefKeyword"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private RefExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func RefKeywordAccessor; public SyntaxToken RefKeyword => (SyntaxToken)RefKeywordAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RefExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RefExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(RefExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RefKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class RefKindEx { public const RefKind In = (RefKind)3; public const RefKind RefReadOnly = (RefKind)3; public const RefKind RefReadOnlyParameter = (RefKind)4; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RefStructConstraintSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RefStructConstraintSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RefStructConstraintSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static RefStructConstraintSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RefStructConstraintSyntaxWrapper)); RefKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RefKeyword"); StructKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "StructKeyword"); } private RefStructConstraintSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func RefKeywordAccessor; public SyntaxToken RefKeyword => (SyntaxToken)RefKeywordAccessor(this.node); private static readonly Func StructKeywordAccessor; public SyntaxToken StructKeyword => (SyntaxToken)StructKeywordAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RefStructConstraintSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RefStructConstraintSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(RefStructConstraintSyntaxWrapper wrapper) => wrapper.node; public static implicit operator AllowsConstraintSyntaxWrapper(RefStructConstraintSyntaxWrapper up) => (AllowsConstraintSyntaxWrapper)up.SyntaxNode; public static explicit operator RefStructConstraintSyntaxWrapper(AllowsConstraintSyntaxWrapper down) => (RefStructConstraintSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RefTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RefTypeSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax"; private static readonly Type WrappedType; private readonly TypeSyntax node; static RefTypeSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RefTypeSyntaxWrapper)); RefKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "RefKeyword"); ReadOnlyKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ReadOnlyKeyword"); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); } private RefTypeSyntaxWrapper(TypeSyntax node) => this.node = node; public TypeSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeSyntax SyntaxNode => this.node; private static readonly Func RefKeywordAccessor; public SyntaxToken RefKeyword => (SyntaxToken)RefKeywordAccessor(this.node); private static readonly Func ReadOnlyKeywordAccessor; public SyntaxToken ReadOnlyKeyword => (SyntaxToken)ReadOnlyKeywordAccessor(this.node); private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); public Boolean IsVar => this.node.IsVar; private static readonly Func IsUnmanagedAccessor; public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(this.node); private static readonly Func IsNotNullAccessor; public Boolean IsNotNull => (Boolean)IsNotNullAccessor(this.node); private static readonly Func IsNintAccessor; public Boolean IsNint => (Boolean)IsNintAccessor(this.node); private static readonly Func IsNuintAccessor; public Boolean IsNuint => (Boolean)IsNuintAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RefTypeSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RefTypeSyntaxWrapper((TypeSyntax)node); } public static implicit operator TypeSyntax(RefTypeSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RelationalPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RelationalPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RelationalPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static RelationalPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RelationalPatternSyntaxWrapper)); OperatorTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OperatorToken"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private RelationalPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OperatorTokenAccessor; public SyntaxToken OperatorToken => (SyntaxToken)OperatorTokenAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator RelationalPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RelationalPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(RelationalPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(RelationalPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator RelationalPatternSyntaxWrapper(PatternSyntaxWrapper down) => (RelationalPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(RelationalPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator RelationalPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (RelationalPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ReturnStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ReturnStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ReturnStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(ReturnStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#RuntimeCapability.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum RuntimeCapability : System.Int32 { ByRefFields = 1, CovariantReturnsOfClasses = 2, DefaultImplementationsOfInterfaces = 3, NumericIntPtr = 4, UnmanagedSignatureCallingConvention = 5, VirtualStaticsInInterfaces = 6, InlineArrayTypes = 7, ByRefLikeGenerics = 8, RuntimeAsyncMethods = 9, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SarifVersion.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum SarifVersion : System.Int32 { Sarif1 = 1, Sarif2 = 2, Default = 1, Latest = 2147483647, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ScopedKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum ScopedKind : System.Byte { None = 0, ScopedRef = 1, ScopedValue = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ScopedTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ScopedTypeSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ScopedTypeSyntax"; private static readonly Type WrappedType; private readonly TypeSyntax node; static ScopedTypeSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ScopedTypeSyntaxWrapper)); ScopedKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ScopedKeyword"); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); } private ScopedTypeSyntaxWrapper(TypeSyntax node) => this.node = node; public TypeSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeSyntax SyntaxNode => this.node; private static readonly Func ScopedKeywordAccessor; public SyntaxToken ScopedKeyword => (SyntaxToken)ScopedKeywordAccessor(this.node); private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); public Boolean IsVar => this.node.IsVar; private static readonly Func IsUnmanagedAccessor; public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(this.node); private static readonly Func IsNotNullAccessor; public Boolean IsNotNull => (Boolean)IsNotNullAccessor(this.node); private static readonly Func IsNintAccessor; public Boolean IsNint => (Boolean)IsNintAccessor(this.node); private static readonly Func IsNuintAccessor; public Boolean IsNuint => (Boolean)IsNuintAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ScopedTypeSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ScopedTypeSyntaxWrapper((TypeSyntax)node); } public static implicit operator TypeSyntax(ScopedTypeSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SemanticEditKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SemanticEditKindEx { public const SemanticEditKind Replace = (SemanticEditKind)4; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SemanticModelOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; [System.FlagsAttribute] public enum SemanticModelOptions : System.Int32 { None = 0, IgnoreAccessibility = 1, DisableNullableAnalysis = 2, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ShebangDirectiveTriviaSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ShebangDirectiveTriviaSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ShebangDirectiveTriviaSyntax); private static readonly Func ContentAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Content"); extension(ShebangDirectiveTriviaSyntax @this) { public SyntaxToken Content => (SyntaxToken)ContentAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SimpleLambdaExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class SimpleLambdaExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(SimpleLambdaExpressionSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func ModifiersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Modifiers"); private static readonly Func BlockAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Block"); private static readonly Func ExpressionBodyAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionBody"); extension(SimpleLambdaExpressionSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxTokenList Modifiers => (SyntaxTokenList)ModifiersAccessor(@this); public BlockSyntax Block => BlockAccessor(@this); public ExpressionSyntax ExpressionBody => ExpressionBodyAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SimpleNameSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class SimpleNameSyntaxShimExtensions { private static readonly Type WrappedType = typeof(SimpleNameSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(SimpleNameSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SingleVariableDesignationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SingleVariableDesignationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SingleVariableDesignationSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static SingleVariableDesignationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SingleVariableDesignationSyntaxWrapper)); IdentifierAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Identifier"); } private SingleVariableDesignationSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func IdentifierAccessor; public SyntaxToken Identifier => (SyntaxToken)IdentifierAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SingleVariableDesignationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SingleVariableDesignationSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(SingleVariableDesignationSyntaxWrapper wrapper) => wrapper.node; public static implicit operator VariableDesignationSyntaxWrapper(SingleVariableDesignationSyntaxWrapper up) => (VariableDesignationSyntaxWrapper)up.SyntaxNode; public static explicit operator SingleVariableDesignationSyntaxWrapper(VariableDesignationSyntaxWrapper down) => (SingleVariableDesignationSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SlicePatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SlicePatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SlicePatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static SlicePatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SlicePatternSyntaxWrapper)); DotDotTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "DotDotToken"); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); } private SlicePatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func DotDotTokenAccessor; public SyntaxToken DotDotToken => (SyntaxToken)DotDotTokenAccessor(this.node); private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SlicePatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SlicePatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(SlicePatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(SlicePatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator SlicePatternSyntaxWrapper(PatternSyntaxWrapper down) => (SlicePatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(SlicePatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator SlicePatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (SlicePatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SpecialType.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SpecialTypeEx { public const SpecialType System_Runtime_CompilerServices_RuntimeFeature = (SpecialType)44; public const SpecialType System_Runtime_CompilerServices_PreserveBaseOverridesAttribute = (SpecialType)45; public const SpecialType System_Runtime_CompilerServices_InlineArrayAttribute = (SpecialType)46; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SpreadElementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SpreadElementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SpreadElementSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static SpreadElementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SpreadElementSyntaxWrapper)); OperatorTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OperatorToken"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private SpreadElementSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OperatorTokenAccessor; public SyntaxToken OperatorToken => (SyntaxToken)OperatorTokenAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SpreadElementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SpreadElementSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(SpreadElementSyntaxWrapper wrapper) => wrapper.node; public static implicit operator CollectionElementSyntaxWrapper(SpreadElementSyntaxWrapper up) => (CollectionElementSyntaxWrapper)up.SyntaxNode; public static explicit operator SpreadElementSyntaxWrapper(CollectionElementSyntaxWrapper down) => (SpreadElementSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#StackAllocArrayCreationExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class StackAllocArrayCreationExpressionSyntaxShimExtensions { private static readonly Type WrappedType = typeof(StackAllocArrayCreationExpressionSyntax); private static readonly Func InitializerAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Initializer"); extension(StackAllocArrayCreationExpressionSyntax @this) { public InitializerExpressionSyntax Initializer => InitializerAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#StatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class StatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(StatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(StatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#StructDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class StructDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(StructDeclarationSyntax); private static readonly Func ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); extension(StructDeclarationSyntax @this) { public ParameterListSyntax ParameterList => ParameterListAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SubpatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SubpatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static SubpatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SubpatternSyntaxWrapper)); NameColonAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NameColon"); ExpressionColonAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ExpressionColon"); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); } private SubpatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func NameColonAccessor; public NameColonSyntax NameColon => NameColonAccessor(this.node); private static readonly Func ExpressionColonAccessor; public BaseExpressionColonSyntaxWrapper ExpressionColon => (BaseExpressionColonSyntaxWrapper)ExpressionColonAccessor(this.node); private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SubpatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SubpatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(SubpatternSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SwitchExpressionArmSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SwitchExpressionArmSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionArmSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static SwitchExpressionArmSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SwitchExpressionArmSyntaxWrapper)); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); WhenClauseAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "WhenClause"); EqualsGreaterThanTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "EqualsGreaterThanToken"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private SwitchExpressionArmSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); private static readonly Func WhenClauseAccessor; public WhenClauseSyntaxWrapper WhenClause => (WhenClauseSyntaxWrapper)WhenClauseAccessor(this.node); private static readonly Func EqualsGreaterThanTokenAccessor; public SyntaxToken EqualsGreaterThanToken => (SyntaxToken)EqualsGreaterThanTokenAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SwitchExpressionArmSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SwitchExpressionArmSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(SwitchExpressionArmSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SwitchExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SwitchExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.SwitchExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static SwitchExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SwitchExpressionSyntaxWrapper)); GoverningExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "GoverningExpression"); SwitchKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SwitchKeyword"); OpenBraceTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenBraceToken"); ArmsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Arms)); CloseBraceTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseBraceToken"); } private SwitchExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func GoverningExpressionAccessor; public ExpressionSyntax GoverningExpression => GoverningExpressionAccessor(this.node); private static readonly Func SwitchKeywordAccessor; public SyntaxToken SwitchKeyword => (SyntaxToken)SwitchKeywordAccessor(this.node); private static readonly Func OpenBraceTokenAccessor; public SyntaxToken OpenBraceToken => (SyntaxToken)OpenBraceTokenAccessor(this.node); private static readonly Func> ArmsAccessor; public SeparatedSyntaxListWrapper Arms => ArmsAccessor(this.node); private static readonly Func CloseBraceTokenAccessor; public SyntaxToken CloseBraceToken => (SyntaxToken)CloseBraceTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator SwitchExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SwitchExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(SwitchExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SwitchStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class SwitchStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(SwitchStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(SwitchStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolDisplayLocalOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolDisplayLocalOptionsEx { public const SymbolDisplayLocalOptions IncludeRef = (SymbolDisplayLocalOptions)4; public const SymbolDisplayLocalOptions IncludeModifiers = (SymbolDisplayLocalOptions)4; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolDisplayMemberOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolDisplayMemberOptionsEx { public const SymbolDisplayMemberOptions IncludeRef = (SymbolDisplayMemberOptions)128; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolDisplayMiscellaneousOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolDisplayMiscellaneousOptionsEx { public const SymbolDisplayMiscellaneousOptions IncludeNullableReferenceTypeModifier = (SymbolDisplayMiscellaneousOptions)64; public const SymbolDisplayMiscellaneousOptions AllowDefaultLiteral = (SymbolDisplayMiscellaneousOptions)128; public const SymbolDisplayMiscellaneousOptions IncludeNotNullableReferenceTypeModifier = (SymbolDisplayMiscellaneousOptions)256; public const SymbolDisplayMiscellaneousOptions CollapseTupleTypes = (SymbolDisplayMiscellaneousOptions)512; public const SymbolDisplayMiscellaneousOptions ExpandValueTuple = (SymbolDisplayMiscellaneousOptions)1024; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolDisplayParameterOptions.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolDisplayParameterOptionsEx { public const SymbolDisplayParameterOptions IncludeModifiers = (SymbolDisplayParameterOptions)2; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolDisplayPartKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolDisplayPartKindEx { public const SymbolDisplayPartKind EnumMemberName = (SymbolDisplayPartKind)28; public const SymbolDisplayPartKind ExtensionMethodName = (SymbolDisplayPartKind)29; public const SymbolDisplayPartKind ConstantName = (SymbolDisplayPartKind)30; public const SymbolDisplayPartKind RecordClassName = (SymbolDisplayPartKind)31; public const SymbolDisplayPartKind RecordStructName = (SymbolDisplayPartKind)32; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SymbolKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolKindEx { public const SymbolKind Discard = (SymbolKind)19; public const SymbolKind FunctionPointerType = (SymbolKind)20; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#SyntaxKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SyntaxKindEx { public const SyntaxKind DotDotToken = (SyntaxKind)8222; public const SyntaxKind QuestionQuestionEqualsToken = (SyntaxKind)8284; public const SyntaxKind GreaterThanGreaterThanGreaterThanToken = (SyntaxKind)8286; public const SyntaxKind GreaterThanGreaterThanGreaterThanEqualsToken = (SyntaxKind)8287; public const SyntaxKind OrKeyword = (SyntaxKind)8438; public const SyntaxKind AndKeyword = (SyntaxKind)8439; public const SyntaxKind NotKeyword = (SyntaxKind)8440; public const SyntaxKind WithKeyword = (SyntaxKind)8442; public const SyntaxKind InitKeyword = (SyntaxKind)8443; public const SyntaxKind RecordKeyword = (SyntaxKind)8444; public const SyntaxKind ManagedKeyword = (SyntaxKind)8445; public const SyntaxKind UnmanagedKeyword = (SyntaxKind)8446; public const SyntaxKind RequiredKeyword = (SyntaxKind)8447; public const SyntaxKind ScopedKeyword = (SyntaxKind)8448; public const SyntaxKind FileKeyword = (SyntaxKind)8449; public const SyntaxKind AllowsKeyword = (SyntaxKind)8450; public const SyntaxKind ExtensionKeyword = (SyntaxKind)8451; public const SyntaxKind NullableKeyword = (SyntaxKind)8486; public const SyntaxKind EnableKeyword = (SyntaxKind)8487; public const SyntaxKind WarningsKeyword = (SyntaxKind)8488; public const SyntaxKind AnnotationsKeyword = (SyntaxKind)8489; public const SyntaxKind VarKeyword = (SyntaxKind)8490; public const SyntaxKind UnderscoreToken = (SyntaxKind)8491; public const SyntaxKind SingleLineRawStringLiteralToken = (SyntaxKind)8518; public const SyntaxKind MultiLineRawStringLiteralToken = (SyntaxKind)8519; public const SyntaxKind Utf8StringLiteralToken = (SyntaxKind)8520; public const SyntaxKind Utf8SingleLineRawStringLiteralToken = (SyntaxKind)8521; public const SyntaxKind Utf8MultiLineRawStringLiteralToken = (SyntaxKind)8522; public const SyntaxKind RazorContentToken = (SyntaxKind)8523; public const SyntaxKind ConflictMarkerTrivia = (SyntaxKind)8564; public const SyntaxKind ExtensionMemberCref = (SyntaxKind)8607; public const SyntaxKind IsPatternExpression = (SyntaxKind)8657; public const SyntaxKind RangeExpression = (SyntaxKind)8658; public const SyntaxKind ImplicitObjectCreationExpression = (SyntaxKind)8659; public const SyntaxKind UnsignedRightShiftExpression = (SyntaxKind)8692; public const SyntaxKind CoalesceAssignmentExpression = (SyntaxKind)8725; public const SyntaxKind UnsignedRightShiftAssignmentExpression = (SyntaxKind)8726; public const SyntaxKind IndexExpression = (SyntaxKind)8741; public const SyntaxKind DefaultLiteralExpression = (SyntaxKind)8755; public const SyntaxKind Utf8StringLiteralExpression = (SyntaxKind)8756; public const SyntaxKind FieldExpression = (SyntaxKind)8757; public const SyntaxKind LocalFunctionStatement = (SyntaxKind)8830; public const SyntaxKind FileScopedNamespaceDeclaration = (SyntaxKind)8845; public const SyntaxKind AllowsConstraintClause = (SyntaxKind)8879; public const SyntaxKind RefStructConstraint = (SyntaxKind)8880; public const SyntaxKind TupleType = (SyntaxKind)8924; public const SyntaxKind TupleElement = (SyntaxKind)8925; public const SyntaxKind TupleExpression = (SyntaxKind)8926; public const SyntaxKind SingleVariableDesignation = (SyntaxKind)8927; public const SyntaxKind ParenthesizedVariableDesignation = (SyntaxKind)8928; public const SyntaxKind ForEachVariableStatement = (SyntaxKind)8929; public const SyntaxKind DeclarationPattern = (SyntaxKind)9000; public const SyntaxKind ConstantPattern = (SyntaxKind)9002; public const SyntaxKind CasePatternSwitchLabel = (SyntaxKind)9009; public const SyntaxKind WhenClause = (SyntaxKind)9013; public const SyntaxKind DiscardDesignation = (SyntaxKind)9014; public const SyntaxKind RecursivePattern = (SyntaxKind)9020; public const SyntaxKind PropertyPatternClause = (SyntaxKind)9021; public const SyntaxKind Subpattern = (SyntaxKind)9022; public const SyntaxKind PositionalPatternClause = (SyntaxKind)9023; public const SyntaxKind DiscardPattern = (SyntaxKind)9024; public const SyntaxKind SwitchExpression = (SyntaxKind)9025; public const SyntaxKind SwitchExpressionArm = (SyntaxKind)9026; public const SyntaxKind VarPattern = (SyntaxKind)9027; public const SyntaxKind ParenthesizedPattern = (SyntaxKind)9028; public const SyntaxKind RelationalPattern = (SyntaxKind)9029; public const SyntaxKind TypePattern = (SyntaxKind)9030; public const SyntaxKind OrPattern = (SyntaxKind)9031; public const SyntaxKind AndPattern = (SyntaxKind)9032; public const SyntaxKind NotPattern = (SyntaxKind)9033; public const SyntaxKind SlicePattern = (SyntaxKind)9034; public const SyntaxKind ListPattern = (SyntaxKind)9035; public const SyntaxKind DeclarationExpression = (SyntaxKind)9040; public const SyntaxKind RefExpression = (SyntaxKind)9050; public const SyntaxKind RefType = (SyntaxKind)9051; public const SyntaxKind ThrowExpression = (SyntaxKind)9052; public const SyntaxKind ImplicitStackAllocArrayCreationExpression = (SyntaxKind)9053; public const SyntaxKind SuppressNullableWarningExpression = (SyntaxKind)9054; public const SyntaxKind NullableDirectiveTrivia = (SyntaxKind)9055; public const SyntaxKind FunctionPointerType = (SyntaxKind)9056; public const SyntaxKind FunctionPointerParameter = (SyntaxKind)9057; public const SyntaxKind FunctionPointerParameterList = (SyntaxKind)9058; public const SyntaxKind FunctionPointerCallingConvention = (SyntaxKind)9059; public const SyntaxKind InitAccessorDeclaration = (SyntaxKind)9060; public const SyntaxKind WithExpression = (SyntaxKind)9061; public const SyntaxKind WithInitializerExpression = (SyntaxKind)9062; public const SyntaxKind RecordDeclaration = (SyntaxKind)9063; public const SyntaxKind DefaultConstraint = (SyntaxKind)9064; public const SyntaxKind PrimaryConstructorBaseType = (SyntaxKind)9065; public const SyntaxKind FunctionPointerUnmanagedCallingConventionList = (SyntaxKind)9066; public const SyntaxKind FunctionPointerUnmanagedCallingConvention = (SyntaxKind)9067; public const SyntaxKind RecordStructDeclaration = (SyntaxKind)9068; public const SyntaxKind ExpressionColon = (SyntaxKind)9069; public const SyntaxKind LineDirectivePosition = (SyntaxKind)9070; public const SyntaxKind LineSpanDirectiveTrivia = (SyntaxKind)9071; public const SyntaxKind InterpolatedSingleLineRawStringStartToken = (SyntaxKind)9072; public const SyntaxKind InterpolatedMultiLineRawStringStartToken = (SyntaxKind)9073; public const SyntaxKind InterpolatedRawStringEndToken = (SyntaxKind)9074; public const SyntaxKind ScopedType = (SyntaxKind)9075; public const SyntaxKind CollectionExpression = (SyntaxKind)9076; public const SyntaxKind ExpressionElement = (SyntaxKind)9077; public const SyntaxKind SpreadElement = (SyntaxKind)9078; public const SyntaxKind ExtensionBlockDeclaration = (SyntaxKind)9079; public const SyntaxKind IgnoredDirectiveTrivia = (SyntaxKind)9080; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ThrowExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ThrowExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ThrowExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static ThrowExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ThrowExpressionSyntaxWrapper)); ThrowKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ThrowKeyword"); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); } private ThrowExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func ThrowKeywordAccessor; public SyntaxToken ThrowKeyword => (SyntaxToken)ThrowKeywordAccessor(this.node); private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator ThrowExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ThrowExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(ThrowExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#ThrowStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ThrowStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ThrowStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(ThrowStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TryStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class TryStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(TryStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(TryStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TupleElementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct TupleElementSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.TupleElementSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static TupleElementSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(TupleElementSyntaxWrapper)); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); IdentifierAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Identifier"); } private TupleElementSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); private static readonly Func IdentifierAccessor; public SyntaxToken Identifier => (SyntaxToken)IdentifierAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator TupleElementSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new TupleElementSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(TupleElementSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TupleExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct TupleExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.TupleExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static TupleExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(TupleExpressionSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); ArgumentsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Arguments"); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); } private TupleExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func> ArgumentsAccessor; public SeparatedSyntaxList Arguments => (SeparatedSyntaxList)ArgumentsAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator TupleExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new TupleExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(TupleExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TupleTypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct TupleTypeSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax"; private static readonly Type WrappedType; private readonly TypeSyntax node; static TupleTypeSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(TupleTypeSyntaxWrapper)); OpenParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OpenParenToken"); ElementsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Elements)); CloseParenTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "CloseParenToken"); IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); } private TupleTypeSyntaxWrapper(TypeSyntax node) => this.node = node; public TypeSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeSyntax SyntaxNode => this.node; private static readonly Func OpenParenTokenAccessor; public SyntaxToken OpenParenToken => (SyntaxToken)OpenParenTokenAccessor(this.node); private static readonly Func> ElementsAccessor; public SeparatedSyntaxListWrapper Elements => ElementsAccessor(this.node); private static readonly Func CloseParenTokenAccessor; public SyntaxToken CloseParenToken => (SyntaxToken)CloseParenTokenAccessor(this.node); public Boolean IsVar => this.node.IsVar; private static readonly Func IsUnmanagedAccessor; public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(this.node); private static readonly Func IsNotNullAccessor; public Boolean IsNotNull => (Boolean)IsNotNullAccessor(this.node); private static readonly Func IsNintAccessor; public Boolean IsNint => (Boolean)IsNintAccessor(this.node); private static readonly Func IsNuintAccessor; public Boolean IsNuint => (Boolean)IsNuintAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator TupleTypeSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new TupleTypeSyntaxWrapper((TypeSyntax)node); } public static implicit operator TypeSyntax(TupleTypeSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TypeDeclarationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class TypeDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(TypeDeclarationSyntax); private static readonly Func ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); extension(TypeDeclarationSyntax @this) { public ParameterListSyntax ParameterList => ParameterListAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TypeKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class TypeKindEx { public const TypeKind FunctionPointer = (TypeKind)13; public const TypeKind Extension = (TypeKind)14; } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TypePatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct TypePatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.TypePatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static TypePatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(TypePatternSyntaxWrapper)); TypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Type"); } private TypePatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func TypeAccessor; public TypeSyntax Type => TypeAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator TypePatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new TypePatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(TypePatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(TypePatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator TypePatternSyntaxWrapper(PatternSyntaxWrapper down) => (TypePatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(TypePatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator TypePatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (TypePatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#TypeSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class TypeSyntaxShimExtensions { private static readonly Type WrappedType = typeof(TypeSyntax); private static readonly Func IsUnmanagedAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsUnmanaged"); private static readonly Func IsNotNullAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNotNull"); private static readonly Func IsNintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNint"); private static readonly Func IsNuintAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "IsNuint"); extension(TypeSyntax @this) { public Boolean IsUnmanaged => (Boolean)IsUnmanagedAccessor(@this); public Boolean IsNotNull => (Boolean)IsNotNullAccessor(@this); public Boolean IsNint => (Boolean)IsNintAccessor(@this); public Boolean IsNuint => (Boolean)IsNuintAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#UnaryOperatorKind.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum UnaryOperatorKind : System.Int32 { None = 0, BitwiseNegation = 1, Not = 2, Plus = 3, Minus = 4, True = 5, False = 6, Hat = 7, } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#UnaryPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct UnaryPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.UnaryPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static UnaryPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(UnaryPatternSyntaxWrapper)); OperatorTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "OperatorToken"); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); } private UnaryPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func OperatorTokenAccessor; public SyntaxToken OperatorToken => (SyntaxToken)OperatorTokenAccessor(this.node); private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator UnaryPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new UnaryPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(UnaryPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(UnaryPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator UnaryPatternSyntaxWrapper(PatternSyntaxWrapper down) => (UnaryPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(UnaryPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator UnaryPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (UnaryPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#UnsafeStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class UnsafeStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(UnsafeStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(UnsafeStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#UsingDirectiveSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class UsingDirectiveSyntaxShimExtensions { private static readonly Type WrappedType = typeof(UsingDirectiveSyntax); private static readonly Func GlobalKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "GlobalKeyword"); private static readonly Func UnsafeKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "UnsafeKeyword"); private static readonly Func NamespaceOrTypeAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "NamespaceOrType"); extension(UsingDirectiveSyntax @this) { public SyntaxToken GlobalKeyword => (SyntaxToken)GlobalKeywordAccessor(@this); public SyntaxToken UnsafeKeyword => (SyntaxToken)UnsafeKeywordAccessor(@this); public TypeSyntax NamespaceOrType => NamespaceOrTypeAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#UsingStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class UsingStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(UsingStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); private static readonly Func AwaitKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "AwaitKeyword"); extension(UsingStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); public SyntaxToken AwaitKeyword => (SyntaxToken)AwaitKeywordAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#VarPatternSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct VarPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.VarPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static VarPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(VarPatternSyntaxWrapper)); VarKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "VarKeyword"); DesignationAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Designation"); } private VarPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func VarKeywordAccessor; public SyntaxToken VarKeyword => (SyntaxToken)VarKeywordAccessor(this.node); private static readonly Func DesignationAccessor; public VariableDesignationSyntaxWrapper Designation => (VariableDesignationSyntaxWrapper)DesignationAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator VarPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new VarPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(VarPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(VarPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator VarPatternSyntaxWrapper(PatternSyntaxWrapper down) => (VarPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(VarPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator VarPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (VarPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#VariableDesignationSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct VariableDesignationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static VariableDesignationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(VariableDesignationSyntaxWrapper)); } private VariableDesignationSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator VariableDesignationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new VariableDesignationSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(VariableDesignationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#WhenClauseSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct WhenClauseSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static WhenClauseSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(WhenClauseSyntaxWrapper)); WhenKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "WhenKeyword"); ConditionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Condition"); } private WhenClauseSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; private static readonly Func WhenKeywordAccessor; public SyntaxToken WhenKeyword => (SyntaxToken)WhenKeywordAccessor(this.node); private static readonly Func ConditionAccessor; public ExpressionSyntax Condition => ConditionAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator WhenClauseSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new WhenClauseSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(WhenClauseSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#WhileStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class WhileStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(WhileStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(WhileStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#WithExpressionSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct WithExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.WithExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static WithExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(WithExpressionSyntaxWrapper)); ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Expression"); WithKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "WithKeyword"); InitializerAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Initializer"); } private WithExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func ExpressionAccessor; public ExpressionSyntax Expression => ExpressionAccessor(this.node); private static readonly Func WithKeywordAccessor; public SyntaxToken WithKeyword => (SyntaxToken)WithKeywordAccessor(this.node); private static readonly Func InitializerAccessor; public InitializerExpressionSyntax Initializer => InitializerAccessor(this.node); public String Language => this.node.Language; public Int32 RawKind => this.node.RawKind; public TextSpan FullSpan => this.node.FullSpan; public TextSpan Span => this.node.Span; public Int32 SpanStart => this.node.SpanStart; public Boolean IsMissing => this.node.IsMissing; public Boolean IsStructuredTrivia => this.node.IsStructuredTrivia; public Boolean HasStructuredTrivia => this.node.HasStructuredTrivia; public Boolean ContainsSkippedText => this.node.ContainsSkippedText; public Boolean ContainsDiagnostics => this.node.ContainsDiagnostics; public Boolean ContainsDirectives => this.node.ContainsDirectives; public Boolean HasLeadingTrivia => this.node.HasLeadingTrivia; public Boolean HasTrailingTrivia => this.node.HasTrailingTrivia; public SyntaxNode Parent => this.node.Parent; public SyntaxTrivia ParentTrivia => this.node.ParentTrivia; public Boolean ContainsAnnotations => this.node.ContainsAnnotations; public static explicit operator WithExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new WithExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(WithExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Snapshots/Snap#YieldStatementSyntax.g.cs.verified.cs ================================================ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class YieldStatementSyntaxShimExtensions { private static readonly Type WrappedType = typeof(YieldStatementSyntax); private static readonly Func> AttributeListsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "AttributeLists"); extension(YieldStatementSyntax @this) { public SyntaxList AttributeLists => (SyntaxList)AttributeListsAccessor(@this); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/SonarAnalyzer.ShimLayer.Generator.Test.csproj ================================================  net10.0 false Never ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/NewEnumStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Diagnostics.CodeAnalysis; using SonarAnalyzer.TestFramework.Extensions; namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class NewEnumStrategyTest { [Experimental("RSEXPERIMENTAL001")] // Microsoft.CodeAnalysis.SemanticModelOptions' is for evaluation purposes only and is subject to change or removal in future updates. private enum EnumWithExperimentalAttribute { Lorem, Ipsum, } [TestMethod] public void Generate() { using var typeLoader = new TypeLoader(); var type = typeLoader.LoadLatest().Single(x => x.Type.Name == nameof(IncrementalGeneratorOutputKind)); var sut = new NewEnumStrategy(type.Type, type.Members.OfType().Where(x => x.Name != "value__").ToArray()); sut.Generate([]).Should().BeIgnoringLineEndings(""" // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; [System.FlagsAttribute] public enum IncrementalGeneratorOutputKind : System.Int32 { None = 0, Source = 1, PostInit = 2, Implementation = 4, Host = 8, } """); } [TestMethod] public void Generate_IgnoreExperimentalAttribute() { #pragma warning disable RSEXPERIMENTAL001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var type = typeof(EnumWithExperimentalAttribute); var sut = new NewEnumStrategy(type, type.GetMembers().OfType().Where(x => x.Name != "value__").ToArray()); sut.Generate([]).Should().BeIgnoringLineEndings(""" // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public enum EnumWithExperimentalAttribute : System.Int32 { Lorem = 0, Ipsum = 1, } """); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/NoChangeStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class NoChangeStrategyTest { [TestMethod] public void Generate() { var sut = new NoChangeStrategy(typeof(NamespaceKind)); sut.Generate([]).Should().BeNull(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/PartialEnumStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.TestFramework.Extensions; namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class PartialEnumStrategyTest { [TestMethod] public void Generate() { var type = typeof(SymbolKind); var sut = new PartialEnumStrategy(type, type.GetMembers().OfType().Where(x => x.Name is nameof(SymbolKind.Discard) or nameof(SymbolKind.RangeVariable)).ToArray()); sut.Generate([]).Should().BeIgnoringLineEndings(""" // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static class SymbolKindEx { public const SymbolKind RangeVariable = (SymbolKind)16; public const SymbolKind Discard = (SymbolKind)19; } """); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/SkipStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class SkipStrategyTest { [TestMethod] public void SkipStrategy() { var sut = new SkipStrategy(typeof(SyntaxNode)); sut.Generate([]).Should().BeNull(); } [TestMethod] public void ReturnTypeSnippet() => new SkipStrategy(typeof(SyntaxNode)).Invoking(x => x.ReturnTypeSnippet()).Should().Throw(); [TestMethod] public void ToConversionSnippet() => new SkipStrategy(typeof(SyntaxNode)).Invoking(x => x.ToConversionSnippet("Lorem ipsum")).Should().Throw(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/SyntaxNodeExtendStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.TestFramework.Extensions; namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class SyntaxNodeExtendStrategyTest { [TestMethod] public void Generate_TypeDeclarationSyntax_Empty() { var sut = new SyntaxNodeExtendStrategy(typeof(TypeDeclarationSyntax), []); sut.Generate([]).Should().BeNull(); } [TestMethod] public void Generate_ClassDeclarationSyntax_ParameterList() { var sut = new SyntaxNodeExtendStrategy(typeof(ClassDeclarationSyntax), [ new(typeof(ClassDeclarationSyntax).GetMember(nameof(ClassDeclarationSyntax.ParameterList))[0], false), new(typeof(ClassDeclarationSyntax).GetMember(nameof(ClassDeclarationSyntax.SemicolonToken))[0], false), new(typeof(ClassDeclarationSyntax).GetMember(nameof(ClassDeclarationSyntax.Span))[0], true), // exisiting member, should be ignored ]); var result = sut.Generate([]); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public static partial class ClassDeclarationSyntaxShimExtensions { private static readonly Type WrappedType = typeof(ClassDeclarationSyntax); private static readonly Func ParameterListAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ParameterList"); private static readonly Func SemicolonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "SemicolonToken"); extension(ClassDeclarationSyntax @this) { public ParameterListSyntax ParameterList => (ParameterListSyntax)ParameterListAccessor(@this); public SyntaxToken SemicolonToken => (SyntaxToken)SemicolonTokenAccessor(@this); } } """); } [TestMethod] public void Generate_ProcessStartInfo_GetterOnly() { var sut = new SyntaxNodeExtendStrategy(typeof(ProcessStartInfo), [ // FileName has a getter and a setter. We do not generate setters, because there are no SyntaxNode properties with setters. new(typeof(ProcessStartInfo).GetMember(nameof(ProcessStartInfo.FileName))[0], false), ]); var result = sut.Generate([]); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; using System.Diagnostics; namespace SonarAnalyzer.ShimLayer; public static partial class ProcessStartInfoShimExtensions { private static readonly Type WrappedType = typeof(ProcessStartInfo); private static readonly Func FileNameAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "FileName"); extension(ProcessStartInfo @this) { public String FileName => (String)FileNameAccessor(@this); } } """); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/Strategies/SyntaxNodeWrapStrategyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.TestFramework.Extensions; namespace SonarAnalyzer.ShimLayer.Generator.Strategies.Test; [TestClass] public class SyntaxNodeWrapStrategyTest { [TestMethod] public void Generate_Empty() { var sut = new SyntaxNodeWrapStrategy( typeof(RecordDeclarationSyntax), typeof(TypeDeclarationSyntax), []); var result = sut.Generate([]); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RecordDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax"; private static readonly Type WrappedType; private readonly TypeDeclarationSyntax node; static RecordDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RecordDeclarationSyntaxWrapper)); } private RecordDeclarationSyntaxWrapper(TypeDeclarationSyntax node) => this.node = node; public TypeDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeDeclarationSyntax SyntaxNode => this.node; public static explicit operator RecordDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RecordDeclarationSyntaxWrapper((TypeDeclarationSyntax)node); } public static implicit operator TypeDeclarationSyntax(RecordDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_RecordDeclarationSyntax() { var skippedPropertyTypeMember = (PropertyInfo)typeof(RecordDeclarationSyntax).GetMember("ConstraintClauses")[0]; var sut = new SyntaxNodeWrapStrategy( typeof(RecordDeclarationSyntax), typeof(TypeDeclarationSyntax), [ new(typeof(RecordDeclarationSyntax).GetMember(nameof(RecordDeclarationSyntax.Span))[0], true), new(typeof(RecordDeclarationSyntax).GetMember(nameof(RecordDeclarationSyntax.ClassOrStructKeyword))[0], false), new(skippedPropertyTypeMember, false) // PropertyType is skipped and this should not render anything ]); var model = new StrategyModel(new() { { skippedPropertyTypeMember.PropertyType, new SkipStrategy(skippedPropertyTypeMember.PropertyType) } }); sut.Generate(model).Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RecordDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax"; private static readonly Type WrappedType; private readonly TypeDeclarationSyntax node; static RecordDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RecordDeclarationSyntaxWrapper)); ClassOrStructKeywordAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "ClassOrStructKeyword"); } private RecordDeclarationSyntaxWrapper(TypeDeclarationSyntax node) => this.node = node; public TypeDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeDeclarationSyntax SyntaxNode => this.node; public TextSpan Span => this.node.Span; private static readonly Func ClassOrStructKeywordAccessor; public SyntaxToken ClassOrStructKeyword => (SyntaxToken)ClassOrStructKeywordAccessor(this.node); public static explicit operator RecordDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RecordDeclarationSyntaxWrapper((TypeDeclarationSyntax)node); } public static implicit operator TypeDeclarationSyntax(RecordDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_IsPatternSyntax() { var sut = new SyntaxNodeWrapStrategy( typeof(IsPatternExpressionSyntax), typeof(ExpressionSyntax), [ new(typeof(IsPatternExpressionSyntax).GetMember(nameof(IsPatternExpressionSyntax.Pattern))[0], false), ]); var patternSyntaxStrategy = new SyntaxNodeWrapStrategy(typeof(PatternSyntax), typeof(CSharpSyntaxNode), []); var result = sut.Generate(new() { { typeof(PatternSyntax), patternSyntaxStrategy } }); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct IsPatternExpressionSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.IsPatternExpressionSyntax"; private static readonly Type WrappedType; private readonly ExpressionSyntax node; static IsPatternExpressionSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(IsPatternExpressionSyntaxWrapper)); PatternAccessor = LightupHelpers.CreateSyntaxPropertyAccessor(WrappedType, "Pattern"); } private IsPatternExpressionSyntaxWrapper(ExpressionSyntax node) => this.node = node; public ExpressionSyntax Node => this.node; [Obsolete("Use Node instead")] public ExpressionSyntax SyntaxNode => this.node; private static readonly Func PatternAccessor; public PatternSyntaxWrapper Pattern => (PatternSyntaxWrapper)PatternAccessor(this.node); public static explicit operator IsPatternExpressionSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new IsPatternExpressionSyntaxWrapper((ExpressionSyntax)node); } public static implicit operator ExpressionSyntax(IsPatternExpressionSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_ConstantPatternSyntax() { var sut = new SyntaxNodeWrapStrategy(typeof(ConstantPatternSyntax), typeof(CSharpSyntaxNode), []); var model = new Dictionary() { { typeof(ConstantPatternSyntax), sut }, { typeof(PatternSyntax), new SyntaxNodeWrapStrategy(typeof(PatternSyntax), typeof(CSharpSyntaxNode), []) }, { typeof(ExpressionOrPatternSyntax), new SyntaxNodeWrapStrategy(typeof(ExpressionOrPatternSyntax), typeof(CSharpSyntaxNode), []) }, }; var result = sut.Generate(new(model)); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct ConstantPatternSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.ConstantPatternSyntax"; private static readonly Type WrappedType; private readonly CSharpSyntaxNode node; static ConstantPatternSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(ConstantPatternSyntaxWrapper)); } private ConstantPatternSyntaxWrapper(CSharpSyntaxNode node) => this.node = node; public CSharpSyntaxNode Node => this.node; [Obsolete("Use Node instead")] public CSharpSyntaxNode SyntaxNode => this.node; public static explicit operator ConstantPatternSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new ConstantPatternSyntaxWrapper((CSharpSyntaxNode)node); } public static implicit operator CSharpSyntaxNode(ConstantPatternSyntaxWrapper wrapper) => wrapper.node; public static implicit operator PatternSyntaxWrapper(ConstantPatternSyntaxWrapper up) => (PatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ConstantPatternSyntaxWrapper(PatternSyntaxWrapper down) => (ConstantPatternSyntaxWrapper)down.SyntaxNode; public static implicit operator ExpressionOrPatternSyntaxWrapper(ConstantPatternSyntaxWrapper up) => (ExpressionOrPatternSyntaxWrapper)up.SyntaxNode; public static explicit operator ConstantPatternSyntaxWrapper(ExpressionOrPatternSyntaxWrapper down) => (ConstantPatternSyntaxWrapper)down.SyntaxNode; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_SkippedMembers_DoNotProduceEmptyLines() { var unsupportedMember = new MemberDescriptor(typeof(SyntaxNode).GetMembers().OfType().First(x => x.ReturnType.IsNested), true); var sut = new SyntaxNodeWrapStrategy( typeof(SyntaxNode), typeof(SyntaxNode), Enumerable.Repeat(unsupportedMember, 20).ToList()); // This should not produce 20 empty lines var result = sut.Generate(new() { { typeof(PatternSyntax), sut } }); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct SyntaxNodeWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.SyntaxNode"; private static readonly Type WrappedType; private readonly SyntaxNode node; static SyntaxNodeWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(SyntaxNodeWrapper)); } private SyntaxNodeWrapper(SyntaxNode node) => this.node = node; public SyntaxNode Node => this.node; [Obsolete("Use Node instead")] public SyntaxNode SyntaxNode => this.node; public static explicit operator SyntaxNodeWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new SyntaxNodeWrapper((SyntaxNode)node); } public static implicit operator SyntaxNode(SyntaxNodeWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_PropagateMemberAttributes() { var sut = new SyntaxNodeWrapStrategy( typeof(IndexerDeclarationSyntax), typeof(SyntaxNode), [ new(typeof(IndexerDeclarationSyntax).GetMember("Semicolon")[0], true), // Has ObsoleteAttribute to render new(typeof(AliasQualifiedNameSyntax).GetMember("Parent")[0], true) // Has NullableAttribute to ignore ]); var result = sut.Generate([]); result.Should().BeIgnoringLineEndings(""" // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct IndexerDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.IndexerDeclarationSyntax"; private static readonly Type WrappedType; private readonly SyntaxNode node; static IndexerDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(IndexerDeclarationSyntaxWrapper)); } private IndexerDeclarationSyntaxWrapper(SyntaxNode node) => this.node = node; public SyntaxNode Node => this.node; [Obsolete("Use Node instead")] public SyntaxNode SyntaxNode => this.node; [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("This member is obsolete.", true)] public SyntaxToken Semicolon => this.node.Semicolon; public SyntaxNode Parent => this.node.Parent; public static explicit operator IndexerDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new IndexerDeclarationSyntaxWrapper((SyntaxNode)node); } public static implicit operator SyntaxNode(IndexerDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } [TestMethod] public void Generate_GenericMembers() { var sut = new SyntaxNodeWrapStrategy( typeof(RecordDeclarationSyntax), typeof(TypeDeclarationSyntax), [ // This class is not authentic. There's a mix of different types to demonstrate what will be rendered new(typeof(RecordDeclarationSyntax).GetMember(nameof(RecordDeclarationSyntax.Members))[0], false), // SyntaxList with accessor new(typeof(RecordDeclarationSyntax).GetMember(nameof(RecordDeclarationSyntax.AttributeLists))[0], true), // SyntaxList passthrough new(typeof(TupleExpressionSyntax).GetMember(nameof(TupleExpressionSyntax.Arguments))[0], false), // SeparatedSyntaxList new(typeof(SwitchExpressionSyntax).GetMember(nameof(SwitchExpressionSyntax.Arms))[0], false) // SeparatedSyntaxListWrapper ]); var result = sut.Generate(new() { { typeof(SwitchExpressionArmSyntax), new SyntaxNodeWrapStrategy(typeof(SwitchExpressionArmSyntax), typeof(CSharpSyntaxNode), []) } }); result.Should().BeIgnoringLineEndings( """ // /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Immutable; namespace SonarAnalyzer.ShimLayer; public readonly partial struct RecordDeclarationSyntaxWrapper: ISyntaxWrapper { public const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax"; private static readonly Type WrappedType; private readonly TypeDeclarationSyntax node; static RecordDeclarationSyntaxWrapper() { WrappedType = SyntaxNodeTypes.LatestType(typeof(RecordDeclarationSyntaxWrapper)); MembersAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Members"); ArgumentsAccessor = LightupHelpers.CreateSyntaxPropertyAccessor>(WrappedType, "Arguments"); ArmsAccessor = LightupHelpers.CreateSeparatedSyntaxListPropertyAccessor(WrappedType, nameof(Arms)); } private RecordDeclarationSyntaxWrapper(TypeDeclarationSyntax node) => this.node = node; public TypeDeclarationSyntax Node => this.node; [Obsolete("Use Node instead")] public TypeDeclarationSyntax SyntaxNode => this.node; private static readonly Func> MembersAccessor; public SyntaxList Members => (SyntaxList)MembersAccessor(this.node); public SyntaxList AttributeLists => this.node.AttributeLists; private static readonly Func> ArgumentsAccessor; public SeparatedSyntaxList Arguments => (SeparatedSyntaxList)ArgumentsAccessor(this.node); private static readonly Func> ArmsAccessor; public SeparatedSyntaxListWrapper Arms => ArmsAccessor(this.node); public static explicit operator RecordDeclarationSyntaxWrapper(SyntaxNode node) { if (node is null) { return default; } if (!IsInstance(node)) { throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'"); } return new RecordDeclarationSyntaxWrapper((TypeDeclarationSyntax)node); } public static implicit operator TypeDeclarationSyntax(RecordDeclarationSyntaxWrapper wrapper) => wrapper.node; public static bool IsInstance(SyntaxNode node) => node is not null && LightupHelpers.CanWrapNode(node, WrappedType); } """); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.ShimLayer.Generator.Test/packages.lock.json ================================================ { "version": 1, "dependencies": { "net10.0": { "altcover": { "type": "Direct", "requested": "[9.0.102, )", "resolved": "9.0.102", "contentHash": "q3Rf5t0M9kXlcO5qhsaAe6NrFSNd5enrhKmF/Ezgmomqw34PbUTbRSYjSDNhS3YGDyUrPTkyPn14EfLDJWztcA==" }, "Combinatorial.MSTest": { "type": "Direct", "requested": "[2.0.0, )", "resolved": "2.0.0", "contentHash": "9tB2TMPkuEkYYUq64WREHMMyPt9NfKAyuitpK9yw3zbVe6v/vYkClZTR02+yKFUG+g8XHi5LMTt8jHz4RufGqw==", "dependencies": { "MSTest.TestFramework": "4.0.1" } }, "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": { "type": "Direct", "requested": "[3.3.1, )", "resolved": "3.3.1", "contentHash": "eT+kgNCDdTRbQ5WF6BGx1HI3D5jYfHteza/koefhWC2vNZGxObA74XxwWfg40dy3uUv7dn3OGKLK5GUPLroVog==" }, "Microsoft.NET.Test.Sdk": { "type": "Direct", "requested": "[18.4.0, )", "resolved": "18.4.0", "contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==", "dependencies": { "Microsoft.CodeCoverage": "18.4.0", "Microsoft.TestPlatform.TestHost": "18.4.0" } }, "MSTest.TestAdapter": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "lZRgNzaQnffK4XLjM/og4Eoqp/3IkpcyJQQcyKXkPdkzCT3+ghpwHa9zG1xYhQDbUFoc54M+/waLwh31K9stDQ==", "dependencies": { "MSTest.TestFramework": "4.2.1", "Microsoft.Testing.Extensions.VSTestBridge": "2.2.1", "Microsoft.Testing.Platform.MSBuild": "2.2.1" } }, "MSTest.TestFramework": { "type": "Direct", "requested": "[4.2.1, )", "resolved": "4.2.1", "contentHash": "I4/RbS2TpGZ56CE98+jPbrGlcerYtw2LvPVKzQGvyQQcJDekPy2Kd+fnThXYn+geJ1sW+vA9B7++rFNxvKcWxA==", "dependencies": { "MSTest.Analyzers": "4.2.1" } }, "SonarAnalyzer.CSharp.Styling": { "type": "Direct", "requested": "[10.21.0.135717, )", "resolved": "10.21.0.135717", "contentHash": "hl264jF539oB7m2jED5QGM345eFSiDAdoJc8TH0HM6L7ZeqT5TDqZDQeZ8IDP02dVIpH/Fhhn+HsGfEcj8ohyQ==" }, "StyleCop.Analyzers": { "type": "Direct", "requested": "[1.2.0-beta.556, )", "resolved": "1.2.0-beta.556", "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Verify.MSTest": { "type": "Direct", "requested": "[31.16.1, )", "resolved": "31.16.1", "contentHash": "IL/zKYBpM1ERMJ71BtYc1JQfT5VJ8mj9LrlJsLoWe/J7NN546YJsONs7ENWmOWcLvsyd/0+EL+XqnCmOBHHGZA==", "dependencies": { "Argon": "0.33.5", "DiffEngine": "19.1.2", "MSTest.TestFramework": "4.2.1", "SimpleInfoName": "3.2.0", "Verify": "31.16.1" } }, "Argon": { "type": "Transitive", "resolved": "0.33.5", "contentHash": "J6821zxO+EqMzO9C/V5uiWc2eBGyzN7Z8Z0xq3Q1/e6IxYcHDA32OgiZX5/7/f8rVPQQa7aYtm6f0UfnrgKNBg==" }, "Castle.Core": { "type": "Transitive", "resolved": "5.1.1", "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", "dependencies": { "System.Diagnostics.EventLog": "6.0.0" } }, "DiffEngine": { "type": "Transitive", "resolved": "19.1.2", "contentHash": "5fteTgsAXovFNT8wnZT3US51KzVOipI2ptvBG5O27BuHqBPgdu6G8dwAnfF7BoP7RCJyLoT4AyoQ5KS3b3YhCw==", "dependencies": { "EmptyFiles": "8.17.2" } }, "EmptyFiles": { "type": "Transitive", "resolved": "8.17.2", "contentHash": "2oyDVmM/DU3g0h2kqcV05zjOUfo9AdwPoduIGh0LZL6nXqSN4qhZna2M/aJoYiQrmIznJ52wxYCmxDnWaRZ1JQ==" }, "FluentAssertions": { "type": "Transitive", "resolved": "7.2.1", "contentHash": "MilqBF2lZrT0V1azIA6DG6I/snS0rG3A8ohT2Jjkhq6zOFk76nqx4BPnEnzrX6jFVa6xvkDU++z3PebCGZyJ4g==", "dependencies": { "System.Configuration.ConfigurationManager": "6.0.0" } }, "FluentAssertions.Analyzers": { "type": "Transitive", "resolved": "0.34.1", "contentHash": "2BnAAB8CCPdRA9P1+lAvBZOleR2BTmsxGMtGt+LnABJUARGqoGMWEFxG3znZYqHVIrMP0Za/kyw6atyt8x2mzA==" }, "Google.Protobuf": { "type": "Transitive", "resolved": "3.6.1", "contentHash": "741fGeDQjixBJaU2j+0CbrmZXsNJkTn/hWbOh4fLVXndHsCclJmWznCPWrJmPoZKvajBvAz3e8ECJOUvRtwjNQ==", "dependencies": { "NETStandard.Library": "1.6.1" } }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", "dependencies": { "System.Diagnostics.DiagnosticSource": "5.0.0" } }, "Microsoft.Build.Framework": { "type": "Transitive", "resolved": "17.11.48", "contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ==" }, "Microsoft.Build.Locator": { "type": "Transitive", "resolved": "1.11.2", "contentHash": "tY+/S54G29CGsbL3slVu4vqtpciwVnb3fKOmrhgzEQmu/VziFaWmD/E1e/2KH7cDucuycGSkWsSXndBs5Uawow==" }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", "resolved": "5.3.0-2.25625.1", "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.CSharp.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "mRwxchBs3ewXK4dqK4R/eVCx99VIq1k/lhwArlu+fJuV0uzmbkTTRw4jR9gN9sOcAQfX0uV9KQlmCk1yC0JNog==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.CSharp": "[5.3.0]", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.VisualBasic": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "AJxddsIOmfimuihaLuSAm4c/zskHoL1ypAjIpSOZqHlNm2iuw0twsB8nbKczJyfClqD7+iYjdIeE5EV8WAyxRA==", "dependencies": { "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]" } }, "Microsoft.CodeAnalysis.VisualBasic.Workspaces": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "pAGdr4qs7+v287DPiiM8px1cBXnhe8LxymkVGTnCwv2OEjCk5HO2zIoFvype4ivKJTRW3aTUUV8ab+915wbv+w==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "Microsoft.CodeAnalysis.VisualBasic": "[5.3.0]", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.Common": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "QSf1ge9A+XFZbGL+gIqXYBIKlm8QdQVLvHDPZiydG11W6mJY7XBMusrsgIEz6L8GYMzGJKTM78m9icliGMF7NA==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Common": "[5.3.0]", "System.Composition": "9.0.0" } }, "Microsoft.CodeAnalysis.Workspaces.MSBuild": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "2Mg/ppo5dza1e4DJWX4+RIHMc3FgnYzuHTaLRZDupiK1LTi/PAZ1PBpF/iivHVNKQKwipHE984gy37MbM0RO9Q==", "dependencies": { "Humanizer.Core": "2.14.1", "Microsoft.Build.Framework": "17.11.48", "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", "Microsoft.CodeAnalysis.Workspaces.Common": "[5.3.0]", "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0", "Microsoft.VisualStudio.SolutionPersistence": "1.0.52", "System.Composition": "9.0.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow==" }, "Microsoft.Composition": { "type": "Transitive", "resolved": "1.0.27", "contentHash": "pwu80Ohe7SBzZ6i69LVdzowp6V+LaVRzd5F7A6QlD42vQkX0oT7KXKWWPlM/S00w1gnMQMRnEdbtOV12z6rXdQ==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "MCPrg7v3QgNMr0vX4vzRXvkNGgLg8vKWX0nKCWUxu2uPyMsaRgiRc1tHBnbTcfJMhMKj2slE/j2M9oGkd25DNw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "crjWyORoug0kK7RSNJBTeSE6VX8IQgLf3nUpTB9m62bPXp/tzbnOsnbe8TXEG0AASNaKZddnpHKw7fET8E++Pg==", "dependencies": { "Microsoft.Extensions.DependencyInjection": "9.0.0", "Microsoft.Extensions.Logging.Abstractions": "9.0.0", "Microsoft.Extensions.Options": "9.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.Options": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Microsoft.Testing.Extensions.Telemetry": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "7zB8BjffOyvqfHF26rFVPuK0w1fCf5+j1tLuhHIr76CqxXkGb+fMJtq6YNOV+m6qPytExHMXxluk3RgJ+dSIqw==", "dependencies": { "Microsoft.ApplicationInsights": "2.23.0", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.TrxReport.Abstractions": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "RD6D1Jx6cKDA5IHd1H2q8ylIuQG3PD+gdULI0JC8CvsRtaypFzTFpB5xDPuQi8o6kAkcM04cBhAiJPxZboNH2Q==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Extensions.VSTestBridge": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "D8AGlkNtlTQPe3zf4SLnHBMr13lerMe0RuHSoRfnRatcuX/T7YbRtgn39rWBjKhXsNio0WXKrPKv3gfWE2I46w==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.3.0", "Microsoft.Testing.Extensions.Telemetry": "2.2.1", "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.1", "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.Testing.Platform": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "9bbPuls/b6/vUFzxbSjJLZlJHyKBfOZE5kjIY+ITI2ASqlFPJhR83BdLydJeQOCLEZhEbrEcz5xtt1B69nwSVg==" }, "Microsoft.Testing.Platform.MSBuild": { "type": "Transitive", "resolved": "2.2.1", "contentHash": "CSJOcZHfKlTyPbS0CTJk6iEnU4gJC+eUA5z72UBnMDRdgVHYOmB8k9Y7jT233gZjnCOQiYFg3acQHRfu2H62nw==", "dependencies": { "Microsoft.Testing.Platform": "2.2.1" } }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg==", "dependencies": { "System.Reflection.Metadata": "8.0.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", "resolved": "18.4.0", "contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==", "dependencies": { "Microsoft.TestPlatform.ObjectModel": "18.4.0", "Newtonsoft.Json": "13.0.3" } }, "Microsoft.VisualStudio.SolutionPersistence": { "type": "Transitive", "resolved": "1.0.52", "contentHash": "oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" }, "MSTest.Analyzers": { "type": "Transitive", "resolved": "4.2.1", "contentHash": "1i9jgE/42KGGyZ4s0MdrYM/Uu/dRYhbRfYQifcO0AZ6vw4sBXRjoQGQRGNSm771AYgPAmoGl0u4sJc2lMET6HQ==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, "NSubstitute": { "type": "Transitive", "resolved": "5.3.0", "contentHash": "lJ47Cps5Qzr86N99lcwd+OUvQma7+fBgr8+Mn+aOC0WrlqMNkdivaYD9IvnZ5Mqo6Ky3LS7ZI+tUq1/s9ERd0Q==", "dependencies": { "Castle.Core": "5.1.1" } }, "NuGet.Common": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "1mp7zmyAGmQfT93ELC4c+MYOrJ8Ff4ekFZRek4JSVLb/wOO/o0bsPfLmqujCsJ2Hlwc+fpq1TQEnjSEgWdt8ng==", "dependencies": { "NuGet.Frameworks": "7.3.1" } }, "NuGet.Configuration": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "tGxWBo47EQONOaqY+MbEWMrNjFthHgfavLM1HE0RcLyOXVCoQKBlZGV7v0hrS/rJrQKw6ZaBeHetX+ZJgS7Lxg==", "dependencies": { "NuGet.Common": "7.3.1", "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "NuGet.Frameworks": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "VUPAE5l/Ir4Gb3dv+v0jq0Qe4nfwxfrfiYN7QhwlGyWPXFKYXSSke1t1bV/ZYd6idtTtRDqJPM49Lt/U8NTocg==" }, "NuGet.Packaging": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "5bT8uOrNBx4Srhbrd3HonYmyKhWJkvQyTWTYE2jvfPgYx2aCbPmq8MCYko7ce78hEN9mpq2xlrztVhguYPc+GQ==", "dependencies": { "Newtonsoft.Json": "13.0.3", "NuGet.Configuration": "7.3.1", "NuGet.Versioning": "7.3.1", "System.Security.Cryptography.Pkcs": "8.0.1" } }, "NuGet.Protocol": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "vYd5vtCJ/tpMQjbAs6rrftNgeh5Ip1w7tnEsDZCpGObKgUgOxHQBekCul3TnzmbNgobvJEkDJNaec5/ZBv66Hg==", "dependencies": { "NuGet.Packaging": "7.3.1" } }, "NuGet.Versioning": { "type": "Transitive", "resolved": "7.3.1", "contentHash": "TJrQWSmD1vakfav7qIhDXgDFfaZFfMJ+v6P8tcND9ZqXajD5B/ZzaoGYNzL4D3eDue6vAOUvwzu42G+19JNVUA==" }, "SimpleInfoName": { "type": "Transitive", "resolved": "3.2.0", "contentHash": "K8ivHRbPWfncijk62Dan/r/z55gwq3aFzqB6yFlD9X0bbpIaacHyHH2cpcIdz0FECUpERUZTwxts0z4gRWpQpA==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", "resolved": "1.2.0.556", "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "System.Collections.Immutable": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" }, "System.Composition": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Convention": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0", "System.Composition.TypedParts": "9.0.0" } }, "System.Composition.AttributedModel": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==" }, "System.Composition.Convention": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==", "dependencies": { "System.Composition.AttributedModel": "9.0.0" } }, "System.Composition.Hosting": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==", "dependencies": { "System.Composition.Runtime": "9.0.0" } }, "System.Composition.Runtime": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==" }, "System.Composition.TypedParts": { "type": "Transitive", "resolved": "9.0.0", "contentHash": "aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==", "dependencies": { "System.Composition.AttributedModel": "9.0.0", "System.Composition.Hosting": "9.0.0", "System.Composition.Runtime": "9.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { "System.Security.Cryptography.ProtectedData": "6.0.0", "System.Security.Permissions": "6.0.0" } }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw==" }, "System.Drawing.Common": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", "dependencies": { "System.Collections.Immutable": "8.0.0" } }, "System.Reflection.MetadataLoadContext": { "type": "Transitive", "resolved": "10.0.5", "contentHash": "z9yyFZcuCwtZXrxxDc2nBfB5lTHf96gS/rsD38Lcv6Ok25TOYPjNKlqpTZUqu2HTLPAM4w+/WjGL6gi9cIJO6w==" }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.1", "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", "dependencies": { "System.Security.AccessControl": "6.0.0", "System.Windows.Extensions": "6.0.0" } }, "System.Windows.Extensions": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", "dependencies": { "System.Drawing.Common": "6.0.0" } }, "Verify": { "type": "Transitive", "resolved": "31.16.1", "contentHash": "nw/NcGR7RjkFyin5Vh8Txm7jn+TDi9PI4dYaFUj3gKx5oBGEqCdEvHfiTOZ3rshHzcQcfVi669itNx0ApFwTxA==", "dependencies": { "Argon": "0.33.5", "DiffEngine": "19.1.2", "SimpleInfoName": "3.2.0" } }, "Internal.SonarAnalyzer.ShimLayer.Generator": { "type": "Project", "dependencies": { "System.Reflection.MetadataLoadContext": "[10.0.5, )" } }, "sonaranalyzer.cfg": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer.Lightup": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.core": { "type": "Project", "dependencies": { "Google.Protobuf": "[3.6.1, )", "Microsoft.CodeAnalysis.Workspaces.Common": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.CFG": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.shimlayer.lightup": { "type": "Project", "dependencies": { "Microsoft.CodeAnalysis.CSharp.Workspaces": "[1.3.2, )", "Microsoft.Composition": "[1.0.27, )", "SonarAnalyzer.ShimLayer": "[10.26.0, )", "System.Collections.Immutable": "[1.1.37, )" } }, "sonaranalyzer.testframework": { "type": "Project", "dependencies": { "Combinatorial.MSTest": "[2.0.0, )", "FluentAssertions": "[7.2.1, )", "FluentAssertions.Analyzers": "[0.34.1, )", "MSTest.TestAdapter": "[4.2.1, )", "MSTest.TestFramework": "[4.2.1, )", "Microsoft.Build.Locator": "[1.11.2, )", "Microsoft.CodeAnalysis.CSharp.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[5.3.0, )", "Microsoft.CodeAnalysis.Workspaces.MSBuild": "[5.3.0, )", "Microsoft.NET.Test.Sdk": "[18.4.0, )", "NSubstitute": "[5.3.0, )", "NuGet.Protocol": "[7.3.1, )", "SonarAnalyzer.Core": "[10.26.0, )", "altcover": "[9.0.102, )" } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/AnalysisContext/SonarAnalysisContextBaseTest.ShouldAnalyzeTree.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.Syntax.Utilities; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.AnalysisContext; public partial class SonarAnalysisContextBaseTest { private const string GeneratedFileName = "Generated.g."; private const string OtherFileName = "OtherFile"; [TestMethod] public void ShouldAnalyzeTree_SonarLint() { var options = AnalysisScaffolding.CreateOptions(); // No SonarProjectConfig.xml ShouldAnalyzeTree(options).Should().BeTrue(); } [TestMethod] public void ShouldAnalyzeTree_Scanner_UnchangedFiles_NotAvailable() { var options = AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)); // SonarProjectConfig.xml without UnchangedFiles.txt ShouldAnalyzeTree(options).Should().BeTrue(); } [TestMethod] public void ShouldAnalyzeTree_Scanner_UnchangedFiles_Empty() { var options = CreateOptions(Array.Empty()); ShouldAnalyzeTree(options).Should().BeTrue(); } [TestMethod] public void ShouldAnalyzeTree_Scanner_UnchangedFiles_ContainsTreeFile() { var options = CreateOptions(new[] { OtherFileName + ".cs" }); ShouldAnalyzeTree(options).Should().BeFalse("File is known to be Unchanged in Incremental PR analysis"); } [TestMethod] public void ShouldAnalyzeTree_Scanner_UnchangedFiles_ContainsOtherFile() { var options = CreateOptions(new[] { "ThisIsNotInCompilation.cs", "SomethingElse.cs" }); ShouldAnalyzeTree(options).Should().BeTrue(); } [TestMethod] public void ShouldAnalyzeTree_Scanner_WhenRazorFileHasNotChanged_ReturnsFalseForTheAssociatedGeneratedFile() { // The generated files have a different root that is not absolute and might not exist on disk. const string generatedFileName = "Microsoft.NET.Sdk.Razor.SourceGenerators\\Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator\\Pages_Component_razor.g"; const string content = """ #pragma checksum "C:\Component.razor" "{8829d00f-11b8-4213-878b-770e8597ac16}" "35c3e85c77eb4f50f311a96f96be44f36c36b570ce2579ec311010076f7ac44d" """; var options = CreateOptions(new[] { @"C:\Component.razor" }); // In the configuration file we always provide full paths. ShouldAnalyzeTree(options, generatedFileName, content).Should().BeFalse("File is known to be Unchanged in Incremental PR analysis."); } [TestMethod] [DataRow(GeneratedFileName, false)] [DataRow(OtherFileName, true)] public void ShouldAnalyzeTree_GeneratedFile_NoSonarLintXml(string fileName, bool expected) { var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var (compilation, tree) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml, @"TestResources\Foo.xml")); sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().Be(expected); sonarLintXml.ToStringCallCount.Should().Be(0, "this file doesn't have 'SonarLint.xml' name"); } [TestMethod] public void ShouldAnalyzeTree_GeneratedFile_ShouldAnalyzeGeneratedProvider_IsCached() { var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var additionalText = Substitute.For(); additionalText.Path.Returns("SonarLint.xml"); additionalText.GetText(default).Returns(sonarLintXml); var tree = CreateDummyCompilation(AnalyzerLanguage.CSharp, OtherFileName).Tree; var sut = CreateSut(new AnalyzerOptions(ImmutableArray.Create(additionalText))); // Call ShouldAnalyzeGenerated multiple times... sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().BeTrue(); sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().BeTrue(); sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().BeTrue(); // GetText should be called every time ShouldAnalyzeGenerated is called... additionalText.Received(3).GetText(Arg.Any()); sonarLintXml.ToStringCallCount.Should().Be(1); // ... but we should only try to read the file once } [TestMethod] [DataRow(GeneratedFileName, false)] [DataRow(OtherFileName, true)] public void ShouldAnalyzeTree_GeneratedFile_InvalidSonarLintXml(string fileName, bool expected) { var sonarLintXml = new DummySourceText("Not valid xml"); var (compilation, tree) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml)); // 1. Read -> no error sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().Be(expected); sonarLintXml.ToStringCallCount.Should().Be(1); // should have attempted to read the file // 2. Read again to check that the load error doesn't prevent caching from working sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().Be(expected); sonarLintXml.ToStringCallCount.Should().Be(1); // should not have attempted to read the file again } [TestMethod] [DataRow(GeneratedFileName)] [DataRow(OtherFileName)] public void ShouldAnalyzeTree_GeneratedFile_AnalyzeGenerated_AnalyzeAllFiles(string fileName) { var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(analyzeGeneratedCode: true)); var (compilation, tree) = CreateDummyCompilation(AnalyzerLanguage.CSharp, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml)); sut.ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance).Should().BeTrue(); } [TestMethod] [DataRow(GeneratedFileName, LanguageNames.CSharp, false)] [DataRow(OtherFileName, LanguageNames.CSharp, true)] [DataRow(GeneratedFileName, LanguageNames.VisualBasic, false)] [DataRow(OtherFileName, LanguageNames.VisualBasic, true)] public void ShouldAnalyzeTree_CorrectSettingUsed(string fileName, string language, bool expected) { var sonarLintXml = new DummySourceText(AnalysisScaffolding.GenerateSonarLintXmlContent(language: language, analyzeGeneratedCode: false)); var analyzerLanguage = language == LanguageNames.CSharp ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; var (compilation, tree) = CreateDummyCompilation(analyzerLanguage, fileName); var sut = CreateSut(compilation, CreateOptions(sonarLintXml)); GeneratedCodeRecognizer generatedCodeRecognizer = language == LanguageNames.CSharp ? CSharpGeneratedCodeRecognizer.Instance : VisualBasicGeneratedCodeRecognizer.Instance; sut.ShouldAnalyzeTree(tree, generatedCodeRecognizer).Should().Be(expected); sonarLintXml.ToStringCallCount.Should().Be(1, "file should be read once per language"); // Read again to check caching sut.ShouldAnalyzeTree(tree, generatedCodeRecognizer).Should().Be(expected); sonarLintXml.ToStringCallCount.Should().Be(1, "file should not have been read again"); } // Until https://github.com/SonarSource/sonar-dotnet/issues/2228, we were considering a file as generated if the word "generated" was contained inside a region. [TestMethod] [DataRow("generated stuff")] [DataRow("Contains FooGenerated methods")] [DataRow("Windows Form Designer generated code")] // legacy Windows Forms used to include generated code in dev files, surrounded by such a region [DataRow("Windows Form Designer GeNeRaTed code")] // legacy Windows Forms used to include generated code in dev files, surrounded by such a region public void ShouldAnalyzeTree_IssuesRaisedOnPartiallyGenerated_LegacyWinFormsFile(string regionName) { new VerifierBuilder() .AddSnippet($$""" class Sample { void HandWrittenEventHandler() { ; // Noncompliant } #region {{regionName}} void GeneratedStuff() { ; // Noncompliant } #endregion } """) .Verify(); } [TestMethod] public void ShouldAnalyzeTree_NoIssue_OnGeneratedFile_WithGeneratedName() { const string sourceCS = """ class Sample { void MethodWithEmptyStatement() { ; // Noncompliant } } """; const string sourceVB = """ Module Sample Sub Main() Dim Foo() As String ' Noncompliant End Sub End Module """; VerifyEmpty("test.g.cs", sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.g.vb", sourceVB, new VB.ArrayDesignatorOnVariable()); } [TestMethod] public void ShouldAnalyzeTree_NoIssueOnGeneratedFile_WithAutoGeneratedComment() { const string autogeneratedExpandedTagCS = """ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:2.0.50727.3053 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ """; const string autogeneratedCollapsedTagCS = """ // """; const string autogeneratedGeneratedBySwaggerCS = """ /* * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) * * OpenAPI spec version: 1.0.0 * * Generated by: https://github.com/swagger-api/swagger-codegen.git */ """; const string autogeneratedGeneratedByProtobufCS = "// Generated by the protocol buffer compiler. DO NOT EDIT!"; const string sourceCS = """ // Extra line for concatenation class Sample { void MethodWithEmptyStatement() { ; // Noncompliant } } """; const string sourceVB = """ '------------------------------------------------------------------------------ ' ' This code was generated by a tool. ' Runtime Version:2.0.50727.4927 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' '------------------------------------------------------------------------------ Module Module1 Sub Main() Dim Foo() As String ' Noncompliant End Sub End Module """; VerifyEmpty("test.cs", autogeneratedExpandedTagCS + sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.cs", autogeneratedCollapsedTagCS + sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.cs", autogeneratedGeneratedBySwaggerCS + sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.cs", autogeneratedGeneratedByProtobufCS + sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.vb", sourceVB, new VB.ArrayDesignatorOnVariable()); } [TestMethod] public void ShouldAnalyzeTree_NoIssueOnGeneratedFile_WithExcludedAttribute() { const string sourceCS = """ class Sample { [System.Diagnostics.DebuggerNonUserCodeAttribute()] void M() { ; // Noncompliant } } """; const string sourceVB = """ Module Module1 Sub Main() Dim Foo() As String ' Noncompliant End Sub End Module """; VerifyEmpty("test.cs", sourceCS, new CS.EmptyStatement()); VerifyEmpty("test.vb", sourceVB, new VB.ArrayDesignatorOnVariable()); } [TestMethod] public void ShouldAnalyzeTree_NoIssueOnGeneratedAnnotatedLambda_WithExcludedAttribute() { const string sourceCs = """ using System; using System.Diagnostics; using System.Runtime.CompilerServices; class Sample { public void Bar() { [DebuggerNonUserCodeAttribute()] int Do() => 1; Action a = [CompilerGenerated] () => { ;;; }; Action x = true ? ([DebuggerNonUserCodeAttribute] () => { ;;; }) : [GenericAttribute] () => { ;;; }; // FN? Empty statement in lambda Call([DebuggerNonUserCodeAttribute] (x) => { ;;; }); } private void Call(Action action) => action(1); } public class GenericAttribute : Attribute { } """; VerifyEmpty("test.cs", sourceCs, new CS.EmptyStatement()); } [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, false)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] public void ShouldAnalyzeTree_Exclusions_ReturnExpected(string filePath, string[] exclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, exclusions: exclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, false)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] public void ShouldAnalyzeTree_GlobalExclusions_ReturnExpected(string filePath, string[] globalExclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, globalExclusions: globalExclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, false)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] public void ShouldAnalyzeTree_TestExclusions_ReturnExpected(string filePath, string[] testExclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, testExclusions: testExclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, false)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] public void ShouldAnalyzeTree_GlobalTestExclusions_ReturnExpected(string filePath, string[] globalTestExclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, globalTestExclusions: globalTestExclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, false)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, true)] public void ShouldAnalyzeTree_Inclusions_ReturnExpected(string filePath, string[] inclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, inclusions: inclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Product, true)] [DataRow("Foo", new string[] { "Foo" }, ProjectType.Test, true)] [DataRow("Foo", new string[] { "NotFoo" }, ProjectType.Test, false)] public void ShouldAnalyzeTree_TestInclusions_ReturnExpected(string filePath, string[] testInclusions, ProjectType projectType, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, projectType, expectedResult, testInclusions: testInclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, new string[] { "Foo" }, false)] [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "Foo" }, false)] [DataRow("Foo", new string[] { "Foo" }, new string[] { "NotFoo" }, true)] [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "NotFoo" }, false)] public void ShouldAnalyzeTree_MixedInput_ProductProject_ReturnExpected(string filePath, string[] inclusions, string[] exclusions, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, ProjectType.Product, expectedResult, inclusions: inclusions, exclusions: exclusions); [TestMethod] [DataRow("Foo", new string[] { "Foo" }, new string[] { "Foo" }, false)] [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "Foo" }, false)] [DataRow("Foo", new string[] { "Foo" }, new string[] { "NotFoo" }, true)] [DataRow("Foo", new string[] { "NotFoo" }, new string[] { "NotFoo" }, false)] public void ShouldAnalyzeTree_MixedInput_TestProject_ReturnExpected(string filePath, string[] testInclusions, string[] testExclusions, bool expectedResult) => ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly(filePath, ProjectType.Test, expectedResult, testInclusions: testInclusions, testExclusions: testExclusions); private void ShouldAnalyzeTree_WithExclusionInclusionParametersSet_ReturnsTrueForIncludedFilesOnly( string fileName, ProjectType projectType, bool shouldAnalyze, string language = LanguageNames.CSharp, string[] exclusions = null, string[] inclusions = null, string[] globalExclusions = null, string[] testExclusions = null, string[] testInclusions = null, string[] globalTestExclusions = null) { var analyzerLanguage = language == LanguageNames.CSharp ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; var sonarLintXml = AnalysisScaffolding.CreateSonarLintXml( TestContext, language: language, exclusions: exclusions, inclusions: inclusions, globalExclusions: globalExclusions, testExclusions: testExclusions, testInclusions: testInclusions, globalTestExclusions: globalTestExclusions); var options = AnalysisScaffolding.CreateOptions(sonarLintXml); var compilation = SolutionBuilder .Create() .AddProject(analyzerLanguage) .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .AddSnippet(string.Empty, fileName) .GetCompilation(); var tree = compilation.SyntaxTrees.Single(x => x.FilePath.Contains(fileName)); var sut = CreateSut(compilation, options); GeneratedCodeRecognizer codeRecognizer = language == LanguageNames.CSharp ? CSharpGeneratedCodeRecognizer.Instance : VisualBasicGeneratedCodeRecognizer.Instance; sut.ShouldAnalyzeTree(tree, codeRecognizer).Should().Be(shouldAnalyze); } private AnalyzerOptions CreateOptions(string[] unchangedFiles) => AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, unchangedFiles)); private static AnalyzerOptions CreateOptions(SourceText sourceText, string path = @"TestResources\SonarLint.xml") => AnalysisScaffolding.CreateOptions(path, sourceText); private static (Compilation Compilation, SyntaxTree Tree) CreateDummyCompilation(AnalyzerLanguage language, string treeFileName) { var compilation = SolutionBuilder.Create().AddProject(language) .AddSnippet(string.Empty, GeneratedFileName + language.FileExtension) .AddSnippet(string.Empty, OtherFileName + language.FileExtension) .GetCompilation(); return (compilation, compilation.SyntaxTrees.Single(x => x.FilePath.Contains(treeFileName))); } private static bool ShouldAnalyzeTree(AnalyzerOptions options, string fileName = OtherFileName, string content = "") { var compilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).AddSnippet(content, fileName + AnalyzerLanguage.CSharp.FileExtension).GetCompilation(); var tree = compilation.SyntaxTrees.Single(x => x.FilePath.Contains(fileName)); return CreateSut(options).ShouldAnalyzeTree(tree, CSharpGeneratedCodeRecognizer.Instance); } private static void VerifyEmpty(string fileName, string snippet, DiagnosticAnalyzer analyzer) { var builder = new VerifierBuilder().AddAnalyzer(() => analyzer).AddSnippet(snippet, fileName); var language = AnalyzerLanguage.FromPath(fileName); builder = language.LanguageName switch { LanguageNames.CSharp => builder.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Latest), LanguageNames.VisualBasic => builder.WithLanguageVersion(Microsoft.CodeAnalysis.VisualBasic.LanguageVersion.Latest), _ => throw new UnexpectedLanguageException(language) }; builder.VerifyNoIssues(); } private sealed class DummySourceText : SourceText { private readonly string textToReturn; public int ToStringCallCount { get; private set; } public override char this[int position] => throw new NotImplementedException(); public override Encoding Encoding => throw new NotImplementedException(); public override int Length => throw new NotImplementedException(); public DummySourceText(string textToReturn) => this.textToReturn = textToReturn; public override string ToString() { ToStringCallCount++; return textToReturn; } public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) => throw new NotImplementedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/AnalysisContext/SonarAnalysisContextBaseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; namespace SonarAnalyzer.Test.AnalysisContext; [TestClass] public partial class SonarAnalysisContextBaseTest { private const string MainTag = "MainSourceScope"; private const string TestTag = "TestSourceScope"; private const string UtilityTag = "Utility"; private const string DummyID = "Sxxx"; private const string StyleID = "Txxx"; public TestContext TestContext { get; set; } [TestMethod] [DataRow(true, ProjectType.Product, MainTag)] [DataRow(true, ProjectType.Product, MainTag, UtilityTag)] [DataRow(true, ProjectType.Product, MainTag, TestTag)] [DataRow(true, ProjectType.Product, MainTag, TestTag, UtilityTag)] [DataRow(true, ProjectType.Test, TestTag)] [DataRow(true, ProjectType.Test, TestTag, UtilityTag)] [DataRow(true, ProjectType.Test, MainTag, TestTag)] [DataRow(true, ProjectType.Test, MainTag, TestTag, UtilityTag)] [DataRow(false, ProjectType.Product, TestTag)] [DataRow(false, ProjectType.Product, TestTag, TestTag)] [DataRow(false, ProjectType.Test, MainTag)] [DataRow(false, ProjectType.Test, MainTag, MainTag)] public void HasMatchingScope_SingleDiagnostic_WithOneOrMoreScopes_SonarLint(bool expectedResult, ProjectType projectType, params string[] ruleTags) { var sut = CreateSut(projectType, false); sut.HasMatchingScope(AnalysisScaffolding.CreateDescriptor(DummyID, ruleTags)).Should().Be(expectedResult); sut.HasMatchingScope(AnalysisScaffolding.CreateDescriptor(StyleID, ruleTags)).Should().Be(expectedResult); } [TestMethod] [DataRow(true, DummyID, ProjectType.Product, MainTag)] [DataRow(true, StyleID, ProjectType.Product, MainTag)] [DataRow(true, DummyID, ProjectType.Product, MainTag, UtilityTag)] [DataRow(true, DummyID, ProjectType.Product, MainTag, TestTag)] [DataRow(true, StyleID, ProjectType.Product, MainTag, TestTag)] [DataRow(true, DummyID, ProjectType.Product, MainTag, TestTag, UtilityTag)] [DataRow(true, DummyID, ProjectType.Test, TestTag)] [DataRow(true, StyleID, ProjectType.Test, TestTag)] [DataRow(true, DummyID, ProjectType.Test, TestTag, UtilityTag)] [DataRow(true, DummyID, ProjectType.Test, MainTag, TestTag, UtilityTag)] // Utility rules with scope Test&Main do run on test code under scanner context. [DataRow(false, DummyID, ProjectType.Test, MainTag, TestTag)] // Rules with scope Test&Main do not run on test code under scanner context for now. [DataRow(true, StyleID, ProjectType.Test, MainTag, TestTag)] // Style rules with Test&Main scope do run on test code under scanner context [DataRow(false, DummyID, ProjectType.Product, TestTag)] [DataRow(false, StyleID, ProjectType.Product, TestTag)] [DataRow(false, DummyID, ProjectType.Product, TestTag, UtilityTag)] [DataRow(false, DummyID, ProjectType.Product, TestTag, TestTag)] [DataRow(false, StyleID, ProjectType.Product, TestTag, TestTag)] [DataRow(false, DummyID, ProjectType.Test, MainTag)] [DataRow(false, StyleID, ProjectType.Test, MainTag)] [DataRow(false, DummyID, ProjectType.Test, MainTag, UtilityTag)] [DataRow(false, DummyID, ProjectType.Test, MainTag, MainTag)] [DataRow(false, StyleID, ProjectType.Test, MainTag, MainTag)] public void HasMatchingScope_SingleDiagnostic_WithOneOrMoreScopes_Scanner(bool expectedResult, string id, ProjectType projectType, params string[] ruleTags) { var diagnostic = AnalysisScaffolding.CreateDescriptor(id, ruleTags); CreateSut(projectType, true).HasMatchingScope(diagnostic).Should().Be(expectedResult); } [TestMethod] [DataRow(true, ProjectType.Product, MainTag, MainTag)] [DataRow(true, ProjectType.Product, MainTag, MainTag)] [DataRow(true, ProjectType.Product, MainTag, TestTag)] [DataRow(true, ProjectType.Test, TestTag, TestTag)] [DataRow(true, ProjectType.Test, TestTag, MainTag)] [DataRow(false, ProjectType.Product, TestTag, TestTag)] [DataRow(false, ProjectType.Test, MainTag, MainTag)] public void HasMatchingScope_MultipleDiagnostics_WithSingleScope_SonarLint(bool expectedResult, ProjectType projectType, params string[] rulesTag) { var diagnostics = rulesTag.Select(x => AnalysisScaffolding.CreateDescriptor(DummyID, x)).ToImmutableArray(); CreateSut(projectType, false).HasMatchingScope(diagnostics).Should().Be(expectedResult); } [TestMethod] [DataRow(true, ProjectType.Product, MainTag, MainTag)] [DataRow(true, ProjectType.Product, MainTag, TestTag)] [DataRow(true, ProjectType.Test, TestTag, TestTag)] [DataRow(true, ProjectType.Test, TestTag, MainTag)] // Rules with scope Test&Main will run to let the Test diagnostics to be detected. ReportDiagnostic should filter Main issues out. [DataRow(false, ProjectType.Product, TestTag, TestTag)] [DataRow(false, ProjectType.Test, MainTag, MainTag)] public void HasMatchingScope_MultipleDiagnostics_WithSingleScope_Scanner(bool expectedResult, ProjectType projectType, params string[] rulesTag) { var diagnostics = rulesTag.Select(x => AnalysisScaffolding.CreateDescriptor(DummyID, x)).ToImmutableArray(); CreateSut(projectType, true).HasMatchingScope(diagnostics).Should().Be(expectedResult); } [TestMethod] public void ProjectConfiguration_LoadsExpectedValues() { var options = AnalysisScaffolding.CreateOptions(@"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"); var config = CreateSut(options).ProjectConfiguration(); config.AnalysisConfigPath.Should().Be(@"c:\foo\bar\.sonarqube\conf\SonarQubeAnalysisConfig.xml"); } [TestMethod] public void ProjectConfiguration_UsesCachedValue() { var options = AnalysisScaffolding.CreateOptions(@"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"); var firstSut = CreateSut(options); var secondSut = CreateSut(options); var firstConfig = firstSut.ProjectConfiguration(); var secondConfig = secondSut.ProjectConfiguration(); secondConfig.Should().BeSameAs(firstConfig); } [TestMethod] public void ProjectConfiguration_WhenFileChanges_RebuildsCache() { var firstOptions = AnalysisScaffolding.CreateOptions(@"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"); var secondOptions = AnalysisScaffolding.CreateOptions(@"TestResources\SonarProjectConfig\Path_Unix\SonarProjectConfig.xml"); var firstConfig = CreateSut(firstOptions).ProjectConfiguration(); var secondConfig = CreateSut(secondOptions).ProjectConfiguration(); secondConfig.Should().NotBeSameAs(firstConfig); } [TestMethod] [DataRow(null)] [DataRow("/foo/bar/does-not-exit")] [DataRow("/foo/bar/x.xml")] public void ProjectConfiguration_WhenAdditionalFileNotPresent_ReturnsEmptyConfig(string folder) { var options = AnalysisScaffolding.CreateOptions(folder); var config = CreateSut(options).ProjectConfiguration(); config.AnalysisConfigPath.Should().BeNull(); config.ProjectPath.Should().BeNull(); config.FilesToAnalyzePath.Should().BeNull(); config.OutPath.Should().BeNull(); config.ProjectType.Should().Be(ProjectType.Unknown); config.TargetFramework.Should().BeNull(); } [TestMethod] public void ProjectConfiguration_WhenFileIsMissing_ThrowException() { var sut = CreateSut(AnalysisScaffolding.CreateOptions(@"ThisPathDoesNotExist\SonarProjectConfig.xml")); sut.Invoking(x => x.ProjectConfiguration()) .Should() .Throw() .WithMessage("File 'SonarProjectConfig.xml' has been added as an AdditionalFile but could not be read and parsed."); } [TestMethod] public void ProjectConfiguration_WhenInvalidXml_ThrowException() { var sut = CreateSut(AnalysisScaffolding.CreateOptions(@"TestResources\SonarProjectConfig\Invalid_Xml\SonarProjectConfig.xml")); sut.Invoking(x => x.ProjectConfiguration()) .Should() .Throw() .WithMessage("File 'SonarProjectConfig.xml' has been added as an AdditionalFile but could not be read and parsed."); } [TestMethod] [DataRow("cs")] [DataRow("vbnet")] public void SonarLintFile_LoadsExpectedValues(string language) { var analyzerLanguage = language == "cs" ? AnalyzerLanguage.CSharp : AnalyzerLanguage.VisualBasic; var (compilation, _) = CreateDummyCompilation(analyzerLanguage, GeneratedFileName); var options = AnalysisScaffolding.CreateOptions($@"TestResources\SonarLintXml\All_properties_{language}\SonarLint.xml"); var sut = CreateSut(compilation, options).SonarLintXml(); sut.IgnoreHeaderComments(analyzerLanguage.LanguageName).Should().BeTrue(); sut.AnalyzeGeneratedCode(analyzerLanguage.LanguageName).Should().BeFalse(); sut.AnalyzeRazorCode(analyzerLanguage.LanguageName).Should().BeFalse(); AssertArrayContent(sut.Exclusions, nameof(sut.Exclusions)); AssertArrayContent(sut.Inclusions, nameof(sut.Inclusions)); AssertArrayContent(sut.GlobalExclusions, nameof(sut.GlobalExclusions)); AssertArrayContent(sut.TestExclusions, nameof(sut.TestExclusions)); AssertArrayContent(sut.TestInclusions, nameof(sut.TestInclusions)); AssertArrayContent(sut.GlobalTestExclusions, nameof(sut.GlobalTestExclusions)); static void AssertArrayContent(string[] array, string folder) { array.Should().HaveCount(2); array[0].Should().BeEquivalentTo($"Fake/{folder}/**/*"); array[1].Should().BeEquivalentTo($"Fake/{folder}/Second*/**/*"); } } [TestMethod] public void SonarLintFile_UsesCachedValue() { var options = AnalysisScaffolding.CreateOptions(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"); var firstSut = CreateSut(options); var secondSut = CreateSut(options); var firstFile = firstSut.SonarLintXml(); var secondFile = secondSut.SonarLintXml(); secondFile.Should().BeSameAs(firstFile); } [TestMethod] public void SonarLintFile_WhenFileChanges_RebuildsCache() { var firstOptions = AnalysisScaffolding.CreateOptions(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"); var secondOptions = AnalysisScaffolding.CreateOptions(@"TestResources\SonarLintXml\All_properties_vbnet\SonarLint.xml"); var firstFile = CreateSut(firstOptions).SonarLintXml(); var secondFile = CreateSut(secondOptions).SonarLintXml(); secondFile.Should().NotBeSameAs(firstFile); } [TestMethod] [DataRow(null)] [DataRow(@"\foo\bar\does-not-exit")] [DataRow(@"\foo\bar\x.xml")] [DataRow("path//aSonarLint.xml")] // different name [DataRow("path//SonarLint.xmla")] // different extension public void SonarLintFile_WhenAdditionalFileNotPresent_ReturnsDefaultValues(string folder) { var sut = CreateSut(AnalysisScaffolding.CreateOptions(folder)).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } [TestMethod] public void SonarLintFile_WhenInvalidXml_ReturnsDefaultValues() { var sut = CreateSut(AnalysisScaffolding.CreateOptions(@"TestResources\SonarLintXml\Invalid_Xml\SonarLint.xml")).SonarLintXml(); CheckSonarLintXmlDefaultValues(sut); } [TestMethod] public void SonarLintFile_WhenFileIsMissing_ThrowException() { var sut = CreateSut(AnalysisScaffolding.CreateOptions(@"ThisPathDoesNotExist\SonarLint.xml")); sut.Invoking(x => x.SonarLintXml()) .Should() .Throw() .WithMessage("File 'SonarLint.xml' has been added as an AdditionalFile but could not be read and parsed."); } [TestMethod] public void ReportIssue_Null_Throws() { var compilation = TestCompiler.CompileCS("// Nothing to see here").Model.Compilation; var sut = CreateSut(ProjectType.Product, false); var rule = AnalysisScaffolding.CreateDescriptor("Sxxxx", DiagnosticDescriptorFactory.MainSourceScopeTag); var recognizer = CSharpGeneratedCodeRecognizer.Instance; sut.Invoking(x => x.ReportIssue(recognizer, null, primaryLocation: null, secondaryLocations: [])).Should().Throw().And.ParamName.Should().Be("rule"); sut.Invoking(x => x.ReportIssue(recognizer, rule, primaryLocation: null, secondaryLocations: null)).Should().NotThrow(); } [TestMethod] public void ReportIssue_NullLocation_UsesEmpty() { Diagnostic lastDiagnostic = null; var compilation = TestCompiler.CompileCS("// Nothing to see here").Model.Compilation; var compilationContext = new CompilationAnalysisContext(compilation, AnalysisScaffolding.CreateOptions(), x => lastDiagnostic = x, _ => true, default); var sut = new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), compilationContext); var rule = AnalysisScaffolding.CreateDescriptor("Sxxxx", DiagnosticDescriptorFactory.MainSourceScopeTag); var recognizer = CSharpGeneratedCodeRecognizer.Instance; sut.ReportIssue(recognizer, rule, location: null); lastDiagnostic.Should().NotBeNull(); lastDiagnostic.Location.Should().Be(Location.None); sut.ReportIssue(recognizer, rule, primaryLocation: null, secondaryLocations: []); lastDiagnostic.Should().NotBeNull(); lastDiagnostic.Location.Should().Be(Location.None); } private static void CheckSonarLintXmlDefaultValues(SonarLintXmlReader sut) { sut.AnalyzeRazorCode(LanguageNames.CSharp).Should().BeTrue(); sut.AnalyzeGeneratedCode(LanguageNames.CSharp).Should().BeFalse(); sut.IgnoreHeaderComments(LanguageNames.CSharp).Should().BeFalse(); sut.Exclusions.Should().NotBeNull().And.HaveCount(0); sut.Inclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestExclusions.Should().NotBeNull().And.HaveCount(0); sut.TestInclusions.Should().NotBeNull().And.HaveCount(0); sut.GlobalTestExclusions.Should().NotBeNull().And.HaveCount(0); } private SonarCompilationReportingContext CreateSut(ProjectType projectType, bool isScannerRun) => CreateSut(AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType, isScannerRun))); private static SonarCompilationReportingContext CreateSut(AnalyzerOptions options) => CreateSut(new SnippetCompiler("// Nothing to see here").Model.Compilation, options); private static SonarCompilationReportingContext CreateSut(Compilation compilation, AnalyzerOptions options) { var compilationContext = new CompilationAnalysisContext(compilation, options, _ => { }, _ => true, default); return new(AnalysisScaffolding.CreateSonarAnalysisContext(), compilationContext); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/AnalysisContext/SonarAnalysisContextTest.Parametrized.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * This program is free software; you can redistribute it and/or * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ extern alias common; using common::SonarAnalyzer.Core.AnalysisContext; using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using CS = SonarAnalyzer.CSharp.Core.Extensions.SonarParametrizedAnalysisContextExtensions; using VB = SonarAnalyzer.VisualBasic.Core.Extensions.SonarParametrizedAnalysisContextExtensions; namespace SonarAnalyzer.Test.AnalysisContext; public partial class SonarAnalysisContextTest { [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterNodeAction_UnchangedFiles_SonarParametrizedAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sonarContext = new SonarAnalysisContext(context, DummyMainDescriptor); var sut = new SonarParametrizedAnalysisContext(sonarContext); sut.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); sonarContext.RegisterCompilationStartAction(sut.ExecutePostponedActions); context.AssertDelegateInvoked(expected); } [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterTreeAction_UnchangedFiles_SonarParametrizedAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); sut.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); sut.ExecutePostponedActions(new(sut, MockCompilationStartAnalysisContext(context))); // Manual invocation, because SonarParametrizedAnalysisContext stores actions separately context.AssertDelegateInvoked(expected); } [TestMethod] public void RegisterTreeAction_Extension_SonarParametrizedAnalysisContext_CS() { var context = new DummyAnalysisContext(TestContext); var self = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); CS.RegisterTreeAction(self, context.DelegateAction); self.ExecutePostponedActions(new(self, MockCompilationStartAnalysisContext(context))); // Manual invocation, because SonarParametrizedAnalysisContext stores actions separately context.AssertDelegateInvoked(true); } [TestMethod] public void RegisterTreeAction_Extension_SonarParametrizedAnalysisContext_VB() { var context = new DummyAnalysisContext(TestContext); var self = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); VB.RegisterTreeAction(self, context.DelegateAction); self.ExecutePostponedActions(new(self, MockCompilationStartAnalysisContext(context))); // Manual invocation, because SonarParametrizedAnalysisContext stores actions separately context.AssertDelegateInvoked(true); } [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterSemanticModelAction_UnchangedFiles_SonarParametrizedAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); sut.RegisterSemanticModelAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); ExecutePostponedActions(sut, context); context.AssertDelegateInvoked(expected); } [TestMethod] public void RegisterSemanticModelAction_Extension_SonarParametrizedAnalysisContext_CS() { var context = new DummyAnalysisContext(TestContext); var self = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); CS.RegisterSemanticModelAction(self, context.DelegateAction); ExecutePostponedActions(self, context); context.AssertDelegateInvoked(true); } [TestMethod] public void RegisterSemanticModelAction_Extension_SonarParametrizedAnalysisContext_VB() { var context = new DummyAnalysisContext(TestContext); var self = new SonarParametrizedAnalysisContext(new(context, DummyMainDescriptor)); VB.RegisterSemanticModelAction(self, context.DelegateAction); ExecutePostponedActions(self, context); context.AssertDelegateInvoked(true); } private static void ExecutePostponedActions(SonarParametrizedAnalysisContext self, DummyAnalysisContext dummyAnalysisContext) { var sub = Substitute.For(dummyAnalysisContext.Model.Compilation, dummyAnalysisContext.Options, CancellationToken.None); sub .When(x => x.RegisterSemanticModelAction(Arg.Any>())) .Do(x => x.Arg>().Invoke(dummyAnalysisContext.CreateSemanticModelAnalysisContext())); self.ExecutePostponedActions(new(self, sub)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/AnalysisContext/SonarAnalysisContextTest.Register.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using CS = SonarAnalyzer.CSharp.Core.Extensions.SonarAnalysisContextExtensions; using RoslynAnalysisContext = Microsoft.CodeAnalysis.Diagnostics.AnalysisContext; using VB = SonarAnalyzer.VisualBasic.Core.Extensions.SonarAnalysisContextExtensions; namespace SonarAnalyzer.Test.AnalysisContext; public partial class SonarAnalysisContextTest { private const string SnippetFileName = "snippet0.cs"; private const string AnotherFileName = "Any other file name to make snippet0 considered as changed.cs"; private static readonly ImmutableArray DummyMainDescriptor = new[] { AnalysisScaffolding.CreateDescriptorMain() }.ToImmutableArray(); [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterNodeAction_UnchangedFiles_SonarAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarAnalysisContext(context, DummyMainDescriptor); sut.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); context.AssertDelegateInvoked(expected); } [TestMethod] [DataRow("snippet1.cs")] [DataRow("Other file is unchanged.cs")] public void RegisterNodeActionInAllFiles_UnchangedFiles_GeneratedFiles_AlwaysRuns(string unchangedFileName) => new VerifierBuilder() .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, unchangedFileName)) .AddSnippet(""" // public class Something { } // Noncompliant """) .Verify(); [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterTreeAction_UnchangedFiles_SonarAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarAnalysisContext(context, DummyMainDescriptor); sut.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); context.AssertDelegateInvoked(expected); } [TestMethod] [DataRow("", "", 0)] [DataRow("MainSourceScope", "", 1)] [DataRow("MainSourceScope", "//", 0)] [DataRow("TestSourceScope", "", 0)] [DataRow("TestSourceScope", "//", 0)] public async Task RegisterTreeAction_ScopeAndGeneratedCode(string scope, string autogenerated, int expected) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var treeContextExecuted = 0; var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptor("TEST", scope), analysisContext => analysisContext.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, treeContext => { treeContextExecuted++; })); var compilation = snippet.Compilation.WithAnalyzers([analyzer]); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().BeEmpty(); treeContextExecuted.Should().Be(expected); } [TestMethod] [DataRow("", "", 0, 0)] [DataRow("MainSourceScope", "", 1, 1)] [DataRow("MainSourceScope", "//", 1, 0)] [DataRow("TestSourceScope", "", 0, 0)] [DataRow("TestSourceScope", "//", 0, 0)] public async Task RegisterCompilationStartAction_RegisterTreeAction_ScopeAndGeneratedCode(string scope, string autogenerated, int expectedCompilationStartStartExecuted, int expectedTreeContextExecuted) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var compilationStartStartExecuted = 0; var treeContextExecuted = 0; var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptor("TEST", scope), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => { compilationStartStartExecuted++; compilationStartContext.RegisterTreeAction(CSharpGeneratedCodeRecognizer.Instance, treeContext => { treeContextExecuted++; }); })); var compilation = snippet.Compilation.WithAnalyzers([analyzer]); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().BeEmpty(); compilationStartStartExecuted.Should().Be(expectedCompilationStartStartExecuted); treeContextExecuted.Should().Be(expectedTreeContextExecuted); } [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterSemanticModelAction_UnchangedFiles_SonarAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarAnalysisContext(context, DummyMainDescriptor); sut.RegisterSemanticModelAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); context.AssertDelegateInvoked(expected); } [TestMethod] public void RegisterSemanticModelAction_Extension_SonarAnalysisContext_CS() { var context = new DummyAnalysisContext(TestContext); var self = new SonarAnalysisContext(context, DummyMainDescriptor); CS.RegisterSemanticModelAction(self, context.DelegateAction); context.AssertDelegateInvoked(true); } [TestMethod] public void RegisterSemanticModelAction_Extension_SonarAnalysisContext_VB() { var context = new DummyAnalysisContext(TestContext); var self = new SonarAnalysisContext(context, DummyMainDescriptor); VB.RegisterSemanticModelAction(self, context.DelegateAction); context.AssertDelegateInvoked(true); } [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void RegisterCodeBlockStartAction_UnchangedFiles_SonarAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var sut = new SonarAnalysisContext(context, DummyMainDescriptor); sut.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, context.DelegateAction); context.AssertDelegateInvoked(expected); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterSemanticModel() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); sut.RegisterSemanticModelActionInAllFiles(_ => { }); startContext.AssertExpectedInvocationCounts(expectedSemanticModelCount: 1); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterCompilationEndAction() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); sut.RegisterCompilationEndAction(_ => { }); startContext.AssertExpectedInvocationCounts(expectedCompilationEndCount: 1); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterSymbolAction() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); sut.RegisterSymbolAction(_ => { }); startContext.AssertExpectedInvocationCounts(expectedSymbolCount: 1); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterNodeAction() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); sut.RegisterNodeAction(CSharpGeneratedCodeRecognizer.Instance, _ => { }); startContext.AssertExpectedInvocationCounts(expectedNodeCount: 1); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterCompilationEnd_ReportsIssue() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); sut.RegisterCompilationEndAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterSymbol_ReportsIssue() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); sut.RegisterSymbolAction(x => x.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnostic)); startContext.RaisedDiagnostic.Should().NotBeNull().And.BeSameAs(diagnostic); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterSymbolStartAction() { var context = new DummyAnalysisContext(TestContext); var startContext = new DummyCompilationStartAnalysisContext(context); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); var invocationCount = 0; sut.RegisterSymbolStartAction(x => { x.Cancel.Should().Be(startContext.CancellationToken); x.Compilation.Should().Be(startContext.Compilation); x.Options.Should().Be(startContext.Options); x.Symbol.Should().NotBeNull(); invocationCount++; }, SymbolKind.NamedType); startContext.RaisedDiagnostic.Should().BeNull(); invocationCount.Should().Be(1); } [TestMethod] public void SonarCompilationStartAnalysisContext_RegisterCodeBlockStartAction_RegistrationIsCalled() { var context = new DummyAnalysisContext(TestContext); var startContext = Substitute.For(context.Model.Compilation, context.Options, null); startContext.RegisterCodeBlockStartAction(Arg.Do>>(x => x(context.CreateCodeBlockStartAnalysisContext()))); var sut = new SonarCompilationStartAnalysisContext(new(context, DummyMainDescriptor), startContext); var diagnostic = Diagnostic.Create(DiagnosticDescriptorFactory.CreateUtility("TEST", "Test report"), context.Tree.GetRoot().GetLocation()); var registrationCalled = false; sut.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, x => { registrationCalled = true; x.Model.Should().Be(context.Model); x.CodeBlock.Should().Be(context.ClassDeclarationNode); x.OwningSymbol.Should().Be(context.OwningSymbol); }); registrationCalled.Should().BeTrue(); } [TestMethod] [DataRow("", "", 0, 0)] [DataRow("MainSourceScope", "", 1, 1)] [DataRow("MainSourceScope", "//", 1, 0)] [DataRow("TestSourceScope", "", 0, 0)] [DataRow("TestSourceScope", "//", 0, 0)] public async Task SonarCompilationStartAnalysisContext_RegisterCodeBlockStart_ScopeAndGeneratedCode(string scope, string autogenerated, int expectedCompilationStartStartExecuted, int expectedCodeBlockStartExecuted) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var compilationStartStartExecuted = 0; var codeBlockStartExecuted = 0; var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptor("TEST", scope), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => { compilationStartStartExecuted++; compilationStartContext.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, codeBlockStart => { codeBlockStartExecuted++; }); })); var compilation = snippet.Compilation.WithAnalyzers([analyzer]); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().BeEmpty(); compilationStartStartExecuted.Should().Be(expectedCompilationStartStartExecuted); codeBlockStartExecuted.Should().Be(expectedCodeBlockStartExecuted); } [TestMethod] [DataRow("", "", 0)] [DataRow("MainSourceScope", "", 1)] [DataRow("MainSourceScope", "//", 0)] [DataRow("TestSourceScope", "", 0)] [DataRow("TestSourceScope", "//", 0)] public async Task SonarAnalysisContext_RegisterCodeBlockStart_ScopeAndGeneratedCode(string scope, string autogenerated, int expected) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var codeBlockStartExecuted = 0; var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptor("TEST", scope), analysisContext => analysisContext.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, codeBlockStart => { codeBlockStartExecuted++; })); var compilation = snippet.Compilation.WithAnalyzers([analyzer]); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().BeEmpty(); codeBlockStartExecuted.Should().Be(expected); } [TestMethod] [DataRow("", "", 0)] [DataRow("MainSourceScope", "", 1)] [DataRow("MainSourceScope", "//", 1)] [DataRow("TestSourceScope", "", 0)] [DataRow("TestSourceScope", "//", 0)] public async Task SonarCompilationStartAnalysisContext_RegisterSymbolStartAction_ScopeAndGeneratedCode(string scope, string autogenerated, int expected) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var symbolStartExecuted = 0; var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptor("TEST", scope), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { symbolStartExecuted++; }, SymbolKind.NamedType))); var compilation = snippet.Compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().BeEmpty(); symbolStartExecuted.Should().Be(expected); } [TestMethod] [DataRow("", "")] [DataRow("MainSourceScope", "", "Node", "CodeBlockStart_Node", "CodeBlock", "CodeBlockStart_End", "SymbolEnd")] [DataRow("MainSourceScope", "//", "Node", "CodeBlockStart_Node", "CodeBlock", "CodeBlockStart_End")] // Note: "SymbolEnd" is missing here. ReportIssues does not forward the call. // https://github.com/SonarSource/sonar-dotnet/issues/8876 [DataRow("TestSourceScope", "")] [DataRow("TestSourceScope", "//")] public async Task SonarCompilationStartAnalysisContext_RegisterSymbolStartAction_RegisterAndReporting_ScopeAndGeneratedCode( string scope, string autogenerated, params string[] expectedDiagnostics) { var snippet = new SnippetCompiler($$""" {{autogenerated}} class C { public void M() => ToString(); } """); var diagnosticDescriptor = new DiagnosticDescriptor("TEST", "Title", "{0}", "Category", DiagnosticSeverity.Warning, true, customTags: [scope]); var location = Location.Create(snippet.Tree, TextSpan.FromBounds(0, 0)); var analyzer = new TestAnalyzerCS(diagnosticDescriptor, analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => compilationStartContext.RegisterSymbolStartAction(symbolStartContext => { symbolStartContext.RegisterCodeBlockAction(codeBlockContext => codeBlockContext.ReportIssue(diagnosticDescriptor, location, "CodeBlock")); symbolStartContext.RegisterCodeBlockStartAction(codeBlockStartContext => { codeBlockStartContext.RegisterNodeAction(nodeContext => nodeContext.ReportIssue(diagnosticDescriptor, location, "CodeBlockStart_Node"), SyntaxKind.InvocationExpression); codeBlockStartContext.RegisterCodeBlockEndAction(codeBlockEndContext => codeBlockEndContext.ReportIssue(diagnosticDescriptor, location, "CodeBlockStart_End")); }); symbolStartContext.RegisterSymbolEndAction(symbolEndContext => symbolEndContext.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, diagnosticDescriptor, location, "SymbolEnd")); symbolStartContext.RegisterSyntaxNodeAction(nodeContext => nodeContext.ReportIssue(diagnosticDescriptor, location, "Node"), SyntaxKind.InvocationExpression); }, SymbolKind.NamedType))); var compilation = snippet.Compilation.WithAnalyzers([analyzer]); var diagnostics = await compilation.GetAllDiagnosticsAsync(); diagnostics.Should().HaveCount(expectedDiagnostics.Length); // Ordering is only partially guaranteed and therefore we use BeEquivalentTo https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md diagnostics.Select(x => x.GetMessage()).Should().BeEquivalentTo(expectedDiagnostics); } [TestMethod] public async Task SonarCompilationStartAnalysisContext_RegisterSymbolStartAction_RegisterOperationAction_NotImplemented() { var snippet = new SnippetCompiler("""class C { }"""); var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptorMain(), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => compilationStartContext.RegisterSymbolStartAction(symbolStartContext => symbolStartContext.RegisterOperationAction(_ => { }, ImmutableArray.Create(OperationKind.AddressOf)), SymbolKind.NamedType))); var compilation = snippet.Compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); var diagnostics = await compilation.GetAllDiagnosticsAsync(); var ad0001 = diagnostics.Should().ContainSingle().Which; ad0001.Id.Should().Be("AD0001"); ad0001.GetMessage().Should().Contain("'System.NotImplementedException' with message 'SonarOperationAnalysisContext wrapper type not implemented.'"); } [TestMethod] public async Task SonarCompilationStartAnalysisContext_RegisterSymbolStartAction_RegisterOperationBlockAction_NotImplemented() { var snippet = new SnippetCompiler("""class C { }"""); var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptorMain(), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => compilationStartContext.RegisterSymbolStartAction(symbolStartContext => symbolStartContext.RegisterOperationBlockAction(_ => { }), SymbolKind.NamedType))); var compilation = snippet.Compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); var diagnostics = await compilation.GetAllDiagnosticsAsync(); var ad0001 = diagnostics.Should().ContainSingle().Which; ad0001.Id.Should().Be("AD0001"); ad0001.GetMessage().Should().Contain("'System.NotImplementedException' with message 'SonarOperationBlockAnalysisContext wrapper type not implemented.'"); } [TestMethod] public async Task SonarCompilationStartAnalysisContext_RegisterSymbolStartAction_RegisterOperationBlockStartAction_NotImplemented() { var snippet = new SnippetCompiler("""class C { }"""); var analyzer = new TestAnalyzerCS(AnalysisScaffolding.CreateDescriptorMain(), analysisContext => analysisContext.RegisterCompilationStartAction(compilationStartContext => compilationStartContext.RegisterSymbolStartAction(symbolStartContext => symbolStartContext.RegisterOperationBlockStartAction(_ => { }), SymbolKind.NamedType))); var compilation = snippet.Compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); var diagnostics = await compilation.GetAllDiagnosticsAsync(); var ad0001 = diagnostics.Should().ContainSingle().Which; ad0001.Id.Should().Be("AD0001"); ad0001.GetMessage().Should().Contain("'System.NotImplementedException' with message 'SonarOperationBlockStartAnalysisContext wrapper type not implemented.'"); } #if NET [TestMethod] [DataRow("S109", "razor")] [DataRow("S103", "razor")] [DataRow("S1192", "razor")] [DataRow("S104", "razor")] [DataRow("S113", "razor")] [DataRow("S1451", "razor")] [DataRow("S1147", "razor")] [DataRow("S109", "cshtml")] [DataRow("S103", "cshtml")] [DataRow("S1192", "cshtml")] [DataRow("S104", "cshtml")] [DataRow("S113", "cshtml")] [DataRow("S1451", "cshtml")] [DataRow("S1147", "cshtml")] public void DisabledRules_ForRazor_DoNotRaise(string ruleId, string extension) => new VerifierBuilder() .AddAnalyzer(() => new DummyAnalyzerWithLocation(ruleId, DiagnosticDescriptorFactory.MainSourceScopeTag)) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .AddSnippet(Snippet(extension), $"SomeFile.{extension}") .VerifyNoIssues(); [TestMethod] [DataRow("razor")] [DataRow("cshtml")] public void TestRules_ForRazor_DoNotRaise(string extension) => new VerifierBuilder() .AddAnalyzer(() => new DummyAnalyzerWithLocation("DummyId", DiagnosticDescriptorFactory.TestSourceScopeTag)) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Test)) .AddSnippet(Snippet(extension), $"SomeFile.{extension}") .VerifyNoIssues(); [TestMethod] [DataRow("razor")] [DataRow("cshtml")] public void AllScopedRules_ForRazor_Raise(string extension) { var keyword = extension == "razor" ? "code" : "functions"; new VerifierBuilder() .AddAnalyzer(() => new DummyAnalyzerWithLocation("DummyId", DiagnosticDescriptorFactory.TestSourceScopeTag, DiagnosticDescriptorFactory.MainSourceScopeTag)) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .AddSnippet($$""" @{{keyword}} { private int magicNumber = RaiseHere(); // Noncompliant private static int RaiseHere() { return 42; } } """, $"SomeFile.{extension}") .Verify(); } [TestMethod] [DataRow("razor")] [DataRow("cshtml")] public void RaisedIssue_WithinRazorGeneratedCode_ShouldNotBeReported(string extension) => new VerifierBuilder() .AddAnalyzer(() => new DummyAnalyzerCS()) .AddSnippet(@"

Some Html

", $"SomeFile.{extension}") .VerifyNoIssues(); private static string Snippet(string extension) { var keyword = extension == "razor" ? "code" : "functions"; return $$""" @{{keyword}} { private int magicNumber = RaiseHere(); private static int RaiseHere() { return 42; } } """; } #endif private static CompilationStartAnalysisContext MockCompilationStartAnalysisContext(DummyAnalysisContext context) { var mock = Substitute.For(context.Model.Compilation, context.Options, CancellationToken.None); mock.When(x => x.RegisterSyntaxNodeAction(Arg.Any>(), Arg.Any>())) .Do(x => x.Arg>()(context.CreateSyntaxNodeAnalysisContext())); // Invoke to call RegisterSyntaxTreeAction mock.When(x => x.RegisterSyntaxTreeAction(Arg.Any>())) .Do(x => x.Arg>()(new SyntaxTreeAnalysisContext(context.Tree, context.Options, _ => { }, _ => true, default))); mock.When(x => x.RegisterSemanticModelAction(Arg.Any>())) .Do(x => x.Arg>()(new SemanticModelAnalysisContext(context.Model, context.Options, _ => { }, _ => true, default))); mock.When(x => x.RegisterCodeBlockStartAction(Arg.Any>>())) .Do(x => x.Arg>>()(context.CreateCodeBlockStartAnalysisContext())); return mock; } private sealed class DummyAnalysisContext : RoslynAnalysisContext { public readonly AnalyzerOptions Options; public readonly SemanticModel Model; public readonly SyntaxTree Tree; private bool delegateWasInvoked; public SyntaxNode ClassDeclarationNode => Tree.GetRoot().DescendantNodes().OfType().First(); public ISymbol OwningSymbol => Model.GetDeclaredSymbol(ClassDeclarationNode); public DummyAnalysisContext(TestContext testContext, params string[] unchangedFiles) { Options = AnalysisScaffolding.CreateOptions(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(testContext, unchangedFiles)); (Tree, Model) = TestCompiler.CompileCS("public class Sample { }"); } public void DelegateAction(T arg) => delegateWasInvoked = true; public void AssertDelegateInvoked(bool expected, string because = "") => delegateWasInvoked.Should().Be(expected, because); public SyntaxNodeAnalysisContext CreateSyntaxNodeAnalysisContext() => new(Tree.GetRoot(), Model, Options, _ => { }, _ => true, default); public SemanticModelAnalysisContext CreateSemanticModelAnalysisContext() => new(Model, Options, _ => { }, _ => true, default); public CodeBlockStartAnalysisContext CreateCodeBlockStartAnalysisContext() where TSyntaxKind : struct => // SyntaxNode codeBlock, ISymbol owningSymbol, SemanticModel semanticModel, AnalyzerOptions options, CancellationToken cancellationToken Substitute.For>(ClassDeclarationNode, OwningSymbol, Model, Options, CancellationToken.None); public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); public override void RegisterCodeBlockStartAction(Action> action) => action(new DummyCodeBlockStartAnalysisContext(this)); public override void RegisterCompilationAction(Action action) => throw new NotImplementedException(); public override void RegisterCompilationStartAction(Action action) => action(MockCompilationStartAnalysisContext(this)); // Directly invoke to let the inner registrations be added into this.actions public override void RegisterSemanticModelAction(Action action) => action(CreateSemanticModelAnalysisContext()); public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) => throw new NotImplementedException(); public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => action(CreateSyntaxNodeAnalysisContext()); public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); } private class DummyCodeBlockStartAnalysisContext : CodeBlockStartAnalysisContext where TSyntaxKind : struct { public DummyCodeBlockStartAnalysisContext(DummyAnalysisContext baseContext) : base(baseContext.Tree.GetRoot(), null, baseContext.Model, baseContext.Options, default) { } public override void RegisterCodeBlockEndAction(Action action) => throw new NotImplementedException(); public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => throw new NotImplementedException(); } private class DummyCompilationStartAnalysisContext : CompilationStartAnalysisContext { private readonly DummyAnalysisContext context; private int compilationEndCount; private int semanticModelCount; private int symbolCount; private int nodeCount; public Diagnostic RaisedDiagnostic { get; private set; } public DummyCompilationStartAnalysisContext(DummyAnalysisContext context) : base(context.Model.Compilation, context.Options, default) => this.context = context; public void AssertExpectedInvocationCounts(int expectedCompilationEndCount = 0, int expectedSemanticModelCount = 0, int expectedSymbolCount = 0, int expectedNodeCount = 0) { compilationEndCount.Should().Be(expectedCompilationEndCount); semanticModelCount.Should().Be(expectedSemanticModelCount); symbolCount.Should().Be(expectedSymbolCount); nodeCount.Should().Be(expectedNodeCount); } public override void RegisterCodeBlockAction(Action action) => throw new NotImplementedException(); public override void RegisterCodeBlockStartAction(Action> action) => action(context.CreateCodeBlockStartAnalysisContext()); public override void RegisterCompilationEndAction(Action action) { compilationEndCount++; action(new CompilationAnalysisContext(context.Model.Compilation, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, default)); } public override void RegisterSemanticModelAction(Action action) { semanticModelCount++; action(new SemanticModelAnalysisContext(context.Model, context.Options, reportDiagnostic: x => RaisedDiagnostic = x, isSupportedDiagnostic: _ => true, default)); } public override void RegisterSymbolAction(Action action, ImmutableArray symbolKinds) { symbolCount++; action(new SymbolAnalysisContext(Substitute.For(), context.Model.Compilation, context.Options, x => RaisedDiagnostic = x, _ => true, default)); } public override void RegisterSyntaxNodeAction(Action action, ImmutableArray syntaxKinds) => nodeCount++; public override void RegisterSyntaxTreeAction(Action action) => throw new NotImplementedException(); public override void RegisterSymbolStartAction(Action action, SymbolKind symbolKind) { var symbolStartAnalysisContext = Substitute.For(Substitute.For(), context.Model.Compilation, context.Options, default); action(symbolStartAnalysisContext); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] private class DummyAnalyzerForGenerated : SonarDiagnosticAnalyzer { private readonly DiagnosticDescriptor rule = AnalysisScaffolding.CreateDescriptorMain(); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) => context.RegisterNodeActionInAllFiles(c => c.ReportIssue(rule, c.Node), SyntaxKind.ClassDeclaration); } [DiagnosticAnalyzer(LanguageNames.CSharp)] private sealed class TestAnalyzerCS(DiagnosticDescriptor rule, Action register) : SonarDiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); protected override void Initialize(SonarAnalysisContext context) => register(context); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/AnalysisContext/SonarAnalysisContextTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Test.Rules; namespace SonarAnalyzer.Test.AnalysisContext; [TestClass] public partial class SonarAnalysisContextTest { // Various classes that invoke all the `ReportIssue` methods in AnalysisContextExtensions // We mention in comments the type of Context that is used to invoke (directly or indirectly) the `ReportIssue` method private readonly List testCases = [ // SyntaxNodeAnalysisContext // S3244 - MAIN and TEST new TestSetup("AnonymousDelegateEventUnsubscribe.cs", new AnonymousDelegateEventUnsubscribe()), // S2699 - TEST only new TestSetup( "TestMethodShouldContainAssertion.NUnit.cs", new TestMethodShouldContainAssertion(), // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 TestMethodShouldContainAssertionTest.WithTestReferences(NuGetMetadataReference.NUnit("3.14.0")).References), // ToDo: Reuse the entire builder in TestSetup // SyntaxTreeAnalysisContext // S3244 - MAIN and TEST new TestSetup("AsyncAwaitIdentifier.cs", new AsyncAwaitIdentifier()), // CompilationAnalysisContext // S3244 - MAIN and TEST new TestSetup( @"Hotspots\RequestsWithExcessiveLength.cs", new RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled), RequestsWithExcessiveLengthTest.GetAdditionalReferences()), // CodeBlockAnalysisContext // S5693 - MAIN and TEST new TestSetup("GetHashCodeEqualsOverride.cs", new GetHashCodeEqualsOverride()), // SymbolAnalysisContext // S2953 - MAIN only new TestSetup("DisposeNotImplementingDispose.cs", new DisposeNotImplementingDispose()), // S1694 - MAIN only new TestSetup("AbstractClassToInterface.cs", new AbstractClassToInterface()), ]; public TestContext TestContext { get; set; } [TestMethod] public void Constructor_Null() => ((Func)(() => new(null, default))).Should().Throw().And.ParamName.Should().Be("analysisContext"); [TestMethod] public void WhenShouldAnalysisBeDisabledReturnsTrue_NoIssueReported() { SonarAnalysisContext.ShouldExecuteRegisteredAction = (_, _) => false; try { foreach (var testCase in testCases) { // ToDo: We should find a way to ack the fact the action was not run testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .VerifyNoIssuesIgnoreErrors(); } } finally { SonarAnalysisContext.ShouldExecuteRegisteredAction = null; } } [TestMethod] public void ByDefault_ExecuteRule() { foreach (var testCase in testCases) { // ToDo: We test that a rule is enabled only by checking the issues are reported testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .Verify(); } } [TestMethod] public void WhenProjectType_IsTest_RunRulesWithTestScope_SonarLint() { var sonarProjectConfig = AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Test, false); foreach (var testCase in testCases) { var hasTestScope = testCase.Analyzer.SupportedDiagnostics.Any(x => x.CustomTags.Contains(DiagnosticDescriptorFactory.TestSourceScopeTag)); if (hasTestScope) { testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .Verify(); } else { // MAIN-only testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .VerifyNoIssuesIgnoreErrors(); } } } [TestMethod] public void WhenProjectType_IsTest_RunRulesWithTestScope_Scanner() { var sonarProjectConfig = AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Test); foreach (var testCase in testCases) { var hasProductScope = testCase.Analyzer.SupportedDiagnostics.Any(x => x.CustomTags.Contains(DiagnosticDescriptorFactory.MainSourceScopeTag)); if (hasProductScope) { // MAIN-only and MAIN & TEST rules testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .VerifyNoIssuesIgnoreErrors(); } else { testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .Verify(); } } } [TestMethod] public void WhenProjectType_IsTest_RunRulesWithMainScope() { var sonarProjectConfig = AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product); foreach (var testCase in testCases) { var hasProductScope = testCase.Analyzer.SupportedDiagnostics.Any(d => d.CustomTags.Contains(DiagnosticDescriptorFactory.MainSourceScopeTag)); if (hasProductScope) { testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .Verify(); } else { // TEST-only rule testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .WithAdditionalFilePath(sonarProjectConfig) .VerifyNoIssues(); } } } [TestMethod] public void WhenAnalysisDisabledBaseOnSyntaxTree_ReportIssuesForEnabledRules() { testCases.Should().HaveCountGreaterThan(2); try { var testCase = testCases[0]; var testCase2 = testCases[2]; SonarAnalysisContext.ShouldExecuteRegisteredAction = (diags, tree) => tree.FilePath.EndsWith(new FileInfo(testCase.Path).Name, StringComparison.OrdinalIgnoreCase); testCase.Builder.WithConcurrentAnalysis(false).Verify(); testCase2.Builder.VerifyNoIssues(); } finally { SonarAnalysisContext.ShouldExecuteRegisteredAction = null; } } [TestMethod] public void WhenReportDiagnosticActionNotNull_AllowToControlWhetherOrNotToReport() { try { SonarAnalysisContext.ReportDiagnostic = context => { // special logic for rules with SyntaxNodeAnalysisContext if (context.Diagnostic.Id != AnonymousDelegateEventUnsubscribe.DiagnosticId && context.Diagnostic.Id != TestMethodShouldContainAssertion.DiagnosticId) { // Verifier expects all diagnostics to increase the counter in order to check that all rules call the // extension method and not the direct `ReportDiagnostic`. SuppressionHandler.IncrementReportCount(context.Diagnostic.Id); context.ReportDiagnostic(context.Diagnostic); } }; // Because the Verifier sets the SonarAnalysisContext.ShouldDiagnosticBeReported delegate we end up in a case // where the Debug.Assert of the AnalysisContextExtensions.ReportDiagnostic() method will raise. using (new AssertIgnoreScope()) { foreach (var testCase in testCases) { // special logic for rules with SyntaxNodeAnalysisContext if (testCase.Analyzer is AnonymousDelegateEventUnsubscribe || testCase.Analyzer is TestMethodShouldContainAssertion) { testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .VerifyNoIssues(); } else { testCase.Builder .WithOptions(LanguageOptions.FromCSharp8) .Verify(); } } } } finally { SonarAnalysisContext.ReportDiagnostic = null; } } [TestMethod] [DataRow(ProjectType.Product, false)] [DataRow(ProjectType.Test, true)] public void IsTestProject_Standalone(ProjectType projectType, bool expectedResult) { var compilation = new SnippetCompiler("// Nothing to see here", TestCompiler.ProjectTypeReference(projectType)).Model.Compilation; var context = new CompilationAnalysisContext(compilation, AnalysisScaffolding.CreateOptions(), null, null, default); var sut = new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.IsTestProject().Should().Be(expectedResult); } [TestMethod] [DataRow(ProjectType.Product, false)] [DataRow(ProjectType.Test, true)] public void IsTestProject_WithConfigFile(ProjectType projectType, bool expectedResult) { var configPath = AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType); var context = new CompilationAnalysisContext(null, AnalysisScaffolding.CreateOptions(configPath), null, null, default); var sut = new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context); sut.IsTestProject().Should().Be(expectedResult); } [TestMethod] [DataRow(SnippetFileName, false)] [DataRow(AnotherFileName, true)] public void ReportDiagnosticIfNonGenerated_UnchangedFiles_CompilationAnalysisContext(string unchangedFileName, bool expected) { var context = new DummyAnalysisContext(TestContext, unchangedFileName); var wasReported = false; var location = context.Tree.GetRoot().GetLocation(); var symbol = Substitute.For(); symbol.Locations.Returns([location]); var symbolContext = new SymbolAnalysisContext(symbol, context.Model.Compilation, context.Options, _ => wasReported = true, _ => true, default); var sut = new SonarSymbolReportingContext(new SonarAnalysisContext(context, DummyMainDescriptor), symbolContext); sut.ReportIssue(CSharpGeneratedCodeRecognizer.Instance, DummyMainDescriptor[0], location); wasReported.Should().Be(expected); } private sealed class TestSetup { public string Path { get; } public DiagnosticAnalyzer Analyzer { get; } public VerifierBuilder Builder { get; } public TestSetup(string testCase, SonarDiagnosticAnalyzer analyzer) : this(testCase, analyzer, Enumerable.Empty()) { } public TestSetup(string testCase, SonarDiagnosticAnalyzer analyzer, IEnumerable additionalReferences) { Path = testCase; Analyzer = analyzer; additionalReferences = additionalReferences .Concat(MetadataReferenceFacade.SystemComponentModelPrimitives) .Concat(MetadataReferenceFacade.NetStandard) .Concat(MetadataReferenceFacade.SystemData); Builder = new VerifierBuilder().AddAnalyzer(() => analyzer).AddPaths(Path).AddReferences(additionalReferences); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/CfgSerializer/DotWriterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Test; [TestClass] public class DotWriterTest { [TestMethod] public void WriteGraphStart() { var writer = new DotWriter(); writer.WriteGraphStart("test"); writer.ToString().Should().BeIgnoringLineEndings("digraph \"test\" {\r\n"); writer.Invoking(x => x.WriteGraphStart("second")).Should().Throw(); } [TestMethod] public void WriteGraphEnd() { var writer = new DotWriter(); writer.Invoking(x => x.WriteGraphEnd()).Should().Throw(); writer.WriteGraphStart("test"); writer.WriteGraphEnd(); writer.ToString().Should().BeIgnoringLineEndings("digraph \"test\" {\r\n}\r\n"); } [TestMethod] public void WriteSubGraphStart() { var writer = new DotWriter(); writer.WriteSubGraphStart(42, "test"); writer.ToString().Should().BeIgnoringLineEndings("subgraph \"cluster_42\" {\r\nlabel = \"test\"\r\n"); } [TestMethod] public void WriteSubGraphEnd() { var writer = new DotWriter(); writer.WriteSubGraphEnd(); writer.ToString().Should().BeIgnoringLineEndings("}\r\n"); } [TestMethod] public void WriteNode_WithItems() { var writer = new DotWriter(); writer.WriteRecordNode("1", "header", "a", "b", "c"); writer.ToString().Should().BeIgnoringLineEndings("1 [shape=record label=\"{header|a|b|c}\"]\r\n"); } [TestMethod] public void WriteNode_WithEncoding() { var writer = new DotWriter(); writer.WriteRecordNode("1", "header", "\r", "\n", "{", "}", "<", ">", "|", "\""); writer.ToString().Should().BeIgnoringLineEndings(@"1 [shape=record label=""{header||\n|\{|\}|\<|\>|\||\""}""]" + "\r\n"); } [TestMethod] public void WriteNode_NoItems() { var writer = new DotWriter(); writer.WriteRecordNode("1", "header"); writer.ToString().Should().BeIgnoringLineEndings("1 [shape=record label=\"{header}\"]\r\n"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Extensions/IOperationExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; namespace SonarAnalyzer.CFG.Extensions.Test; [TestClass] public class IOperationExtensionsTest { [TestMethod] public void DescendantsAndSelf_Null_ReturnsNull() => IOperationExtensions.DescendantsAndSelf(null).Should().BeEmpty(); [TestMethod] public void OperationWrapperSonarPropertyShortcuts() { var operation = Operation(""" public class Sample { public void Method() { var value = 42; } } """).ToVariableDeclaration(); operation.Parent().Should().NotBeNull(); operation.Parent().Kind.Should().Be(OperationKind.VariableDeclarationGroup); operation.Children().Should().HaveCount(1); operation.Children().Single().Kind.Should().Be(OperationKind.VariableDeclarator); operation.Language().Should().Be("C#"); operation.IsImplicit().Should().Be(false); operation.SemanticModel().Should().Be(operation.SemanticModel()); } [TestMethod] public void AsForEachLoop_ForEachLoop_ConvertsToWrapper() => Operation(""" public class Sample { public void Method(int[] items) { foreach (var item in items) { // Do something with item } } } """).Descendants().OfType().Single().AsForEachLoop().Should().NotBeNull(); [TestMethod] [DataRow("for (var i = 0; i < 9; i++) { }")] [DataRow("while (true) { }")] [DataRow("do { } while (true);")] public void AsForEachLoop_OtherLoop_ConvertsToWrapper(string loop) => Operation($$""" public class Sample { public void Method() { {{loop}} } } """).Descendants().OfType().Single().AsForEachLoop().Should().BeNull(); [TestMethod] public void ExtensionsMethodsUsedByArchitecture() { IOperation operation = null; // These extension methods are used by sonar-architecture. Do not remove them. Assert.Throws(() => operation.AsForEachLoop()); Assert.Throws(() => operation.AsVariableDeclarator()); operation.ToArrayCreation(); operation.ToCatchClause(); operation.ToConversion(); operation.ToInvocation(); operation.ToIsType(); operation.ToLocalFunction(); operation.ToMemberReference(); operation.ToObjectCreation(); operation.ToPattern(); operation.ToVariableDeclaration(); operation.ToVariableDeclarator(); } private static IOperation Operation(string code) where T : SyntaxNode { var (tree, model) = TestCompiler.CompileCS(code); return model.GetOperation(tree.Single()); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/BasicBlockTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Operations; namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class BasicBlockTest { [TestMethod] public void Wrap_ReturnsNull() => BasicBlock.Wrap(null).Should().BeNull(); [TestMethod] public void ValidateReflection() { const string code = @" public class Sample { int field; public void Method(bool condition) { if (condition) field = 42; } }"; var cfg = TestCompiler.CompileCfgCS(code); /* * Entry 0 * | * | * Block 1 * BranchValue: condition * / \ * Else / \ WhenFalse * / \ * Block 2 | * field=42 | * \ / * \ / * \ / * Exit 3 */ var entry = cfg.EntryBlock; var branch = cfg.Blocks[1]; var assign = cfg.Blocks[2]; var exit = cfg.ExitBlock; entry.Kind.Should().Be(BasicBlockKind.Entry); branch.Kind.Should().Be(BasicBlockKind.Block); assign.Kind.Should().Be(BasicBlockKind.Block); exit.Kind.Should().Be(BasicBlockKind.Exit); entry.BranchValue.Should().BeNull(); branch.BranchValue.Should().BeAssignableTo().Subject.Parameter.Name.Should().Be("condition"); assign.BranchValue.Should().BeNull(); exit.BranchValue.Should().BeNull(); entry.ConditionalSuccessor.Should().BeNull(); branch.ConditionalSuccessor.Should().NotBeNull(); assign.ConditionalSuccessor.Should().BeNull(); exit.ConditionalSuccessor.Should().BeNull(); entry.ConditionKind.Should().Be(ControlFlowConditionKind.None); branch.ConditionKind.Should().Be(ControlFlowConditionKind.WhenFalse); assign.ConditionKind.Should().Be(ControlFlowConditionKind.None); exit.ConditionKind.Should().Be(ControlFlowConditionKind.None); entry.EnclosingRegion.Should().Be(cfg.Root); branch.EnclosingRegion.Should().Be(cfg.Root); assign.EnclosingRegion.Should().Be(cfg.Root); exit.EnclosingRegion.Should().Be(cfg.Root); entry.FallThroughSuccessor.Should().NotBeNull(); branch.FallThroughSuccessor.Should().NotBeNull(); assign.FallThroughSuccessor.Should().NotBeNull(); exit.FallThroughSuccessor.Should().BeNull(); entry.IsReachable.Should().Be(true); branch.IsReachable.Should().Be(true); assign.IsReachable.Should().Be(true); exit.IsReachable.Should().Be(true); entry.Operations.Should().BeEmpty(); branch.Operations.Should().BeEmpty(); assign.Operations.Should().HaveCount(1).And.Subject.Single().Should().BeAssignableTo(); exit.Operations.Should().BeEmpty(); entry.OperationsAndBranchValue.Should().BeEmpty(); branch.OperationsAndBranchValue.Should().HaveCount(1).And.Subject.Single().Should().BeAssignableTo(); assign.OperationsAndBranchValue.Should().HaveCount(1).And.Subject.Single().Should().BeAssignableTo(); exit.OperationsAndBranchValue.Should().BeEmpty(); entry.Ordinal.Should().Be(0); branch.Ordinal.Should().Be(1); assign.Ordinal.Should().Be(2); exit.Ordinal.Should().Be(3); entry.Predecessors.Should().BeEmpty(); branch.Predecessors.Should().HaveCount(1); assign.Predecessors.Should().HaveCount(1); exit.Predecessors.Should().HaveCount(2); entry.SuccessorBlocks.Should().HaveCount(1).And.Subject.Single().Should().Be(branch); branch.SuccessorBlocks.Should().HaveCount(2).And.Subject.Should().ContainInOrder(assign, exit); assign.SuccessorBlocks.Should().HaveCount(1).And.Subject.Single().Should().Be(exit); exit.SuccessorBlocks.Should().HaveCount(0); } [TestMethod] public void ValidateOperations() { const string code = @" public class Sample { int Pow(int num, int exponent) { num = num * Pow(num, exponent - 1); return 42; } }"; var cfg = TestCompiler.CompileCfgCS(code); var entry = cfg.EntryBlock; var body = cfg.Blocks[1]; var exit = cfg.ExitBlock; entry.Kind.Should().Be(BasicBlockKind.Entry); entry.Operations.Should().BeEmpty(); body.Kind.Should().Be(BasicBlockKind.Block); body.Operations.Should().HaveCount(1).And.Subject.Single().Should().BeAssignableTo(); body.BranchValue.Should().BeAssignableTo(); body.OperationsAndBranchValue.Should().Equal(body.Operations[0], body.BranchValue); exit.Kind.Should().Be(BasicBlockKind.Exit); exit.Operations.Should().BeEmpty(); } [TestMethod] public void ValidateSwitchExpressionCase() { const string code = @" public class Sample { public int Method(bool condition) => condition switch { true => 42, _ => 43 }; }"; var cfg = TestCompiler.CompileCfgCS(code); /* * * Block 1 * true => 42 * Else / \ WhenFalse * / \ * / \ * Block 2 Block 3 * => 42 _ => 43 * | | \ * | Else | \ WhenFalse from Block 3 - Block 5 should not be reachable * | | \ * | Block 4 Block 5 * | => 43 no match => throw exception * \ / * \ / * \ / * \ / * \ / * Block 6 */ var block1 = cfg.Blocks[1]; var block2 = cfg.Blocks[2]; var block3 = cfg.Blocks[3]; var block4 = cfg.Blocks[4]; var block5 = cfg.Blocks[5]; var block6 = cfg.Blocks[6]; block1.FallThroughSuccessor.Destination.Should().Be(block2); block1.ConditionalSuccessor.Destination.Should().Be(block3); block1.SuccessorBlocks.Should().ContainInOrder(block2, block3); block2.FallThroughSuccessor.Destination.Should().Be(block6); block2.ConditionalSuccessor.Should().BeNull(); block2.SuccessorBlocks.Single().Should().Be(block6); block3.FallThroughSuccessor.Destination.Should().Be(block4); block3.ConditionalSuccessor.Destination.Should().Be(block5); block3.SuccessorBlocks.Single().Should().Be(block4); // We don't add the unreachable ConditionalSuccessor in this case block6.SuccessorBlocks.Single().Should().Be(cfg.ExitBlock); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/CaptureIdTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using StyleCop.Analyzers.Lightup; using IFlowCaptureReferenceOperation = Microsoft.CodeAnalysis.FlowAnalysis.IFlowCaptureReferenceOperation; namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class CaptureIdTest { [TestMethod] public void Null_ThrowsException() { Action a = () => new CaptureId(null).ToString(); a.Should().Throw(); } [TestMethod] public void Equals_ReturnsFalse() { var capture = new CaptureId(new object()); capture.Equals(42).Should().BeFalse(); capture.Equals(null).Should().BeFalse(); } [TestMethod] public void ValidateReflection() { const string code = @" public class Sample { public string Method(object a, object b) => a?.ToString() + b?.ToString(); }"; var cfg = TestCompiler.CompileCfgCS(code); var outerLocalLifetimeRegion = cfg.Root.NestedRegions.Single(); outerLocalLifetimeRegion.Kind.Should().Be(ControlFlowRegionKind.LocalLifetime); outerLocalLifetimeRegion.NestedRegions.Should().HaveCount(2).And.OnlyContain(x => x.Kind == ControlFlowRegionKind.LocalLifetime); var nestedRegionA = outerLocalLifetimeRegion.NestedRegions.First(); var nestedRegionB = outerLocalLifetimeRegion.NestedRegions.Last(); var captureA = FindCapture(nestedRegionA, "a"); var captureB = FindCapture(nestedRegionB, "b"); // Assert nestedRegionA.CaptureIds.Should().HaveCount(1).And.Contain(captureA).And.NotContain(captureB); nestedRegionB.CaptureIds.Should().HaveCount(1).And.Contain(captureB).And.NotContain(captureA); nestedRegionA.CaptureIds.Single().GetHashCode().Should().Be(captureA.GetHashCode()).And.NotBe(captureB.GetHashCode()); nestedRegionA.CaptureIds.Single().Equals((object)captureA).Should().BeTrue(); nestedRegionA.CaptureIds.Single().Equals((object)captureB).Should().BeFalse(); CaptureId FindCapture(ControlFlowRegion region, string expectedName) { var flowCapture = (IFlowCaptureReferenceOperation)cfg.Blocks[region.FirstBlockOrdinal].BranchValue.ChildOperations.Single(); flowCapture.Syntax.ToString().Should().Be(expectedName); return new CaptureId(flowCapture.Id); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/CfgAllPathValidatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class CfgAllPathValidatorTest { [TestMethod] public void ValidateCfgPaths() { const string code = @" public class Sample { public int Method2(bool condition) { if (condition) return 42; else return 1; } }"; var cfg = TestCompiler.CompileCfgCS(code); /* * Entry 0 * | * | * Block 1 * / \ * Else / \ WhenFalse * / \ * Block 2 Block 3 * \ / * \ / * \ / * Exit 4 */ var validator = new TestCfgValidator(cfg, 0, 1, 2); validator.CheckAllPaths().Should().BeTrue(); // only entry block is valid validator = new TestCfgValidator(cfg, 0); validator.CheckAllPaths().Should().BeTrue(); // only exit block is valid validator = new TestCfgValidator(cfg, 4); validator.CheckAllPaths().Should().BeFalse(); var nonEntryBlockValid = new TestNonEntryBlockValidator(cfg); nonEntryBlockValid.CheckAllPaths().Should().BeTrue(); } // This test fails on the Sonar version of CfgAllPathValidator [TestMethod] public void ValidAfterBranching() { const string code = @" public class Sample { internal string Prop; public void Method(string input) { var x = true && true; Prop = input; } } "; var cfg = TestCompiler.CompileCfgCS(code); /* * Entry 0 * | * | * Block 1 * / \ * Else / \ WhenFalse * / \ * Block 2 Block 3 * \ / * \ / * \ / * Block 4 * | * | * Block 5 * | * | * Exit 6 */ var validator = new OnlyOneBlockIsValid(cfg, 5); validator.CheckAllPaths().Should().BeTrue(); } [TestMethod] public void LoopInCfg() { const string code = @" public class Sample { public void Method(string input) { var a = input; A: if (input != """") goto C; else goto B; C: input = System.String.Empty; goto A; B: input = input; } } "; var cfg = TestCompiler.CompileCfgCS(code); /* * Entry 0 * | * Block 1 * | * Block 2 <----> Block 3 * | * Block 4 * | * Exit 5 */ var validator = new OnlyOneBlockIsValid(cfg, 4); validator.CheckAllPaths().Should().BeTrue(); } private class TestNonEntryBlockValidator : CfgAllPathValidator { public TestNonEntryBlockValidator(ControlFlowGraph cfg) : base(cfg) { } protected override bool IsValid(BasicBlock block) => block.Ordinal > 0; protected override bool IsInvalid(BasicBlock block) => false; } private class TestCfgValidator : CfgAllPathValidator { private readonly int[] validBlocks; public TestCfgValidator(ControlFlowGraph cfg, params int[] validBlocks) : base(cfg) => this.validBlocks = validBlocks; protected override bool IsValid(BasicBlock block) => validBlocks.Contains(block.Ordinal); protected override bool IsInvalid(BasicBlock block) => !validBlocks.Contains(block.Ordinal); } private class OnlyOneBlockIsValid : CfgAllPathValidator { private readonly int validBlock; public OnlyOneBlockIsValid(ControlFlowGraph cfg, int validBlock) : base(cfg) => this.validBlock = validBlock; protected override bool IsValid(BasicBlock block) => validBlock == block.Ordinal; protected override bool IsInvalid(BasicBlock block) => false; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/ControlFlowBranchTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class ControlFlowBranchTest { [TestMethod] public void ValidateReflection() { const string code = @" public class Sample { public void Method(bool condition) { } // Empty, just Entry and Exit block }"; var cfg = TestCompiler.CompileCfgCS(code); var entry = cfg.EntryBlock; var exit = cfg.ExitBlock; var branch = entry.FallThroughSuccessor; branch.Source.Should().Be(entry); branch.Destination.Should().Be(exit); branch.Semantics.Should().Be(ControlFlowBranchSemantics.Regular); branch.IsConditionalSuccessor.Should().Be(false); branch.EnteringRegions.Should().BeEmpty(); branch.LeavingRegions.Should().BeEmpty(); branch.FinallyRegions.Should().BeEmpty(); } [TestMethod] public void ValidateReflection_Regions() { const string code = @" public class Sample { int field; public void Method(bool condition) { field = 0; try { field = 1; } finally { field = 42; } } }"; var cfg = TestCompiler.CompileCfgCS(code); /* * Entry 0 * | * V * Block 1 * field = 0 * | * +---------+-- TryAndFinally region ------------------------------+ * | | | * | +--Try-+-region -+ +-- Finally region --------------------+ | * | | | | | | | * | | v | | Block 3 | | * | | Block 2 | | field = 42 | | * | | field = 1 | | | | | * | | | | | | StructuredExceptionHandling | | * | +------+---------+ | V | | * | | | (null) | | * | | | | | * | | +--------------------------------------+ | * +---------+------------------------------------------------------+ * | * v * Exit 4 */ var initBlock = cfg.Blocks[1]; var tryBlock = cfg.Blocks[2]; var finallyBlock = cfg.Blocks[3]; var exitBlock = cfg.ExitBlock; initBlock.Kind.Should().Be(BasicBlockKind.Block); tryBlock.Kind.Should().Be(BasicBlockKind.Block); finallyBlock.Kind.Should().Be(BasicBlockKind.Block); exitBlock.Kind.Should().Be(BasicBlockKind.Exit); var tryAndFinallyRegion = cfg.Root.NestedRegions.Single(x => x.Kind == ControlFlowRegionKind.TryAndFinally); var tryRegion = tryAndFinallyRegion.NestedRegions.Single(x => x.Kind == ControlFlowRegionKind.Try); var finallyRegion = tryAndFinallyRegion.NestedRegions.Single(x => x.Kind == ControlFlowRegionKind.Finally); var entering = initBlock.FallThroughSuccessor; entering.Destination.Should().Be(tryBlock); entering.EnteringRegions.Should().HaveCount(2).And.ContainInOrder(tryAndFinallyRegion, tryRegion); entering.LeavingRegions.Should().BeEmpty(); entering.FinallyRegions.Should().BeEmpty(); var exiting = tryBlock.FallThroughSuccessor; exiting.Destination.Should().Be(exitBlock); exiting.EnteringRegions.Should().BeEmpty(); exiting.LeavingRegions.Should().HaveCount(2).And.ContainInOrder(tryRegion, tryAndFinallyRegion); exiting.FinallyRegions.Should().HaveCount(1).And.Contain(finallyRegion); var insideFinally = finallyBlock.FallThroughSuccessor; insideFinally.Destination.Should().BeNull(); insideFinally.Semantics.Should().Be(ControlFlowBranchSemantics.StructuredExceptionHandling); entering.EnteringRegions.Should().HaveCount(2).And.ContainInOrder(tryAndFinallyRegion, tryRegion); // Weird, but Roslyn does it this way. entering.LeavingRegions.Should().BeEmpty(); entering.FinallyRegions.Should().BeEmpty(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/ControlFlowRegionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class ControlFlowRegionTest { [TestMethod] public void ValidateReflection() { const string code = @" public class Sample { public void Method() { var value = LocalMethod(); try { throw new System.Exception(); } catch(System.InvalidOperationException) { } finally { value = 0; } int LocalMethod() => 42; } }"; var root = TestCompiler.CompileCfgCS(code).Root; root.Should().NotBeNull(); root.Kind.Should().Be(ControlFlowRegionKind.Root); root.EnclosingRegion.Should().BeNull(); root.ExceptionType.Should().BeNull(); root.FirstBlockOrdinal.Should().Be(0); root.LastBlockOrdinal.Should().Be(5); root.NestedRegions.Should().HaveCount(1); root.Locals.Should().BeEmpty(); root.LocalFunctions.Should().BeEmpty(); root.CaptureIds.Should().BeEmpty(); var localLifetime = root.NestedRegions.Single(); localLifetime.Kind.Should().Be(ControlFlowRegionKind.LocalLifetime); localLifetime.EnclosingRegion.Should().Be(root); localLifetime.ExceptionType.Should().BeNull(); localLifetime.FirstBlockOrdinal.Should().Be(1); localLifetime.LastBlockOrdinal.Should().Be(4); localLifetime.NestedRegions.Should().HaveCount(1); localLifetime.Locals.Should().HaveCount(1).And.Contain(x => x.Name == "value"); localLifetime.LocalFunctions.Should().HaveCount(1).And.Contain(x => x.Name == "LocalMethod"); localLifetime.CaptureIds.Should().BeEmpty(); var tryFinallyRegion = localLifetime.NestedRegions.Single(); tryFinallyRegion.Kind.Should().Be(ControlFlowRegionKind.TryAndFinally); var tryRegion = tryFinallyRegion.NestedRegions.First(); tryRegion.Kind.Should().Be(ControlFlowRegionKind.Try); var tryCatchRegion = tryRegion.NestedRegions.First(); tryCatchRegion.Kind.Should().Be(ControlFlowRegionKind.TryAndCatch); var catchRegion = tryCatchRegion.NestedRegions.Last(); catchRegion.Kind.Should().Be(ControlFlowRegionKind.Catch); catchRegion.ExceptionType.Should().NotBeNull(); catchRegion.ExceptionType.Name.Should().Be("InvalidOperationException"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynCfgSerializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.CFG.Sonar.Test; [TestClass] public class RoslynCfgSerializerTest { [TestMethod] public void Serialize_MethodNameUsedInTitle() { const string code = """ class Sample { void Method() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code), "GraphTitle"); dot.Should().BeIgnoringLineEndings( """ digraph "GraphTitle" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{EXIT #1}"] cfg0_block0 -> cfg0_block1 } """); } [TestMethod] public void Serialize_EmptyMethod() { const string code = """ class Sample { void Method() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings( """ digraph "RoslynCfg" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{EXIT #1}"] cfg0_block0 -> cfg0_block1 } """); } [TestMethod] public void Serialize_OperationSequence() { const string code = """ class Sample { void Method() { A(); B(); var c = C(); } private void A() { } private void B() { } private int C() => 42; } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: c" cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: A();|1#: 0#.Operation: InvocationOperation: A: A()|2#: 1#.Instance: InstanceReferenceOperation: A|##########|0#: ExpressionStatementOperation: B();|1#: 0#.Operation: InvocationOperation: B: B()|2#: 1#.Instance: InstanceReferenceOperation: B|##########|0#: SimpleAssignmentOperation: c = C()|1#: 0#.Target: LocalReferenceOperation: c = C()|1#: 0#.Value: InvocationOperation: C: C()|2#: 1#.Instance: InstanceReferenceOperation: C|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block2 [shape=record label="{EXIT #2}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 } """); } [TestMethod] public void Serialize_Switch() { const string code = """ class Sample { void Foo(int a) { switch (a) { case 1: c1(); break; case 2: c2(); break; } } private void c1() { } private void c2() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Captures: #Capture-0" cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: a|1#: 0#.Value: ParameterReferenceOperation: a|##########|## BranchValue ##|0#: BinaryOperation: 1|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: c1();|1#: 0#.Operation: InvocationOperation: c1: c1()|2#: 1#.Instance: InstanceReferenceOperation: c1|##########}"] cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: BinaryOperation: 2|1#: 0#.LeftOperand: FlowCaptureReferenceOperation: #Capture-0: a|1#: 0#.RightOperand: LiteralOperation: 2|##########}"] cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: c2();|1#: 0#.Operation: InvocationOperation: c2: c2()|2#: 1#.Instance: InstanceReferenceOperation: c2|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block5 [shape=record label="{EXIT #5}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 [label="Else"] cfg0_block1 -> cfg0_block3 [label="WhenFalse"] cfg0_block3 -> cfg0_block4 [label="Else"] cfg0_block2 -> cfg0_block5 cfg0_block3 -> cfg0_block5 [label="WhenFalse"] cfg0_block4 -> cfg0_block5 } """); } [TestMethod] public void Serialize_If() { const string code = """ class Sample { void Method() { if (true) { Bar(); } } void Bar() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: LiteralOperation: true|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] cfg0_block3 [shape=record label="{EXIT #3}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 [label="Else"] cfg0_block1 -> cfg0_block3 [label="WhenFalse"] cfg0_block2 -> cfg0_block3 } """); } [TestMethod] public void Serialize_Foreach_Simple() { const string code = """ class Sample { void Method(int[] items) { foreach (var i in items) { Bar(i); } } private void Bar(int i) { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Captures: #Capture-0" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" subgraph "cluster_4" { label = "LocalLifetime region, Locals: i" cfg0_block3 [shape=record label="{BLOCK #3|0#: SimpleAssignmentOperation: var|1#: 0#.Target: LocalReferenceOperation: var|1#: 0#.Value: ConversionOperation: var|2#: 1#.Operand: PropertyReferenceOperation: var|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########}"] } cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: items|##########}"] } subgraph "cluster_5" { label = "Finally region, Captures: #Capture-1" cfg0_block4 [shape=record label="{BLOCK #4|0#: FlowCaptureOperation: #Capture-1: items|1#: 0#.Value: ConversionOperation: items|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: items|##########|## BranchValue ##|0#: IsNullOperation: items|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] cfg0_block5 [shape=record label="{BLOCK #5|0#: InvocationOperation: Dispose: items|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: items|##########}"] cfg0_block6 [shape=record label="{BLOCK #6}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: items|1#: 0#.Value: InvocationOperation: GetEnumerator: items|2#: 1#.Instance: ConversionOperation: items|3#: 2#.Operand: ParameterReferenceOperation: items|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block7 [shape=record label="{EXIT #7}"] cfg0_block2 -> cfg0_block3 [label="Else"] cfg0_block1 -> cfg0_block2 cfg0_block3 -> cfg0_block2 cfg0_block4 -> cfg0_block5 [label="Else"] cfg0_block4 -> cfg0_block6 [label="WhenTrue"] cfg0_block5 -> cfg0_block6 cfg0_block6 -> NoDestination_cfg0_block6 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block2 -> cfg0_block7 [label="WhenFalse"] } """); } [TestMethod] public void Serialize_Foreach_TupleVarDeclaration() { const string code = """ public class Sample { public void Method((string key, string value)[] values) { foreach (var (key, value) in values) { string i = key; string j = value; } } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Captures: #Capture-0" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" subgraph "cluster_4" { label = "LocalLifetime region, Locals: key, value" subgraph "cluster_5" { label = "LocalLifetime region, Locals: i, j" cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: i = key|1#: 0#.Target: LocalReferenceOperation: i = key|1#: 0#.Value: LocalReferenceOperation: key|##########|0#: SimpleAssignmentOperation: j = value|1#: 0#.Target: LocalReferenceOperation: j = value|1#: 0#.Value: LocalReferenceOperation: value|##########}"] } cfg0_block3 [shape=record label="{BLOCK #3|0#: DeconstructionAssignmentOperation: var (key, value)|1#: 0#.Target: DeclarationExpressionOperation: var (key, value)|2#: 1#.Expression: TupleOperation: (key, value)|3#: LocalReferenceOperation: key|3#: LocalReferenceOperation: value|1#: 0#.Value: ConversionOperation: var (key, value)|2#: 1#.Operand: PropertyReferenceOperation: var (key, value)|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] } cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: InvocationOperation: MoveNext: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] } subgraph "cluster_6" { label = "Finally region, Captures: #Capture-1" cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: values|1#: 0#.Value: ConversionOperation: values|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: values|##########|## BranchValue ##|0#: IsNullOperation: values|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] cfg0_block6 [shape=record label="{BLOCK #6|0#: InvocationOperation: Dispose: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] cfg0_block7 [shape=record label="{BLOCK #7}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: values|1#: 0#.Value: InvocationOperation: GetEnumerator: values|2#: 1#.Instance: ConversionOperation: values|3#: 2#.Operand: ParameterReferenceOperation: values|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block8 [shape=record label="{EXIT #8}"] cfg0_block3 -> cfg0_block4 cfg0_block2 -> cfg0_block3 [label="Else"] cfg0_block1 -> cfg0_block2 cfg0_block4 -> cfg0_block2 cfg0_block5 -> cfg0_block6 [label="Else"] cfg0_block5 -> cfg0_block7 [label="WhenTrue"] cfg0_block6 -> cfg0_block7 cfg0_block7 -> NoDestination_cfg0_block7 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block2 -> cfg0_block8 [label="WhenFalse"] } """); } [TestMethod] public void Serialize_For() { const string code = """ class Sample { void Method() { for (var i = 0; i < 10; i++) { Bar(i); } } private void Bar(int i) { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: i" cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: i = 0|1#: 0#.Target: LocalReferenceOperation: i = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: BinaryOperation: i \< 10|1#: 0#.LeftOperand: LocalReferenceOperation: i|1#: 0#.RightOperand: LiteralOperation: 10|##########}"] cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Bar(i);|1#: 0#.Operation: InvocationOperation: Bar: Bar(i)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: i|3#: 2#.Value: LocalReferenceOperation: i|##########|0#: ExpressionStatementOperation: i++|1#: 0#.Operation: IncrementOrDecrementOperation: i++|2#: 1#.Target: LocalReferenceOperation: i|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block4 [shape=record label="{EXIT #4}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 cfg0_block3 -> cfg0_block2 cfg0_block2 -> cfg0_block3 [label="Else"] cfg0_block2 -> cfg0_block4 [label="WhenFalse"] } """); } [TestMethod] public void Serialize_Using() { const string code = """ class Sample { void Method() { using (var x = new System.IO.MemoryStream()) { Bar(); } } private void Bar() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: x" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] } subgraph "cluster_4" { label = "Finally region" cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: IsNullOperation: x = new System.IO.MemoryStream()|1#: 0#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Dispose: x = new System.IO.MemoryStream()|1#: 0#.Instance: ConversionOperation: x = new System.IO.MemoryStream()|2#: 1#.Operand: LocalReferenceOperation: x = new System.IO.MemoryStream()|##########}"] cfg0_block5 [shape=record label="{BLOCK #5}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: x = new System.IO.MemoryStream()|1#: 0#.Target: LocalReferenceOperation: x = new System.IO.MemoryStream()|1#: 0#.Value: ObjectCreationOperation: new System.IO.MemoryStream()|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block6 [shape=record label="{EXIT #6}"] cfg0_block1 -> cfg0_block2 cfg0_block3 -> cfg0_block4 [label="Else"] cfg0_block3 -> cfg0_block5 [label="WhenTrue"] cfg0_block4 -> cfg0_block5 cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block2 -> cfg0_block6 } """); } [TestMethod] public void Serialize_Lock() { const string code = """ class Sample { object x; void Method() { lock (x) { Bar(); } } private void Bar() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: N/A, Captures: #Capture-0" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" cfg0_block2 [shape=record label="{BLOCK #2|0#: InvocationOperation: Enter: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|1#: ArgumentOperation: x|2#: 1#.Value: LocalReferenceOperation: x|##########|0#: ExpressionStatementOperation: Bar();|1#: 0#.Operation: InvocationOperation: Bar: Bar()|2#: 1#.Instance: InstanceReferenceOperation: Bar|##########}"] } subgraph "cluster_4" { label = "Finally region" cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: LocalReferenceOperation: x|##########}"] cfg0_block4 [shape=record label="{BLOCK #4|0#: InvocationOperation: Exit: x|1#: ArgumentOperation: x|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-0: x|##########}"] cfg0_block5 [shape=record label="{BLOCK #5}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: FlowCaptureOperation: #Capture-0: x|1#: 0#.Value: FieldReferenceOperation: x|2#: 1#.Instance: InstanceReferenceOperation: x|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block6 [shape=record label="{EXIT #6}"] cfg0_block1 -> cfg0_block2 cfg0_block3 -> cfg0_block4 [label="Else"] cfg0_block3 -> cfg0_block5 [label="WhenFalse"] cfg0_block4 -> cfg0_block5 cfg0_block5 -> NoDestination_cfg0_block5 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block2 -> cfg0_block6 } """); } [TestMethod] public void Serialize_Regions() { const string code = """ class Sample { void Method() { Before(); try { InTry(); } finally { InFinally(); } After(); } private void Before() { } private void InTry() { } private void InFinally() { } private void After() { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "TryAndFinally region" subgraph "cluster_2" { label = "Try region" cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: InTry();|1#: 0#.Operation: InvocationOperation: InTry: InTry()|2#: 1#.Instance: InstanceReferenceOperation: InTry|##########}"] } subgraph "cluster_3" { label = "Finally region" cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: InFinally();|1#: 0#.Operation: InvocationOperation: InFinally: InFinally()|2#: 1#.Instance: InstanceReferenceOperation: InFinally|##########}"] } } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: Before();|1#: 0#.Operation: InvocationOperation: Before: Before()|2#: 1#.Instance: InstanceReferenceOperation: Before|##########}"] cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: After();|1#: 0#.Operation: InvocationOperation: After: After()|2#: 1#.Instance: InstanceReferenceOperation: After|##########}"] cfg0_block5 [shape=record label="{EXIT #5}"] cfg0_block1 -> cfg0_block2 cfg0_block3 -> NoDestination_cfg0_block3 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block2 -> cfg0_block4 cfg0_block4 -> cfg0_block5 } """); } [TestMethod] public void Serialize_Region_ExceptionType() { const string code = """ class Sample { void Method() { try { } catch(System.InvalidOperationException ex) { } } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "TryAndCatch region" subgraph "cluster_2" { label = "Try region" cfg0_block1 [shape=record label="{BLOCK #1}"] } subgraph "cluster_3" { label = "Catch region: System.InvalidOperationException, Locals: ex" cfg0_block2 [shape=record label="{BLOCK #2|0#: SimpleAssignmentOperation: (System.InvalidOperationException ex)|1#: 0#.Target: LocalReferenceOperation: (System.InvalidOperationException ex)|1#: 0#.Value: CaughtExceptionOperation: (System.InvalidOperationException ex)|##########}"] } } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block3 [shape=record label="{EXIT #3}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block3 cfg0_block2 -> cfg0_block3 } """); } [TestMethod] public void Serialize_LocalFunctions() { const string code = """ class Sample { void Method() { var fourty = 40; Local(); int Local() => fourty + LocalStatic(); static int LocalStatic() => LocalStaticArg(1); static int LocalStaticArg(int one) => one + 1; // Overloaded } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: fourty" cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: fourty = 40|1#: 0#.Target: LocalReferenceOperation: fourty = 40|1#: 0#.Value: LiteralOperation: 40|##########|0#: ExpressionStatementOperation: Local();|1#: 0#.Operation: InvocationOperation: Local: Local()|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block2 [shape=record label="{EXIT #2}"] subgraph "cluster_3" { label = "RoslynCfg.Local" cfg2_block0 [shape=record label="{ENTRY #0}"] cfg2_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: fourty + LocalStatic()|1#: 0#.LeftOperand: LocalReferenceOperation: fourty|1#: 0#.RightOperand: InvocationOperation: LocalStatic: LocalStatic()|##########}"] cfg2_block2 [shape=record label="{EXIT #2}"] } subgraph "cluster_5" { label = "RoslynCfg.LocalStatic" cfg4_block0 [shape=record label="{ENTRY #0}"] cfg4_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: InvocationOperation: LocalStaticArg: LocalStaticArg(1)|1#: ArgumentOperation: 1|2#: 1#.Value: LiteralOperation: 1|##########}"] cfg4_block2 [shape=record label="{EXIT #2}"] } subgraph "cluster_7" { label = "RoslynCfg.LocalStaticArg" cfg6_block0 [shape=record label="{ENTRY #0}"] cfg6_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: one + 1|1#: 0#.LeftOperand: ParameterReferenceOperation: one|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] cfg6_block2 [shape=record label="{EXIT #2}"] } cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 cfg2_block0 -> cfg2_block1 cfg2_block1 -> cfg2_block2 [label="Return"] cfg4_block0 -> cfg4_block1 cfg4_block1 -> cfg4_block2 [label="Return"] cfg6_block0 -> cfg6_block1 cfg6_block1 -> cfg6_block2 [label="Return"] } """); } [TestMethod] public void Serialize_Lambdas() { const string code = """ class Sample { void Method(int arg) { Bar(x => { return arg + 1; }); Bar(x => arg - 1); } private void Bar(System.Func f) { } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: Bar(x =\> \{ return arg + 1; \});|1#: 0#.Operation: InvocationOperation: Bar: Bar(x =\> \{ return arg + 1; \})|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: x =\> \{ return arg + 1; \}|3#: 2#.Value: DelegateCreationOperation: x =\> \{ return arg + 1; \}|4#: 3#.Target: FlowAnonymousFunctionOperation: x =\> \{ return arg + 1; \}|##########|0#: ExpressionStatementOperation: Bar(x =\> arg - 1);|1#: 0#.Operation: InvocationOperation: Bar: Bar(x =\> arg - 1)|2#: 1#.Instance: InstanceReferenceOperation: Bar|2#: ArgumentOperation: x =\> arg - 1|3#: 2#.Value: DelegateCreationOperation: x =\> arg - 1|4#: 3#.Target: FlowAnonymousFunctionOperation: x =\> arg - 1|##########}"] cfg0_block2 [shape=record label="{EXIT #2}"] subgraph "cluster_2" { label = "RoslynCfg.anonymous" cfg1_block0 [shape=record label="{ENTRY #0}"] cfg1_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: arg + 1|1#: 0#.LeftOperand: ParameterReferenceOperation: arg|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] cfg1_block2 [shape=record label="{EXIT #2}"] } subgraph "cluster_4" { label = "RoslynCfg.anonymous" cfg3_block0 [shape=record label="{ENTRY #0}"] cfg3_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: arg - 1|1#: 0#.LeftOperand: ParameterReferenceOperation: arg|1#: 0#.RightOperand: LiteralOperation: 1|##########}"] cfg3_block2 [shape=record label="{EXIT #2}"] } cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 cfg1_block0 -> cfg1_block1 cfg1_block1 -> cfg1_block2 [label="Return"] cfg3_block0 -> cfg3_block1 cfg3_block1 -> cfg3_block2 [label="Return"] } """); } [TestMethod] public void Serialize_CaptureId() { const string code = """ class Sample { void Method(bool arg) { bool b = arg && false; b = arg || true; } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: b" subgraph "cluster_2" { label = "LocalLifetime region, Captures: #Capture-0" cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|0#: FlowCaptureOperation: #Capture-0: false|1#: 0#.Value: LiteralOperation: false|##########}"] cfg0_block3 [shape=record label="{BLOCK #3|0#: FlowCaptureOperation: #Capture-0: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: b = arg && false|1#: 0#.Target: LocalReferenceOperation: b = arg && false|1#: 0#.Value: FlowCaptureReferenceOperation: #Capture-0: arg && false|##########}"] } subgraph "cluster_3" { label = "LocalLifetime region, Captures: #Capture-1, #Capture-2" cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: b|1#: 0#.Value: LocalReferenceOperation: b|##########|## BranchValue ##|0#: ParameterReferenceOperation: arg|##########}"] cfg0_block6 [shape=record label="{BLOCK #6|0#: FlowCaptureOperation: #Capture-2: true|1#: 0#.Value: LiteralOperation: true|##########}"] cfg0_block7 [shape=record label="{BLOCK #7|0#: FlowCaptureOperation: #Capture-2: arg|1#: 0#.Value: LiteralOperation: arg|##########}"] cfg0_block8 [shape=record label="{BLOCK #8|0#: ExpressionStatementOperation: b = arg \|\| true;|1#: 0#.Operation: SimpleAssignmentOperation: b = arg \|\| true|2#: 1#.Target: FlowCaptureReferenceOperation: #Capture-1: b|2#: 1#.Value: FlowCaptureReferenceOperation: #Capture-2: arg \|\| true|##########}"] } } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block9 [shape=record label="{EXIT #9}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 [label="Else"] cfg0_block1 -> cfg0_block3 [label="WhenFalse"] cfg0_block2 -> cfg0_block4 cfg0_block3 -> cfg0_block4 cfg0_block4 -> cfg0_block5 cfg0_block5 -> cfg0_block6 [label="Else"] cfg0_block5 -> cfg0_block7 [label="WhenTrue"] cfg0_block6 -> cfg0_block8 cfg0_block7 -> cfg0_block8 cfg0_block8 -> cfg0_block9 } """); } [TestMethod] public void Serialize_InvalidOperation() { const string code = """ class Sample { void Method() { undefined(); } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code, ignoreErrors: true)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|0#: ExpressionStatementOperation: undefined();|1#: 0#.Operation: INVALID: undefined()|2#: INVALID: undefined|##########}"] cfg0_block2 [shape=record label="{EXIT #2}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 } """); } [TestMethod] public void Serialize_TryCatchChain() { const string code = """ class Sample { void Method() { try { } catch { } try { } catch { } } } """; var dot = CfgSerializer.Serialize(TestCompiler.CompileCfgCS(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfg" { subgraph "cluster_1" { label = "TryAndCatch region" subgraph "cluster_2" { label = "Try region" cfg0_block1 [shape=record label="{BLOCK #1}"] } subgraph "cluster_3" { label = "Catch region: object" cfg0_block2 [shape=record label="{BLOCK #2}"] } } subgraph "cluster_4" { label = "TryAndCatch region" subgraph "cluster_5" { label = "Try region" cfg0_block3 [shape=record label="{BLOCK #3}"] } subgraph "cluster_6" { label = "Catch region: object" cfg0_block4 [shape=record label="{BLOCK #4}"] } } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block5 [shape=record label="{EXIT #5}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block3 cfg0_block2 -> cfg0_block3 cfg0_block3 -> cfg0_block5 cfg0_block4 -> cfg0_block5 } """); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynControlFlowGraphTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Operations; using SonarAnalyzer.CFG.Common; using SonarAnalyzer.CFG.Extensions; using StyleCop.Analyzers.Lightup; using FlowAnalysis = Microsoft.CodeAnalysis.FlowAnalysis; namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class RoslynControlFlowGraphTest { [TestMethod] public void IsAvailable_IsTrue() => ControlFlowGraph.IsAvailable.Should().BeTrue(); [TestMethod] public void Create_ReturnsCfg_CS() { const string code = """ public class Sample { public int Method() { return 42; } } """; TestCompiler.CompileCfgCS(code).Should().NotBeNull(); } [TestMethod] public void Create_ReturnsCfg_TopLevelStatements() { const string code = """ MethodA(); MethodB(); void MethodA() { } void MethodB() { } """; TestCompiler.CompileCfg(code, AnalyzerLanguage.CSharp, outputKind: OutputKind.ConsoleApplication).Should().NotBeNull(); } [TestMethod] public void Create_ReturnsCfg_VB() { const string code = """ Public Class Sample Public Function Method() As Integer Return 42 End Function End Class """; TestCompiler.CompileCfg(code, AnalyzerLanguage.VisualBasic).Should().NotBeNull(); } [TestMethod] public void ValidateReflection() { const string code = """ public class Sample { public int Method() { System.Action a = () => { }; return LocalMethod(); int LocalMethod() => 42; } } """; var cfg = TestCompiler.CompileCfgCS(code); cfg.Should().NotBeNull(); cfg.Root.Should().NotBeNull(); cfg.Blocks.Should().NotBeNull().And.HaveCount(3); // Enter, Instructions, Exit cfg.OriginalOperation.Should().NotBeNull().And.BeAssignableTo(); cfg.Parent.Should().BeNull(); cfg.LocalFunctions.Should().HaveCount(1); var localFunctionCfg = cfg.GetLocalFunctionControlFlowGraph(cfg.LocalFunctions.Single(), default); localFunctionCfg.Should().NotBeNull(); localFunctionCfg.Parent.Should().Be(cfg); var anonymousFunction = cfg.Blocks.SelectMany(x => x.Operations).SelectMany(OperationExtensions.DescendantsAndSelf).OfType().Single(); cfg.GetAnonymousFunctionControlFlowGraph(IFlowAnonymousFunctionOperationWrapper.FromOperation(anonymousFunction), default).Should().NotBeNull(); } [TestMethod] public void FlowAnonymousFunctionOperations_FindsAll() { const string code = """ public class Sample { private System.Action Simple(int a) { var x = 42; if (a == 42) { return (x) => { }; } return x => { }; } } """; var cfg = TestCompiler.CompileCfgCS(code); var anonymousFunctionOperations = ControlFlowGraphExtensions.FlowAnonymousFunctionOperations(cfg).ToList(); anonymousFunctionOperations.Should().HaveCount(2); cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunctionOperations[0], default).Should().NotBeNull(); cfg.GetAnonymousFunctionControlFlowGraph(anonymousFunctionOperations[1], default).Should().NotBeNull(); } [TestMethod] public void RoslynCfgSupportedVersions() { // We are running on 3 rd major version - it is the minimum requirement RoslynVersion.IsRoslynCfgSupported().Should().BeTrue(); // If we set minimum requirement to 2 - we will able to pass the check even with old MsBuild RoslynVersion.IsRoslynCfgSupported(2).Should().BeTrue(); // If we set minimum requirement to 100 - we won't be able to pass the check RoslynVersion.IsRoslynCfgSupported(100).Should().BeFalse(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Roslyn/RoslynLvaSerializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; namespace SonarAnalyzer.CFG.Roslyn.Test; [TestClass] public class RoslynLvaSerializerTest { [TestMethod] public void Serialize_TryCatchFinally() { const string code = """ class Sample { void Method() { var value = 0; try { Use(0); value = 42; } catch { Use(value); value = 1; } finally { Use(value); } } void Use(int v) {} } """; var dot = CfgSerializer.Serialize(CreateLva(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfgLva" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: value" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" subgraph "cluster_4" { label = "TryAndCatch region" subgraph "cluster_5" { label = "Try region" cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(0);|1#: 0#.Operation: InvocationOperation: Use: Use(0)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: 0|3#: 2#.Value: LiteralOperation: 0|##########|0#: ExpressionStatementOperation: value = 42;|1#: 0#.Operation: SimpleAssignmentOperation: value = 42|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 42|##########}"] } subgraph "cluster_6" { label = "Catch region: object" cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = 1;|1#: 0#.Operation: SimpleAssignmentOperation: value = 1|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 1|##########}"] } } } subgraph "cluster_7" { label = "Finally region" cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block5 [shape=record label="{EXIT #5}"] cfg0_block1 -> cfg0_block2 cfg0_block1 -> cfg0_block3 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block3 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block4 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block3 -> cfg0_block4 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block4 -> NoDestination_cfg0_block4 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block4 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block4 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block5 cfg0_block3 -> cfg0_block5 } """); } [TestMethod] public void Serialize_TryCatchFinallyRethrow() { const string code = """ class Sample { void Method() { var value = 0; try { Use(0); value = 42; } catch { Use(value); value = 1; throw; } finally { Use(value); } } void Use(int v) {} } """; var dot = CfgSerializer.Serialize(CreateLva(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfgLva" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: value" subgraph "cluster_2" { label = "TryAndFinally region" subgraph "cluster_3" { label = "Try region" subgraph "cluster_4" { label = "TryAndCatch region" subgraph "cluster_5" { label = "Try region" cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(0);|1#: 0#.Operation: InvocationOperation: Use: Use(0)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: 0|3#: 2#.Value: LiteralOperation: 0|##########|0#: ExpressionStatementOperation: value = 42;|1#: 0#.Operation: SimpleAssignmentOperation: value = 42|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 42|##########}"] } subgraph "cluster_6" { label = "Catch region: object" cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = 1;|1#: 0#.Operation: SimpleAssignmentOperation: value = 1|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LiteralOperation: 1|##########}"] } } } subgraph "cluster_7" { label = "Finally region" cfg0_block4 [shape=record label="{BLOCK #4|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########}"] } } cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block5 [shape=record label="{EXIT #5}"] cfg0_block1 -> cfg0_block2 cfg0_block1 -> cfg0_block3 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block3 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block3 -> NoDestination_cfg0_block3 [label="Rethrow"] cfg0_block2 -> cfg0_block4 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block3 -> cfg0_block4 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block4 -> NoDestination_cfg0_block4 [label="StructuredExceptionHandling"] cfg0_block0 -> cfg0_block1 cfg0_block4 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block5 } """); } [TestMethod] public void Serialize_While() { const string code = """ class Sample { void Method() { var value = 0; while (value < 10) { Use(value); value++; } } void Use(int v) {} } """; var dot = CfgSerializer.Serialize(CreateLva(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfgLva" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: value" cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|## BranchValue ##|0#: BinaryOperation: value \< 10|1#: 0#.LeftOperand: LocalReferenceOperation: value|1#: 0#.RightOperand: LiteralOperation: 10|##########}"] cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value++;|1#: 0#.Operation: IncrementOrDecrementOperation: value++|2#: 1#.Target: LocalReferenceOperation: value|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block4 [shape=record label="{EXIT #4}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 cfg0_block3 -> cfg0_block2 cfg0_block2 -> cfg0_block3 [label="Else"] cfg0_block2 -> cfg0_block4 [label="WhenFalse"] } """); } [TestMethod] public void Serialize_Foreach() { const string code = """ class Sample { void Method(int[] values) { var value = 0; foreach (var v in values) { Use(value); value = v; } } void Use(int v) {} } """; var dot = CfgSerializer.Serialize(CreateLva(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfgLva" { subgraph "cluster_1" { label = "LocalLifetime region, Locals: value" subgraph "cluster_2" { label = "LocalLifetime region, Captures: #Capture-0" subgraph "cluster_3" { label = "TryAndFinally region" subgraph "cluster_4" { label = "Try region" subgraph "cluster_5" { label = "LocalLifetime region, Locals: v" cfg0_block4 [shape=record label="{BLOCK #4|0#: SimpleAssignmentOperation: var|1#: 0#.Target: LocalReferenceOperation: var|1#: 0#.Value: ConversionOperation: var|2#: 1#.Operand: PropertyReferenceOperation: var|3#: 2#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: LocalReferenceOperation: value|##########|0#: ExpressionStatementOperation: value = v;|1#: 0#.Operation: SimpleAssignmentOperation: value = v|2#: 1#.Target: LocalReferenceOperation: value|2#: 1#.Value: LocalReferenceOperation: v|##########}"] } cfg0_block3 [shape=record label="{BLOCK #3|## BranchValue ##|0#: InvocationOperation: MoveNext: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-0: values|##########}"] } subgraph "cluster_6" { label = "Finally region, Captures: #Capture-1" cfg0_block5 [shape=record label="{BLOCK #5|0#: FlowCaptureOperation: #Capture-1: values|1#: 0#.Value: ConversionOperation: values|2#: 1#.Operand: FlowCaptureReferenceOperation: #Capture-0: values|##########|## BranchValue ##|0#: IsNullOperation: values|1#: 0#.Operand: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] cfg0_block6 [shape=record label="{BLOCK #6|0#: InvocationOperation: Dispose: values|1#: 0#.Instance: FlowCaptureReferenceOperation: #Capture-1: values|##########}"] cfg0_block7 [shape=record label="{BLOCK #7}"] } } cfg0_block2 [shape=record label="{BLOCK #2|0#: FlowCaptureOperation: #Capture-0: values|1#: 0#.Value: InvocationOperation: GetEnumerator: values|2#: 1#.Instance: ConversionOperation: values|3#: 2#.Operand: ParameterReferenceOperation: values|##########}"] } cfg0_block1 [shape=record label="{BLOCK #1|0#: SimpleAssignmentOperation: value = 0|1#: 0#.Target: LocalReferenceOperation: value = 0|1#: 0#.Value: LiteralOperation: 0|##########}"] } cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block8 [shape=record label="{EXIT #8}"] cfg0_block3 -> cfg0_block4 [label="Else"] cfg0_block2 -> cfg0_block3 cfg0_block4 -> cfg0_block3 cfg0_block3 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block3 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block2 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block4 -> cfg0_block5 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block5 -> cfg0_block6 [label="Else"] cfg0_block5 -> cfg0_block7 [label="WhenTrue"] cfg0_block6 -> cfg0_block7 cfg0_block7 -> NoDestination_cfg0_block7 [label="StructuredExceptionHandling"] cfg0_block1 -> cfg0_block2 cfg0_block0 -> cfg0_block1 cfg0_block7 -> cfg0_block8 [label="LVA" fontcolor="blue" penwidth="2" color="blue"] cfg0_block3 -> cfg0_block8 [label="WhenFalse"] } """); } [TestMethod] public void Serialize_IfElse() { const string code = """ class Sample { void Method(int value) { if (value % 2 == 0) { Use(value); } else { Use(value); } } void Use(int v) {} } """; var dot = CfgSerializer.Serialize(CreateLva(code)); dot.Should().BeIgnoringLineEndings(""" digraph "RoslynCfgLva" { cfg0_block0 [shape=record label="{ENTRY #0}"] cfg0_block1 [shape=record label="{BLOCK #1|## BranchValue ##|0#: BinaryOperation: value % 2 == 0|1#: 0#.LeftOperand: BinaryOperation: value % 2|2#: 1#.LeftOperand: ParameterReferenceOperation: value|2#: 1#.RightOperand: LiteralOperation: 2|1#: 0#.RightOperand: LiteralOperation: 0|##########}"] cfg0_block2 [shape=record label="{BLOCK #2|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: ParameterReferenceOperation: value|##########}"] cfg0_block3 [shape=record label="{BLOCK #3|0#: ExpressionStatementOperation: Use(value);|1#: 0#.Operation: InvocationOperation: Use: Use(value)|2#: 1#.Instance: InstanceReferenceOperation: Use|2#: ArgumentOperation: value|3#: 2#.Value: ParameterReferenceOperation: value|##########}"] cfg0_block4 [shape=record label="{EXIT #4}"] cfg0_block0 -> cfg0_block1 cfg0_block1 -> cfg0_block2 [label="Else"] cfg0_block1 -> cfg0_block3 [label="WhenFalse"] cfg0_block2 -> cfg0_block4 cfg0_block3 -> cfg0_block4 } """); } private static RoslynLiveVariableAnalysis CreateLva(string code) { var cfg = TestCompiler.CompileCfgCS(code); return new RoslynLiveVariableAnalysis(cfg, CSharpSyntaxClassifier.Instance, CancellationToken.None); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/BlockIdProviderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Sonar; namespace SonarAnalyzer.Test.CFG.Sonar; [TestClass] public class BlockIdProviderTest { private BlockIdProvider blockId; [TestInitialize] public void TestInitialize() { blockId = new BlockIdProvider(); } [TestMethod] public void Get_Returns_Same_Id_For_Same_Block() { var block = new TemporaryBlock(); blockId.Get(block).Should().Be("0"); blockId.Get(block).Should().Be("0"); blockId.Get(block).Should().Be("0"); } [TestMethod] public void Get_Returns_Different_Id_For_Different_Block() { var id1 = blockId.Get(new TemporaryBlock()); var id2 = blockId.Get(new TemporaryBlock()); var id3 = blockId.Get(new TemporaryBlock()); id1.Should().Be("0"); id2.Should().Be("1"); id3.Should().Be("2"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarCfgSerializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SonarAnalyzer.CFG.Sonar.Test; [TestClass] public class SonarCfgSerializerTest { [TestMethod] public void Serialize_Empty_Method() { const string code = """ class C { void Foo() { } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{EXIT}"] } """); } [TestMethod] public void Serialize_Branch_Jump() { const string code = """ class C { void Foo(int a) { switch (a) { case 1: c1(); break; case 2: c2(); break; } } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{BRANCH:SwitchStatement|a}"] 1 [shape=record label="{BINARY:CaseSwitchLabel|a}"] 2 [shape=record label="{JUMP:BreakStatement|c1|c1()}"] 3 [shape=record label="{BINARY:CaseSwitchLabel|a}"] 5 [shape=record label="{JUMP:BreakStatement|c2|c2()}"] 4 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 [label="True"] 1 -> 3 [label="False"] 2 -> 4 3 -> 5 [label="True"] 3 -> 4 [label="False"] 5 -> 4 } """); } [TestMethod] public void Serialize_BinaryBranch_Simple() { const string code = """ class C { void Foo() { if (true) { Bar(); } } void Bar() { } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{BINARY:TrueLiteralExpression|true}"] 1 [shape=record label="{SIMPLE|Bar|Bar()}"] 2 [shape=record label="{EXIT}"] 0 -> 1 [label="True"] 0 -> 2 [label="False"] 1 -> 2 } """); } [TestMethod] public void Serialize_Foreach_Binary_Simple() { const string code = """ class C { void Foo() { foreach (var i in items) { Bar(); } } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{FOREACH:ForEachStatement|items}"] 1 [shape=record label="{BINARY:ForEachStatement}"] 2 [shape=record label="{SIMPLE|Bar|Bar()}"] 3 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 [label="True"] 1 -> 3 [label="False"] 2 -> 1 } """); } [TestMethod] public void Serialize_Foreach_Binary_VarDeclaration() { const string code = """ namespace Namespace { public class Test { public void ForEach((string key, string value)[] values) { foreach (var (key, value) in values) { string i = key; string j = value; } } } }; """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "ForEach"); dot.Should().BeIgnoringLineEndings(""" digraph "ForEach" { 0 [shape=record label="{FOREACH:ForEachVariableStatement|values}"] 1 [shape=record label="{BINARY:ForEachVariableStatement}"] 2 [shape=record label="{SIMPLE|key|i = key|value|j = value}"] 3 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 [label="True"] 1 -> 3 [label="False"] 2 -> 1 } """); } [TestMethod] public void Serialize_For_Binary_Simple() { const string code = """ class C { void Foo() { for (var i = 0; i < 10; i++) { Bar(); } } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{FOR:ForStatement|0|i = 0}"] 1 [shape=record label="{BINARY:ForStatement|i|10|i \< 10}"] 2 [shape=record label="{SIMPLE|Bar|Bar()}"] 4 [shape=record label="{SIMPLE|i|i++}"] 3 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 [label="True"] 1 -> 3 [label="False"] 2 -> 4 4 -> 1 } """); } [TestMethod] public void Serialize_Jump_Using() { const string code = """ class C { void Foo() { using (x) { Bar(); } } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{JUMP:UsingStatement|x}"] 1 [shape=record label="{USING:UsingStatement|Bar|Bar()}"] 2 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 } """); } [TestMethod] public void Serialize_Lock_Simple() { const string code = """ class C { void Foo() { lock (x) { Bar(); } } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{LOCK:LockStatement|x}"] 1 [shape=record label="{SIMPLE|Bar|Bar()}"] 2 [shape=record label="{EXIT}"] 0 -> 1 1 -> 2 } """); } [TestMethod] public void Serialize_Lambda() { const string code = """ class C { void Foo() { Bar(x => { return 1 + 1; }); } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Foo"); dot.Should().BeIgnoringLineEndings(""" digraph "Foo" { 0 [shape=record label="{SIMPLE|Bar|x =\>\n \{\n return 1 + 1;\n \}|Bar(x =\>\n \{\n return 1 + 1;\n \})}"] 1 [shape=record label="{EXIT}"] 0 -> 1 } """); } [TestMethod] public void Serialize_Range() { const string code = """ internal class Test { public void Range() { Range r = 1..4; } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); dot.Should().BeIgnoringLineEndings(""" digraph "Range" { 0 [shape=record label="{SIMPLE|1..4|r = 1..4}"] 1 [shape=record label="{EXIT}"] 0 -> 1 } """); } [TestMethod] public void Serialize_Index() { const string code = """ internal class Test { public void Index() { Index index = ^1; } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Index"); dot.Should().BeIgnoringLineEndings(""" digraph "Index" { 0 [shape=record label="{SIMPLE|^1|index = ^1}"] 1 [shape=record label="{EXIT}"] 0 -> 1 } """); } [TestMethod] public void Serialize_IndexInRange() { const string code = """ internal class Test { public void Range() { Range range = ^2..^0; } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); dot.Should().BeIgnoringLineEndings(""" digraph "Range" { 0 [shape=record label="{SIMPLE|^2..^0|range = ^2..^0}"] 1 [shape=record label="{EXIT}"] 0 -> 1 } """); } [TestMethod] public void Serialize_RangeInIndexer() { const string code = """ internal class Test { public void Range() { var ints = new[] { 1, 2 }; var lastTwo = ints[^2..^1]; } } """; var dot = CfgSerializer.Serialize(CreateMethodCfg(code), "Range"); dot.Should().BeIgnoringLineEndings(""" digraph "Range" { 0 [shape=record label="{SIMPLE|new[] \{ 1, 2 \}|1|2|\{ 1, 2 \}|ints = new[] \{ 1, 2 \}|ints|^2..^1|ints[^2..^1]|lastTwo = ints[^2..^1]}"] 1 [shape=record label="{EXIT}"] 0 -> 1 } """); } private static IControlFlowGraph CreateMethodCfg(string code) { var (tree, model) = TestCompiler.CompileIgnoreErrorsCS(code); return CSharpControlFlowGraph.Create(tree.First().Body, model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/CFG/Sonar/SonarControlFlowGraphTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.CFG.Sonar.Test { [TestClass] public class SonarControlFlowGraphTest { private const string SimpleReturn = "return"; private const string SimpleThrow = "throw"; private const string SimpleYieldBreak = "yield break"; private const string ExpressionReturn = "return ii"; private const string ExpressionThrow = "throw ii"; #region Top level - build CFG expression body / body [TestMethod] public void Cfg_Constructed_for_Body() { var cfg = Build("i = 4 + 5;"); VerifyMinimalCfg(cfg); } [TestMethod] public void Cfg_Constructed_for_ExpressionBody() { const string input = @" namespace NS { public class Foo { public int Bar() => 4 + 5; } }"; var (method, model) = CompileWithMethodBody(input); var expression = method.ExpressionBody.Expression; var cfg = CSharpControlFlowGraph.Create(expression, model); VerifyMinimalCfg(cfg); } [TestMethod] public void Cfg_Constructed_for_Ctor_ComplexArguments() { const string input = @" namespace NS { public class Foo { private static int num = 10; public Foo() : this(true ? 5 : num + num) { var x = num; } public Foo(int i) {} } }"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var cfg = CSharpControlFlowGraph.Create(FirstConstructorBody(tree), semanticModel); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToArray(); var conditionalBlock = (BinaryBranchBlock)blocks[0]; var trueCondition = blocks[1]; var falseCondition = blocks[2]; var constructorBody = blocks[3]; var exit = cfg.ExitBlock; conditionalBlock.TrueSuccessorBlock.Should().Be(trueCondition); conditionalBlock.FalseSuccessorBlock.Should().Be(falseCondition); trueCondition.SuccessorBlocks.Should().Equal(constructorBody); falseCondition.SuccessorBlocks.Should().Equal(constructorBody); constructorBody.SuccessorBlocks.Should().Equal(exit); exit.PredecessorBlocks.Should().Equal(constructorBody); VerifyAllInstructions(conditionalBlock, "true"); VerifyAllInstructions(trueCondition, "5"); VerifyAllInstructions(falseCondition, "num", "num", "num + num"); VerifyAllInstructions(constructorBody, ": this(true ? 5 : num + num)", "num", "x = num"); VerifyAllInstructions(exit); } [TestMethod] public void Cfg_Constructed_for_Ctor_This() { const string input = @" namespace NS { public class Foo { public Foo() : this(5) {} public Foo(int i) {} } }"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var cfg = CSharpControlFlowGraph.Create(FirstConstructorBody(tree), semanticModel); VerifyCfg(cfg, 2); var blocks = cfg.Blocks.ToArray(); var constructorBody = blocks[0]; var exit = cfg.ExitBlock; constructorBody.SuccessorBlocks.Should().Equal(exit); exit.PredecessorBlocks.Should().Equal(constructorBody); VerifyAllInstructions(constructorBody, "5", ": this(5)"); VerifyAllInstructions(exit); } [TestMethod] public void Cfg_Constructed_for_Ctor_Base() { const string input = @" namespace NS { public class Foo : Bar { public Foo() : base(5) {} } public class Bar { public Bar(int i) {} } }"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var cfg = CSharpControlFlowGraph.Create(FirstConstructorBody(tree), semanticModel); VerifyCfg(cfg, 2); var blocks = cfg.Blocks.ToArray(); var constructorBody = blocks[0]; var exit = cfg.ExitBlock; constructorBody.SuccessorBlocks.Should().Equal(exit); exit.PredecessorBlocks.Should().Equal(constructorBody); VerifyAllInstructions(constructorBody, "5", ": base(5)"); VerifyAllInstructions(exit); } [TestMethod] public void Cfg_ExtremelyNestedExpression_NotSupported_FromExpression() { var (method, model) = CompileWithMethodBody(string.Format(TestInput, $"var x = {ExtremelyNestedExpression()};")); var equalsValueSyntax = method.DescendantNodes(x => !(x is ExpressionSyntax)).OfType().Single(); Action a = () => CSharpControlFlowGraph.Create(equalsValueSyntax.Value, model); a.Should().Throw().WithMessage("Too complex expression"); CSharpControlFlowGraph.TryGet(equalsValueSyntax.Value, model, out _).Should().BeFalse(); } [TestMethod] public void Cfg_ExtremelyNestedExpression_NotSupported_FromBodyMethod() { var input = @$" public class Sample {{ public string Main() {{ return {ExtremelyNestedExpression()}; }} }}"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var method = FirstMethod(tree); Action a = () => CSharpControlFlowGraph.Create(method.Body, semanticModel); a.Should().Throw().WithMessage("Too complex expression"); CSharpControlFlowGraph.TryGet(method.Body, semanticModel, out _).Should().BeFalse(); } [TestMethod] public void Cfg_ExtremelyNestedExpression_NotSupported_FromArrowMethod() { var input = @$" public class Sample {{ public string Main() =>{ExtremelyNestedExpression()}; }}"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var method = FirstMethod(tree); Action a = () => CSharpControlFlowGraph.Create(method.ExpressionBody, semanticModel); a.Should().Throw().WithMessage("Too complex expression"); CSharpControlFlowGraph.TryGet(method.ExpressionBody, semanticModel, out _).Should().BeFalse(); } [TestMethod] public void Cfg_ExtremelyNestedExpression_IsSupported_InSimpleLambda() { var input = @$" public class Sample {{ public void Main() => Go(x => x + {ExtremelyNestedExpression()}); public void Go(System.Func arg) {{ }} }}"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var method = FirstMethod(tree); CSharpControlFlowGraph.Create(method.ExpressionBody, semanticModel).Should().NotBeNull(); CSharpControlFlowGraph.TryGet(method, semanticModel, out _).Should().BeTrue(); } [TestMethod] public void Cfg_ExtremelyNestedExpression_IsSupported_InParenthesizedLambda() { var input = @$" public class Sample {{ public void Main() => Go(() => {ExtremelyNestedExpression()}); public void Go(System.Func arg) {{ }} }}"; var (tree, semanticModel) = TestCompiler.CompileCS(input); var method = FirstMethod(tree); CSharpControlFlowGraph.Create(method.ExpressionBody, semanticModel).Should().NotBeNull(); CSharpControlFlowGraph.TryGet(method, semanticModel, out _).Should().BeTrue(); } #endregion #region Empty statement [TestMethod] public void Cfg_EmptyStatement() { var cfg = Build(";;;;;"); VerifyEmptyCfg(cfg); } #endregion #region Variable declaration [TestMethod] public void Cfg_VariableDeclaration() { var cfg = Build("var x = 10, y = 11; var z = 12;"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "10", "x = 10", "11", "y = 11", "12", "z = 12"); } #endregion #region If statement [TestMethod] public void Cfg_If() { var cfg = Build("if (true) { var x = 10; }"); VerifyCfg(cfg, 3); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var trueBlock = cfg.Blocks.ToList()[1]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); trueBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { branchBlock, trueBlock }); VerifyAllInstructions(branchBlock, "true"); VerifyAllInstructions(trueBlock, "10", "x = 10"); } [TestMethod] public void Cfg_If_Branch_Or_Condition() { var cfg = Build("if (a || b) { var x = 10; }"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var trueBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockA.FalseSuccessorBlock.Should().Be(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); } [TestMethod] public void Cfg_If_Branch_And_Condition() { var cfg = Build("if (a && b) { var x = 10; }"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var trueBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(branchBlockB); branchBlockA.FalseSuccessorBlock.Should().Be(exitBlock); branchBlockB.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); } [TestMethod] public void Cfg_If_Branch_And_Condition_Parentheses() { var cfg = Build("if (((a && b))) { var x = 10; }"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var trueBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(branchBlockB); branchBlockA.FalseSuccessorBlock.Should().Be(exitBlock); branchBlockB.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); } [TestMethod] public void Cfg_If_Else() { var cfg = Build("if (true) { var x = 10; } else { var y = 11; }"); VerifyCfg(cfg, 4); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var trueBlock = cfg.Blocks.ToList()[1]; var falseBlock = cfg.Blocks.ToList()[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); trueBlock.SuccessorBlocks.Should().Equal(exitBlock); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); } [TestMethod] public void Cfg_If_ElseIf() { var cfg = Build("if (true) { var x = 10; } else if (false) { var y = 11; }"); VerifyCfg(cfg, 5); var firstCondition = cfg.EntryBlock as BinaryBranchBlock; var trueBlockX = cfg.Blocks.ToList()[1]; var secondCondition = cfg.Blocks.ToList()[2] as BinaryBranchBlock; var trueBlockY = cfg.Blocks.ToList()[3]; var exitBlock = cfg.ExitBlock; firstCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, secondCondition }); firstCondition.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); trueBlockX.SuccessorBlocks.Should().Equal(exitBlock); secondCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlockY, exitBlock }); secondCondition.BranchingNode.Kind().Should().Be(SyntaxKind.FalseLiteralExpression); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, trueBlockY, secondCondition }, x => x.IgnoringCyclicReferences()); } [TestMethod] public void Cfg_If_ElseIf_Else() { var cfg = Build("if (true) { var x = 10; } else if (false) { var y = 11; } else { var z = 12; }"); VerifyCfg(cfg, 6); var firstCondition = cfg.EntryBlock as BinaryBranchBlock; var trueBlockX = cfg.Blocks.ToList()[1]; var secondCondition = cfg.Blocks.ToList()[2] as BinaryBranchBlock; var trueBlockY = cfg.Blocks.ToList()[3]; var falseBlockZ = cfg.Blocks.ToList()[4]; var exitBlock = cfg.ExitBlock; firstCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, secondCondition }); firstCondition.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); trueBlockX.SuccessorBlocks.Should().Equal(exitBlock); secondCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlockY, falseBlockZ }); secondCondition.BranchingNode.Kind().Should().Be(SyntaxKind.FalseLiteralExpression); trueBlockY.SuccessorBlocks.Should().Equal(exitBlock); falseBlockZ.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, trueBlockY, falseBlockZ }, x => x.IgnoringCyclicReferences()); } [TestMethod] public void Cfg_NestedIf() { var cfg = Build("if (true) { if (false) { var x = 10; } else { var y = 10; } }"); VerifyCfg(cfg, 5); var firstCondition = cfg.EntryBlock as BinaryBranchBlock; firstCondition.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.TrueLiteralExpression)); var secondCondition = cfg.Blocks.ToList()[1] as BinaryBranchBlock; secondCondition.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.FalseLiteralExpression)); var trueBlockX = cfg.Blocks.ToList()[2]; var falseBlockY = cfg.Blocks.ToList()[3]; var exitBlock = cfg.ExitBlock; firstCondition.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { secondCondition, exitBlock }); firstCondition.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); secondCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, falseBlockY }); secondCondition.BranchingNode.Kind().Should().Be(SyntaxKind.FalseLiteralExpression); trueBlockX.SuccessorBlocks.Should().Equal(exitBlock); falseBlockY.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlockX, falseBlockY, firstCondition }); } [TestMethod] public void Cfg_If_Coalesce() { var cfg = Build("if (a ?? b) { var y = 10; }"); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockALeft = (BranchBlock)blocks[1]; var branchBlockB = (BinaryBranchBlock)blocks[2]; var trueBlock = blocks[3]; var exit = blocks[4]; branchBlockA.TrueSuccessorBlock.Should().Be(branchBlockB); branchBlockA.FalseSuccessorBlock.Should().Be(branchBlockALeft); branchBlockALeft.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, exit }); branchBlockB.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exit); trueBlock.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_Conditional_ComplexCondition_Coalesce() { var cfg = Build("var a = (x ?? y) ? b : c;"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var branchBlockX = (BinaryBranchBlock)blocks[0]; var branchBlockXLeft = (BranchBlock)blocks[1]; var branchBlockY = (BinaryBranchBlock)blocks[2]; var condTrue = blocks[3]; var condFalse = blocks[4]; var after = blocks[5]; var exitBlock = cfg.ExitBlock; branchBlockX.TrueSuccessorBlock.Should().Be(branchBlockY); branchBlockX.FalseSuccessorBlock.Should().Be(branchBlockXLeft); branchBlockXLeft.SuccessorBlocks.Should().BeEquivalentTo(new[] { condTrue, condFalse }); branchBlockY.TrueSuccessorBlock.Should().Be(condTrue); branchBlockY.FalseSuccessorBlock.Should().Be(condFalse); condFalse.SuccessorBlocks.Should().Equal(after); condTrue.SuccessorBlocks.Should().Equal(after); after.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(branchBlockX, "x"); VerifyAllInstructions(branchBlockXLeft); VerifyAllInstructions(branchBlockY, "y"); VerifyAllInstructions(condTrue, "b"); VerifyAllInstructions(condFalse, "c"); VerifyAllInstructions(after, "a = (x ?? y) ? b : c"); } [TestMethod] public void Cfg_If_Patterns_Constant_Complex_Condition() { var cfg = Build("cw0(); if (x is 10 && o is null) { cw1(); } cw2()"); VerifyCfg(cfg, 5); var xBranchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var oBranchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var trueBlock = cfg.Blocks.ElementAt(2); var falseBlock = cfg.Blocks.ElementAt(3); var exitBlock = cfg.ExitBlock; xBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { oBranchBlock, falseBlock }); xBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.IsPatternExpression); VerifyAllInstructions(xBranchBlock, "cw0", "cw0()", "x", "10", "x is 10"); oBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); oBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.IsPatternExpression); VerifyAllInstructions(oBranchBlock, "o", "null", "o is null"); trueBlock.SuccessorBlocks.Should().Equal(falseBlock); VerifyAllInstructions(trueBlock, "cw1", "cw1()"); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(falseBlock, "cw2", "cw2()"); exitBlock.PredecessorBlocks.Should().Equal(falseBlock); } [TestMethod] public void Cfg_If_Patterns_Single_Var_Complex_Condition() { var cfg = Build("cw0(); if (x is int i && o is string s) { cw1(); } cw2()"); VerifyCfg(cfg, 5); var xBranchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var oBranchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var trueBlock = cfg.Blocks.ElementAt(2); var falseBlock = cfg.Blocks.ElementAt(3); var exitBlock = cfg.ExitBlock; xBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { oBranchBlock, falseBlock }); xBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.IsPatternExpression); VerifyAllInstructions(xBranchBlock, "cw0", "cw0()", "x", "x is int i"); oBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); oBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.IsPatternExpression); VerifyAllInstructions(oBranchBlock, "o", "o is string s"); trueBlock.SuccessorBlocks.Should().Equal(falseBlock); VerifyAllInstructions(trueBlock, "cw1", "cw1()"); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(falseBlock, "cw2", "cw2()"); exitBlock.PredecessorBlocks.Should().Equal(falseBlock); } [TestMethod] public void Cfg_If_Patterns_Not_Null() { var cfg = Build("cw0(); if (!(x is null)) { cw1(); } cw2()"); VerifyCfg(cfg, 4); var xBranchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var trueBlock = cfg.Blocks.ElementAt(1); var falseBlock = cfg.Blocks.ElementAt(2); var exitBlock = cfg.ExitBlock; xBranchBlock.TrueSuccessorBlock.Should().Be(trueBlock); xBranchBlock.FalseSuccessorBlock.Should().Be(falseBlock); xBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.LogicalNotExpression); VerifyAllInstructions(xBranchBlock, "cw0", "cw0()", "x", "null", "x is null", "!(x is null)"); trueBlock.SuccessorBlocks.Should().Equal(falseBlock); VerifyAllInstructions(trueBlock, "cw1", "cw1()"); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(falseBlock, "cw2", "cw2()"); exitBlock.PredecessorBlocks.Should().Equal(falseBlock); } #endregion #region While statement [TestMethod] public void Cfg_While() { var cfg = Build("while (true) { var x = 10; }"); VerifyCfg(cfg, 3); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var loopBodyBlock = cfg.Blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.PredecessorBlocks.Should().Equal(loopBodyBlock); exitBlock.PredecessorBlocks.Should().Equal(branchBlock); VerifyAllInstructions(branchBlock, "true"); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); } [TestMethod] public void Cfg_While_ComplexCondition_Or() { var cfg = Build("while (a || b) { var x = 10; }"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var branchBlockA = blocks[0] as BinaryBranchBlock; var branchBlockB = blocks[1] as BinaryBranchBlock; var loopBodyBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(loopBodyBlock); branchBlockA.FalseSuccessorBlock.Should().Be(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(loopBodyBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlockA); exitBlock.PredecessorBlocks.Should().Equal(branchBlockB); VerifyAllInstructions(branchBlockA, "a"); VerifyAllInstructions(branchBlockB, "b"); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); } [TestMethod] public void Cfg_While_ComplexCondition_And() { var cfg = Build("while (a && b) { var x = 10; }"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var branchBlockA = blocks[0] as BinaryBranchBlock; var branchBlockB = blocks[1] as BinaryBranchBlock; var loopBodyBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(branchBlockB); branchBlockA.FalseSuccessorBlock.Should().Be(exitBlock); branchBlockB.TrueSuccessorBlock.Should().Be(loopBodyBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlockA); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { branchBlockB, branchBlockA }); VerifyAllInstructions(branchBlockA, "a"); VerifyAllInstructions(branchBlockB, "b"); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); } [TestMethod] public void Cfg_NestedWhile() { var cfg = Build("while (true) while(false) { var x = 10; }"); VerifyCfg(cfg, 4); var firstBranchBlock = cfg.EntryBlock as BinaryBranchBlock; firstBranchBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.TrueLiteralExpression)); var blocks = cfg.Blocks.ToList(); var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var secondBranchBlock = blocks[1] as BinaryBranchBlock; secondBranchBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.FalseLiteralExpression)); var exitBlock = cfg.ExitBlock; firstBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { secondBranchBlock, exitBlock }); firstBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); secondBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, firstBranchBlock }); secondBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.FalseLiteralExpression); loopBodyBlock.SuccessorBlocks.Should().Equal(secondBranchBlock); firstBranchBlock.PredecessorBlocks.Should().Equal(secondBranchBlock); secondBranchBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { firstBranchBlock, loopBodyBlock }); exitBlock.PredecessorBlocks.Should().Equal(firstBranchBlock); } #endregion #region Do statement [TestMethod] public void Cfg_DoWhile_ComplexCondition() { var cfg = Build("do { var x = 10; } while (a || b);"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var loopBodyBlock = blocks[0]; var branchBlockA = (BinaryBranchBlock)blocks[1]; var branchBlockB = (BinaryBranchBlock)blocks[2]; var exitBlock = cfg.ExitBlock; loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlockA); branchBlockA.TrueSuccessorBlock.Should().Be(loopBodyBlock); branchBlockA.FalseSuccessorBlock.Should().Be(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(loopBodyBlock); branchBlockB.FalseSuccessorBlock.Should().Be(exitBlock); exitBlock.PredecessorBlocks.Should().Equal(branchBlockB); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); } [TestMethod] public void Cfg_DoWhile() { var cfg = Build("do { var x = 10; } while (true);"); VerifyCfg(cfg, 3); var branchBlock = cfg.Blocks.ToList()[1] as BinaryBranchBlock; var loopBodyBlock = cfg.EntryBlock; var exitBlock = cfg.ExitBlock; loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); branchBlock.PredecessorBlocks.Should().Equal(loopBodyBlock); exitBlock.PredecessorBlocks.Should().Equal(new[] { branchBlock }); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); VerifyAllInstructions(branchBlock, "true"); } [TestMethod] public void Cfg_NestedDoWhile() { var cfg = Build("do { do { var x = 10; } while (false); } while (true);"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var loopBodyBlock = cfg.EntryBlock; var falseBranchBlock = blocks[1] as BinaryBranchBlock; falseBranchBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.FalseLiteralExpression)); var trueBranchBlock = blocks[2] as BinaryBranchBlock; trueBranchBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.TrueLiteralExpression)); var exitBlock = cfg.ExitBlock; loopBodyBlock.SuccessorBlocks.Should().Equal(falseBranchBlock); falseBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, trueBranchBlock }); falseBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.FalseLiteralExpression); trueBranchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); trueBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.TrueLiteralExpression); falseBranchBlock.PredecessorBlocks.Should().Equal(loopBodyBlock); trueBranchBlock.PredecessorBlocks.Should().Equal(falseBranchBlock); exitBlock.PredecessorBlocks.Should().Equal(trueBranchBlock); } [TestMethod] public void Cfg_DoWhile_Continue() { var cfg = Build(@" int p; do { p = unknown(); if (unknown()) { p = 0; continue; } } while (!p);"); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToArray(); var defBlock = blocks[0]; var ifBlock = blocks[1] as BinaryBranchBlock; var continueJump = blocks[2] as JumpBlock; var doCondition = blocks[3] as BinaryBranchBlock; var exitBlock = blocks[4]; defBlock.Should().Be(cfg.EntryBlock); defBlock.SuccessorBlocks.Should().Equal(ifBlock); VerifyAllInstructions(defBlock, "p"); ifBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { continueJump, doCondition }); ifBlock.BranchingNode.Kind().Should().Be(SyntaxKind.InvocationExpression); VerifyAllInstructions(ifBlock, "unknown", "unknown()", "p = unknown()", "unknown", "unknown()"); continueJump.SuccessorBlocks.Should().Equal(doCondition); continueJump.JumpNode.Kind().Should().Be(SyntaxKind.ContinueStatement); VerifyAllInstructions(continueJump, "0", "p = 0"); doCondition.SuccessorBlocks.Should().BeEquivalentTo(new[] { ifBlock, exitBlock }); doCondition.BranchingNode.Kind().Should().Be(SyntaxKind.LogicalNotExpression); VerifyAllInstructions(doCondition, "p", "!p"); exitBlock.Should().Be(cfg.ExitBlock); } #endregion #region Foreach statement [TestMethod] public void Cfg_Foreach() { var cfg = Build("foreach (var item in collection) { var x = 10; }"); VerifyCfg(cfg, 4); var collectionBlock = cfg.EntryBlock as ForeachCollectionProducerBlock; collectionBlock.Should().NotBeNull(); var blocks = cfg.Blocks.ToList(); var foreachBlock = blocks[1] as BinaryBranchBlock; var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; collectionBlock.SuccessorBlocks.Should().Contain(foreachBlock); foreachBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); foreachBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForEachStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(foreachBlock); exitBlock.PredecessorBlocks.Should().Equal(foreachBlock); VerifyAllInstructions(collectionBlock, "collection"); VerifyNoInstruction(foreachBlock); } [TestMethod] public void Cfg_NestedForeach() { var cfg = Build("foreach (var item1 in collection1) { foreach (var item2 in collection2) { var x = 10; } }"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var collection1Block = cfg.EntryBlock; var foreach1Block = blocks[1] as BinaryBranchBlock; var collection2Block = blocks[2]; var foreach2Block = blocks[3] as BinaryBranchBlock; var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; collection1Block.Instructions.Should().Contain(n => n.ToString() == "collection1"); collection2Block.Instructions.Should().Contain(n => n.ToString() == "collection2"); collection1Block.SuccessorBlocks.Should().Contain(foreach1Block); foreach1Block.SuccessorBlocks.Should().BeEquivalentTo(new[] { collection2Block, exitBlock }); foreach1Block.BranchingNode.Kind().Should().Be(SyntaxKind.ForEachStatement); collection2Block.SuccessorBlocks.Should().Contain(foreach2Block); foreach2Block.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, foreach1Block }); foreach2Block.BranchingNode.Kind().Should().Be(SyntaxKind.ForEachStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(foreach2Block); exitBlock.PredecessorBlocks.Should().Equal(foreach1Block); } [TestMethod] public void Cfg_Foreach_VarDeclaration() { var cfg = Build("foreach (var (key, value) in collection) { string j = value; } "); VerifyCfg(cfg, 4); var allBlocks = cfg.Blocks.ToList(); var forEachCollectionBlock = cfg.EntryBlock as ForeachCollectionProducerBlock; forEachCollectionBlock.Should().NotBeNull(); VerifyAllInstructions(forEachCollectionBlock, "collection"); var foreachBlock = allBlocks[1] as BinaryBranchBlock; VerifyNoInstruction(foreachBlock); var loopBodyBlock = allBlocks[2] as SimpleBlock; VerifyAllInstructions(loopBodyBlock, "value", "j = value"); var exitBlock = cfg.ExitBlock; forEachCollectionBlock.SuccessorBlocks.Should().Contain(foreachBlock); foreachBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { loopBodyBlock, exitBlock }); foreachBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForEachVariableStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(foreachBlock); exitBlock.PredecessorBlocks.Should().Equal(foreachBlock); } [TestMethod] public void Cfg_Foreach_AsyncStream() { var cfg = Build("await foreach (var number in GetAsync()) { var x = 10; }"); VerifyCfg(cfg, 4); var collectionBlock = cfg.EntryBlock as ForeachCollectionProducerBlock; var blocks = cfg.Blocks.ToList(); var foreachBlock = (BinaryBranchBlock)blocks[1]; var loopBodyBlock = blocks[2]; var exitBlock = cfg.ExitBlock; collectionBlock.SuccessorBlocks.Should().Contain(foreachBlock); VerifyAllInstructions(collectionBlock, "GetAsync", "GetAsync()"); foreachBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); foreachBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForEachStatement); VerifyNoInstruction(foreachBlock); loopBodyBlock.SuccessorBlocks.Should().Equal(foreachBlock); VerifyAllInstructions(loopBodyBlock, "10", "x = 10"); exitBlock.PredecessorBlocks.Should().Equal(foreachBlock); } #endregion #region For statement [TestMethod] public void Cfg_For() { var cfg = Build("for (var i = 0; true; i++) { var x = 10; }"); VerifyForStatement(cfg); VerifyAllInstructions(cfg.EntryBlock, "0", "i = 0"); var condition = cfg.EntryBlock.SuccessorBlocks[0]; VerifyAllInstructions(condition, "true"); var body = condition.SuccessorBlocks[0]; VerifyAllInstructions(condition.SuccessorBlocks[0], "10", "x = 10"); VerifyAllInstructions(body.SuccessorBlocks[0], "i", "i++"); cfg = Build("var y = 11; for (var i = 0; true; i++) { var x = 10; }"); VerifyForStatement(cfg); cfg = Build("for (var i = 0; ; i++) { var x = 10; }"); VerifyForStatement(cfg); cfg = Build("for (i = 0, j = 11; ; i++) { var x = 10; }"); VerifyForStatement(cfg); cfg.EntryBlock.Should().BeAssignableTo(); } [TestMethod] public void Cfg_For_NoInitializer() { var cfg = Build("for (; true; i++) { var x = 10; }"); VerifyForStatementNoInitializer(cfg); cfg = Build("for (; ; i++) { var x = 10; }"); VerifyForStatementNoInitializer(cfg); } [TestMethod] public void Cfg_For_NoIncrementor() { var cfg = Build("for (var i = 0; true;) { var x = 10; }"); VerifyForStatementNoIncrementor(cfg); cfg = Build("for (var i = 0; ;) { var x = 10; }"); VerifyForStatementNoIncrementor(cfg); } [TestMethod] public void Cfg_For_Empty() { var cfg = Build("for (; true;) { var x = 10; }"); VerifyForStatementEmpty(cfg); cfg = Build("for (;;) { var x = 10; }"); VerifyForStatementEmpty(cfg); } [TestMethod] public void Cfg_NestedFor() { var cfg = Build("for (var i = 0; true; i++) { for (var j = 0; false; j++) { var x = 10; } }"); VerifyCfg(cfg, 8); var initBlockI = cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var branchBlockTrue = blocks .First(b => b.Instructions.Any(n => n.ToString() == "true")) as BinaryBranchBlock; var incrementorBlockI = blocks .First(b => b.Instructions.Any(n => n.ToString() == "i++")); var branchBlockFalse = blocks .First(b => b.Instructions.Any(n => n.ToString() == "false")) as BinaryBranchBlock; var incrementorBlockJ = blocks .First(b => b.Instructions.Any(n => n.ToString() == "j++")); var initBlockJ = blocks .First(b => b.Instructions.Any(n => n.ToString() == "j = 0")); var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; initBlockI.SuccessorBlocks.Should().Equal(branchBlockTrue); branchBlockTrue.SuccessorBlocks.Should().BeEquivalentTo(new[] { initBlockJ, exitBlock }); branchBlockTrue.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); initBlockJ.SuccessorBlocks.Should().Equal(branchBlockFalse); branchBlockFalse.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, incrementorBlockI }); branchBlockFalse.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(incrementorBlockJ); incrementorBlockJ.SuccessorBlocks.Should().Equal(branchBlockFalse); incrementorBlockI.SuccessorBlocks.Should().Equal(branchBlockTrue); exitBlock.PredecessorBlocks.Should().Equal(branchBlockTrue); } #endregion #region Return, throw, yield break statement [TestMethod] public void Cfg_Return() { var cfg = Build($"if (true) {{ var y = 12; {SimpleReturn}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ReturnStatement); cfg = Build($"if (true) {{ {SimpleReturn}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ReturnStatement); } [TestMethod] public void Cfg_Throw_Statement_InvalidThrow() { var cfg = Build($"if (true) {{ var y = 12; {SimpleThrow}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ThrowStatement); cfg = Build($"if (true) {{ {SimpleThrow}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ThrowStatement); } [TestMethod] public void Cfg_Throw_Statement() { var throwException = "throw new InvalidOperationException(\"\")"; var cfg = Build($"if (true) {{ var y = 12; {throwException}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ThrowStatement); cfg = Build($"if (true) {{ {throwException}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ThrowStatement); } [TestMethod] public void Cfg_YieldBreak() { var cfg = Build($"if (true) {{ var y = 12; {SimpleYieldBreak}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.YieldBreakStatement); cfg = Build($"if (true) {{ {SimpleYieldBreak}; }} var x = 11;"); VerifyJumpWithNoExpression(cfg, SyntaxKind.YieldBreakStatement); } [TestMethod] public void Cfg_Return_Value() { var cfg = Build($"if (true) {{ var y = 12; {ExpressionReturn}; }} var x = 11;"); VerifyJumpWithExpression(cfg, SyntaxKind.ReturnStatement); } [TestMethod] public void Cfg_Return_JustBeforeExit() { var cfg = Build(@" return; cw0(); return;"); VerifyCfg(cfg, 3); var blocks = cfg.Blocks.ToList(); var block1 = (JumpBlock)blocks[0]; var block2 = (SimpleBlock)blocks[1]; var exit = (ExitBlock)blocks.Last(); block1.Instructions.Should().BeEmpty(); block1.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(block2, "cw0", "cw0()"); block2.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_Throw_Value() { var cfg = Build($"if (true) {{ var y = 12; {ExpressionThrow}; }} var x = 11;"); VerifyJumpWithExpression(cfg, SyntaxKind.ThrowStatement); } #endregion #region Lock statement [TestMethod] public void Cfg_Lock() { var cfg = Build("lock(this) { var x = 10; }"); VerifyCfg(cfg, 3); var lockBlock = cfg.EntryBlock as LockBlock; var bodyBlock = cfg.Blocks.Skip(1).First(); var exitBlock = cfg.ExitBlock; lockBlock.SuccessorBlocks.Should().Equal(bodyBlock); bodyBlock.SuccessorBlocks.Should().Equal(exitBlock); lockBlock.LockNode.Kind().Should().Be(SyntaxKind.LockStatement); VerifyAllInstructions(cfg.EntryBlock, "this"); } [TestMethod] public void Cfg_NestedLock() { var cfg = Build("lock(this) { lock(that) { var x = 10; }}"); VerifyCfg(cfg, 4); var lockBlock = cfg.EntryBlock as LockBlock; var innerLockBlock = cfg.Blocks.Skip(1).First() as LockBlock; var bodyBlock = cfg.Blocks.Skip(2).First(); var exitBlock = cfg.ExitBlock; lockBlock.SuccessorBlocks.Should().Equal(innerLockBlock); innerLockBlock.SuccessorBlocks.Should().Equal(bodyBlock); bodyBlock.SuccessorBlocks.Should().Equal(exitBlock); lockBlock.LockNode.Kind().Should().Be(SyntaxKind.LockStatement); lockBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.ThisExpression)); innerLockBlock.LockNode.Kind().Should().Be(SyntaxKind.LockStatement); innerLockBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.IdentifierName) && n.ToString() == "that"); } #endregion #region Using statement [TestMethod] public void Cfg_UsingDeclaration() { var cfg = Build("using(var stream = new MemoryStream()) { var x = 10; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.UsingStatement); VerifyAllInstructions(cfg.EntryBlock, "new MemoryStream()", "stream = new MemoryStream()"); var usingBlock = cfg.Blocks.Skip(1).First() as UsingEndBlock; usingBlock.Should().NotBeNull(); usingBlock.Identifiers.Select(n => n.ValueText).Should().Equal(new[] { "stream" }); usingBlock.Should().BeOfType(); } [TestMethod] public void Cfg_UsingAssignment() { var cfg = Build("Stream stream; using(stream = new MemoryStream()) { var x = 10; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.UsingStatement); VerifyAllInstructions(cfg.EntryBlock, "stream", "new MemoryStream()", "stream = new MemoryStream()"); var usingBlock = cfg.Blocks.Skip(1).First() as UsingEndBlock; usingBlock.Should().NotBeNull(); usingBlock.Identifiers.Select(n => n.ValueText).Should().Equal(new[] { "stream" }); usingBlock.Should().BeOfType(); } [TestMethod] public void Cfg_UsingExpression() { var cfg = Build("using(new MemoryStream()) { var x = 10; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.UsingStatement); VerifyAllInstructions(cfg.EntryBlock, "new MemoryStream()"); } [TestMethod] public void Cfg_UsingLocalDeclaration() { var cfg = Build("using var stream = new MemoryStream();"); VerifyCfg(cfg, 2); cfg.EntryBlock.Should().BeOfType(); VerifyAllInstructions(cfg.EntryBlock, "new MemoryStream()", "stream = new MemoryStream()"); } #endregion #region Fixed statement [TestMethod] public void Cfg_Fixed() { var cfg = Build("fixed (int* p = &pt.x) { *p = 1; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.FixedStatement); VerifyAllInstructions(cfg.EntryBlock, "pt", "pt.x", "&pt.x", "p = &pt.x"); } #endregion #region Checked/unchecked statement [TestMethod] public void Cfg_Checked() { var cfg = Build("checked { var i = int.MaxValue + 1; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.CheckedStatement); VerifyNoInstruction(cfg.EntryBlock); VerifyInstructions(cfg.EntryBlock.SuccessorBlocks[0], 1, "int.MaxValue"); cfg = Build("unchecked { var i = int.MaxValue + 1; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.UncheckedStatement); VerifyNoInstruction(cfg.EntryBlock); VerifyInstructions(cfg.EntryBlock.SuccessorBlocks[0], 1, "int.MaxValue"); } #endregion #region Unsafe statement [TestMethod] public void Cfg_Unsafe() { var cfg = Build("unsafe { int* p = &i; *p *= *p; }"); VerifySimpleJumpBlock(cfg, SyntaxKind.UnsafeStatement); VerifyNoInstruction(cfg.EntryBlock); VerifyInstructions(cfg.EntryBlock.SuccessorBlocks[0], 0, "i"); } #endregion #region Logical && and || [TestMethod] public void Cfg_LogicalAnd() { var cfg = Build("var b = a && c;"); VerifyCfg(cfg, 4); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var trueABlock = blocks[1]; var afterOp = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueABlock, afterOp }); trueABlock.SuccessorBlocks.Should().Equal(afterOp); afterOp.SuccessorBlocks.Should().Equal(exitBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.LogicalAndExpression); VerifyAllInstructions(branchBlock, "a"); VerifyAllInstructions(trueABlock, "c"); VerifyAllInstructions(afterOp, "b = a && c"); } [TestMethod] public void Cfg_LogicalOr() { var cfg = Build("var b = a || c;"); VerifyCfg(cfg, 4); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var falseABlock = blocks[1]; var afterOp = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterOp, falseABlock }); falseABlock.SuccessorBlocks.Should().Equal(afterOp); afterOp.SuccessorBlocks.Should().Equal(exitBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.LogicalOrExpression); VerifyAllInstructions(branchBlock, "a"); VerifyAllInstructions(falseABlock, "c"); VerifyAllInstructions(afterOp, "b = a || c"); } [TestMethod] public void Cfg_LogicalAnd_Multiple() { var cfg = Build("var b = a && c && d;"); VerifyCfg(cfg, 6); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var trueABlock = blocks[1]; var afterAC = blocks[2] as BinaryBranchBlock; var trueACBlock = blocks[3]; var afterOp = blocks[4]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueABlock, afterAC }); trueABlock.SuccessorBlocks.Should().Equal(afterAC); afterAC.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueACBlock, afterOp }); trueACBlock.SuccessorBlocks.Should().Equal(afterOp); afterOp.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_LogicalAnd_With_For() { var cfg = Build("for(x = 10; a && c; y++) { var z = 11; }"); VerifyCfg(cfg, 7); var initBlock = cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var aBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "a")) as BinaryBranchBlock; var cBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "c")); var acBlock = blocks[3] as BinaryBranchBlock; var bodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "z = 11")); var incrementBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "y++")); var exitBlock = cfg.ExitBlock; initBlock.SuccessorBlocks.Should().Equal(aBlock); aBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { cBlock, acBlock }); cBlock.SuccessorBlocks.Should().Equal(acBlock); acBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { bodyBlock, exitBlock }); bodyBlock.SuccessorBlocks.Should().Equal(incrementBlock); incrementBlock.SuccessorBlocks.Should().Equal(aBlock); acBlock.Instructions.Should().BeEmpty(); } #endregion #region Coalesce expression [TestMethod] public void Cfg_Coalesce() { var cfg = Build("var a = b ?? c;"); VerifyCfg(cfg, 4); var branchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var bNullBlock = blocks[1]; var assignmentBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { bNullBlock, assignmentBlock }); bNullBlock.SuccessorBlocks.Should().Equal(assignmentBlock); assignmentBlock.SuccessorBlocks.Should().Equal(exitBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); VerifyAllInstructions(branchBlock, "b"); VerifyAllInstructions(bNullBlock, "c"); VerifyAllInstructions(assignmentBlock, "a = b ?? c"); } [TestMethod] public void Cfg_Coalesce_Self() { var cfg = Build("a = a ?? c;"); VerifyCfg(cfg, 4); var branchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var bNullBlock = blocks[1]; var afterOp = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.TrueSuccessorBlock.Should().Be(bNullBlock); branchBlock.FalseSuccessorBlock.Should().Be(afterOp); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); bNullBlock.SuccessorBlocks.Should().Equal(afterOp); afterOp.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(branchBlock, "a"); VerifyAllInstructions(bNullBlock, "c"); VerifyAllInstructions(afterOp, "a = a ?? c"); } [TestMethod] public void Cfg_Coalesce_Multiple() { var cfg = Build("var a = b ?? c ?? d;"); VerifyCfg(cfg, 5); var bBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var cBlock = (BinaryBranchBlock)blocks[1]; var dBlock = blocks[2]; var bcdBlock = blocks[3]; // b ?? c ?? d var exitBlock = cfg.ExitBlock; bBlock.TrueSuccessorBlock.Should().Be(cBlock); bBlock.FalseSuccessorBlock.Should().Be(bcdBlock); bBlock.Instructions.Should().ContainSingle("b"); bBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); cBlock.TrueSuccessorBlock.Should().Be(dBlock); cBlock.FalseSuccessorBlock.Should().Be(bcdBlock); cBlock.Instructions.Should().ContainSingle("c"); cBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); dBlock.SuccessorBlocks.Should().Equal(bcdBlock); dBlock.Instructions.Should().ContainSingle("d"); bcdBlock.SuccessorBlocks.Should().Equal(exitBlock); bcdBlock.Instructions.Should().ContainSingle("a = b ?? c ?? d"); } [TestMethod] public void Cfg_Coalesce_MultipleAssignments() { var cfg = Build("a = a ?? (b = b ?? c);"); VerifyCfg(cfg, 6); var firstBranchBlockWithA = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var secondBranchBlockWithB = (BinaryBranchBlock)blocks[1]; var simpleBlockWithC = (SimpleBlock)blocks[2]; var simpleBlockWithAssignmentToB = (SimpleBlock)blocks[3]; var simpleBlockWithFullExpression = (SimpleBlock)blocks[4]; var exitBlock = cfg.ExitBlock; firstBranchBlockWithA.TrueSuccessorBlock.Should().Be(secondBranchBlockWithB); firstBranchBlockWithA.FalseSuccessorBlock.Should().Be(simpleBlockWithFullExpression); firstBranchBlockWithA.Instructions.Should().ContainSingle("a"); firstBranchBlockWithA.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); secondBranchBlockWithB.TrueSuccessorBlock.Should().Be(simpleBlockWithC); secondBranchBlockWithB.FalseSuccessorBlock.Should().Be(simpleBlockWithAssignmentToB); secondBranchBlockWithB.Instructions.Should().ContainSingle("b"); secondBranchBlockWithB.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); simpleBlockWithC.SuccessorBlock.Should().Be(simpleBlockWithAssignmentToB); simpleBlockWithC.Instructions.Should().ContainSingle("c"); simpleBlockWithAssignmentToB.SuccessorBlock.Should().Be(simpleBlockWithFullExpression); simpleBlockWithAssignmentToB.Instructions.Should().ContainSingle("b = b ?? c"); simpleBlockWithFullExpression.SuccessorBlock.Should().Be(exitBlock); simpleBlockWithFullExpression.Instructions.Should().ContainSingle("a = a ?? (b = b ?? c)"); } [TestMethod] public void Cfg_Coalesce_Throw() { var cfg = Build(@"var a = b ?? throw new Exception(""Test"");"); VerifyCfg(cfg, 4); var branchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var throwBlock = blocks[1]; var assignmentBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.TrueSuccessorBlock.Should().Be(throwBlock); branchBlock.FalseSuccessorBlock.Should().Be(assignmentBlock); throwBlock.SuccessorBlocks.Should().Equal(exitBlock); assignmentBlock.SuccessorBlocks.Should().Equal(exitBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); VerifyAllInstructions(branchBlock, "b"); VerifyAllInstructions(throwBlock, @"""Test""", @"new Exception(""Test"")"); VerifyAllInstructions(assignmentBlock, @"a = b ?? throw new Exception(""Test"")"); } [TestMethod] public void Cfg_Coalesce_ThrowCoalesce() { var cfg = Build(@"var a = b ?? throw ex ?? new Exception(""Test"");"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var firstBranchBlockWithB = (BinaryBranchBlock)cfg.EntryBlock; var secondBranchBlockWithEx = (BinaryBranchBlock)blocks[1]; var simpleBlockWithException = (SimpleBlock)blocks[2]; var jumpBlockThrow = (JumpBlock)blocks[3]; var simpleBlockWithFullExpression = (SimpleBlock)blocks[4]; var exitBlock = cfg.ExitBlock; firstBranchBlockWithB.TrueSuccessorBlock.Should().Be(secondBranchBlockWithEx); firstBranchBlockWithB.FalseSuccessorBlock.Should().Be(simpleBlockWithFullExpression); firstBranchBlockWithB.Instructions.Should().ContainSingle("b"); firstBranchBlockWithB.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); secondBranchBlockWithEx.TrueSuccessorBlock.Should().Be(simpleBlockWithException); secondBranchBlockWithEx.FalseSuccessorBlock.Should().Be(jumpBlockThrow); secondBranchBlockWithEx.Instructions.Should().ContainSingle("ex"); secondBranchBlockWithEx.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); simpleBlockWithException.SuccessorBlock.Should().Be(jumpBlockThrow); VerifyAllInstructions(simpleBlockWithException, @"""Test""", @"new Exception(""Test"")"); jumpBlockThrow.SuccessorBlock.Should().Be(exitBlock); simpleBlockWithFullExpression.SuccessorBlock.Should().Be(exitBlock); simpleBlockWithFullExpression.Instructions.Should().ContainSingle(@"a = b ?? throw ex ?? new Exception(""Test"")"); } #endregion #region Null-coalescing assigment [TestMethod] public void Cfg_NullCoalescingAssignment() { // is similar with "a = a ?? b;" /// var cfg = Build("a ??= b;"); VerifyCfg(cfg, 4); var branchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var blockWithB = blocks[1]; var assignmentBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.TrueSuccessorBlock.Should().Be(blockWithB); branchBlock.FalseSuccessorBlock.Should().Be(assignmentBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.CoalesceAssignmentExpression); VerifyAllInstructions(branchBlock, "a"); blockWithB.SuccessorBlocks.Should().Equal(assignmentBlock); VerifyAllInstructions(blockWithB, "b"); assignmentBlock.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(assignmentBlock, "a ??= b"); } [TestMethod] public void Cfg_NullCoalescingAssignment_Multiple() { // is similar with "a = a ?? (b = b ?? c);" /// var cfg = Build("a ??= b ??= c;"); VerifyCfg(cfg, 6); var firstBranchBlockWithA = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var secondBranchBlockWithB = (BinaryBranchBlock)blocks[1]; var simpleBlockWithC = (SimpleBlock)blocks[2]; var simpleBlockWithAssignmentToB = (SimpleBlock)blocks[3]; var simpleBlockWithFullExpression = (SimpleBlock)blocks[4]; var exitBlock = cfg.ExitBlock; firstBranchBlockWithA.TrueSuccessorBlock.Should().Be(secondBranchBlockWithB); firstBranchBlockWithA.FalseSuccessorBlock.Should().Be(simpleBlockWithFullExpression); firstBranchBlockWithA.Instructions.Should().ContainSingle("a"); firstBranchBlockWithA.BranchingNode.Kind().Should().Be(SyntaxKindEx.CoalesceAssignmentExpression); secondBranchBlockWithB.TrueSuccessorBlock.Should().Be(simpleBlockWithC); secondBranchBlockWithB.FalseSuccessorBlock.Should().Be(simpleBlockWithAssignmentToB); secondBranchBlockWithB.Instructions.Should().ContainSingle("b"); secondBranchBlockWithB.BranchingNode.Kind().Should().Be(SyntaxKindEx.CoalesceAssignmentExpression); simpleBlockWithC.SuccessorBlock.Should().Be(simpleBlockWithAssignmentToB); simpleBlockWithC.Instructions.Should().ContainSingle("c"); simpleBlockWithAssignmentToB.SuccessorBlock.Should().Be(simpleBlockWithFullExpression); simpleBlockWithAssignmentToB.Instructions.Should().ContainSingle("b = b ?? c"); simpleBlockWithFullExpression.SuccessorBlock.Should().Be(exitBlock); simpleBlockWithFullExpression.Instructions.Should().ContainSingle("a = a ?? (b = b ?? c)"); } [TestMethod] public void Cfg_NullCoalescingAssignment_Coalesce() { // similar to a = a ?? b ?? c var cfg = Build("a ??= b ?? c;"); VerifyCfg(cfg, 5); var coalesceAssignmentBranchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var coalesceBranchBlock = (BinaryBranchBlock)blocks[1]; var blockWithC = (SimpleBlock)blocks[2]; var assignmentBlock = blocks[3]; var exitBlock = cfg.ExitBlock; coalesceAssignmentBranchBlock.TrueSuccessorBlock.Should().Be(coalesceBranchBlock); coalesceAssignmentBranchBlock.FalseSuccessorBlock.Should().Be(assignmentBlock); coalesceAssignmentBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.CoalesceAssignmentExpression); coalesceAssignmentBranchBlock.Instructions.Should().ContainSingle("a"); coalesceBranchBlock.TrueSuccessorBlock.Should().Be(blockWithC); coalesceBranchBlock.FalseSuccessorBlock.Should().Be(assignmentBlock); coalesceBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.CoalesceExpression); coalesceBranchBlock.Instructions.Should().ContainSingle("b"); blockWithC.SuccessorBlocks.Should().Equal(assignmentBlock); blockWithC.Instructions.Should().ContainSingle("c"); assignmentBlock.SuccessorBlocks.Should().Equal(exitBlock); assignmentBlock.Instructions.Should().ContainSingle("a ??= b ?? c"); } [TestMethod] public void Cfg_NullCoalescingAssignment_Conditional() { var cfg = Build("a ??= b ? c : d;"); VerifyCfg(cfg, 6); var nullCoalesceAssignmentBranchBlock = (BinaryBranchBlock)cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var conditionalBranchBlock = (BinaryBranchBlock)blocks[1]; var cBlock = (SimpleBlock)blocks[2]; var dBlock = (SimpleBlock)blocks[3]; var simpleBlockWithFullExpression = (SimpleBlock)blocks[4]; var exitBlock = cfg.ExitBlock; nullCoalesceAssignmentBranchBlock.TrueSuccessorBlock.Should().Be(conditionalBranchBlock); nullCoalesceAssignmentBranchBlock.FalseSuccessorBlock.Should().Be(simpleBlockWithFullExpression); nullCoalesceAssignmentBranchBlock.Instructions.Should().ContainSingle("a"); nullCoalesceAssignmentBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKindEx.CoalesceAssignmentExpression); conditionalBranchBlock.TrueSuccessorBlock.Should().Be(cBlock); conditionalBranchBlock.FalseSuccessorBlock.Should().Be(dBlock); conditionalBranchBlock.Instructions.Should().ContainSingle("b"); conditionalBranchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.IdentifierName); cBlock.SuccessorBlock.Should().Be(simpleBlockWithFullExpression); cBlock.Instructions.Should().ContainSingle("c"); dBlock.SuccessorBlock.Should().Be(simpleBlockWithFullExpression); dBlock.Instructions.Should().ContainSingle("d"); simpleBlockWithFullExpression.SuccessorBlock.Should().Be(exitBlock); simpleBlockWithFullExpression.Instructions.Should().HaveCount(1); simpleBlockWithFullExpression.Instructions.Should().ContainSingle("a ??= b ? c : d"); } #endregion #region Conditional expression [TestMethod] public void Cfg_Conditional() { var cfg = Build("var a = cond ? b : c;"); VerifyCfg(cfg, 5); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var condFalse = blocks[2]; var condTrue = blocks[1]; var after = blocks[3]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { condTrue, condFalse }); condFalse.SuccessorBlocks.Should().Equal(after); condTrue.SuccessorBlocks.Should().Equal(after); after.SuccessorBlocks.Should().Equal(exitBlock); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.IdentifierName); VerifyAllInstructions(branchBlock, "cond"); VerifyAllInstructions(condTrue, "b"); VerifyAllInstructions(condFalse, "c"); VerifyAllInstructions(after, "a = cond ? b : c"); } [TestMethod] public void Cfg_Conditional_ComplexCondition_Or() { var cfg = Build("var a = x || y ? b : c;"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var condFalse = blocks[3]; var condTrue = blocks[2]; var after = blocks[4]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(condTrue); branchBlockA.FalseSuccessorBlock.Should().Be(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(condTrue); branchBlockB.FalseSuccessorBlock.Should().Be(condFalse); condFalse.SuccessorBlocks.Should().Equal(after); condTrue.SuccessorBlocks.Should().Equal(after); after.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(branchBlockA, "x"); VerifyAllInstructions(branchBlockB, "y"); VerifyAllInstructions(condTrue, "b"); VerifyAllInstructions(condFalse, "c"); VerifyAllInstructions(after, "a = x || y ? b : c"); } [TestMethod] public void Cfg_Conditional_ComplexCondition_And() { var cfg = Build("var a = x && y ? b : c;"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var branchBlockA = (BinaryBranchBlock)blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var condFalse = blocks[3]; var condTrue = blocks[2]; var after = blocks[4]; var exitBlock = cfg.ExitBlock; branchBlockA.TrueSuccessorBlock.Should().Be(branchBlockB); branchBlockA.FalseSuccessorBlock.Should().Be(condFalse); branchBlockB.TrueSuccessorBlock.Should().Be(condTrue); branchBlockB.FalseSuccessorBlock.Should().Be(condFalse); condFalse.SuccessorBlocks.Should().Equal(after); condTrue.SuccessorBlocks.Should().Equal(after); after.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(branchBlockA, "x"); VerifyAllInstructions(branchBlockB, "y"); VerifyAllInstructions(condTrue, "b"); VerifyAllInstructions(condFalse, "c"); VerifyAllInstructions(after, "a = x && y ? b : c"); } [TestMethod] public void Cfg_ConditionalMultiple() { var cfg = Build("var a = cond1 ? (cond2?x:y) : (cond3?p:q);"); VerifyCfg(cfg, 9); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var cond2Block = blocks[1]; VerifyAllInstructions(cond2Block, "cond2"); var cond3Block = blocks[4]; VerifyAllInstructions(cond3Block, "cond3"); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { cond2Block, cond3Block }); cond2Block.SuccessorBlocks.Should().HaveCount(2); cond3Block.SuccessorBlocks.Should().HaveCount(2); cond2Block .SuccessorBlocks[0] .SuccessorBlocks[0] .SuccessorBlocks[0].Should().Be(cfg.ExitBlock); var assignmentBlock = cfg.ExitBlock.PredecessorBlocks.First(); assignmentBlock.Instructions.Should().HaveCount(1); assignmentBlock.Instructions.Should().Contain(i => i.ToString() == "a = cond1 ? (cond2?x:y) : (cond3?p:q)"); } #endregion #region Throw expression [TestMethod] public void Cfg_Throw_Expression_NullCoalesce() { var throwException = "throw new InvalidOperationException(\"\")"; var cfg = Build($"object x = null; var y = x ?? {throwException};"); VerifyJumpWithNoExpression(cfg, SyntaxKind.ThrowExpression); } [TestMethod] public void Cfg_Throw_Expression_Ternary() { var throwException = "throw new InvalidOperationException(\"\")"; var cfg = Build($"var x = true ? 1 : {throwException};"); VerifyCfg(cfg, 5); var binaryBranch = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var trueBlock = blocks[1] as SimpleBlock; var falseJumpBlock = blocks[2] as JumpBlock; var assignmentBlock = blocks[3] as SimpleBlock; var exitBlock = cfg.ExitBlock; binaryBranch.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseJumpBlock }); trueBlock.SuccessorBlocks.Should().Equal(assignmentBlock); falseJumpBlock.SuccessorBlock.Should().Be(exitBlock); falseJumpBlock.WouldBeSuccessor.Should().Be(assignmentBlock); assignmentBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { falseJumpBlock, assignmentBlock }); } [TestMethod] public void Cfg_Throw_Expression_MethodArgument() { var throwException = "throw new InvalidOperationException(\"\")"; var cfg = Build($"Console.WriteLine({throwException});"); VerifyCfg(cfg, 3); var jumpBlock = cfg.EntryBlock as JumpBlock; var blocks = cfg.Blocks.ToList(); var methodCallBlock = blocks[1] as SimpleBlock; var exitBlock = cfg.ExitBlock; jumpBlock.SuccessorBlocks.Should().Equal(exitBlock); jumpBlock.WouldBeSuccessor.Should().Be(methodCallBlock); methodCallBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { methodCallBlock, jumpBlock }); } #endregion #region Ranges and Indices [TestMethod] public void Cfg_Range_Expression() { var cfg = Build($"Range r = 1..4;"); VerifyCfg(cfg, 2); var rangeBlock = cfg.EntryBlock as SimpleBlock; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(rangeBlock, "1..4", "r = 1..4"); rangeBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().Equal(rangeBlock); } [TestMethod] public void Cfg_Index_Expression() { var cfg = Build($"Index index = ^1;"); VerifyCfg(cfg, 2); var rangeBlock = cfg.EntryBlock as SimpleBlock; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(rangeBlock, "^1", "index = ^1"); rangeBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().Equal(rangeBlock); } #endregion #region Conditional access [TestMethod] public void Cfg_ConditionalAccess() { var cfg = Build("var a = o?.method(1);"); VerifyCfg(cfg, 4); var blocks = cfg.Blocks.ToList(); var oIsNullBranch = blocks[0] as BinaryBranchBlock; var oNotNull = blocks[1]; var assignment = blocks[2]; var exitBlock = cfg.ExitBlock; oIsNullBranch.TrueSuccessorBlock.Should().Be(assignment); oIsNullBranch.FalseSuccessorBlock.Should().Be(oNotNull); oNotNull.SuccessorBlocks.Should().Equal(assignment); assignment.SuccessorBlocks.Should().Equal(exitBlock); oIsNullBranch.BranchingNode.Kind().Should().Be(SyntaxKind.ConditionalAccessExpression); VerifyAllInstructions(oIsNullBranch, "o"); VerifyAllInstructions(oNotNull, "method", ".method" /* This is equivalent to o.method */, "1", ".method(1)"); VerifyAllInstructions(assignment, "a = o?.method(1)"); } [TestMethod] public void Cfg_ConditionalAccessNested() { var cfg = Build("var a = o?.method()?[10];"); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToList(); var oIsNullBranch = blocks[0] as BinaryBranchBlock; var methodCallIsNull = blocks[1] as BinaryBranchBlock; var arrayAccess = blocks[2]; var assignment = blocks[3]; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(oIsNullBranch, "o"); VerifyAllInstructions(methodCallIsNull, "method", ".method", ".method()"); VerifyAllInstructions(arrayAccess, "10", "[10]"); VerifyAllInstructions(assignment, "a = o?.method()?[10]"); oIsNullBranch.TrueSuccessorBlock.Should().Be(assignment); oIsNullBranch.FalseSuccessorBlock.Should().Be(methodCallIsNull); methodCallIsNull.TrueSuccessorBlock.Should().Be(assignment); methodCallIsNull.FalseSuccessorBlock.Should().Be(arrayAccess); arrayAccess.SuccessorBlocks.Should().Equal(assignment); assignment.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_ConditionalAccess_Coalesce() { var cfg = Build("var a = aObj?.booleanVal ?? false"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var aObjIsNull = blocks[0] as BinaryBranchBlock; var boolFieldAccess = blocks[1]; var coalesceIsNullBranch = blocks[2] as BinaryBranchBlock; var falseBlock = blocks[3]; var assignment = blocks[4]; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(aObjIsNull, "aObj"); VerifyAllInstructions(boolFieldAccess, "booleanVal", ".booleanVal"); VerifyAllInstructions(coalesceIsNullBranch); VerifyAllInstructions(falseBlock, "false"); VerifyAllInstructions(assignment, "a = aObj?.booleanVal ?? false"); aObjIsNull.TrueSuccessorBlock.Should().Be(coalesceIsNullBranch); aObjIsNull.FalseSuccessorBlock.Should().Be(boolFieldAccess); boolFieldAccess.SuccessorBlocks.Should().Equal(coalesceIsNullBranch); coalesceIsNullBranch.TrueSuccessorBlock.Should().Be(falseBlock); coalesceIsNullBranch.FalseSuccessorBlock.Should().Be(assignment); falseBlock.SuccessorBlocks.Should().Equal(assignment); assignment.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_ConditionalAccess_Conditional() { var cfg = Build("a?.booleanVal == null ? true : false"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var aIsNullBranch = blocks[0] as BinaryBranchBlock; var boolFieldAccess = blocks[1]; var nullCheckBranch = blocks[2] as BinaryBranchBlock; var trueBlock = blocks[3]; var falseBlock = blocks[4]; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(aIsNullBranch, "a"); VerifyAllInstructions(boolFieldAccess, "booleanVal", ".booleanVal"); VerifyAllInstructions(nullCheckBranch, "null", "a?.booleanVal == null"); VerifyAllInstructions(trueBlock, "true"); VerifyAllInstructions(falseBlock, "false"); aIsNullBranch.TrueSuccessorBlock.Should().Be(nullCheckBranch); aIsNullBranch.FalseSuccessorBlock.Should().Be(boolFieldAccess); boolFieldAccess.SuccessorBlocks.Should().Equal(nullCheckBranch); nullCheckBranch.TrueSuccessorBlock.Should().Be(trueBlock); nullCheckBranch.FalseSuccessorBlock.Should().Be(falseBlock); trueBlock.SuccessorBlocks.Should().Equal(exitBlock); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_ConditionalAccess_is() { var cfg = Build("if(a?.booleanVal is null) {return 1;}"); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToList(); var aIsNullBranch = blocks[0] as BinaryBranchBlock; var boolFieldAccess = blocks[1]; var isNullCheck = blocks[2] as BinaryBranchBlock; var returnBlock = blocks[3]; var exitBlock = cfg.ExitBlock; VerifyAllInstructions(aIsNullBranch, "a"); VerifyAllInstructions(boolFieldAccess, "booleanVal", ".booleanVal"); VerifyAllInstructions(isNullCheck, "null", "a?.booleanVal is null"); VerifyAllInstructions(returnBlock, "1"); aIsNullBranch.TrueSuccessorBlock.Should().Be(isNullCheck); aIsNullBranch.FalseSuccessorBlock.Should().Be(boolFieldAccess); boolFieldAccess.SuccessorBlocks.Should().Equal(isNullCheck); isNullCheck.TrueSuccessorBlock.Should().Be(returnBlock); isNullCheck.FalseSuccessorBlock.Should().Be(exitBlock); returnBlock.SuccessorBlocks.Should().Equal(exitBlock); } #endregion #region Break [TestMethod] public void Cfg_For_Break() { var cfg = Build("cw0(); for (a; b && c; d) { if (e) { cw1(); break; } cw2(); } cw3();"); VerifyCfg(cfg, 10); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")); var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var b = blocks .First(block => block.Instructions.Any(n => n.ToString() == "b")) as BinaryBranchBlock; var c = blocks .First(block => block.Instructions.Any(n => n.ToString() == "c")); var bc = blocks[3] as BinaryBranchBlock; var d = blocks .First(block => block.Instructions.Any(n => n.ToString() == "d")); var e = blocks .First(block => block.Instructions.Any(n => n.ToString() == "e")) as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(b); b.SuccessorBlocks.Should().BeEquivalentTo(new[] { c, bc }); c.SuccessorBlocks.Should().Equal(bc); bc.SuccessorBlocks.Should().BeEquivalentTo(new[] { e, cw3 }); e.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); cw2.SuccessorBlocks.Should().Equal(d); d.SuccessorBlocks.Should().Equal(b); bc.Instructions.Should().BeEmpty(); } [TestMethod] public void Cfg_While_Break() { var cfg = Build("cw0(); while (b && c) { if (e) { cw1(); break; } cw2(); } cw3();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeWhile = blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var branchBlockC = (BinaryBranchBlock)blocks[2]; var branchBlockE = (BinaryBranchBlock)blocks[3]; var trueBlock = (JumpBlock)blocks[4]; var afterIf = blocks[5]; var afterWhile = blocks[6]; var exit = blocks[7]; beforeWhile.SuccessorBlocks.Should().Equal(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(branchBlockC); branchBlockB.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockC.TrueSuccessorBlock.Should().Be(branchBlockE); branchBlockC.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockE.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockE.FalseSuccessorBlock.Should().Be(afterIf); trueBlock.SuccessorBlock.Should().Be(afterWhile); afterIf.SuccessorBlocks.Should().Equal(branchBlockB); afterWhile.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_Foreach_Break() { var cfg = Build("cw0(); foreach (var x in xs) { if (e) { cw1(); break; } cw2(); } cw3();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")); var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var xs = blocks.OfType().First(n => n.BranchingNode.IsKind(SyntaxKind.ForEachStatement)); var e = blocks .First(block => block.Instructions.Any(n => n.ToString() == "e")) as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(xs); xs.SuccessorBlocks.Should().BeEquivalentTo(new[] { e, cw3 }); e.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); cw2.SuccessorBlocks.Should().Equal(xs); } [TestMethod] public void Cfg_Do_Break() { var cfg = Build("cw0(); do { if (e) { cw1(); break; } cw2(); } while (b && c); cw3();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeDo = blocks[0]; var branchBlockE = (BinaryBranchBlock)blocks[1]; var trueBlock = (JumpBlock)blocks[2]; var afterIf = blocks[3]; var branchBlockB = (BinaryBranchBlock)blocks[4]; var branchBlockC = (BinaryBranchBlock)blocks[5]; var afterWhile = blocks[6]; var exit = blocks[7]; beforeDo.SuccessorBlocks.Should().Equal(branchBlockE); branchBlockE.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockE.FalseSuccessorBlock.Should().Be(afterIf); trueBlock.SuccessorBlock.Should().Be(afterWhile); afterIf.SuccessorBlocks.Should().Equal(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(branchBlockC); branchBlockB.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockC.TrueSuccessorBlock.Should().Be(branchBlockE); branchBlockC.FalseSuccessorBlock.Should().Be(afterWhile); afterWhile.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_Switch_Break() { var cfg = Build("cw0(); switch(a) { case 1: case 2: cw1(); break; } cw3();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var case1Branch = blocks[1] as BinaryBranchBlock; var case2Branch = blocks[2] as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(case1Branch); case1Branch.TrueSuccessorBlock.Should().Be(cw1); case1Branch.FalseSuccessorBlock.Should().Be(case2Branch); case2Branch.TrueSuccessorBlock.Should().Be(cw1); case2Branch.FalseSuccessorBlock.Should().Be(cw3); cw1.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); } #endregion #region Continue [TestMethod] public void Cfg_For_Continue() { var cfg = Build("cw0(); for (a; b && c; d) { if (e) { cw1(); continue; } cw2(); } cw3();"); VerifyCfg(cfg, 10); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")); var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var b = blocks .First(block => block.Instructions.Any(n => n.ToString() == "b")) as BinaryBranchBlock; var c = blocks .First(block => block.Instructions.Any(n => n.ToString() == "c")); var bc = blocks[3] as BinaryBranchBlock; var d = blocks .First(block => block.Instructions.Any(n => n.ToString() == "d")); var e = blocks .First(block => block.Instructions.Any(n => n.ToString() == "e")) as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(b); b.SuccessorBlocks.Should().BeEquivalentTo(new[] { c, bc }); c.SuccessorBlocks.Should().Equal(bc); bc.SuccessorBlocks.Should().BeEquivalentTo(new[] { e, cw3 }); e.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(d); cw3.SuccessorBlocks.Should().Equal(exitBlock); cw2.SuccessorBlocks.Should().Equal(d); d.SuccessorBlocks.Should().Equal(b); bc.Instructions.Should().BeEmpty(); } [TestMethod] public void Cfg_While_Continue() { var cfg = Build("cw0(); while (b && c) { if (e) { cw1(); continue; } cw2(); } cw3();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeWhile = blocks[0]; var branchBlockB = (BinaryBranchBlock)blocks[1]; var branchBlockC = (BinaryBranchBlock)blocks[2]; var branchBlockE = (BinaryBranchBlock)blocks[3]; var trueBlock = (JumpBlock)blocks[4]; var afterIf = blocks[5]; var afterWhile = blocks[6]; var exit = blocks[7]; beforeWhile.SuccessorBlocks.Should().Equal(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(branchBlockC); branchBlockB.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockC.TrueSuccessorBlock.Should().Be(branchBlockE); branchBlockC.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockE.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockE.FalseSuccessorBlock.Should().Be(afterIf); trueBlock.SuccessorBlock.Should().Be(branchBlockB); afterIf.SuccessorBlocks.Should().Equal(branchBlockB); afterWhile.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_Foreach_Continue() { var cfg = Build("cw0(); foreach (var x in xs) { if (e) { cw1(); continue; } cw2(); } cw3();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")); var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var foreachBlock = blocks.OfType().First(n => n.BranchingNode.IsKind(SyntaxKind.ForEachStatement)); var e = blocks .First(block => block.Instructions.Any(n => n.ToString() == "e")) as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(foreachBlock); foreachBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { e, cw3 }); e.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(foreachBlock); cw3.SuccessorBlocks.Should().Equal(exitBlock); cw2.SuccessorBlocks.Should().Equal(foreachBlock); foreachBlock.Instructions.Should().BeEmpty(); } [TestMethod] public void Cfg_Foreach_Finally() { var cfg = Build(@" BeforeForeach(); foreach (var item in collection) { BeforeTry(); try { InsideTry(); } finally { InsideFinally(); } AfterFinally(); } AfterForeach(); "); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeForeach = (ForeachCollectionProducerBlock)blocks[0]; var foreachDecision = (BinaryBranchBlock)blocks[1]; var beforeTry = (SimpleBlock)blocks[2]; var insideTry = (BranchBlock)blocks[3]; var insideFinally = (BranchBlock)blocks[4]; var afterFinally = (SimpleBlock)blocks[5]; var afterForeach = (SimpleBlock)blocks[6]; var exit = (ExitBlock)blocks[7]; beforeForeach.SuccessorBlock.Should().Be(foreachDecision); foreachDecision.TrueSuccessorBlock.Should().Be(beforeTry); foreachDecision.FalseSuccessorBlock.Should().Be(afterForeach); beforeTry.SuccessorBlock.Should().Be(insideTry); insideTry.SuccessorBlocks.Should().Equal(insideFinally); insideFinally.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { afterFinally, exit }); afterFinally.SuccessorBlock.Should().Be(foreachDecision); afterForeach.SuccessorBlock.Should().Be(exit); } [TestMethod] public void Cfg_Do_Continue() { var cfg = Build("cw0(); do { if (e) { cw1(); continue; } cw2(); } while (b && c); cw3();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeDo = blocks[0]; var branchBlockE = (BinaryBranchBlock)blocks[1]; var trueBlock = (JumpBlock)blocks[2]; var afterIf = blocks[3]; var branchBlockB = (BinaryBranchBlock)blocks[4]; var branchBlockC = (BinaryBranchBlock)blocks[5]; var afterWhile = blocks[6]; var exit = blocks[7]; beforeDo.SuccessorBlocks.Should().Equal(branchBlockE); branchBlockE.TrueSuccessorBlock.Should().Be(trueBlock); branchBlockE.FalseSuccessorBlock.Should().Be(afterIf); trueBlock.SuccessorBlock.Should().Be(branchBlockB); afterIf.SuccessorBlocks.Should().Equal(branchBlockB); branchBlockB.TrueSuccessorBlock.Should().Be(branchBlockC); branchBlockB.FalseSuccessorBlock.Should().Be(afterWhile); branchBlockC.TrueSuccessorBlock.Should().Be(branchBlockE); branchBlockC.FalseSuccessorBlock.Should().Be(afterWhile); afterWhile.SuccessorBlocks.Should().Equal(exit); } #endregion #region Try/Finally [TestMethod] public void Cfg_Try_Finally() { var cfg = Build(@" before(); try { inside(); } finally { fin(); } after();"); VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var insideTryBlock = blocks[1]; var finallyBlock = blocks[2]; var afterFinallyBlock = blocks[3]; var exit = blocks[4]; tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(insideTryBlock); insideTryBlock.Should().BeOfType(); VerifyAllInstructions(insideTryBlock, "inside", "inside()"); insideTryBlock.SuccessorBlocks.Should().Equal(finallyBlock); finallyBlock.Should().BeOfType(); VerifyAllInstructions(finallyBlock, "fin", "fin()"); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); afterFinallyBlock.Should().BeOfType(); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); exit.Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchSome() { var cfg = Build(@" before(); try { inside(); } catch(Exception1 e) { cat1(); } catch(Exception2 e) { cat2(); } after();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var beforeTryBlock = blocks[0]; var insideTryBlock = blocks[1]; var catchBlock1 = blocks[2]; var catchBlock2 = blocks[3]; var afterFinallyBlock = blocks[4]; var exit = blocks[5]; beforeTryBlock.Should().BeOfType(); VerifyAllInstructions(beforeTryBlock, "before", "before()"); beforeTryBlock.SuccessorBlocks.Should().Equal(insideTryBlock); insideTryBlock.Should().BeOfType(); VerifyAllInstructions(insideTryBlock, "inside", "inside()"); insideTryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock1 /*caught ex*/, catchBlock2 /*caught ex*/, afterFinallyBlock /*no ex*/, exit /*uncaught ex*/}); catchBlock1.Should().BeOfType(); VerifyAllInstructions(catchBlock1, "cat1", "cat1()"); catchBlock1.SuccessorBlocks.Should().Equal(afterFinallyBlock); catchBlock2.Should().BeOfType(); VerifyAllInstructions(catchBlock2, "cat2", "cat2()"); catchBlock2.SuccessorBlocks.Should().Equal(afterFinallyBlock); afterFinallyBlock.Should().BeOfType(); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); exit.Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchAll() { var cfg = Build(@" before(); try { inside(); } catch(Exception1 e) { cat1(); } catch { cat2(); } after();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var tryEndBlock = blocks[1]; var catchBlock1 = blocks[2]; var catchBlock2 = blocks[3]; var afterFinallyBlock = blocks[4]; var exit = blocks[5]; tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(tryEndBlock); tryEndBlock.Should().BeOfType(); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock1 /*caught ex*/, catchBlock2 /*caught ex*/, afterFinallyBlock /*no ex*/}); catchBlock1.Should().BeOfType(); VerifyAllInstructions(catchBlock1, "cat1", "cat1()"); catchBlock1.SuccessorBlocks.Should().Equal(afterFinallyBlock); catchBlock2.Should().BeOfType(); VerifyAllInstructions(catchBlock2, "cat2", "cat2()"); catchBlock2.SuccessorBlocks.Should().Equal(afterFinallyBlock); afterFinallyBlock.Should().BeOfType(); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); exit.Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchSome_Finally() { var cfg = Build(@" before(); try { inside(); } catch(Exception1 e) { cat1(); } catch(Exception2 e) { cat2(); } finally { fin(); } after();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var insideTryBlock = blocks[1]; var catchBlock1 = blocks[2]; var catchBlock2 = blocks[3]; var finallyBlock = blocks[4]; var afterFinallyBlock = blocks[5]; var exit = blocks[6]; tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(insideTryBlock); insideTryBlock.Should().BeOfType(); VerifyAllInstructions(insideTryBlock, "inside", "inside()"); insideTryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock1 /*caught ex*/, catchBlock2 /*caught ex*/, finallyBlock }); catchBlock1.Should().BeOfType(); VerifyAllInstructions(catchBlock1, "cat1", "cat1()"); catchBlock1.SuccessorBlocks.Should().Equal(finallyBlock); catchBlock2.Should().BeOfType(); VerifyAllInstructions(catchBlock2, "cat2", "cat2()"); catchBlock2.SuccessorBlocks.Should().Equal(finallyBlock); finallyBlock.Should().BeOfType(); VerifyAllInstructions(finallyBlock, "fin", "fin()"); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); afterFinallyBlock.Should().BeOfType(); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); exit.Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchAll_Finally() { var cfg = Build(@" before(); try { inside(); } catch(Exception1 e) { cat1(); } catch { cat2(); } finally { fin(); } after();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var tryEndBlock = blocks[1]; var catchBlock1 = blocks[2]; var catchBlock2 = blocks[3]; var finallyBlock = blocks[4]; var afterFinallyBlock = blocks[5]; var exit = blocks[6]; tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(tryEndBlock); tryEndBlock.Should().BeOfType(); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock1 /*caught ex*/, catchBlock2 /*caught ex*/, finallyBlock /*no ex*/}); catchBlock1.Should().BeOfType(); VerifyAllInstructions(catchBlock1, "cat1", "cat1()"); catchBlock1.SuccessorBlocks.Should().Equal(finallyBlock); catchBlock2.Should().BeOfType(); VerifyAllInstructions(catchBlock2, "cat2", "cat2()"); catchBlock2.SuccessorBlocks.Should().Equal(finallyBlock); finallyBlock.Should().BeOfType(); VerifyAllInstructions(finallyBlock, "fin", "fin()"); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); afterFinallyBlock.Should().BeOfType(); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); exit.Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchAll_Finally_Conditional_Return() { var cfg = Build(@" before(); try { if (true) { return; } inside(); } catch { cat(); } finally { fin(); } after(); "); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var binaryBlock = blocks[1]; var returnBlock = blocks[2]; var tryEndBlock = blocks[3]; var catchBlock = blocks[4]; var finallyBlock = blocks[5]; var afterFinallyBlock = blocks[6]; var exit = blocks[7]; VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(binaryBlock); VerifyAllInstructions(binaryBlock, "true"); binaryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { tryEndBlock /*false*/, returnBlock /*true*/}); VerifyAllInstructions(returnBlock); returnBlock.SuccessorBlocks.Should().Equal(finallyBlock); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock /*exception thrown*/, finallyBlock /*no exception*/}); VerifyAllInstructions(catchBlock, "cat", "cat()"); catchBlock.SuccessorBlocks.Should().Equal(finallyBlock); VerifyAllInstructions(finallyBlock, "fin", "fin()"); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); afterFinallyBlock.SuccessorBlocks.Should().Equal(exit); blocks.Last().Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchSome_Finally_Conditional_Return() { var cfg = Build(@" before(); try { if (true) { return; } inside(); } catch(SomeException) { cat(); } finally { fin(); } after(); "); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var binaryBlock = blocks[1]; var returnBlock = blocks[2]; var tryEndBlock = blocks[3]; var catchBlock = blocks[4]; var finallyBlock = blocks[5]; var afterFinallyBlock = blocks[6]; var exit = blocks[7]; VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(binaryBlock); VerifyAllInstructions(binaryBlock, "true"); binaryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { tryEndBlock /*false*/, returnBlock /*true*/}); VerifyAllInstructions(returnBlock); returnBlock.SuccessorBlocks.Should().Equal(finallyBlock); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock /*caught exception thrown*/, finallyBlock }); VerifyAllInstructions(catchBlock, "cat", "cat()"); catchBlock.SuccessorBlocks.Should().Equal(finallyBlock); VerifyAllInstructions(finallyBlock, "fin", "fin()"); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); afterFinallyBlock.SuccessorBlocks.Should().Equal(exit); blocks.Last().Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchSome_Conditional_Return() { var cfg = Build(@" before(); try { if (true) { return; } inside(); } catch(SomeException) { cat(); } after(); "); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var binaryBlock = blocks[1]; var returnBlock = blocks[2]; var tryEndBlock = blocks[3]; var catchBlock = blocks[4]; var afterFinallyBlock = blocks[5]; var exit = blocks[6]; VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(binaryBlock); VerifyAllInstructions(binaryBlock, "true"); binaryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { tryEndBlock /*false*/, returnBlock /*true*/}); VerifyAllInstructions(returnBlock); returnBlock.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock /*caught exception thrown*/, afterFinallyBlock /*no exception*/, exit /*uncaught exception*/}); VerifyAllInstructions(catchBlock, "cat", "cat()"); catchBlock.SuccessorBlocks.Should().Equal(afterFinallyBlock); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); afterFinallyBlock.SuccessorBlocks.Should().Equal(exit); blocks.Last().Should().BeOfType(); } [TestMethod] public void Cfg_Try_CatchAll_Conditional_Return() { var cfg = Build(@" before(); try { if (true) { return; } inside(); } catch { cat(); } after(); "); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var binaryBlock = blocks[1]; var returnBlock = blocks[2]; var tryEndBlock = blocks[3]; var catchBlock = blocks[4]; var afterFinallyBlock = blocks[5]; var exit = blocks[6]; VerifyAllInstructions(tryStartBlock, "before", "before()"); tryStartBlock.SuccessorBlocks.Should().Equal(binaryBlock); VerifyAllInstructions(binaryBlock, "true"); binaryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { tryEndBlock /*false*/, returnBlock /*true*/}); VerifyAllInstructions(returnBlock); returnBlock.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(tryEndBlock, "inside", "inside()"); tryEndBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock /*caught exception thrown*/, afterFinallyBlock /*no exception*/}); VerifyAllInstructions(catchBlock, "cat", "cat()"); catchBlock.SuccessorBlocks.Should().Equal(afterFinallyBlock); VerifyAllInstructions(afterFinallyBlock, "after", "after()"); afterFinallyBlock.SuccessorBlocks.Should().Equal(exit); blocks.Last().Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_Exception_Filter() { var cfg = Build(@" cw0(); try { cw1(); } catch(Exception e) when (e is InvalidOperationException) { cw2(); } cw5();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var tryBodyBlock = blocks[1]; var whenBlock = blocks[2]; var catchBlock = blocks[3]; var afterTryBlock = blocks[4]; var exit = blocks.Last(); tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "cw0", "cw0()"); tryStartBlock.SuccessorBlocks.Should().Equal(tryBodyBlock); tryBodyBlock.Should().BeOfType(); VerifyAllInstructions(tryBodyBlock, "cw1", "cw1()"); tryBodyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { whenBlock, afterTryBlock, exit }); whenBlock.Should().BeOfType(); VerifyAllInstructions(whenBlock, "e", "e is InvalidOperationException"); whenBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock, afterTryBlock }); catchBlock.Should().BeOfType(); VerifyAllInstructions(catchBlock, "cw2", "cw2()"); catchBlock.SuccessorBlocks.Should().Equal(afterTryBlock); afterTryBlock.Should().BeOfType(); VerifyAllInstructions(afterTryBlock, "cw5", "cw5()"); afterTryBlock.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_ThrowInsideTry() { var cfg = Build(@" bool shouldCatch = false; try { shouldCatch = true; throw new InvalidOperationException(""bar""); } catch(Exception e) when (shouldCatch) { cw2(); } cw5();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var tryStartBlock = blocks[0]; var tryBodyBlock = blocks[1]; var whenBlock = blocks[2]; var catchBlock = blocks[3]; var afterTryBlock = blocks[4]; var exit = blocks.Last(); tryStartBlock.Should().BeOfType(); VerifyAllInstructions(tryStartBlock, "false", "shouldCatch = false"); tryStartBlock.SuccessorBlocks.Should().Equal(tryBodyBlock); tryBodyBlock.Should().BeOfType(); VerifyAllInstructions(tryBodyBlock, "true", "shouldCatch = true", "\"bar\"", "new InvalidOperationException(\"bar\")"); tryBodyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { whenBlock, afterTryBlock, exit }); whenBlock.Should().BeOfType(); VerifyAllInstructions(whenBlock, "shouldCatch"); whenBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBlock, afterTryBlock }); catchBlock.Should().BeOfType(); VerifyAllInstructions(catchBlock, "cw2", "cw2()"); catchBlock.SuccessorBlocks.Should().Equal(afterTryBlock); afterTryBlock.Should().BeOfType(); VerifyAllInstructions(afterTryBlock, "cw5", "cw5()"); afterTryBlock.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_WithBreak_Inside_DoWhile() { var cfg = Build(@" var attempts = 0; do { cw0(); try { attempts++; cw1(); break; } catch(Exception e) { cw2(); } } while (true); cw5();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var beforeDoBlock = (SimpleBlock)blocks[0]; var doBlock = (SimpleBlock)blocks[1]; var tryBody = (BranchBlock)blocks[2]; var tryStatementBranch = (BranchBlock)blocks[3]; var catchBlock = (SimpleBlock)blocks[4]; var whileStmt = (BinaryBranchBlock)blocks[5]; var afterDoWhile = (SimpleBlock)blocks[6]; var exit = (ExitBlock)blocks.Last(); VerifyAllInstructions(beforeDoBlock, "0", "attempts = 0"); beforeDoBlock.SuccessorBlocks.Should().Equal(doBlock); VerifyAllInstructions(doBlock, "cw0", "cw0()"); doBlock.SuccessorBlocks.Should().Equal(tryBody); VerifyAllInstructions(tryBody, "attempts", "attempts++", "cw1", "cw1()"); // this is wrong, the tryBody should not have a connection with whileStmt, it can lead to FNs tryBody.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { catchBlock, whileStmt, afterDoWhile }); tryStatementBranch.ReversedInstructions.Should().BeEmpty(); tryStatementBranch.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { catchBlock, whileStmt }); VerifyAllInstructions(catchBlock, "cw2", "cw2()"); catchBlock.SuccessorBlocks.Should().Equal(whileStmt); VerifyAllInstructions(whileStmt, "true"); whileStmt.TrueSuccessorBlock.Should().Be(doBlock); whileStmt.FalseSuccessorBlock.Should().Be(afterDoWhile); VerifyAllInstructions(afterDoWhile, "cw5", "cw5()"); afterDoWhile.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } // This should be fixed in https://github.com/SonarSource/sonar-dotnet/issues/474 [TestMethod] public void Cfg_TryCatchFinally_InsideLoop_WithBreakInsideTry_AndContinueInsideCatch() { var cfg = Build(@" do { cw0(); try { cw1(); break; } catch(ArgumentNullException e) { cw2(); continue; } finally { cw3(); // CS0157 control cannot leave the body of a finally, so we cannot have jumps here } // the below is not reachable cw4(); } while (true); cw5();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var doBeforeTry = (SimpleBlock)blocks[0]; var tryStatement = (BranchBlock)blocks[1]; var tryBody = (BranchBlock)blocks[2]; var catchBody = (JumpBlock)blocks[3]; var finallyBlock = (BranchBlock)blocks[4]; var afterTry = (SimpleBlock)blocks[5]; var whileStmt = (BinaryBranchBlock)blocks[6]; var afterDoWhile = (SimpleBlock)blocks[7]; var exit = (ExitBlock)blocks.Last(); VerifyAllInstructions(doBeforeTry, "cw0", "cw0()"); doBeforeTry.SuccessorBlocks.Should().Equal(tryStatement); VerifyAllInstructions(tryStatement, "cw1", "cw1()"); tryStatement.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { catchBody, finallyBlock, afterDoWhile }); tryBody.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { catchBody, finallyBlock }); VerifyAllInstructions(catchBody, "cw2", "cw2()"); catchBody.SuccessorBlock.Should().Be(whileStmt); // ToDo: this is wrong, `finally` should be connected to // - EXIT // - WHILE (because of `continue`) // - afterDoWhile (because of `break`) finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { afterTry, exit }); afterTry.SuccessorBlock.Should().Be(whileStmt); VerifyAllInstructions(whileStmt, "true"); whileStmt.TrueSuccessorBlock.Should().Be(doBeforeTry); whileStmt.FalseSuccessorBlock.Should().Be(afterDoWhile); VerifyAllInstructions(afterDoWhile, "cw5", "cw5()"); afterDoWhile.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } // This should be fixed in https://github.com/SonarSource/sonar-dotnet/issues/474 [TestMethod] public void Cfg_TryFinally_InsideLoop_WithBreakAndContinueInsideTry() { var cfg = Build(@" do { cw0(); try { if (cond) { cw1(); continue; } else { cw2(); break; } } finally { cw3(); } // the below is not reachable cw4(); } while (true); cw5();"); VerifyCfg(cfg, 10); var blocks = cfg.Blocks.ToList(); var doBeforeTry = (SimpleBlock)blocks[0]; var ifInsideTry = (BinaryBranchBlock)blocks[1]; var thenContinue = (JumpBlock)blocks[2]; var elseIf = (JumpBlock)blocks[3]; var tryStatement = (BranchBlock)blocks[4]; var finallyBody = (BranchBlock)blocks[5]; var afterTry = (SimpleBlock)blocks[6]; var whileStmt = (BinaryBranchBlock)blocks[7]; var afterDoWhile = (SimpleBlock)blocks[8]; var exit = (ExitBlock)blocks.Last(); VerifyAllInstructions(doBeforeTry, "cw0", "cw0()"); doBeforeTry.SuccessorBlock.Should().Be(ifInsideTry); ifInsideTry.TrueSuccessorBlock.Should().Be(thenContinue); ifInsideTry.FalseSuccessorBlock.Should().Be(elseIf); VerifyAllInstructions(thenContinue, "cw1", "cw1()"); // ToDo: it should lead to `finally` which should lead to `whileStmt` thenContinue.SuccessorBlock.Should().Be(whileStmt); VerifyAllInstructions(elseIf, "cw2", "cw2()"); // ToDo: it should lead to `finally` which should lead to `afterDoWhile` elseIf.SuccessorBlock.Should().Be(afterDoWhile); // ToDo: this is weird and is basically skipped tryStatement.SuccessorBlocks.Should().Equal(finallyBody); finallyBody.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { afterTry, exit }); afterTry.SuccessorBlock.Should().Be(whileStmt); VerifyAllInstructions(whileStmt, "true"); whileStmt.TrueSuccessorBlock.Should().Be(doBeforeTry); whileStmt.FalseSuccessorBlock.Should().Be(afterDoWhile); VerifyAllInstructions(afterDoWhile, "cw5", "cw5()"); afterDoWhile.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_Inside_DoWhile_WithThrow_InsideCatch() { var cfg = Build(@" var attempts = 0; do { cw0(); try { attempts++; cw1(); break; } catch(Exception e) { cw2(); if (attempts > retries) { cw3(); throw; } cw4(); } } while (true); cw5();"); VerifyCfg(cfg, 10); var blocks = cfg.Blocks.ToList(); var beforeDoBlock = (SimpleBlock)blocks[0]; var insideDoBeforeTry = (SimpleBlock)blocks[1]; var insideTry = (BranchBlock)blocks[2]; // this block is initially created for the `insideTry`, // and it gets replaced when seeing the `break;` var temporaryStrayBlock = (BranchBlock)blocks[3]; var catchBodyWithIf = (BinaryBranchBlock)blocks[4]; var insideIfInsideCatch = (JumpBlock)blocks[5]; var afterIfInsideCatch = (SimpleBlock)blocks[6]; var whileStmt = (BinaryBranchBlock)blocks[7]; var afterDoWhile = (SimpleBlock)blocks[8]; var exit = (ExitBlock)blocks.Last(); VerifyAllInstructions(beforeDoBlock, "0", "attempts = 0"); beforeDoBlock.SuccessorBlocks.Should().Equal(insideDoBeforeTry); VerifyAllInstructions(insideDoBeforeTry, "cw0", "cw0()"); insideDoBeforeTry.SuccessorBlocks.Should().Equal(insideTry); VerifyAllInstructions(insideTry, "attempts", "attempts++", "cw1", "cw1()"); insideTry.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { catchBodyWithIf, whileStmt, afterDoWhile }); temporaryStrayBlock.ReversedInstructions.Should().BeEmpty(); temporaryStrayBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { catchBodyWithIf, whileStmt }); VerifyAllInstructions(catchBodyWithIf, "cw2", "cw2()", "attempts", "retries", "attempts > retries"); catchBodyWithIf.TrueSuccessorBlock.Should().Be(insideIfInsideCatch); catchBodyWithIf.FalseSuccessorBlock.Should().Be(afterIfInsideCatch); VerifyAllInstructions(insideIfInsideCatch, "cw3", "cw3()"); insideIfInsideCatch.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(afterIfInsideCatch, "cw4", "cw4()"); afterIfInsideCatch.SuccessorBlocks.Should().Equal(whileStmt); VerifyAllInstructions(whileStmt, "true"); whileStmt.TrueSuccessorBlock.Should().Be(insideDoBeforeTry); whileStmt.FalseSuccessorBlock.Should().Be(afterDoWhile); VerifyAllInstructions(afterDoWhile, "cw5", "cw5()"); afterDoWhile.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryFinally_Inside_DoWhile_WithThrow_InsideCatch() { var cfg = Build(@" var attempts = 0; do { cw0(); try { if (attempts) cw1(); } finally { attempts = 1; } } while (true); cw5();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var beforeDoBlock = (SimpleBlock)blocks[0]; var insideDoBeforeTry = (SimpleBlock)blocks[1]; var insideTryIfStatement = (BinaryBranchBlock)blocks[2]; var insideIf = (SimpleBlock)blocks[3]; var finallyBifurcation = (BranchBlock)blocks[4]; var finallyBlock = (BranchBlock)blocks[5]; var whileStmt = (BinaryBranchBlock)blocks[6]; var afterDoWhile = (SimpleBlock)blocks[7]; var exit = (ExitBlock)blocks.Last(); VerifyAllInstructions(beforeDoBlock, "0", "attempts = 0"); beforeDoBlock.SuccessorBlocks.Should().Equal(insideDoBeforeTry); VerifyAllInstructions(insideDoBeforeTry, "cw0", "cw0()"); insideDoBeforeTry.SuccessorBlocks.Should().Equal(insideTryIfStatement); VerifyAllInstructions(insideTryIfStatement, "attempts"); insideTryIfStatement.TrueSuccessorBlock.Should().Be(insideIf); insideTryIfStatement.FalseSuccessorBlock.Should().Be(finallyBifurcation); insideIf.SuccessorBlock.Should().Be(finallyBifurcation); finallyBifurcation.SuccessorBlocks.Should().Equal(finallyBlock); finallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { whileStmt, exit }); whileStmt.TrueSuccessorBlock.Should().Be(insideDoBeforeTry); whileStmt.FalseSuccessorBlock.Should().Be(afterDoWhile); afterDoWhile.SuccessorBlock.Should().Be(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatchFinally_Return_Nested() { var cfg = Build(@" before_out(); try { before_in(); try { foo(); return; } catch { cat_in(); } finally { fin_in(); } after_in(); } catch { cat_out(); } finally { fin_out(); } after_out();"); VerifyCfg(cfg, 11); var blocks = cfg.Blocks.ToList(); var beforeOuterTry = blocks[0]; var innerTryStartBlock = blocks[1]; var innerReturnBlock = blocks[2]; var innerTryEndBlock = blocks[3]; var innerCatchBlock = blocks[4]; var innerFinallyBlock = blocks[5]; var outerTryBlock = blocks[6]; // innerAfterFinallyBlock is not generated, its instructions are in outerTryBlock var outerCatchBlock = blocks[7]; var outerFinallyBlock = blocks[8]; var afterFinallyBlock = blocks[9]; var exit = blocks[10]; exit.Should().BeOfType(); beforeOuterTry.Should().BeOfType(); beforeOuterTry.SuccessorBlocks.Should().Equal(innerTryStartBlock); innerTryStartBlock.Should().BeOfType(); innerTryStartBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { innerReturnBlock /*no ex*/, outerCatchBlock, outerFinallyBlock }); innerReturnBlock.Should().BeOfType(); innerReturnBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { innerTryEndBlock, innerCatchBlock }); innerTryEndBlock.Should().BeOfType(); VerifyAllInstructions(innerTryEndBlock, "foo", "foo()"); innerTryEndBlock.SuccessorBlocks.Should().Equal(innerFinallyBlock); innerCatchBlock.Should().BeOfType(); innerCatchBlock.SuccessorBlocks.Should().Equal(innerFinallyBlock); innerFinallyBlock.Should().BeOfType(); innerFinallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { outerTryBlock, outerFinallyBlock }); outerTryBlock.Should().BeOfType(); outerTryBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { outerCatchBlock /*ex*/, outerFinallyBlock /*no ex*/}); outerCatchBlock.Should().BeOfType(); outerCatchBlock.SuccessorBlocks.Should().Equal(outerFinallyBlock); outerFinallyBlock.Should().BeOfType(); outerFinallyBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { afterFinallyBlock, exit }); afterFinallyBlock.Should().BeOfType(); afterFinallyBlock.SuccessorBlocks.Should().Equal(exit); } [TestMethod] public void Cfg_TryCatch_ReturnVariable_InCatch() { var cfg = Build(@" var number = 5; try { bar(); return 0; } catch { return number; } foo();"); VerifyCfg(cfg, 6); var blocks = cfg.Blocks.ToList(); var beforeOuterTry = (SimpleBlock)blocks[0]; var tryStatementBlock = (BranchBlock)blocks[1]; var tryReturn = (JumpBlock)blocks[2]; var catchReturn = (JumpBlock)blocks[3]; var afterTry = (SimpleBlock)blocks[4]; var exit = blocks[5]; VerifyAllInstructions(beforeOuterTry, "5", "number = 5"); beforeOuterTry.SuccessorBlocks.Should().Equal(tryStatementBlock); VerifyNoInstruction(tryStatementBlock); tryStatementBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { tryReturn, catchReturn }); VerifyAllInstructions(tryReturn, "bar", "bar()", "0"); tryReturn.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(catchReturn, "number"); catchReturn.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(afterTry, "foo", "foo()"); afterTry.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_NestedReturnVariable_InCatch() { var cfg = Build(@" var number = 5; try { bar(); return 0; } catch { if (cond) return number; } foo();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var beforeOuterTry = (SimpleBlock)blocks[0]; var tryStatementBlock = (BranchBlock)blocks[1]; var tryReturn = (JumpBlock)blocks[2]; var ifInsideCatch = (BinaryBranchBlock)blocks[3]; var returnInCatch = (JumpBlock)blocks[4]; var afterTry = (SimpleBlock)blocks[5]; var exit = (ExitBlock)blocks[6]; VerifyAllInstructions(beforeOuterTry, "5", "number = 5"); beforeOuterTry.SuccessorBlocks.Should().Equal(tryStatementBlock); VerifyNoInstruction(tryStatementBlock); tryStatementBlock.SuccessorBlocks.Should().BeEquivalentTo(new Block[] { tryReturn, ifInsideCatch }); VerifyAllInstructions(tryReturn, "bar", "bar()", "0"); tryReturn.SuccessorBlocks.Should().Equal(exit); ifInsideCatch.TrueSuccessorBlock.Should().Be(returnInCatch); ifInsideCatch.FalseSuccessorBlock.Should().Be(afterTry); VerifyAllInstructions(returnInCatch, "number"); returnInCatch.SuccessorBlocks.Should().Equal(exit); VerifyAllInstructions(afterTry, "foo", "foo()"); afterTry.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } [TestMethod] public void Cfg_TryCatch_MultipleReturnsInTry() { var cfg = Build(@" beforeTry(); try { if (cond) return; insideOne(); if (cond) return; insideTwo(); } catch { catchOne(); } afterTry();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var beforeOuterTry = (SimpleBlock)blocks[0]; var firstIf = (BinaryBranchBlock)blocks[1]; var firstIfReturn = (JumpBlock)blocks[2]; var secondIf = (BinaryBranchBlock)blocks[3]; var secondIfReturn = (JumpBlock)blocks[4]; var tryStatementBranch = (BranchBlock)blocks[5]; var insideCatch = (SimpleBlock)blocks[6]; var afterTry = (SimpleBlock)blocks[7]; var exit = (ExitBlock)blocks[8]; beforeOuterTry.SuccessorBlocks.Should().Equal(firstIf); firstIf.TrueSuccessorBlock.Should().Be(firstIfReturn); firstIfReturn.SuccessorBlock.Should().Be(exit); firstIf.FalseSuccessorBlock.Should().Be(secondIf); secondIf.TrueSuccessorBlock.Should().Be(secondIfReturn); secondIfReturn.SuccessorBlock.Should().Be(exit); secondIf.FalseSuccessorBlock.Should().Be(tryStatementBranch); // ToDo: this tryStatementBranch is not always used as such, or is it? tryStatementBranch.SuccessorBlocks.Should().BeEquivalentTo(new[] { insideCatch, afterTry }); insideCatch.SuccessorBlocks.Should().Equal(afterTry); afterTry.SuccessorBlocks.Should().Equal(exit); exit.Should().BeOfType(); } #endregion #region Switch [TestMethod] public void Cfg_Switch() { var cfg = Build("cw0(); switch(a) { case 1: case 2: cw1(); break; case 3: default: case 4: cw2(); break; } cw3();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")) as JumpBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var case1Jump = blocks[3] as JumpBlock; var defaultCaseJump = blocks[6] as JumpBlock; var branchCase1 = blocks[1] as BinaryBranchBlock; var branchCase2 = blocks[2] as BinaryBranchBlock; var branchCase3 = blocks[4] as BinaryBranchBlock; var branchDefault = blocks[5] as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(branchCase1); branchCase1.TrueSuccessorBlock.Should().Be(case1Jump); branchCase1.FalseSuccessorBlock.Should().Be(branchCase2); branchCase2.TrueSuccessorBlock.Should().Be(case1Jump); branchCase2.FalseSuccessorBlock.Should().Be(branchCase3); case1Jump.SuccessorBlocks.Should().Equal(cw3); branchCase3.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchCase3.FalseSuccessorBlock.Should().Be(branchDefault); branchDefault.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchDefault.FalseSuccessorBlock.Should().Be(defaultCaseJump); defaultCaseJump.SuccessorBlocks.Should().Equal(cw3); cw1.SuccessorBlocks.Should().Equal(cw3); cw2.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); VerifyAllInstructions(cfg.EntryBlock, "cw0", "cw0()", "a"); VerifyAllInstructions(cw1, "cw1", "cw1()"); } [TestMethod] public void Cfg_Switch_NoDefault() { var cfg = Build("cw0(); switch(a) { case 1: case 2: cw1(); break; case 3: case 4: cw2(); break; } cw3();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")) as JumpBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var case1Jump = blocks[3] as JumpBlock; var case3Jump = blocks[6] as JumpBlock; var branchCase1 = blocks[1] as BinaryBranchBlock; var branchCase2 = blocks[2] as BinaryBranchBlock; var branchCase3 = blocks[4] as BinaryBranchBlock; var branchDefault = blocks[5] as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(branchCase1); case1Jump.SuccessorBlocks.Should().Equal(cw3); case3Jump.SuccessorBlocks.Should().Equal(cw3); cw1.SuccessorBlocks.Should().Equal(cw3); cw2.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); branchCase2.Should().NotBeNull(); branchCase3.Should().NotBeNull(); branchDefault.Should().NotBeNull(); } [TestMethod] public void Cfg_Switch_GotoCase() { var cfg = Build("cw0(); switch(a) { case 1: case 2: cw1(); goto case 3; case 3: default: case 4: cw2(); break; } cw3();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")) as JumpBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var case1Jump = blocks[3] as JumpBlock; var defaultCaseJump = blocks[6] as JumpBlock; var branchCase1 = blocks[1] as BinaryBranchBlock; var branchCase2 = blocks[2] as BinaryBranchBlock; var branchCase3 = blocks[4] as BinaryBranchBlock; var branchDefault = blocks[5] as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(branchCase1); case1Jump.SuccessorBlocks.Should().Equal(defaultCaseJump); defaultCaseJump.SuccessorBlocks.Should().Equal(cw3); branchCase1.TrueSuccessorBlock.Should().Be(case1Jump); branchCase1.FalseSuccessorBlock.Should().Be(branchCase2); branchCase2.TrueSuccessorBlock.Should().Be(case1Jump); branchCase2.FalseSuccessorBlock.Should().Be(branchCase3); branchCase3.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchCase3.FalseSuccessorBlock.Should().Be(branchDefault); branchDefault.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchDefault.FalseSuccessorBlock.Should().Be(defaultCaseJump); cw1.SuccessorBlocks.Should().Equal(defaultCaseJump); cw2.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_Switch_Null() { var cfg = Build("cw0(); switch(a) { case \"\": case null: cw1(); break; case \"a\": cw2(); goto case null; } cw3();"); VerifyCfg(cfg, 8); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var caseEmptyJump = blocks[3] as JumpBlock; var caseAJump = blocks[5] as JumpBlock; var branchEmpty = blocks[1] as BinaryBranchBlock; var branchNull = blocks[2] as BinaryBranchBlock; var branchA = blocks[4] as BinaryBranchBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(branchEmpty); caseEmptyJump.SuccessorBlocks.Should().Equal(cw3); caseAJump.SuccessorBlocks.Should().Equal(caseEmptyJump); branchEmpty.TrueSuccessorBlock.Should().Be(caseEmptyJump); branchEmpty.FalseSuccessorBlock.Should().Be(branchNull); branchNull.TrueSuccessorBlock.Should().Be(caseEmptyJump); branchNull.FalseSuccessorBlock.Should().Be(branchA); branchA.TrueSuccessorBlock.Should().Be(caseAJump); branchA.FalseSuccessorBlock.Should().Be(cw3); } [TestMethod] public void Cfg_Switch_GotoDefault() { var cfg = Build("cw0(); switch(a) { case 1: case 2: cw1(); goto default; case 3: default: case 4: cw2(); break; } cw3();"); VerifyCfg(cfg, 9); var blocks = cfg.Blocks.ToList(); var cw0 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw0")) as BranchBlock; var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")) as JumpBlock; var cw3 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw3")); var case1Jump = blocks[3] as JumpBlock; var defaultCaseJump = blocks[6] as JumpBlock; var branchCase1 = blocks[1] as BinaryBranchBlock; var branchCase2 = blocks[2] as BinaryBranchBlock; var branchCase3 = blocks[4] as BinaryBranchBlock; var branchDefault = blocks[5] as BinaryBranchBlock; var exitBlock = cfg.ExitBlock; cw0.Should().BeSameAs(cfg.EntryBlock); cw0.SuccessorBlocks.Should().Equal(branchCase1); case1Jump.SuccessorBlocks.Should().Equal(defaultCaseJump); defaultCaseJump.SuccessorBlocks.Should().Equal(cw3); branchCase1.TrueSuccessorBlock.Should().Be(case1Jump); branchCase1.FalseSuccessorBlock.Should().Be(branchCase2); branchCase2.TrueSuccessorBlock.Should().Be(case1Jump); branchCase2.FalseSuccessorBlock.Should().Be(branchCase3); branchCase3.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchCase3.FalseSuccessorBlock.Should().Be(branchDefault); branchDefault.TrueSuccessorBlock.Should().Be(defaultCaseJump); branchDefault.FalseSuccessorBlock.Should().Be(defaultCaseJump); cw1.SuccessorBlocks.Should().Equal(defaultCaseJump); cw2.SuccessorBlocks.Should().Equal(cw3); cw3.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_Switch_Patterns_Default() { var cfg = Build("cw0(); switch(o) { case int i: case string s: cw1(); break; default: case double d: cw2(); break; } cw3();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseStringBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var firstSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var caseDoubleBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var secondSection_DefaultBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseStringBlock); VerifyAllInstructions(caseIntBlock, "int i"); caseStringBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseStringBlock.FalseSuccessorBlock.Should().Be(caseDoubleBlock); VerifyAllInstructions(caseStringBlock, "string s"); firstSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(firstSectionBlock, "cw1", "cw1()"); caseDoubleBlock.TrueSuccessorBlock.Should().Be(secondSection_DefaultBlock); caseDoubleBlock.FalseSuccessorBlock.Should().Be(secondSection_DefaultBlock); VerifyAllInstructions(caseDoubleBlock, "double d"); secondSection_DefaultBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(secondSection_DefaultBlock, "cw2", "cw2()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw3", "cw3()"); } [TestMethod] public void Cfg_Switch_Patterns_Two_Case_When() { var cfg = Build("cw0(); switch(o) { case int i when i > 0: case string s when s.Length > 0: cw1(); break; } cw2();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseIntWhenBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var caseStringBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var caseStringWhenBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var firstSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(caseIntWhenBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseStringBlock); VerifyAllInstructions(caseIntBlock, "int i"); caseIntWhenBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseIntWhenBlock.FalseSuccessorBlock.Should().Be(caseStringBlock); VerifyAllInstructions(caseIntWhenBlock, "i", "0", "i > 0"); caseStringBlock.TrueSuccessorBlock.Should().Be(caseStringWhenBlock); caseStringBlock.FalseSuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(caseStringBlock, "string s"); caseStringWhenBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseStringWhenBlock.FalseSuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(caseStringWhenBlock, "s", "s.Length", "0", "s.Length > 0"); firstSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(firstSectionBlock, "cw1", "cw1()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw2", "cw2()"); } [TestMethod] public void Cfg_Switch_One_Simple_Case_And_One_Case_With_When() { var cfg = Build("cw(); switch(o) { case 0 : cw0(); break; case 1 when s: cw1(); break; } cw2();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseZero = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseZeroBlock = (JumpBlock)cfg.Blocks.ElementAt(2); var caseOne = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var caseOneWhenBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var caseOneWhenBlockBody = (JumpBlock)cfg.Blocks.ElementAt(5); var afterSwitchBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseZero); VerifyAllInstructions(switchBlock, "cw", "cw()", "o"); caseZero.TrueSuccessorBlock.Should().Be(caseZeroBlock); caseZero.FalseSuccessorBlock.Should().Be(caseOne); caseZero.BranchingNode.Kind().Should().Be(SyntaxKind.CaseSwitchLabel); caseZeroBlock.SuccessorBlock.Should().Be(afterSwitchBlock); VerifyAllInstructions(caseZeroBlock, "cw0", "cw0()"); caseOne.TrueSuccessorBlock.Should().Be(caseOneWhenBlock); caseOne.FalseSuccessorBlock.Should().Be(afterSwitchBlock); caseOne.BranchingNode.Kind().Should().Be(SyntaxKind.CasePatternSwitchLabel); caseOneWhenBlock.TrueSuccessorBlock.Should().Be(caseOneWhenBlockBody); caseOneWhenBlock.FalseSuccessorBlock.Should().Be(afterSwitchBlock); VerifyAllInstructions(caseOneWhenBlock, "s"); caseOneWhenBlockBody.SuccessorBlock.Should().Be(afterSwitchBlock); VerifyAllInstructions(caseOneWhenBlockBody, "cw1", "cw1()"); afterSwitchBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(afterSwitchBlock, "cw2", "cw2()"); } [TestMethod] public void Cfg_Switch_Case_With_When_And_Default() { var cfg = Build("cw(); switch(o) { case 1 when i > 0: cw0(); break; default: cw1(); break; } cw2();"); VerifyCfg(cfg, 7); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseOne = (BranchBlock)cfg.Blocks.ElementAt(1); var caseOneWhenBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var caseOneWhenBlockBody = (JumpBlock)cfg.Blocks.ElementAt(3); var defaultBlock = (SimpleBlock)cfg.Blocks.ElementAt(4); var afterSwitchBlock = (SimpleBlock)cfg.Blocks.ElementAt(5); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(6); switchBlock.SuccessorBlocks.Should().Equal(caseOne); VerifyAllInstructions(switchBlock, "cw", "cw()", "o"); caseOne.SuccessorBlocks.Should().ContainInOrder(caseOneWhenBlock, defaultBlock); caseOne.BranchingNode.Kind().Should().Be(SyntaxKind.CasePatternSwitchLabel); caseOneWhenBlock.TrueSuccessorBlock.Should().Be(caseOneWhenBlockBody); caseOneWhenBlock.FalseSuccessorBlock.Should().Be(defaultBlock); VerifyAllInstructions(caseOneWhenBlock, "i", "0", "i > 0"); caseOneWhenBlockBody.SuccessorBlock.Should().Be(afterSwitchBlock); VerifyAllInstructions(caseOneWhenBlockBody, "cw0", "cw0()"); afterSwitchBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(afterSwitchBlock, "cw2", "cw2()"); } [TestMethod] public void Cfg_SwitchExpression_Return() { var cfg = Build(@" var type = ""test""; return type switch { ""a"" => 1, ""b"" => 2, _ => 3 };"); VerifyCfg(cfg, 7); // The generated CFG is very similar to the one generated for the following conditional expression: // return type == "a" ? 1 : (type == "b" ? 2 : 3); var aArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var aTrue = (SimpleBlock)cfg.Blocks.ElementAt(1); var bArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var bTrue = (SimpleBlock)cfg.Blocks.ElementAt(3); var discardArm = (SimpleBlock)cfg.Blocks.ElementAt(4); var returnStatement = (JumpBlock)cfg.Blocks.ElementAt(5); var exitBlock = cfg.ExitBlock; aArm.TrueSuccessorBlock.Should().Be(aTrue); aArm.FalseSuccessorBlock.Should().Be(bArm); VerifyAllInstructions(aArm, "\"test\"", "type = \"test\"", "type", "\"a\""); aTrue.SuccessorBlock.Should().Be(returnStatement); VerifyAllInstructions(aTrue, "1"); bArm.TrueSuccessorBlock.Should().Be(bTrue); bArm.FalseSuccessorBlock.Should().Be(discardArm); VerifyAllInstructions(bArm, "type", "\"b\""); bTrue.SuccessorBlock.Should().Be(returnStatement); VerifyAllInstructions(bTrue, "2"); discardArm.SuccessorBlock.Should().Be(returnStatement); VerifyAllInstructions(discardArm, "3"); returnStatement.SuccessorBlock.Should().Be(exitBlock); VerifyNoInstruction(returnStatement); } [TestMethod] public void Cfg_SwitchExpression_Assignment() { var cfg = Build(@"var type = ""test""; var result = type switch {""a"" => 1, ""b"" => 2, _ => 3};"); VerifyCfg(cfg, 7); var aArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var aTrue = (SimpleBlock)cfg.Blocks.ElementAt(1); var bArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var bTrue = (SimpleBlock)cfg.Blocks.ElementAt(3); var discardArm = (SimpleBlock)cfg.Blocks.ElementAt(4); var assignment = (SimpleBlock)cfg.Blocks.ElementAt(5); var exitBlock = cfg.ExitBlock; aArm.TrueSuccessorBlock.Should().Be(aTrue); aArm.FalseSuccessorBlock.Should().Be(bArm); VerifyAllInstructions(aArm, "\"test\"", "type = \"test\"", "type", "\"a\""); aTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(aTrue, "1"); bArm.TrueSuccessorBlock.Should().Be(bTrue); bArm.FalseSuccessorBlock.Should().Be(discardArm); VerifyAllInstructions(bArm, "type", "\"b\""); bTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(bTrue, "2"); discardArm.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(discardArm, "3"); assignment.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(assignment, @"result = type switch {""a"" => 1, ""b"" => 2, _ => 3}"); } [TestMethod] public void Cfg_SwitchExpression_InnerSwitch() { var cfg = Build(@" string first = ""foo"", second = ""bar""; var result = first switch {""a"" => second switch {""x"" => 1, _ => 2}, ""b"" => 3, _ => 4};"); VerifyCfg(cfg, 9); var aArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(0); var xArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var xTrue = (SimpleBlock)cfg.Blocks.ElementAt(2); var secondSwitchDiscardArm = (SimpleBlock)cfg.Blocks.ElementAt(3); var bArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var bTrue = (SimpleBlock)cfg.Blocks.ElementAt(5); var firstSwitchDiscard = (SimpleBlock)cfg.Blocks.ElementAt(6); var assignment = (SimpleBlock)cfg.Blocks.ElementAt(7); var exitBlock = cfg.ExitBlock; aArm.TrueSuccessorBlock.Should().Be(xArm); aArm.FalseSuccessorBlock.Should().Be(bArm); VerifyAllInstructions(aArm, "\"foo\"", "first = \"foo\"", "\"bar\"", "second = \"bar\"", "first", "\"a\""); xArm.TrueSuccessorBlock.Should().Be(xTrue); xArm.FalseSuccessorBlock.Should().Be(secondSwitchDiscardArm); VerifyAllInstructions(xArm, "second", "\"x\""); xTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(xTrue, "1"); secondSwitchDiscardArm.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(secondSwitchDiscardArm, "2"); bArm.TrueSuccessorBlock.Should().Be(bTrue); bArm.FalseSuccessorBlock.Should().Be(firstSwitchDiscard); VerifyAllInstructions(bArm, "first", "\"b\""); bTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(bTrue, "3"); firstSwitchDiscard.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(firstSwitchDiscard, "4"); assignment.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(assignment, @"result = first switch {""a"" => second switch {""x"" => 1, _ => 2}, ""b"" => 3, _ => 4}"); } [TestMethod] public void Cfg_SwitchExpression_WhenClause() { var cfg = Build(@"string first = ""foo"", second = ""bar""; var result = first switch {""a"" when second == ""bar"" => 1, ""a"" => 2, ""b"" => 3, _ => 4};"); VerifyCfg(cfg, 10); var aWithWhenClauseArm = (BinaryBranchBlock)cfg.EntryBlock; var whenClause = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var aWithWhenClauseArmTrue = (SimpleBlock)cfg.Blocks.ElementAt(2); var aArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var aTrue = (SimpleBlock)cfg.Blocks.ElementAt(4); var bArm = (BinaryBranchBlock)cfg.Blocks.ElementAt(5); var bTrue = (SimpleBlock)cfg.Blocks.ElementAt(6); var discardArm = (SimpleBlock)cfg.Blocks.ElementAt(7); var assignment = (SimpleBlock)cfg.Blocks.ElementAt(8); var exitBlock = cfg.ExitBlock; aWithWhenClauseArm.TrueSuccessorBlock.Should().Be(whenClause); aWithWhenClauseArm.FalseSuccessorBlock.Should().Be(aArm); VerifyAllInstructions(aWithWhenClauseArm, "\"foo\"", "first = \"foo\"", "\"bar\"", "second = \"bar\"", "first", "\"a\""); whenClause.TrueSuccessorBlock.Should().Be(aWithWhenClauseArmTrue); whenClause.FalseSuccessorBlock.Should().Be(aArm); VerifyAllInstructions(whenClause, "second", "\"bar\"", "second == \"bar\""); aWithWhenClauseArmTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(aWithWhenClauseArmTrue, "1"); aArm.TrueSuccessorBlock.Should().Be(aTrue); aArm.FalseSuccessorBlock.Should().Be(bArm); VerifyAllInstructions(aArm, "first", "\"a\""); aTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(aTrue, "2"); bArm.TrueSuccessorBlock.Should().Be(bTrue); bArm.FalseSuccessorBlock.Should().Be(discardArm); VerifyAllInstructions(bArm, "first", "\"b\""); bTrue.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(bTrue, "3"); discardArm.SuccessorBlock.Should().Be(assignment); VerifyAllInstructions(discardArm, "4"); assignment.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(assignment, @"result = first switch {""a"" when second == ""bar"" => 1, ""a"" => 2, ""b"" => 3, _ => 4}"); } [TestMethod] public void Cfg_VarPattern_InSwitchExpression_IsNotSupported() { var exception = Assert.Throws(() => Build(@"string a = taintedString switch {var x => null};")); exception.Message.Should().Be("VarPattern"); } [TestMethod] public void Cfg_VarPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is var x) { }")); exception.Message.Should().Be("VarPattern"); } [TestMethod] public void Cfg_NotPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is not null) { }")); exception.Message.Should().Be("NotPattern"); } [TestMethod] public void Cfg_AndPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is int and > 0) { }")); exception.Message.Should().Be("AndPattern"); } [TestMethod] public void Cfg_OrPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is string or int) { }")); exception.Message.Should().Be("OrPattern"); } [TestMethod] public void Cfg_ParenthesizedPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is (string s)) { }")); exception.Message.Should().Be("ParenthesizedPattern"); } [TestMethod] public void Cfg_ListPattern_InIf_IsNotSupported() { var exception = Assert.Throws(() => Build(@"if (tainted is []) { }")); exception.Message.Should().Be("ListPattern"); } [TestMethod] public void Cfg_Switch_Patterns_NoDefault() { var cfg = Build("cw0(); switch(o) { case int i: case string s: cw1(); break; case double d: cw2(); break; } cw3();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseStringBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var firstSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var caseDoubleBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var secondSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseStringBlock); VerifyAllInstructions(caseIntBlock, "int i"); caseStringBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseStringBlock.FalseSuccessorBlock.Should().Be(caseDoubleBlock); VerifyAllInstructions(caseStringBlock, "string s"); firstSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(firstSectionBlock, "cw1", "cw1()"); caseDoubleBlock.TrueSuccessorBlock.Should().Be(secondSectionBlock); caseDoubleBlock.FalseSuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(caseDoubleBlock, "double d"); secondSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(secondSectionBlock, "cw2", "cw2()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw3", "cw3()"); } [TestMethod] public void Cfg_Switch_Patterns_GotoDefault() { var cfg = Build("cw0(); switch(o) { case int i: case string s: cw1(); goto default; default: case double d: cw2(); break; } cw3();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseStringBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var firstSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var caseDoubleBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var secondSection_DefaultBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseStringBlock); VerifyAllInstructions(caseIntBlock, "int i"); caseStringBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseStringBlock.FalseSuccessorBlock.Should().Be(caseDoubleBlock); VerifyAllInstructions(caseStringBlock, "string s"); firstSectionBlock.SuccessorBlock.Should().Be(secondSection_DefaultBlock); VerifyAllInstructions(firstSectionBlock, "cw1", "cw1()"); caseDoubleBlock.TrueSuccessorBlock.Should().Be(secondSection_DefaultBlock); caseDoubleBlock.FalseSuccessorBlock.Should().Be(secondSection_DefaultBlock); VerifyAllInstructions(caseDoubleBlock, "double d"); secondSection_DefaultBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(secondSection_DefaultBlock, "cw2", "cw2()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw3", "cw3()"); } [TestMethod] public void Cfg_Switch_Patterns_Null() { var cfg = Build("cw0(); switch(o) { case int i: case null: cw1(); break; } cw2();"); VerifyCfg(cfg, 6); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseNullBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var firstSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(4); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(5); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseNullBlock); VerifyAllInstructions(caseIntBlock, "int i"); caseNullBlock.TrueSuccessorBlock.Should().Be(firstSectionBlock); caseNullBlock.FalseSuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(caseNullBlock, "o"); firstSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(firstSectionBlock, "cw1", "cw1()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw2", "cw2()"); } [TestMethod] public void Cfg_Switch_Patterns_Null_Default() { var cfg = Build(@"cw0(); switch(o) { case int i: cw1(); break; case null: cw2(); break; default: cw3(); break; } cw4();"); VerifyCfg(cfg, 8); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseIntBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var intSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(2); var caseNullBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var nullSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(4); var defaultSectionBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var lastBlock = (SimpleBlock)cfg.Blocks.ElementAt(6); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(7); switchBlock.SuccessorBlocks.Should().Equal(caseIntBlock); VerifyAllInstructions(switchBlock, "cw0", "cw0()", "o"); caseIntBlock.TrueSuccessorBlock.Should().Be(intSectionBlock); caseIntBlock.FalseSuccessorBlock.Should().Be(caseNullBlock); VerifyAllInstructions(caseIntBlock, "int i"); intSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(intSectionBlock, "cw1", "cw1()"); caseNullBlock.TrueSuccessorBlock.Should().Be(nullSectionBlock); caseNullBlock.FalseSuccessorBlock.Should().Be(defaultSectionBlock); VerifyAllInstructions(caseNullBlock, "o"); nullSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(nullSectionBlock, "cw2", "cw2()"); defaultSectionBlock.SuccessorBlock.Should().Be(lastBlock); VerifyAllInstructions(defaultSectionBlock, "cw3", "cw3()"); lastBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(lastBlock, "cw4", "cw4()"); } [TestMethod] public void Cfg_String_And_Throw() { var cfg = Build(@" cw(); switch(o) // switchBlock { case ""0"": cw0(); break; // caseZero default: // defaultBlock throw new InvalidOperationException(""""); } cw1(); // afterSwitchBlock "); VerifyCfg(cfg, 6); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var branchBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseZero = (JumpBlock)cfg.Blocks.ElementAt(2); var defaultBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var afterSwitchBlock = (SimpleBlock)cfg.Blocks.ElementAt(4); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(5); switchBlock.SuccessorBlocks.Should().Equal(branchBlock); VerifyAllInstructions(switchBlock, "cw", "cw()", "o"); branchBlock.TrueSuccessorBlock.Should().Be(caseZero); branchBlock.FalseSuccessorBlock.Should().Be(defaultBlock); VerifyAllInstructions(branchBlock, "o"); caseZero.SuccessorBlock.Should().Be(afterSwitchBlock); VerifyAllInstructions(caseZero, "cw0", "cw0()"); defaultBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(defaultBlock, "\"\"", "new InvalidOperationException(\"\")"); } [TestMethod] public void Cfg_Throws() { var cfg = Build(@" cw(); switch(o) // switchBlock { case 1: if (b) // firstCaseIfBlock { cw0(); // trueBranchBlock } else { throw new InvalidOperationException(""""); // falseBranchBlock } break; default: // defaultThrowBlock throw new InvalidOperationException(""a""); } cw1(); // afterSwitchBlock "); VerifyCfg(cfg, 9); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var case1Block = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var firstCaseIfBlock = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var trueBranchBlock = (SimpleBlock)cfg.Blocks.ElementAt(3); var falseBranchBlock = (JumpBlock)cfg.Blocks.ElementAt(4); var breakJump = (JumpBlock)cfg.Blocks.ElementAt(5); var defaultBranchBlock = (JumpBlock)cfg.Blocks.ElementAt(6); var afterSwitchBlock = (SimpleBlock)cfg.Blocks.ElementAt(7); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(8); switchBlock.SuccessorBlocks.Should().Equal(case1Block); case1Block.TrueSuccessorBlock.Should().Be(firstCaseIfBlock); case1Block.FalseSuccessorBlock.Should().Be(defaultBranchBlock); firstCaseIfBlock.TrueSuccessorBlock.Should().Be(trueBranchBlock); firstCaseIfBlock.FalseSuccessorBlock.Should().Be(falseBranchBlock); trueBranchBlock.SuccessorBlocks.Should().Equal(breakJump); breakJump.SuccessorBlocks.Should().Equal(afterSwitchBlock); afterSwitchBlock.SuccessorBlocks.Should().Equal(exitBlock); falseBranchBlock.SuccessorBlocks.Should().Equal(exitBlock); defaultBranchBlock.SuccessorBlocks.Should().Equal(exitBlock); } [TestMethod] public void Cfg_Enumerable_Patterns() { var cfg = Build(@" cw(); switch(o) { case IEnumerable subList when subList.Any() && b && sum > 0 : cw0(); break; default: cw2(); break; } cw3(); "); VerifyCfg(cfg, 9); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var patternCase = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var sublistAnyCondition = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var bCondition = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var sumGreaterCondition = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var caseInnerBlock = (JumpBlock)cfg.Blocks.ElementAt(5); var defaultBlock = (JumpBlock)cfg.Blocks.ElementAt(6); var afterSwitchBlock = (SimpleBlock)cfg.Blocks.ElementAt(7); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(8); switchBlock.SuccessorBlocks.Should().Equal(patternCase); patternCase.TrueSuccessorBlock.Should().Be(sublistAnyCondition); patternCase.FalseSuccessorBlock.Should().Be(defaultBlock); sublistAnyCondition.TrueSuccessorBlock.Should().Be(bCondition); sublistAnyCondition.FalseSuccessorBlock.Should().Be(defaultBlock); bCondition.TrueSuccessorBlock.Should().Be(sumGreaterCondition); bCondition.FalseSuccessorBlock.Should().Be(defaultBlock); sumGreaterCondition.TrueSuccessorBlock.Should().Be(caseInnerBlock); sumGreaterCondition.FalseSuccessorBlock.Should().Be(defaultBlock); caseInnerBlock.SuccessorBlock.Should().Be(afterSwitchBlock); defaultBlock.SuccessorBlock.Should().Be(afterSwitchBlock); afterSwitchBlock.SuccessorBlock.Should().Be(exitBlock); } [TestMethod] public void Cfg_Default_Statement_First() { var cfg = Build(@" int index = 0; Exception ex = null; switch (index) { default: break; case 0 when ex is InvalidOperationException: ex = null; break; } "); VerifyCfg(cfg, 6); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseZero = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseZeroWhenException = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var caseZeroWhenExceptionBlock = (JumpBlock)cfg.Blocks.ElementAt(3); var defaultBlock = (JumpBlock)cfg.Blocks.ElementAt(4); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(5); switchBlock.SuccessorBlocks.Should().Equal(caseZero); caseZero.TrueSuccessorBlock.Should().Be(caseZeroWhenException); caseZero.FalseSuccessorBlock.Should().Be(defaultBlock); caseZeroWhenException.TrueSuccessorBlock.Should().Be(caseZeroWhenExceptionBlock); caseZeroWhenException.FalseSuccessorBlock.Should().Be(defaultBlock); caseZeroWhenExceptionBlock.SuccessorBlock.Should().Be(exitBlock); defaultBlock.SuccessorBlock.Should().Be(exitBlock); } [TestMethod] public void Cfg_Mixed_Cases_With_The_Same_Action() { var cfg = Build(@" object o = null; Exception ex = null; switch (o) { case 0 when ex is ArgumentException: case 1: case string s when s.Length > 0: case object x: // do stuff break; } "); VerifyCfg(cfg, 9); var switchBlock = (BranchBlock)cfg.Blocks.ElementAt(0); var caseZero = (BinaryBranchBlock)cfg.Blocks.ElementAt(1); var caseZeroWhenException = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var caseOne = (BinaryBranchBlock)cfg.Blocks.ElementAt(3); var caseStringS = (BinaryBranchBlock)cfg.Blocks.ElementAt(4); var caseStringSWhen = (BinaryBranchBlock)cfg.Blocks.ElementAt(5); var caseObjectX = (BinaryBranchBlock)cfg.Blocks.ElementAt(6); var breakBlock = (JumpBlock)cfg.Blocks.ElementAt(7); var exitBlock = (ExitBlock)cfg.Blocks.ElementAt(8); switchBlock.SuccessorBlocks.Should().Equal(caseZero); caseZero.TrueSuccessorBlock.Should().Be(caseZeroWhenException); caseZero.FalseSuccessorBlock.Should().Be(caseOne); caseZeroWhenException.TrueSuccessorBlock.Should().Be(breakBlock); caseZeroWhenException.FalseSuccessorBlock.Should().Be(caseOne); caseOne.TrueSuccessorBlock.Should().Be(breakBlock); caseOne.FalseSuccessorBlock.Should().Be(caseStringS); caseStringS.TrueSuccessorBlock.Should().Be(caseStringSWhen); caseStringS.FalseSuccessorBlock.Should().Be(caseObjectX); caseStringSWhen.TrueSuccessorBlock.Should().Be(breakBlock); caseStringSWhen.FalseSuccessorBlock.Should().Be(caseObjectX); caseObjectX.TrueSuccessorBlock.Should().Be(breakBlock); caseObjectX.FalseSuccessorBlock.Should().Be(exitBlock); breakBlock.SuccessorBlock.Should().Be(exitBlock); } #endregion #region Goto [TestMethod] public void Cfg_Goto_A() { var cfg = Build("var x = 1; a: b: x++; if (x < 42) { cw1(); goto a; } cw2();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cond = blocks .First(block => block.Instructions.Any(n => n.ToString() == "x < 42")); var a = blocks[1] as JumpBlock; var b = blocks[2] as JumpBlock; var entry = cfg.EntryBlock; (a.JumpNode as LabeledStatementSyntax).Identifier.ValueText.Should().Be("a"); (b.JumpNode as LabeledStatementSyntax).Identifier.ValueText.Should().Be("b"); entry.SuccessorBlocks.Should().Equal(a); a.SuccessorBlocks.Should().Equal(b); b.SuccessorBlocks.Should().Equal(cond); cond.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(a); cw2.SuccessorBlocks.Should().Equal(cfg.ExitBlock); } [TestMethod] public void Cfg_Goto_B() { var cfg = Build("var x = 1; a: b: x++; if (x < 42) { cw1(); goto b; } cw2();"); VerifyCfg(cfg, 7); var blocks = cfg.Blocks.ToList(); var cw1 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw1")) as JumpBlock; var cw2 = blocks .First(block => block.Instructions.Any(n => n.ToString() == "cw2")); var cond = blocks .First(block => block.Instructions.Any(n => n.ToString() == "x < 42")); var a = blocks[1] as JumpBlock; var b = blocks[2] as JumpBlock; var entry = cfg.EntryBlock; (a.JumpNode as LabeledStatementSyntax).Identifier.ValueText.Should().Be("a"); (b.JumpNode as LabeledStatementSyntax).Identifier.ValueText.Should().Be("b"); entry.SuccessorBlocks.Should().Equal(a); a.SuccessorBlocks.Should().Equal(b); b.SuccessorBlocks.Should().Equal(cond); cond.SuccessorBlocks.Should().BeEquivalentTo(new[] { cw1, cw2 }); cw1.SuccessorBlocks.Should().Equal(b); cw2.SuccessorBlocks.Should().Equal(cfg.ExitBlock); } #endregion #region Yield return [TestMethod] public void Cfg_YieldReturn() { var cfg = Build(@"yield return 5;"); VerifyMinimalCfg(cfg); cfg.EntryBlock.Should().BeOfType(); var jumpBlock = (JumpBlock)cfg.EntryBlock; jumpBlock.JumpNode.Kind().Should().Be(SyntaxKind.YieldReturnStatement); VerifyAllInstructions(jumpBlock, "5"); } #endregion #region Non-branching expressions [TestMethod] public void Cfg_NonBranchingExpressions() { var cfg = Build(@" x = a < 2; x = a <= 2; x = a > 2; x = a >= 2; x = a == 2; x = a != 2; s = c << 4; s = c >> 4; b = x | 2; b = x & 2; b = x ^ 2; c = ""c"" + 'c'; c = a - b; c = a * b; c = a / b; c = a % b;"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "a", "2", "a < 2", "x = a < 2"); VerifyInstructions(cfg.EntryBlock, 15 * 4, "a", "b", "a % b", "c = a % b"); cfg = Build("b |= 2; b &= false; b ^= 2; c += b; c -= b; c *= b; c /= b; c %= b; s <<= 4; s >>= 4;"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "b", "2", "b |= 2"); VerifyInstructions(cfg.EntryBlock, 9 * 3, "s", "4", "s >>= 4"); cfg = Build("p = c++; p = c--; p = ++c; p = --c; p = +c; p = -c; p = !true; p = ~1; p = &c; p = *c;"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "c", "c++", "p = c++"); VerifyInstructions(cfg.EntryBlock, 9 * 3, "c", "*c", "p = *c"); cfg = Build("o = null;"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "null", "o = null"); cfg = Build("b = (b);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "b", "b = (b)"); cfg = Build(@"var t = typeof(int); var s = sizeof(int); var v = default(int);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "typeof(int)", "t = typeof(int)", "sizeof(int)", "s = sizeof(int)", "default(int)", "v = default(int)"); cfg = Build(@"v = checked(1+1); v = unchecked(1+1);"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 2, "1+1", "checked(1+1)", "v = checked(1+1)"); VerifyInstructions(cfg.EntryBlock, 7, "1+1", "unchecked(1+1)", "v = unchecked(1+1)"); cfg = Build("v = (int)1; v = 1 as object; v = 1 is int;"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "1", "(int)1", "v = (int)1", "1", "1 as object", "v = 1 as object", "1", "1 is int", "v = 1 is int"); cfg = Build(@"var s = $""Some {text}"";"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, @"text", @"$""Some {text}""", @"s = $""Some {text}"""); cfg = Build("this.Method(call, with, arguments);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "this", "this.Method", "call", "with", "arguments", "this.Method(call, with, arguments)"); cfg = Build("x = array[1,2,3]; x = array2[1][2];"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "array", "1", "2", "3", "array[1,2,3]", "x = array[1,2,3]", "array2", "1", "array2[1]", "2", "array2[1][2]", "x = array2[1][2]"); cfg = Build(@"var dict = new Dictionary{ [""one""] = 1 };"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, @"new Dictionary{ [""one""] = 1 }", @"""one""", @"[""one""]", @"1", @"[""one""] = 1", @"{ [""one""] = 1 }", @"dict = new Dictionary{ [""one""] = 1 }"); cfg = Build("var x = new { Prop1 = 10, Prop2 = 20 };"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "10", "20", "new { Prop1 = 10, Prop2 = 20 }", "x = new { Prop1 = 10, Prop2 = 20 }"); cfg = Build("var x = new { Prop1 };"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "Prop1", "new { Prop1 }", "x = new { Prop1 }"); cfg = Build("var x = new MyClass(5) { Prop1 = 10 };"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "5", "new MyClass(5) { Prop1 = 10 }", "10", "Prop1 = 10", "{ Prop1 = 10 }"); cfg = Build("var x = new List{ 10, 20 };"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "new List{ 10, 20 }", "10", "20", "{ 10, 20 }"); cfg = Build("var x = new[,] { { 10, 20 }, { 10, 20 } };"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "new[,] { { 10, 20 }, { 10, 20 } }", "10", "20", "{ 10, 20 }"); VerifyInstructions(cfg.EntryBlock, 7, "{ { 10, 20 }, { 10, 20 } }"); cfg = Build("var x = new int[] { 1 };"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "", "int[]", "new int[] { 1 }", "1", "{ 1 }", "x = new int[] { 1 }"); cfg = Build("var x = new int [1,2][3]{ 10, 20 };"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "1", "2", "3", "int [1,2][3]", "new int [1,2][3]{ 10, 20 }", "10", "20", "{ 10, 20 }"); cfg = Build("var z = x->prop;"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "x", "x->prop"); cfg = Build("var x = await this.Method(__arglist(10,11));"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 2, "__arglist", "10", "11", "__arglist(10,11)", "this.Method(__arglist(10,11))", "await this.Method(__arglist(10,11))"); cfg = Build("var x = 1; var y = __refvalue(__makeref(x), int); var t = __reftype(__makeref(x));"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 2, "x", "__makeref(x)", "__refvalue(__makeref(x), int)"); VerifyInstructions(cfg.EntryBlock, 6, "x", "__makeref(x)", "__reftype(__makeref(x))"); cfg = Build("var x = new Action(()=>{}); var y = new Action(i=>{}); var z = new Action(delegate(){});"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "()=>{}", "new Action(()=>{})"); cfg = Build("var x = from t in ts where t > 42;"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "from t in ts where t > 42", "x = from t in ts where t > 42"); cfg = Build("string.Format(\"\")"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "string", "string.Format", "\"\"", "string.Format(\"\")"); } [TestMethod] public void Cfg_Stackalloc() { var cfg = Build("var x = stackalloc int[10];"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "10", "int[10]", "stackalloc int[10]"); } [TestMethod] public void Cfg_Stackalloc_Initializer() { var cfg = Build("var x = stackalloc int[2] { 10, 20 };"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "2", "int[2]", "10", "20", "{ 10, 20 }", "stackalloc int[2] { 10, 20 }"); } [TestMethod] public void Cfg_Stackalloc_Implicit() { var cfg = Build("var x = stackalloc [] {100, 200, 300};"); VerifyMinimalCfg(cfg); VerifyInstructions(cfg.EntryBlock, 0, "100", "200", "300", "{100, 200, 300}", "stackalloc [] {100, 200, 300}"); } [TestMethod] public void Cfg_NonRemovedCalls() { var cfg = Build(@"System.Diagnostics.Debug.Fail("""");"); VerifyCfg(cfg, 2); cfg.EntryBlock.Instructions.Should().NotBeEmpty(); cfg = Build(@"System.Diagnostics.Debug.Assert(false);"); VerifyCfg(cfg, 2); cfg.EntryBlock.Instructions.Should().NotBeEmpty(); } #endregion #region Method invocation [TestMethod] public void Cfg_Ref_Arg_Should_Be_Last_Instruction() { var cfg = Build(@"Bye(ref x0, x1, x2, x3, ref x4);}"); VerifyCfg(cfg, 2); var instructionsBlock = (SimpleBlock)cfg.Blocks.ElementAt(0); VerifyAllInstructions(instructionsBlock, "Bye", "x1", "x2", "x3", "x0", "x4", "Bye(ref x0, x1, x2, x3, ref x4)"); } [TestMethod] public void Cfg_Ref_Arg_Should_Be_Last_Instruction_WithMethodCallOnObject() { var cfg = Build(@"Bye.Hi(ref x0, x1, x2, x3, ref x4);}"); VerifyCfg(cfg, 2); var instructionsBlock = (SimpleBlock)cfg.Blocks.ElementAt(0); VerifyAllInstructions(instructionsBlock, "Bye", "Bye.Hi", "x1", "x2", "x3", "x0", "x4", "Bye.Hi(ref x0, x1, x2, x3, ref x4)"); } #endregion #region Property Pattern Clause [TestMethod] public void Cfg_PropertyPatternClause_Simple() { var cfg = Build(@"var x = address is Address { State: ""WA"" };"); VerifyCfg(cfg, 2); var entryBlock = (SimpleBlock)cfg.EntryBlock; var exitBlock = cfg.ExitBlock; entryBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "address", "Address { State: \"WA\" }", // RecursivePattern "address is Address { State: \"WA\" }", // IsPatternExpression "x = address is Address { State: \"WA\" }"); // VariableDeclaration } [TestMethod] public void Cfg_PropertyPatternClause_MultipleProperties() { var cfg = Build(@"var x = address is { State: ""WA"", Street: ""Rue"" };"); VerifyCfg(cfg, 2); var entryBlock = (SimpleBlock)cfg.EntryBlock; var exitBlock = cfg.ExitBlock; entryBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "address", "{ State: \"WA\", Street: \"Rue\" }", // Recursive Pattern "address is { State: \"WA\", Street: \"Rue\" }", // IsPatternExpression "x = address is { State: \"WA\", Street: \"Rue\" }"); // VariableDeclaration } [TestMethod] public void Cfg_PropertyPatternClause_WithSingleVariableDesignation() { var cfg = Build(@"var x = address is Address { State: ""WA"" } addr;"); VerifyCfg(cfg, 2); var entryBlock = (SimpleBlock)cfg.EntryBlock; var exitBlock = cfg.ExitBlock; entryBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "address", "Address { State: \"WA\" } addr", // Recursive Pattern "address is Address { State: \"WA\" } addr", // IsPatternExpression "x = address is Address { State: \"WA\" } addr"); // VariableDeclaration } [TestMethod] public void Cfg_PropertyPatternClause_InsideIf() { var cfg = Build(@"if (address is Address { State: ""WA"" }) { return true; }"); VerifyCfg(cfg, 3); var entryBlock = (BinaryBranchBlock)cfg.EntryBlock; var trueBlock = (JumpBlock)cfg.Blocks.ElementAt(1); var exitBlock = cfg.ExitBlock; entryBlock.TrueSuccessorBlock.Should().Be(trueBlock); entryBlock.FalseSuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "address", "Address { State: \"WA\" }", // Recursive Pattern "address is Address { State: \"WA\" }"); // IsPatternExpression trueBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(trueBlock, "true"); VerifyNoInstruction(exitBlock); } [TestMethod] public void Cfg_PropertyPatternClause_InsideIf_WithSingleVariableDesignation() { var cfg = Build(@"if (address is Address { State: ""WA"" } addr) { return true; }"); VerifyCfg(cfg, 3); var entryBlock = (BinaryBranchBlock)cfg.EntryBlock; var trueBlock = (JumpBlock)cfg.Blocks.ElementAt(1); var exitBlock = cfg.ExitBlock; entryBlock.TrueSuccessorBlock.Should().Be(trueBlock); entryBlock.FalseSuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "address", "Address { State: \"WA\" } addr", // Recursive Pattern "address is Address { State: \"WA\" } addr"); // IsPatternExpression trueBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(trueBlock, "true"); VerifyNoInstruction(exitBlock); } [TestMethod] public void Cfg_PropertyPatternClause_InsideSwitch() { var cfg = Build(@"location switch { { State: ""WA"" } adr => salePrice * 0.06M, { State: ""MN"" } => salePrice * 0.75M, _ => 0M };"); VerifyCfg(cfg, 6); var waArmBranch = (BinaryBranchBlock)cfg.EntryBlock; var waArmTrueBranch = (SimpleBlock)cfg.Blocks.ElementAt(1); var mnArmBranch = (BinaryBranchBlock)cfg.Blocks.ElementAt(2); var mnArmTrueBranch = (SimpleBlock)cfg.Blocks.ElementAt(3); var discardArm = (SimpleBlock)cfg.Blocks.ElementAt(4); var exitBlock = cfg.ExitBlock; waArmBranch.TrueSuccessorBlock.Should().Be(waArmTrueBranch); waArmBranch.FalseSuccessorBlock.Should().Be(mnArmBranch); VerifyAllInstructions(waArmBranch, "location", "{ State: \"WA\" } adr" /* RecursivePattern */); waArmTrueBranch.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(waArmTrueBranch, "salePrice", "0.06M", "salePrice * 0.06M"); mnArmBranch.TrueSuccessorBlock.Should().Be(mnArmTrueBranch); mnArmBranch.FalseSuccessorBlock.Should().Be(discardArm); VerifyAllInstructions(mnArmBranch, "location", "{ State: \"MN\" }" /* RecursivePattern */); mnArmTrueBranch.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(mnArmTrueBranch, "salePrice", "0.75M", "salePrice * 0.75M"); discardArm.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(discardArm, "0M"); VerifyNoInstruction(exitBlock); } [TestMethod] public void Cfg_PropertyPatternClause_Nested() { var cfg = Build(@"var result = o is Person { Name: ""John Doe"", Address: { State: ""WA"" } };"); VerifyCfg(cfg, 2); var entryBlock = (SimpleBlock)cfg.EntryBlock; var exitBlock = cfg.ExitBlock; entryBlock.SuccessorBlock.Should().Be(exitBlock); VerifyAllInstructions(entryBlock, "o", "Person { Name: \"John Doe\", Address: { State: \"WA\" } }", // RecursivePattern "o is Person { Name: \"John Doe\", Address: { State: \"WA\" } }", // IsPatternExpression "result = o is Person { Name: \"John Doe\", Address: { State: \"WA\" } }"); // VariableDeclaration } #endregion #region Instance creation [TestMethod] public void Cfg_New() { var cfg = Build(@"var x = new Object()"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "new Object()", "x = new Object()"); } [TestMethod] public void Cfg_New_TargetTyped() { Action a = () => Build(@"Object x = new()"); a.Should().Throw(); // C# 9 ImplicitObjectCreationExpressionSyntax is not supported yet } #endregion #region "Tuples" [TestMethod] public void Cfg_Tuple_Create() { var cfg = Build(@"var x = (true, 42);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "true", "42", "(true, 42)", "x = (true, 42)"); } [TestMethod] public void Cfg_Tuple_ComplexExpression() { const string code = @" var x = (LocalBool(), LocalInt() + 2); bool LocalBool() => true; int LocalInt() => 40; "; var cfg = Build(code); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "LocalBool", "LocalBool()", "LocalInt", "LocalInt()", "2", "LocalInt() + 2", "(LocalBool(), LocalInt() + 2)", "x = (LocalBool(), LocalInt() + 2)"); } [TestMethod] public void Cfg_Tuple_InDeclaration() { var cfg = Build(@"var (a, b) = (true, 42);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "var (a, b)", "true", "42", "(true, 42)", "var (a, b) = (true, 42)"); } [TestMethod] public void Cfg_Tuple_AssignmentTarget() { var cfg = Build(@"bool a; int b; (a, b) = (true, 42);"); VerifyMinimalCfg(cfg); VerifyAllInstructions(cfg.EntryBlock, "a", "b", "a", "b", "(a, b)", "true", "42", "(true, 42)", "(a, b) = (true, 42)"); } #endregion #region Methods to build the CFG for the tests internal const string TestInput = @" using System; namespace NS {{ public class Foo {{ public void Bar() {{ {0} }} }} }}"; internal static (MethodDeclarationSyntax Method, SemanticModel Model) CompileWithMethodBody(string input) { var (tree, semanticModel) = TestCompiler.CompileIgnoreErrorsCS(input); return (tree.First(), semanticModel); } internal static string ExtremelyNestedExpression() { const int count = 2000; const string dna = "ACGT"; return Enumerable.Repeat($@"""{dna}""", count).JoinStr(" +\n"); } private static IControlFlowGraph Build(string methodBody) { var (method, model) = CompileWithMethodBody(string.Format(TestInput, methodBody)); var cfg = CSharpControlFlowGraph.Create(method.Body, model); // when debugging the CFG, it is useful to visualize the CFG var dot = CfgSerializer.Serialize(cfg, "CFG diagnostics"); System.Diagnostics.Debug.WriteLine(dot); return cfg; } private static SyntaxNode FirstConstructorBody(SyntaxTree tree) => tree.First().Body; private static MethodDeclarationSyntax FirstMethod(SyntaxTree tree) => tree.First(); #endregion #region Verify helpers private static void VerifyForStatement(IControlFlowGraph cfg) { VerifyCfg(cfg, 5); var initBlock = cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var branchBlock = blocks[1] as BinaryBranchBlock; var incrementorBlock = blocks[3]; var loopBodyBlock = blocks[2]; var exitBlock = cfg.ExitBlock; initBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(incrementorBlock); incrementorBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { initBlock, incrementorBlock }); exitBlock.PredecessorBlocks.Should().Equal(branchBlock); } private static void VerifyInstructions(Block block, int fromIndex, params string[] instructions) { block.Instructions.Count.Should().BeGreaterOrEqualTo(fromIndex + instructions.Length); for (var i = 0; i < instructions.Length; i++) { block.Instructions[fromIndex + i].ToString().Should().Be(instructions[i]); } } private static void VerifyAllInstructions(Block block, params string[] instructions) { block.Instructions.Should().HaveSameCount(instructions); VerifyInstructions(block, 0, instructions); } private static void VerifyNoInstruction(Block block) => VerifyAllInstructions(block, Array.Empty()); private static void VerifyCfg(IControlFlowGraph cfg, int numberOfBlocks) { VerifyBasicCfgProperties(cfg); cfg.Blocks.Should().HaveCount(numberOfBlocks); if (numberOfBlocks > 1) { cfg.EntryBlock.Should().NotBeSameAs(cfg.ExitBlock); cfg.Blocks.Should().ContainInOrder(new[] { cfg.EntryBlock, cfg.ExitBlock }); } else { cfg.EntryBlock.Should().BeSameAs(cfg.ExitBlock); cfg.Blocks.Should().Contain(cfg.EntryBlock); } } private static void VerifyMinimalCfg(IControlFlowGraph cfg) { VerifyCfg(cfg, 2); cfg.EntryBlock.SuccessorBlocks.Should().Equal(cfg.ExitBlock); } private static void VerifyEmptyCfg(IControlFlowGraph cfg) => VerifyCfg(cfg, 1); private static void VerifyBasicCfgProperties(IControlFlowGraph cfg) { cfg.Should().NotBeNull(); cfg.EntryBlock.Should().NotBeNull(); cfg.ExitBlock.Should().NotBeNull(); cfg.ExitBlock.SuccessorBlocks.Should().BeEmpty(); cfg.ExitBlock.Instructions.Should().BeEmpty(); } private static void VerifyForStatementNoInitializer(IControlFlowGraph cfg) { VerifyCfg(cfg, 5); var blocks = cfg.Blocks.ToList(); var initializerBlock = cfg.EntryBlock as ForInitializerBlock; var branchBlock = blocks[1] as BinaryBranchBlock; var incrementorBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "i++")); var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; initializerBlock.SuccessorBlock.Should().Be(branchBlock); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(incrementorBlock); incrementorBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { incrementorBlock, initializerBlock }); exitBlock.PredecessorBlocks.Should().Equal(branchBlock); } private static void VerifyForStatementNoIncrementor(IControlFlowGraph cfg) { VerifyCfg(cfg, 4); var initBlock = cfg.EntryBlock; var blocks = cfg.Blocks.ToList(); var branchBlock = blocks[1] as BinaryBranchBlock; var loopBodyBlock = blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; initBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { initBlock, loopBodyBlock }); exitBlock.PredecessorBlocks.Should().Equal(branchBlock); } private static void VerifyForStatementEmpty(IControlFlowGraph cfg) { VerifyCfg(cfg, 4); var initializerBlock = cfg.EntryBlock as ForInitializerBlock; var blocks = cfg.Blocks.ToList(); var branchBlock = blocks[1] as BinaryBranchBlock; var loopBodyBlock = cfg.Blocks .First(b => b.Instructions.Any(n => n.ToString() == "x = 10")); var exitBlock = cfg.ExitBlock; initializerBlock.SuccessorBlock.Should().Be(branchBlock); branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, exitBlock }); branchBlock.BranchingNode.Kind().Should().Be(SyntaxKind.ForStatement); loopBodyBlock.SuccessorBlocks.Should().Equal(branchBlock); branchBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { loopBodyBlock, initializerBlock }); exitBlock.PredecessorBlocks.Should().Equal(branchBlock); } private static void VerifySimpleJumpBlock(IControlFlowGraph cfg, SyntaxKind kind) { VerifyCfg(cfg, 3); var jumpBlock = cfg.EntryBlock as JumpBlock; var bodyBlock = cfg.Blocks.ToList()[1]; var exitBlock = cfg.ExitBlock; jumpBlock.SuccessorBlocks.Should().Equal(bodyBlock); bodyBlock.SuccessorBlocks.Should().Equal(exitBlock); jumpBlock.JumpNode.Kind().Should().Be(kind); } private static void VerifyJumpWithNoExpression(IControlFlowGraph cfg, SyntaxKind kind) { VerifyCfg(cfg, 4); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var trueBlock = blocks[1] as JumpBlock; var falseBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); trueBlock.SuccessorBlocks.Should().Equal(exitBlock); trueBlock.JumpNode.Kind().Should().Be(kind); falseBlock.SuccessorBlocks.Should().Equal(exitBlock); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); } private static void VerifyJumpWithExpression(IControlFlowGraph cfg, SyntaxKind kind) { VerifyCfg(cfg, 4); var branchBlock = cfg.EntryBlock as BinaryBranchBlock; var blocks = cfg.Blocks.ToList(); var trueBlock = blocks[1] as JumpBlock; var falseBlock = blocks[2]; var exitBlock = cfg.ExitBlock; branchBlock.SuccessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); trueBlock.SuccessorBlocks.Should().Equal(exitBlock); trueBlock.JumpNode.Kind().Should().Be(kind); trueBlock.Instructions.Should().Contain(n => n.IsKind(SyntaxKind.IdentifierName) && n.ToString() == "ii"); exitBlock.PredecessorBlocks.Should().BeEquivalentTo(new[] { trueBlock, falseBlock }); } #endregion } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/ConcurrentExecutionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.AnalysisContext; namespace SonarAnalyzer.Test.Common; [TestClass] public class ConcurrentExecutionTest { [TestMethod] public void Verify_ConcurrentExecutionIsEnabledByDefault() { var reader = new ConcurrentExecutionReader(); reader.IsConcurrentExecutionEnabled.Should().BeNull(); VerifyNoExceptionThrown("TestCases\\AsyncVoidMethod.cs", [reader]); reader.IsConcurrentExecutionEnabled.Should().BeTrue(); } [TestMethod] [DataRow("true")] [DataRow("tRUE")] [DataRow("loremipsum")] public void Verify_ConcurrentExecutionIsExplicitlyEnabled(string value) { using var scope = new EnvironmentVariableScope(); scope.SetVariable(SonarDiagnosticAnalyzer.EnableConcurrentExecutionVariable, value); var reader = new ConcurrentExecutionReader(); reader.IsConcurrentExecutionEnabled.Should().BeNull(); VerifyNoExceptionThrown("TestCases\\AsyncVoidMethod.cs", [reader]); reader.IsConcurrentExecutionEnabled.Should().BeTrue(); } [TestMethod] [DataRow("false")] [DataRow("fALSE")] public void Verify_ConcurrentExecutionIsExplicitlyDisabled(string value) { using var scope = new EnvironmentVariableScope(); scope.SetVariable(SonarDiagnosticAnalyzer.EnableConcurrentExecutionVariable, value); var reader = new ConcurrentExecutionReader(); reader.IsConcurrentExecutionEnabled.Should().BeNull(); VerifyNoExceptionThrown("TestCases\\AsyncVoidMethod.cs", [reader]); reader.IsConcurrentExecutionEnabled.Should().BeFalse(); } private static void VerifyNoExceptionThrown(string path, DiagnosticAnalyzer[] analyzers, CompilationErrorBehavior checkMode = CompilationErrorBehavior.Default) { var compilation = SolutionBuilder .Create() .AddProject(AnalyzerLanguage.FromPath(path)) .AddDocument(path) .GetCompilation(); ((Action)(() => DiagnosticVerifier.AnalyzerDiagnostics(compilation, analyzers, checkMode))).Should().NotThrow(); } [DiagnosticAnalyzer(LanguageNames.CSharp)] private class ConcurrentExecutionReader : SonarDiagnosticAnalyzer { private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorFactory.CreateUtility("S9999", "Rule test"); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; public new bool? IsConcurrentExecutionEnabled { get; private set; } protected override void Initialize(SonarAnalysisContext context) => IsConcurrentExecutionEnabled = EnableConcurrentExecution; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/MetricsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Metrics; using SonarAnalyzer.CSharp.Metrics; using SonarAnalyzer.VisualBasic.Metrics; namespace SonarAnalyzer.Test.Common; [TestClass] public class MetricsTest { [TestMethod] public void LinesOfCode() { LinesOfCode(AnalyzerLanguage.CSharp, string.Empty).Should().BeEmpty(); LinesOfCode(AnalyzerLanguage.CSharp, "/* ... */\n").Should().BeEmpty(); LinesOfCode(AnalyzerLanguage.CSharp, "namespace X { }").Should().Equal(1); LinesOfCode(AnalyzerLanguage.CSharp, "namespace X \n { \n }").Should().BeEquivalentTo([1, 2, 3]); LinesOfCode(AnalyzerLanguage.CSharp, "public class Sample { public Sample() { System.Console.WriteLine(@\"line1 \n line2 \n line3 \n line 4\"); } }").Should().BeEquivalentTo([1, 2, 3, 4]); LinesOfCode(AnalyzerLanguage.VisualBasic, string.Empty).Should().BeEmpty(); LinesOfCode(AnalyzerLanguage.VisualBasic, "'\n").Should().BeEmpty(); LinesOfCode(AnalyzerLanguage.VisualBasic, "Module Module1 : End Module").Should().BeEquivalentTo([1]); LinesOfCode(AnalyzerLanguage.VisualBasic, "Module Module1 \n \n End Module").Should().BeEquivalentTo([1, 3]); LinesOfCode(AnalyzerLanguage.VisualBasic, """ Public Class SomeClass Public Sub New() Console.WriteLine("line1 line2 line3 line 4") End Sub End Class """).Should().BeEquivalentTo([1, 2, 3, 4, 5, 6, 7, 8]); } [TestMethod] public void CommentsWithoutHeaders() { CommentsWithoutHeaders(AnalyzerLanguage.CSharp, string.Empty).NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, string.Empty).NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "#if DEBUG\nfoo\n#endif").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; \n#if DEBUG\nfoo\n#endif").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "// foo").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "#if DEBUG\nfoo\n#endif\n// foo").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // l1").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // l1\n// l2").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 */").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 \n l2 */").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 \n l2 */").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /// foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /** foo */").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \n \n bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \r \r bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \r\n \r\n bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // NOSONAR").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // ooNOSONARoo").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // nosonar").NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; // nOSonAr").NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* NOSONAR */ /* foo*/").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* NOSONAR */ /* foo */").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* foo*/ /* NOSONAR */").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.CSharp, "using System; /* foo*/ /* NOSONAR */").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, string.Empty).NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, string.Empty).NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "#If DEBUG Then\nfoo\n#End If").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System \n#If DEBUG Then\nfoo\n#End If").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "' foo").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "#If DEBUG Then\nfoo\n#End If\n' foo").NonBlank.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' l1").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' l1\n' l2").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ''' foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' NOSONAR").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' ooNOSONARoo").NoSonar.Should().BeEquivalentTo([1]); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' nosonar").NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' nOSonAr").NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' fndskgjsdkl \n ' {00000000-0000-0000-0000-000000000000}\n").NonBlank.Should().BeEquivalentTo([1, 2]); } [TestMethod] public void CommentsWithHeaders() { CommentsWithHeaders(AnalyzerLanguage.CSharp, string.Empty).NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, string.Empty).NoSonar.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "#if DEBUG\nfoo\n#endif").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; \n#if DEBUG\nfoo\n#endif").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "// foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "#if DEBUG\nfoo\n#endif\n// foo").NonBlank.Should().BeEquivalentTo([4]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // l1").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // l1\n// l2").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 */").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 \n l2 */").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* l1 \n l2 */").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /// foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /** foo */").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \n \n bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \r \r bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /** foo \r\n \r\n bar */").NonBlank.Should().BeEquivalentTo([1, 3]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // NOSONAR").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // ooNOSONARoo").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // nosonar").NoSonar.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; // nOSonAr").NoSonar.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* NOSONAR */ /* foo*/").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* NOSONAR */ /* foo */").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* foo*/ /* NOSONAR */").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.CSharp, "using System; /* foo*/ /* NOSONAR */").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, string.Empty).NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, string.Empty).NoSonar.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "#If DEBUG Then\nfoo\n#End If").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System \n#If DEBUG Then\nfoo\n#End If").NonBlank.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "' foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "#If DEBUG Then\nfoo\n#End If\n' foo").NonBlank.Should().BeEquivalentTo([4]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' l1").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' l1\n' l2").NonBlank.Should().BeEquivalentTo([1, 2]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ''' foo").NonBlank.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' NOSONAR").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' ooNOSONARoo").NoSonar.Should().BeEquivalentTo([1]); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' nosonar").NoSonar.Should().BeEmpty(); CommentsWithHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' nOSonAr").NoSonar.Should().BeEmpty(); CommentsWithoutHeaders(AnalyzerLanguage.VisualBasic, "Imports System ' fndskgjsdkl \n ' {00000000-0000-0000-0000-000000000000}\n").NonBlank.Should().BeEquivalentTo([1, 2]); } [TestMethod] public void Classes() { Classes(AnalyzerLanguage.CSharp, string.Empty).Should().Be(0); Classes(AnalyzerLanguage.CSharp, "class Sample {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "interface IMyInterface {}").Should().Be(1); #if NET Classes(AnalyzerLanguage.CSharp, "record MyRecord {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "record MyPositionalRecord(int Prop) {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "record struct MyRecordStruct {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "record struct MyPositionalRecordStruct(int Prop) {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "file class FileScopedClass {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "file record FileScopedRecord {}").Should().Be(1); #endif Classes(AnalyzerLanguage.CSharp, "struct Sample {}").Should().Be(1); Classes(AnalyzerLanguage.CSharp, "enum MyEnum {}").Should().Be(0); Classes(AnalyzerLanguage.CSharp, "class Sample1 {} namespace MyNamespace { class Sample2 {} }").Should().Be(2); Classes(AnalyzerLanguage.VisualBasic, string.Empty).Should().Be(0); Classes(AnalyzerLanguage.VisualBasic, "Class M \n End Class").Should().Be(1); Classes(AnalyzerLanguage.VisualBasic, "Structure M \n End Structure").Should().Be(1); Classes(AnalyzerLanguage.VisualBasic, "Enum M \n None \n End Enum").Should().Be(0); Classes(AnalyzerLanguage.VisualBasic, "Interface M \n End Interface").Should().Be(1); Classes(AnalyzerLanguage.VisualBasic, "Module M \n End Module").Should().Be(1); Classes(AnalyzerLanguage.VisualBasic, "Class M \n End Class \n Namespace MyNamespace \n Class Sample2 \n End Class \n End Namespace").Should().Be(2); } [TestMethod] public void Accessors() { Functions(AnalyzerLanguage.CSharp, string.Empty).Should().Be(0); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyProperty { get; } }").Should().Be(1); Functions(AnalyzerLanguage.CSharp, "interface Sample { int MyProperty { get; } }").Should().Be(0); Functions(AnalyzerLanguage.CSharp, "abstract class Sample { public abstract int MyProperty { get; } }").Should().Be(0); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyProperty => 42; }").Should().Be(1); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyProperty { get; set; } }").Should().Be(2); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyProperty { get { return 0; } set { } } }").Should().Be(2); Functions(AnalyzerLanguage.CSharp, "class Sample { public event System.EventHandler OnSomething { add { } remove {} } }").Should().Be(2); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyMethod() { return 1; } }").Should().Be(1); Functions(AnalyzerLanguage.CSharp, "class Sample { public int MyMethod() => 1; }").Should().Be(1); Functions(AnalyzerLanguage.CSharp, "abstract class Sample { public abstract int MyMethod(); }").Should().Be(0); Functions(AnalyzerLanguage.VisualBasic, string.Empty).Should().Be(0); Functions(AnalyzerLanguage.VisualBasic, "Class Sample \n Public ReadOnly Property MyProperty As Integer \n End Class").Should().Be(0); // Is this the expected? Functions(AnalyzerLanguage.VisualBasic, "Class Sample \n Public Property MyProperty As Integer \n End Class") .Should().Be(0); // Is this the expected? Functions(AnalyzerLanguage.VisualBasic, """ Class Sample Public Property MyProperty As Integer Get Return 0 End Get Set(value As Integer) End Set End Property End Class """) .Should().Be(2); Functions(AnalyzerLanguage.VisualBasic, """ Class Sample Public Custom Event Click As EventHandler AddHandler(ByVal value As EventHandler) End AddHandler RemoveHandler(ByVal value As EventHandler) End RemoveHandler RaiseEvent(ByVal sender As Object, ByVal e As EventArgs) End RaiseEvent End Event End Class """) .Should().Be(3); } [TestMethod] public void Statements() { Statements(AnalyzerLanguage.CSharp, string.Empty).Should().Be(0); Statements(AnalyzerLanguage.CSharp, "class Sample {}").Should().Be(0); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() {} }").Should().Be(0); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { {} } }").Should().Be(0); Statements(AnalyzerLanguage.CSharp, "class Sample { int MyMethod() { return 0; } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { int l = 42; } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { System.Console.WriteLine(); } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { ; } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { foo: ; } }").Should().Be(2); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { goto foo; foo: ;} }").Should().Be(3); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { while(true) break; } }").Should().Be(2); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { while(false) continue; } }").Should().Be(2); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { throw new System.Exception(); } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { System.Collections.Generic.IEnumerable MyMethod() { yield return 42; } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { System.Collections.Generic.IEnumerable MyMethod() { yield break; } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { while (false) {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { do {} while (false); } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { for (;;) {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { foreach (var e in new [] {1, 2, 3}) {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { using (var e = new System.IO.MemoryStream()) {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { unsafe { fixed (int* p = &this.x) {} } } int x; }").Should().Be(2); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { checked {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { unchecked {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { unsafe {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { if (false) {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int v) { switch (v) { case 0: break; } } }").Should().Be(2); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { try {} catch {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { try {} finally {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { try {} catch {} finally {} } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { int MyMethod() { int a = 42; System.Console.WriteLine(a); return a; } }").Should().Be(3); Statements(AnalyzerLanguage.CSharp, "class Sample { void Bar() { void LocalFunction() { } } }").Should().Be(1); Statements(AnalyzerLanguage.CSharp, "class Sample { void Bar(System.Collections.Generic.List<(int, int)> names) { foreach ((int x, int y) in names){} } }").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, string.Empty).Should().Be(0); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n \n End Class").Should().Be(0); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n End Sub \n End Class").Should().Be(0); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Function MyFunc() As Integer \n Return 0 \n End Function \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Dim a As Integer = 42 \n \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Console.WriteLine() \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Foo: \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n GoTo Foo \n Foo: \n End Sub \n End Class").Should().Be(2); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Exit Sub \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Do : Continue Do : Loop\n End Sub \n End Class").Should().Be(2); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Throw New Exception \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n While False \n End While \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Do \n Loop While True \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n For i As Integer = 0 To -1 \n Next \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n For Each e As Integer In {1, 2, 3} \n Next\n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Using e As New System.IO.MemoryStream() \n End Using \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n If False Then \n End If \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod(v As Integer) \n Select Case v \n Case 0 \n End Select \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Try \n Catch \n End Try \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Try \n Finally \n End Try \n End Sub \n End Class").Should().Be(1); Statements(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Try \n Catch \n Finally \n End Try \n End Sub \n End Class").Should().Be(1); Statements( AnalyzerLanguage.VisualBasic, "Class Sample \n Function MyFunc() As Integer \n Dim a As Integer = 42 \n Console.WriteLine(a) \n Return a \n End " + "Function \n End Class") .Should().Be(3); } #if NET [TestMethod] [DataRow("", 0)] [DataRow("class Sample { }", 0)] [DataRow("abstract class Sample { public abstract void MyMethod1(); }", 0)] [DataRow("interface Interface { void MyMethod1(); }", 0)] [DataRow("class Sample { static Sample() { } }", 1)] [DataRow("class Sample { public Sample() { } }", 1)] [DataRow("class Sample { ~Sample() { } }", 1)] [DataRow("class Sample { public void MyMethod2() { } }", 1)] [DataRow("class Sample { public static Sample operator +(Sample a) { return a; } }", 1)] [DataRow("class Sample { public int MyProperty { get; set; } }", 2)] [DataRow("class Sample { public int MyProperty { get { return 0; } } }", 1)] [DataRow("class Sample { public int MyProperty { set { } } }", 1)] [DataRow("class Sample { public int MyProperty { init { } } }", 1)] [DataRow("class Sample { public int MyProperty { get => 42; } }", 1)] [DataRow("class Sample { public int MyProperty { get { return 0; } set { } } }", 2)] [DataRow("class Sample { public event System.EventHandler OnSomething { add { } remove {} } }", 2)] [DataRow("class Sample { void Bar() { void LocalFunction() { } } }", 2)] [DataRow(""" partial class Sample { public partial int MyProperty { get; set; } // The partial definition part of a property is not counted. } partial class Sample { public partial int MyProperty { get => 1; set { } } } """, 2)] [DataRow(""" partial class Sample { public partial int this[int index] { get; set; } } partial class Sample { public partial int this[int index] { get => 1; set { } } } """, 2)] public void Functions_CSharp(string function, int expected) => Functions(AnalyzerLanguage.CSharp, function).Should().Be(expected); [TestMethod] [DataRow("", 0)] [DataRow("Class Sample \n \n End Class", 0)] [DataRow("MustInherit Class Sample \n MustOverride Sub MyMethod() \n End Class", 0)] [DataRow("Interface MyInterface \n Sub MyMethod() \n End Interface", 0)] [DataRow("Class Sample \n Public Property MyProperty As Integer \n End Class", 0)] [DataRow("Class Sample \n Shared Sub New() \n End Sub \n End Class", 1)] [DataRow("Class Sample \n Sub New() \n End Sub \n End Class", 1)] [DataRow("Class Sample \n Protected Overrides Sub Finalize() \n End Sub \n End Class", 1)] [DataRow("Class Sample \n Sub MyMethod2() \n End Sub \n End Class", 1)] [DataRow("Class Sample \n Public Shared Operator +(a As Sample) As Sample \n Return a \n End Operator \n End Class", 1)] public void Functions_VisualBasic(string function, int expected) => Functions(AnalyzerLanguage.VisualBasic, function).Should().Be(expected); #endif [TestMethod] public void Complexity() { Complexity(AnalyzerLanguage.CSharp, string.Empty) .Should().Be(0); Complexity(AnalyzerLanguage.CSharp, "class Sample { }") .Should().Be(0); Complexity(AnalyzerLanguage.CSharp, "abstract class Sample { public abstract void MyMethod(); }") .Should().Be(0); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { return; } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { return; return; } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { { return; } } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { if (false) { } } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { if (false) { } else { } } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { var t = false ? 0 : 1; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { switch (p) { default: break; } } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { var x = p switch { _ => 1 }; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { switch (p) { case 0: break; default: break; } } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { var x = p switch { 0 => \"zero\", 1 => \"one\", _ => \"other\" }; } }") .Should().Be(4); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(bool first, bool second) { var _ = first switch { true => second switch { true => 1, _ => 2}, _ => 3}; } }") .Should().Be(5); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { foo: ; } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { do { } while (false); } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { for (;;) { } } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(System.Collections.Generic.List p) { foreach (var i in p) { } } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { var a = false; } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { var a = false && false; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int p) { var a = false || true; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { int MyProperty { get; set; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, """ partial class Sample { partial int MyProperty { get; set; } } partial class Sample { partial int MyProperty { get => 1; set { } } } """).Should().Be(4); Complexity(AnalyzerLanguage.CSharp, "class Sample { int MyProperty { get => 0; set {} } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { public Sample() { } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { ~Sample() { } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { public static Sample operator +(Sample a) { return a; } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { public event System.EventHandler OnSomething { add { } remove {} } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { var t = (string)null ?? string.Empty; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int? t) { t ??= 0; } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod(int? a, int? b, int? c) => a ??= b ??= c ??= 0; }") .Should().Be(4); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { int? t = null; t?.ToString(); } }") .Should().Be(2); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { throw new System.Exception(); } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { try { } catch(System.Exception e) { } } }") .Should().Be(1); Complexity(AnalyzerLanguage.CSharp, "class Sample { void MyMethod() { goto Foo; Foo: var i = 0; } }") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, string.Empty) .Should().Be(0); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n End Class") .Should().Be(0); Complexity(AnalyzerLanguage.VisualBasic, "MustInherit Class Sample \n Public MustOverride Sub MyMethod() \n End Class") .Should().Be(0); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Return \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Return \n Return \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n If False Then \n End If \n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n If False Then \n Else \n End If \n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod(p As Integer) \n Select Case p \n Case Else \n Exit Select \n End Select \n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod(p As Integer) \n Select Case p \n Case 3 \n Exit Select \n Case Else \n Exit Select \n End Select \n End Sub \n End Class") .Should().Be(4); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Foo: \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Do \n Loop While True \n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n For i As Integer = 0 To -1 \n Next \n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n For Each e As Integer In {1, 2, 3} \n Next\n End Sub \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Dim a As Boolean = False\n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Dim a As Boolean = False And False\n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub MyMethod() \n Dim a As Boolean = False Or False\n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Public Property MyProperty As Integer \n End Class") .Should().Be(0); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Public Property MyProperty As Integer \n Get \n End Get " + "\n Set(value As Integer) \n End Set \n End Property \n End Class") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Sub New() \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Protected Overrides Sub Finalize() \n End Sub \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Class Sample \n Public Shared Operator +(a As Sample) As Sample \n Return a \n End Operator \n End Class") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, """ Class Sample Public Custom Event OnSomething As EventHandler AddHandler(ByVal value As EventHandler) End AddHandler RemoveHandler(ByVal value As EventHandler) End RemoveHandler RaiseEvent(ByVal sender As Object, ByVal e As EventArgs) End RaiseEvent End Event End Class """) .Should().Be(3); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Function Bar() \n Return 0\n End Function \n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Function Bar() \n If False Then \n Return 1 \n Else \n Return 0 \n End If\n End Function\n End Class\n End Module") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Function Foo() \n Return 42\n End Function\n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n ReadOnly Property MyProp \n Get \n Return \"\" \n End Get \n End Property\n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Foo() \n End Sub\n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Foo() \n Dim Foo = If(True, True, False)\n End Sub\n End Class\n End Module") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Foo() \n Dim Foo = Function() 0\n End Sub\n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Foo() \n Dim Foo = Function() \n Return False \n End Function\n End Sub\n End Class\n End Module") .Should().Be(1); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Foo() \n Throw New AccessViolationException()\n End Sub\n End Class\n End Module") .Should().Be(2); Complexity(AnalyzerLanguage.VisualBasic, "Module Module1\n Class Sample\n Sub Method() \n GoTo Foo \n Foo: \n End Sub\n End Class\n End Module") .Should().Be(2); } [TestMethod] public void CognitiveComplexity() { var csharpText = System.IO.File.ReadAllText(@"TestCases\CognitiveComplexity.cs"); CognitiveComplexity(AnalyzerLanguage.CSharp, csharpText).Should().Be(109); var csharpLatestText = System.IO.File.ReadAllText(@"TestCases\CognitiveComplexity.Latest.cs"); var csharpLatestCompilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp, OutputKind.ConsoleApplication) .AddSnippet(csharpLatestText) .GetCompilation(); var csharpLatestTree = csharpLatestCompilation.SyntaxTrees.Single(); new CSharpMetrics(csharpLatestTree, csharpLatestCompilation.GetSemanticModel(csharpLatestTree)).CognitiveComplexity.Should().Be(49); var visualBasicCode = System.IO.File.ReadAllText(@"TestCases\CognitiveComplexity.vb"); CognitiveComplexity(AnalyzerLanguage.VisualBasic, visualBasicCode).Should().Be(122); } [TestMethod] public void WrongMetrics_CSharp() { var (syntaxTree, semanticModel) = TestCompiler.CompileVB(string.Empty); Assert.Throws(() => new CSharpMetrics(syntaxTree, semanticModel)); } [TestMethod] public void WrongMetrics_VisualBasic() { var (syntaxTree, semanticModel) = TestCompiler.CompileCS(string.Empty); Assert.Throws(() => new VisualBasicMetrics(syntaxTree, semanticModel)); } [TestMethod] public void ExecutableLinesMetricsIsPopulated_CSharp() => ExecutableLines(AnalyzerLanguage.CSharp, "public class Sample { public void Foo(int x) { int i = 0; if (i == 0) {i++;i--;} else { while(true){i--;} } } }") .Should().Equal(1); [TestMethod] public void ExecutableLinesMetricsIsPopulated_VB() => ExecutableLines(AnalyzerLanguage.VisualBasic, """ Module MainMod Private Sub Foo(x As Integer) If x = 42 Then End If End Sub End Module """) .Should().Equal(3); private static ISet LinesOfCode(AnalyzerLanguage language, string text) => MetricsFor(language, text).CodeLines; private static FileComments CommentsWithoutHeaders(AnalyzerLanguage language, string text) => MetricsFor(language, text).GetComments(true); private static FileComments CommentsWithHeaders(AnalyzerLanguage language, string text) => MetricsFor(language, text).GetComments(false); private static int Classes(AnalyzerLanguage language, string text) => MetricsFor(language, text).ClassCount; private static int Statements(AnalyzerLanguage language, string text) => MetricsFor(language, text).StatementCount; private static int Functions(AnalyzerLanguage language, string text) => MetricsFor(language, text).FunctionCount; private static int Complexity(AnalyzerLanguage language, string text) => MetricsFor(language, text).Complexity; private static int CognitiveComplexity(AnalyzerLanguage language, string text) => MetricsFor(language, text).CognitiveComplexity; private static ICollection ExecutableLines(AnalyzerLanguage language, string text) => MetricsFor(language, text).ExecutableLines; private static MetricsBase MetricsFor(AnalyzerLanguage language, string text) { if (language != AnalyzerLanguage.CSharp && language != AnalyzerLanguage.VisualBasic) { throw new ArgumentException("Supplied language is not C# neither VB.Net", nameof(language)); } var (syntaxTree, semanticModel) = TestCompiler.Compile(text, false, language); return language == AnalyzerLanguage.CSharp ? new CSharpMetrics(syntaxTree, semanticModel) : new VisualBasicMetrics(syntaxTree, semanticModel); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/ParameterLoaderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Common; [TestClass] public class ParameterLoaderTest { public TestContext TestContext { get; set; } [TestMethod] [DataRow("path//aSonarLint.xml")] // different name [DataRow("path//SonarLint.xmla")] // different extension public void SetParameterValues_WithInvalidSonarLintPath_DoesNotPopulateParameters(string filePath) { // Arrange var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value } [TestMethod] [DataRow("a/SonarLint.xml")] // unix path [DataRow(@"a\SonarLint.xml")] public void SetParameterValues_WithValidSonarLintPath_PopulatesProperties(string filePath) { // Arrange var compilation = CreateCompilationWithOption(filePath, SourceText.From(File.ReadAllText(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"))); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file } [TestMethod] public void SetParameterValues_SonarLintFileWithIntParameterType_PopulatesProperties() { // Arrange var compilation = CreateCompilationWithOption(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(1); // Value from the xml file } [TestMethod] public void SetParameterValues_SonarLintFileWithStringParameterType_PopulatesProperty() { // Arrange var parameterValue = "1"; var filePath = GenerateSonarLintXmlWithParametrizedRule("S2342", "flagsAttributeFormat", parameterValue); var compilation = CreateCompilationWithOption(filePath); var analyzer = new EnumNameShouldFollowRegex(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.FlagsEnumNamePattern.Should().Be(parameterValue); // value from XML file } [TestMethod] public void SetParameterValues_SonarLintFileWithBooleanParameterType_PopulatesProperty() { // Arrange var parameterValue = true; var filePath = GenerateSonarLintXmlWithParametrizedRule("S1451", "isRegularExpression", parameterValue.ToString()); var compilation = CreateCompilationWithOption(filePath); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().Be(parameterValue); // value from XML file } [TestMethod] public void SetParameterValues_SonarLintFileWithoutRuleParameters_DoesNotPopulateProperties() { // Arrange var compilation = CreateCompilationWithOption(@"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml"); var analyzer = new LineLength(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(200); // Default value } [TestMethod] public void SetParameterValues_CalledTwiceAfterChangeInConfigFile_UpdatesProperties() { // Arrange var maxValue = 1; var ruleParameters = new List() { new SonarLintXmlRule() { Key = "S1067", Parameters = new List() { new SonarLintXmlKeyValuePair() { Key = "max", Value = maxValue.ToString() } } } }; var sonarLintXml = AnalysisScaffolding.GenerateSonarLintXmlContent(rulesParameters: ruleParameters); var filePath = TestFiles.WriteFile(TestContext, "SonarLint.xml", sonarLintXml); var compilation = CreateCompilationWithOption(filePath); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); // Modify the in-memory additional file maxValue = 42; ruleParameters.First().Parameters.First().Value = maxValue.ToString(); var modifiedSonarLintXml = AnalysisScaffolding.GenerateSonarLintXmlContent(rulesParameters: ruleParameters); var modifiedFilePath = TestFiles.WriteFile(TestContext, "SonarLint.xml", modifiedSonarLintXml); compilation = CreateCompilationWithOption(modifiedFilePath); ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); analyzer.Maximum.Should().Be(maxValue); } [TestMethod] [DataRow("")] [DataRow("this is not an xml")] [DataRow(@"")] public void SetParameterValues_WithMalformedXml_DoesNotPopulateProperties(string sonarLintXmlContent) { // Arrange var compilation = CreateCompilationWithOption(@"fakePath\SonarLint.xml", SourceText.From(sonarLintXmlContent)); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value } [TestMethod] public void SetParameterValues_SonarLintFileWithStringInsteadOfIntParameterType_PopulatesProperty() { // Arrange var parameterValue = "fooBar"; var filePath = GenerateSonarLintXmlWithParametrizedRule("S1067", "max", parameterValue); var compilation = CreateCompilationWithOption(filePath); var analyzer = new ExpressionComplexity(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.Maximum.Should().Be(3); // Default value } [TestMethod] public void SetParameterValues_SonarLintFileWithStringInsteadOfBooleanParameterType_PopulatesProperty() { // Arrange var parameterValue = "fooBar"; var filePath = GenerateSonarLintXmlWithParametrizedRule("S1451", "isRegularExpression", parameterValue); var compilation = CreateCompilationWithOption(filePath); var analyzer = new CheckFileLicense(); // Cannot use mock because we use reflection to find properties. // Act ParameterLoader.SetParameterValues(analyzer, compilation.SonarLintXml()); // Assert analyzer.IsRegularExpression.Should().BeFalse(); // Default value } private static SonarCompilationReportingContext CreateCompilationWithOption(string filePath, SourceText text = null) { var options = text is null ? AnalysisScaffolding.CreateOptions(filePath) : AnalysisScaffolding.CreateOptions(filePath, text); var compilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).GetCompilation(); var compilationContext = new CompilationAnalysisContext(compilation, options, _ => { }, _ => true, default); return new(AnalysisScaffolding.CreateSonarAnalysisContext(), compilationContext); } private string GenerateSonarLintXmlWithParametrizedRule(string ruleId, string key, string value) { var ruleParameters = new List() { new SonarLintXmlRule() { Key = ruleId, Parameters = new List() { new SonarLintXmlKeyValuePair() { Key = key, Value = value } } } }; return AnalysisScaffolding.CreateSonarLintXml(TestContext, rulesParameters: ruleParameters); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/RuleCatalogTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using RuleCatalogCS = SonarAnalyzer.CSharp.Core.Rspec.RuleCatalog; using RuleCatalogVB = SonarAnalyzer.VisualBasic.Core.Rspec.RuleCatalog; namespace SonarAnalyzer.Test.Common; [TestClass] public class RuleCatalogTest { [TestMethod] public void RuleCatalog_HasAllFieldsSet_CS() => AssertRuleS103(RuleCatalogCS.Rules["S103"]); [TestMethod] public void RuleCatalog_HasAllFieldsSet_VB() => AssertRuleS103(RuleCatalogVB.Rules["S103"]); [TestMethod] [DataRow("S103", "CODE_SMELL")] [DataRow("S1048", "BUG")] [DataRow("S1313", "SECURITY_HOTSPOT")] public void Type_IsGenerated(string id, string expected) { RuleCatalogCS.Rules[id].Type.Should().Be(expected); RuleCatalogVB.Rules[id].Type.Should().Be(expected); } [TestMethod] [DataRow("S101", SourceScope.All)] [DataRow("S112", SourceScope.Main)] [DataRow("S3431", SourceScope.Tests)] public void SourceScope_IsGenerated(string id, SourceScope expected) { RuleCatalogCS.Rules[id].Scope.Should().Be(expected); RuleCatalogVB.Rules[id].Scope.Should().Be(expected); } [TestMethod] public void Description_TakesFirstParagraph() => ValidateDescription( "S105", "

That is why using spaces is preferable.

", // Asserting existence of the second paragraph that should not be part of the description "The tab width can differ from one development environment to another." + " Using tabs may require other developers to configure their environment (text editor, preferences, etc.) to read source code."); [TestMethod] public void Description_TagsAreRemoved() => ValidateDescription("S1116", "by a semicolon ;", "Empty statements represented by a semicolon ; are statements that do not perform any operation."); [TestMethod] public void Description_HtmlIsDecoded() => ValidateDescription("S1067", "&&", "The complexity of an expression is defined by the number of &&, || and condition ? ifTrue : ifFalse operators it contains."); [TestMethod] public void Description_NewLinesAreSpaces() => ValidateDescription( "S107", "track of their\nposition.", // Html contains new line with no spaces around it "Methods with a long parameter list are difficult to use because maintainers must figure out the role of each parameter and keep track of their position."); private static void AssertRuleS103(RuleDescriptor rule) { rule.Id.Should().Be("S103"); rule.Title.Should().Be("Lines should not be too long"); rule.Type.Should().Be("CODE_SMELL"); rule.DefaultSeverity.Should().Be("Major"); rule.Status.Should().Be("ready"); rule.Scope.Should().Be(SourceScope.All); rule.SonarWay.Should().BeFalse(); rule.Description.Should().Be("Scrolling horizontally to see a full line of code lowers the code readability."); } private static void ValidateDescription(string id, string assertedSourceFragment, string expectedDescription) { var rspecDirectory = Path.Combine(Paths.AnalyzersRoot, "rspec", "cs"); var html = File.ReadAllText(Path.Combine(rspecDirectory, $"{id}.html")).Replace("\r\n", "\n"); html.Should().Contain(assertedSourceFragment, "we need to make sure that the assertion below has expected data fragment as an input"); RuleCatalogCS.Rules[id].Description.Should().Contain(expectedDescription); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/SecurityHotspotTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Test.Rules; namespace SonarAnalyzer.Test.Common; [TestClass] public class SecurityHotspotTest { [TestMethod] public void SecurityHotspotRules_DoNotRaiseIssues_CS() => VerifyNoIssues(AnalyzerLanguage.CSharp, LanguageOptions.FromCSharp9); [TestMethod] public void SecurityHotspotRules_DoNotRaiseIssues_VB() => VerifyNoIssues(AnalyzerLanguage.VisualBasic, LanguageOptions.FromVisualBasic12); private static void VerifyNoIssues(AnalyzerLanguage language, ImmutableArray parseOptions) { foreach (var analyzer in CreateHotspotAnalyzers(language)) { var analyzerName = analyzer.GetType().Name; #if NETFRAMEWORK if (analyzerName is nameof(DisablingCsrfProtection) || analyzerName is nameof(PermissiveCors)) { continue; } #endif new VerifierBuilder() .AddPaths(@$"Hotspots\{TestCaseFileName(analyzerName)}{language.FileExtension}") .AddAnalyzer(() => analyzer) .WithOptions(parseOptions) .AddReferences(AdditionalReferences(analyzerName)) .WithConcurrentAnalysis(analyzerName is not nameof(ClearTextProtocolsAreSensitive)) .VerifyNoIssuesIgnoreErrors(); } } private static IEnumerable CreateHotspotAnalyzers(AnalyzerLanguage language) => new[] { typeof(CSharp.Metrics.CSharpMetrics), typeof(VisualBasic.Metrics.VisualBasicMetrics) } // Any type from those assemblies .SelectMany(x => x.Assembly.GetExportedTypes()) .Where(x => x.IsSubclassOf(typeof(DiagnosticAnalyzer)) && !typeof(UtilityAnalyzerBase).IsAssignableFrom(x) && x.GetCustomAttributes().Any()) .Where(x => typeof(SonarDiagnosticAnalyzer).IsAssignableFrom(x) && x.AnalyzerTargetLanguage() == language) // Avoid IRuleFactory and SE rules .Select(x => (SonarDiagnosticAnalyzer)Activator.CreateInstance(x)) .Where(IsSecurityHotspot); private static bool IsSecurityHotspot(DiagnosticAnalyzer analyzer) => analyzer.SupportedDiagnostics.Any(x => x.IsSecurityHotspot()); private static string TestCaseFileName(string analyzerName) => analyzerName switch { "ConfiguringLoggers" => "ConfiguringLoggers_Log4Net", "CookieShouldBeHttpOnly" => "CookieShouldBeHttpOnly_Nancy", "CookieShouldBeSecure" => "CookieShouldBeSecure_Nancy", "DeliveringDebugFeaturesInProduction" => "DeliveringDebugFeaturesInProduction.NetCore2", "DoNotHardcodeCredentials" => "DoNotHardcodeCredentials.DefaultValues", #if NETFRAMEWORK "ExecutingSqlQueries" => "ExecutingSqlQueries.Net46", "UsingCookies" => "UsingCookies_Net46", #else "DisablingCsrfProtection" => "DisablingCsrfProtection.Latest", "ExecutingSqlQueries" => "ExecutingSqlQueries.EntityFrameworkCoreLatest", "PermissiveCors" => "PermissiveCors.Latest", "UsingCookies" => "UsingCookies_NetCore", #endif _ => analyzerName }; private static IEnumerable AdditionalReferences(string analyzerName) => analyzerName switch { nameof(ClearTextProtocolsAreSensitive) => ClearTextProtocolsAreSensitiveTest.AdditionalReferences, nameof(CookieShouldBeHttpOnly) => CookieShouldBeHttpOnlyTest.AdditionalReferences, nameof(CookieShouldBeSecure) => CookieShouldBeSecureTest.AdditionalReferences, nameof(ConfiguringLoggers) => ConfiguringLoggersTest.Log4NetReferences, nameof(DeliveringDebugFeaturesInProduction) => DeliveringDebugFeaturesInProductionTest.AdditionalReferencesForAspNetCore2, nameof(DisablingRequestValidation) => NuGetMetadataReference.MicrosoftAspNetMvc(TestConstants.NuGetLatestVersion), nameof(DoNotHardcodeCredentials) => DoNotHardcodeCredentialsTest.AdditionalReferences, nameof(DoNotUseRandom) => MetadataReferenceFacade.SystemSecurityCryptography, nameof(ExpandingArchives) => ExpandingArchivesTest.AdditionalReferences, nameof(RequestsWithExcessiveLength) => RequestsWithExcessiveLengthTest.GetAdditionalReferences(), nameof(SpecifyTimeoutOnRegex) => MetadataReferenceFacade.RegularExpressions .Concat(NuGetMetadataReference.SystemComponentModelAnnotations()), #if NET nameof(DisablingCsrfProtection) => DisablingCsrfProtectionTest.AdditionalReferences(), nameof(ExecutingSqlQueries) => ExecutingSqlQueriesTest.ReferencesEntityFrameworkNetCore("7.0.14"), nameof(PermissiveCors) => PermissiveCorsTest.AdditionalReferences, #else nameof(ExecutingSqlQueries) => ExecutingSqlQueriesTest.ReferencesNet46(TestConstants.NuGetLatestVersion), #endif _ => MetadataReferenceFacade.SystemNetHttp .Concat(MetadataReferenceFacade.SystemDiagnosticsProcess) .Concat(MetadataReferenceFacade.SystemSecurityCryptography) }; } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/UnchangedFilesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Common; [TestClass] public class UnchangedFilesTest { public TestContext TestContext { get; set; } [TestMethod] [DataRow("ClassNotInstantiatable.cs", true)] [DataRow("SomeOtherFile.cs", false)] public void UnchangedFiles_SymbolBasedRule(string unchangedFileName, bool expectEmptyResults) { var builder = new VerifierBuilder().AddPaths("ClassNotInstantiatable.cs"); UnchangedFiles_Verify(builder, unchangedFileName, expectEmptyResults); } [TestMethod] [DataRow("AbstractTypesShouldNotHaveConstructors.cs", true)] [DataRow("SomeOtherFile.cs", false)] public void UnchangedFiles_SyntaxNodesBasedRule(string unchangedFileName, bool expectEmptyResults) { var builder = new VerifierBuilder().AddPaths("AbstractTypesShouldNotHaveConstructors.cs"); UnchangedFiles_Verify(builder, unchangedFileName, expectEmptyResults); } [TestMethod] [DataRow("FileLines20.cs", true)] [DataRow("SomeOtherFile.cs", false)] public void UnchangedFiles_SyntaxTreeBasedRule(string unchangedFileName, bool expectEmptyResults) { var builder = new VerifierBuilder().AddAnalyzer(() => new FileLines { Maximum = 10 }).AddPaths("FileLines20.cs").WithAutogenerateConcurrentFiles(false); UnchangedFiles_Verify(builder, unchangedFileName, expectEmptyResults); } [TestMethod] [DataRow(@"LooseFilePermissions.Windows.cs", true)] [DataRow("SomeOtherFile.cs", false)] public void UnchangedFiles_CompilationStartBasedRule(string unchangedFileName, bool expectEmptyResults) { var builder = new VerifierBuilder().AddAnalyzer(() => new LooseFilePermissions()).AddPaths(@"LooseFilePermissions.Windows.cs"); UnchangedFiles_Verify(builder, unchangedFileName, expectEmptyResults); } [TestMethod] [DataRow("UnusedPrivateMember.cs", true)] [DataRow("SomeOtherFile.cs", false)] public void UnchangedFiles_ReportDiagnosticIfNonGeneratedBasedRule(string unchangedFileName, bool expectEmptyResults) { var builder = new VerifierBuilder().AddPaths("UnusedPrivateMember.cs"); UnchangedFiles_Verify(builder, unchangedFileName, expectEmptyResults); } private void UnchangedFiles_Verify(VerifierBuilder builder, string unchangedFileName, bool expectEmptyResults) { builder = builder.WithConcurrentAnalysis(false).WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, unchangedFileName)); if (expectEmptyResults) { builder.VerifyNoIssuesIgnoreErrors(); } else { builder.Verify(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/UniqueQueueTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using SonarAnalyzer.CFG.Common; namespace SonarAnalyzer.Test.Common; [TestClass] public class UniqueQueueTest { [TestMethod] public void Enqueue_UniqueItems() { var sut = new UniqueQueue(); sut.Enqueue(42); sut.Dequeue().Should().Be(42); sut.Enqueue(42); sut.Dequeue().Should().Be(42, "second enqueue of dequeued item should not prevent it from being enqueued again"); } [TestMethod] public void Dequeue_UniqueItems() { var sut = new UniqueQueue(); sut.Enqueue(41); sut.Enqueue(42); sut.Enqueue(42); sut.Enqueue(43); sut.Enqueue(42); sut.Dequeue().Should().Be(41); sut.Enqueue(42); sut.Dequeue().Should().Be(42); sut.Dequeue().Should().Be(43); sut.Invoking(x => x.Dequeue()).Should().Throw(); } [TestMethod] public void GetEnumerator() { var sut = new UniqueQueue(); ((IEnumerable)sut).GetEnumerator().Should().NotBeNull(); // For coverage } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Common/XUnitVersions.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Common; public static class XUnitVersions { public const string Ver2 = "2.0.0"; public const string Ver253 = "2.5.3"; } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Extensions/ICompilationReportExtensionsTests.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.AnalysisContext; using ExtensionsCS = SonarAnalyzer.CSharp.Core.Extensions.ICompilationReportExtensions; using ExtensionsVB = SonarAnalyzer.VisualBasic.Core.Extensions.ICompilationReportExtensions; namespace SonarAnalyzer.Test.Extensions; [TestClass] public class ICompilationReportExtensionsTests { private static readonly DiagnosticDescriptor DummyMainDescriptor = AnalysisScaffolding.CreateDescriptorMain(); [TestMethod] [DataRow("// ", false)] [DataRow("// any random comment", true)] public void ReportIssue_SonarSymbolAnalysisContext_CS(string comment, bool expected) { var (tree, model) = TestCompiler.CompileCS($$""" {{comment}} public class Sample {} """); var wasReported = false; var symbolContext = new SymbolAnalysisContext(Substitute.For(), model.Compilation, AnalysisScaffolding.CreateOptions(), _ => wasReported = true, _ => true, default); var context = new SonarSymbolReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), symbolContext); ExtensionsCS.ReportIssue(context, DummyMainDescriptor, tree.GetRoot()); wasReported.Should().Be(expected); } [TestMethod] [DataRow("' ", false)] [DataRow("' any random comment", true)] public void ReportIssue_SonarSymbolAnalysisContext_VB(string comment, bool expected) { var (tree, model) = TestCompiler.CompileVB($""" {comment} Public Class Sample End Class """); var wasReported = false; var symbolContext = new SymbolAnalysisContext(Substitute.For(), model.Compilation, AnalysisScaffolding.CreateOptions(), _ => wasReported = true, _ => true, default); var context = new SonarSymbolReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), symbolContext); ExtensionsVB.ReportIssue(context, DummyMainDescriptor, tree.GetRoot()); wasReported.Should().Be(expected); wasReported = false; ExtensionsVB.ReportIssue(context, DummyMainDescriptor, tree.GetRoot().GetFirstToken()); wasReported.Should().Be(expected); wasReported = false; ExtensionsVB.ReportIssue(context, DummyMainDescriptor, tree.GetRoot().GetLocation()); wasReported.Should().Be(expected); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Extensions/StringExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CFG.Extensions; namespace SonarAnalyzer.Test.Extensions; [TestClass] public class StringExtensionsTest { [TestMethod] public void TestSplitCamelCaseToWords() { AssertSplitEquivalent("thisIsAName", "THIS", "IS", "A", "NAME"); AssertSplitEquivalent("thisIsSMTPName", "THIS", "IS", "SMTP", "NAME"); AssertSplitEquivalent("ThisIsIt", "THIS", "IS", "IT"); AssertSplitEquivalent("bin2hex", "BIN", "HEX"); AssertSplitEquivalent("HTML", "HTML"); AssertSplitEquivalent("SOME_VALUE", "SOME", "VALUE"); AssertSplitEquivalent("GR8day", "GR", "DAY"); AssertSplitEquivalent("ThisIsEpic", "THIS", "IS", "EPIC"); AssertSplitEquivalent("ThisIsEPIC", "THIS", "IS", "EPIC"); AssertSplitEquivalent("This_is_EPIC", "THIS", "IS", "EPIC"); AssertSplitEquivalent("PEHeader", "PE", "HEADER"); AssertSplitEquivalent("PE_Header", "PE", "HEADER"); AssertSplitEquivalent("BigB_smallc&GIANTD", "BIG", "B", "SMALLC", "GIANTD"); AssertSplitEquivalent("SMTPServer", "SMTP", "SERVER"); AssertSplitEquivalent("__url_foo", "URL", "FOO"); AssertSplitEquivalent(""); AssertSplitEquivalent(null); } private static void AssertSplitEquivalent(string name, params string[] words) => CollectionAssert.AreEquivalent(words, name.SplitCamelCaseToWords().ToList(), $" Value: {name}"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using FluentAssertions.Extensions; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.Core.Syntax.Extensions; using SonarAnalyzer.CSharp.Core.Syntax.Extensions; using SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; using ExtensionsCore = SonarAnalyzer.Core.Syntax.Extensions.SyntaxNodeExtensions; using ExtensionsShared = SonarAnalyzer.CSharp.Core.Syntax.Extensions.SyntaxNodeExtensionsShared; using MicrosoftExtensionsCS = Microsoft.CodeAnalysis.CSharp.Extensions.SyntaxNodeExtensions; using SyntaxCS = Microsoft.CodeAnalysis.CSharp.Syntax; using SyntaxTokenExtensions = SonarAnalyzer.CSharp.Core.Syntax.Extensions.SyntaxTokenExtensionsShared; using SyntaxVB = Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace SonarAnalyzer.Test.Extensions; [TestClass] public class SyntaxNodeExtensionsTest { private const string DefaultFileName = "Test.cs"; public TestContext TestContext { get; set; } [TestMethod] public void GetPreviousStatementsCurrentBlockOfNotAStatement() { const string code = "int x = 42;"; var tree = CSharpSyntaxTree.ParseText(code); var compilationUnit = tree.GetCompilationUnitRoot(); ExtensionsShared.GetPreviousStatementsCurrentBlock(compilationUnit).Should().BeEmpty(); } [TestMethod] public void GetPreviousStatementsCurrentBlockOfFirstStatement() { const string code = "public void M() { int x = 42; }"; var tree = CSharpSyntaxTree.ParseText(code); var aToken = GetFirstTokenOfKind(tree, SyntaxKind.NumericLiteralToken); var parent = SyntaxTokenExtensions.GetBindableParent(aToken); ExtensionsShared.GetPreviousStatementsCurrentBlock(parent).Should().BeEmpty(); } [TestMethod] public void GetPreviousStatementsCurrentBlockOfSecondStatement() { const string code = "public void M() { string s = null; int x = 42; }"; var tree = CSharpSyntaxTree.ParseText(code); var aToken = GetFirstTokenOfKind(tree, SyntaxKind.NumericLiteralToken); var parent = SyntaxTokenExtensions.GetBindableParent(aToken); ExtensionsShared.GetPreviousStatementsCurrentBlock(parent).Should().HaveCount(1); } [TestMethod] public void GetPreviousStatementsCurrentBlockRetrievesOnlyForCurrentBlock() { const string code = "public void M(string y) { string s = null; if (y != null) { int x = 42; } }"; var tree = CSharpSyntaxTree.ParseText(code); var aToken = GetFirstTokenOfKind(tree, SyntaxKind.NumericLiteralToken); var parent = SyntaxTokenExtensions.GetBindableParent(aToken); ExtensionsShared.GetPreviousStatementsCurrentBlock(parent).Should().BeEmpty(); } [TestMethod] public void ContainsGetOrSetOnDependencyProperty_SymbolIsNull_ReturnsFalse() { const string code = """ class Repro { public object Field { get => GetValue(42); // CS0103: GetValue does not exist in this context, Symbol is null set { } } } """; var (tree, model) = TestCompiler.CompileIgnoreErrorsCS(code); var accessor = tree.GetRoot(TestContext.CancellationToken).DescendantNodes().OfType().First(x => x.IsKind(SyntaxKind.GetAccessorDeclaration)); ExtensionsShared.ContainsGetOrSetOnDependencyProperty(accessor, model.Compilation).Should().BeFalse(); } [TestMethod] public void ContainsGetOrSetOnDependencyProperty_NotOnDependencyObject_ReturnsFalse() { // GetValue is a local delegate variable: Symbol resolves to the delegate's Invoke method, // ContainingType is the delegate type (non-null), but does not derive from DependencyObject. const string code = """ class Repro { public object Field { get { var GetValue = () => new object(); return GetValue(); } set { } } } """; var (tree, model) = TestCompiler.CompileCS(code); var accessor = tree.GetRoot(TestContext.CancellationToken).DescendantNodes().OfType().First(x => x.IsKind(SyntaxKind.GetAccessorDeclaration)); ExtensionsShared.ContainsGetOrSetOnDependencyProperty(accessor, model.Compilation).Should().BeFalse(); } [TestMethod] public void ArrowExpressionBody_WithNotExpectedNode_ReturnsNull() { const string code = "var x = 1;"; var tree = CSharpSyntaxTree.ParseText(code); SyntaxNodeExtensionsCSharp.ArrowExpressionBody(tree.GetRoot()).Should().BeNull(); } [TestMethod] public void GetDeclarationTypeName_UnknownType() => #if DEBUG Assert.Throws(() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.Block()), "Unexpected type Block\r\nParameter name: kind"); #else SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.Block()).Should().Be("type"); #endif [TestMethod] public void GetDeclarationTypeName_Class() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.ClassDeclaration("MyClass")).Should().Be("class"); [TestMethod] public void GetDeclarationTypeName_Constructor() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.ConstructorDeclaration("MyConstructor")).Should().Be("constructor"); [TestMethod] public void GetDeclarationTypeName_Delegate() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.DelegateDeclaration(SyntaxFactory.ParseTypeName("void"), "MyDelegate")).Should().Be("delegate"); [TestMethod] public void GetDeclarationTypeName_Destructor() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.DestructorDeclaration("~")).Should().Be("destructor"); [TestMethod] public void GetDeclarationTypeName_Enum() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.EnumDeclaration("MyEnum")).Should().Be("enum"); [TestMethod] public void GetDeclarationTypeName_EnumMember() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.EnumMemberDeclaration("EnumValue1")).Should().Be("enum"); [TestMethod] public void GetDeclarationTypeName_Event() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.EventDeclaration(SyntaxFactory.ParseTypeName("void"), "MyEvent")).Should().Be("event"); [TestMethod] public void GetDeclarationTypeName_EventField() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.EventFieldDeclaration(SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("MyEvent")))).Should().Be("event"); [TestMethod] public void GetDeclarationTypeName_Field() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("int")))).Should().Be("field"); [TestMethod] public void GetDeclarationTypeName_Indexer() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.IndexerDeclaration(SyntaxFactory.ParseTypeName("int"))).Should().Be("indexer"); [TestMethod] public void GetDeclarationTypeName_Interface() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.InterfaceDeclaration("MyInterface")).Should().Be("interface"); [TestMethod] public void GetDeclarationTypeName_LocalFunction() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.LocalFunctionStatement(SyntaxFactory.ParseTypeName("void"), "MyLocalFunction")).Should().Be("local function"); [TestMethod] public void GetDeclarationTypeName_Method() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("void"), "MyMethod")).Should().Be("method"); [TestMethod] public void GetDeclarationTypeName_Property() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("void"), "MyProperty")).Should().Be("property"); [TestMethod] public void GetDeclarationTypeName_Record() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.RecordDeclaration(SyntaxFactory.Token(SyntaxKind.RecordKeyword), "MyRecord")).Should().Be("record"); [TestMethod] public void GetDeclarationTypeName_RecordStruct() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.RecordDeclaration(SyntaxKind.RecordStructDeclaration, SyntaxFactory.Token(SyntaxKind.RecordKeyword), "MyRecord")) .Should().Be("record struct"); [TestMethod] public void GetDeclarationTypeName_Struct() => SyntaxNodeExtensionsCSharp.GetDeclarationTypeName(SyntaxFactory.StructDeclaration("MyStruct")).Should().Be("struct"); [TestMethod] public void CreateCfg_MethodDeclaration_ReturnsCfg_CS() { const string code = """ public class Sample { public void Main() { var x = 42; } } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_PropertyDeclaration_ReturnsCfg_CS() { const string code = """ public class Sample { public int Property => 42; } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_PropertyDeclarationWithoutExpressionBody_ReturnsNull_CS() { const string code = """ public class Sample { public int Property {get; set;} } """; CreateCfgCS(code).Should().BeNull(); } [TestMethod] public void CreateCfg_IndexerDeclaration_ReturnsCfg_CS() { const string code = """ public class Sample { private string field; public string this[int index] => field = null; } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_FieldInitializerWithoutOperation_ReturnsCfg_CS() { const string code = """ public class Sample { private string field = null!; // null! itself doens't have operation, and we can still generate CFG for it from the equals clause } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_MethodBlock_ReturnsCfg_VB() { const string code = """ Public Class Sample Public Sub Main() Dim X As Integer = 42 End Sub End Class """; CreateCfgVB(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_AnyNode_ReturnsCfg_CS() { const string code = """ public class Sample { public void Main() { Main(); } } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_AnyNode_ReturnsCfg_VB() { const string code = """ Public Class Sample Public Sub Main() Main() End Sub End Class """; CreateCfgVB(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_LambdaInsideQuery_CS() { const string code = """ using System; using System.Linq; public class Sample { public void Main(int[] values) { var result = from value in values select new Lazy(() => value); } } """; CreateCfgCS(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_LambdaInsideQuery_VB() { const string code = """ Public Class Sample Public Sub Main(Values() As Integer) Dim Result As IEnumerable(Of Lazy(Of Integer)) = From Value In Values Select New Lazy(Of Integer)(Function() Value) End Sub End Class """; CreateCfgVB(code).Should().NotBeNull(); } [TestMethod] public void CreateCfg_NestingChain_CS() { const string code = """ using System; using System.Linq; public class Sample { public void Main(int[] values) { OuterLocalFunction(); void OuterLocalFunction() { Action outerParenthesizedLambda = (a, b) => { MiddleLocalFunction(42); void MiddleLocalFunction(int c) { var queryExpressionInTheWay = from value in values select new Lazy(() => { return InnerLocalFunction(value); static int InnerLocalFunction(int arg) { Func innerLambda = xxx => xxx; return innerLambda(arg); } }); } }; } } } """; var (tree, semanticModel) = TestCompiler.CompileCS(code); var innerLambda = tree.Single(); innerLambda.Parent.Parent.Should().BeOfType().Subject.Identifier.ValueText.Should().Be("innerLambda"); var cfg = SyntaxNodeExtensionsCSharp.CreateCfg(innerLambda, semanticModel, default); cfg.Should().NotBeNull("It's innerLambda"); cfg.Parent.Should().NotBeNull("It's InnerLocalFunction"); cfg.Parent.Parent.Should().NotBeNull("Lambda iniside Lazy constructor"); cfg.Parent.Parent.Parent.Should().NotBeNull("Anonymous function for query expression"); cfg.Parent.Parent.Parent.Parent.Should().NotBeNull("It's MiddleLocalFunction"); cfg.Parent.Parent.Parent.Parent.Parent.Should().NotBeNull("It's outerParenthesizedLambda"); cfg.Parent.Parent.Parent.Parent.Parent.Parent.Should().NotBeNull("It's OuterLocalFunction"); cfg.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Should().NotBeNull("It's the root CFG"); cfg.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Should().BeNull("Root CFG should not have Parent"); cfg.OriginalOperation.Should().BeAssignableTo().Subject.Symbol.Parameters.Should().HaveCount(1).And.Contain(x => x.Name == "xxx"); } [TestMethod] public void CreateCfg_NestingChain_VB() { const string code = """ Public Class Sample Public Sub Main(Values() As Integer) Dim OuterMultiLineSub As Action = Sub() Dim OuterSingleLineFunc As Func(Of Func(Of Integer, Integer)) = Function() Function(NestedMultiLineFuncArg As Integer) Dim Lst = From Value In Values Select New Lazy(Of Integer)(Function() Dim InnerSingleLineSub = Sub(xxx As Integer) Value.ToString() End Function) End Function End Sub End Sub End Class """; var (tree, semanticModel) = TestCompiler.CompileVB(code); var innerSub = tree.Single().FirstAncestorOrSelf(); innerSub.Parent.Parent.Should().BeOfType().Subject.Names.Single().Identifier.ValueText.Should().Be("InnerSingleLineSub"); var cfg = SyntaxNodeExtensionsVisualBasic.CreateCfg(innerSub, semanticModel, default); cfg.Should().NotBeNull("It's InnerSingleLineSub"); cfg.Parent.Should().NotBeNull("It's multiline function inside Lazy(Of Integer)"); cfg.Parent.Parent.Should().NotBeNull("Lambda iniside Lazy constructor"); cfg.Parent.Parent.Parent.Should().NotBeNull("Anonymous function for query expression"); cfg.Parent.Parent.Parent.Parent.Should().NotBeNull("It's OuterSingleLineFunc"); cfg.Parent.Parent.Parent.Parent.Parent.Should().NotBeNull("It's OuterMultiLineSub"); cfg.Parent.Parent.Parent.Parent.Parent.Parent.Should().NotBeNull("It's the root CFG"); cfg.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Should().BeNull("Root CFG should not have Parent"); cfg.OriginalOperation.Should().BeAssignableTo().Subject.Symbol.Parameters.Should().HaveCount(1).And.Contain(x => x.Name == "xxx"); } [TestMethod] public void CreateCfg_UndefinedSymbol_ReturnsCfg_CS() { const string code = """ public class Sample { public void Main() { Undefined(() => 45); } } """; var (tree, model) = TestCompiler.CompileIgnoreErrorsCS(code); var lambda = tree.Single(); SyntaxNodeExtensionsCSharp.CreateCfg(lambda, model, default).Should().NotBeNull(); } [TestMethod] public void CreateCfg_UndefinedSymbol_ReturnsNull_VB() { const string code = """ Public Class Sample Public Sub Main() Undefined(Function() 42) End Sub End Class """; var (tree, model) = TestCompiler.CompileIgnoreErrorsVB(code); var lambda = tree.Single(); SyntaxNodeExtensionsVisualBasic.CreateCfg(lambda, model, default).Should().BeNull(); } [TestMethod] [DataRow(@"() =>")] [DataRow(@"} () => }")] [DataRow(@"{ () => .")] [DataRow(@"{ () => => =>")] public void CreateCfg_InvalidSyntax_ReturnsCfg_CS(string code) { var (tree, model) = TestCompiler.CompileIgnoreErrorsCS(code); var lambda = tree.Single(); SyntaxNodeExtensionsCSharp.CreateCfg(lambda, model, default).Should().NotBeNull(); } [TestMethod] public void CreateCfg_Performance_UsesCache_CS() { const string code = """ using System; public class Sample { public void Main(string noiseToHaveMoreOperations) { noiseToHaveMoreOperations.ToString(); noiseToHaveMoreOperations.ToString(); noiseToHaveMoreOperations.ToString(); void LocalFunction() { noiseToHaveMoreOperations.ToString(); noiseToHaveMoreOperations.ToString(); noiseToHaveMoreOperations.ToString(); Action a = () => 42.ToString(); } } } """; var (tree, model) = TestCompiler.CompileCS(code); var lambda = tree.Single(); Action a = () => { for (var i = 0; i < 10000; i++) { SyntaxNodeExtensionsCSharp.CreateCfg(lambda, model, default); } }; a.ExecutionTime().Should().BeLessThan(1500.Milliseconds()); // Takes roughly 0.2 sec on CI } [TestMethod] public void CreateCfg_Performance_UsesCache_VB() { const string code = """ Public Class Sample Public Sub Main(NoiseToHaveMoreOperations As String) NoiseToHaveMoreOperations.ToString() NoiseToHaveMoreOperations.ToString() NoiseToHaveMoreOperations.ToString() Dim Outer As Action = Sub() NoiseToHaveMoreOperations.ToString() NoiseToHaveMoreOperations.ToString() NoiseToHaveMoreOperations.ToString() Dim Inner As Action = Sub() NoiseToHaveMoreOperations.ToString() End Sub End Sub End Class """; var (tree, model) = TestCompiler.CompileVB(code); var lambda = tree.Single(); Action a = () => { for (var i = 0; i < 10000; i++) { SyntaxNodeExtensionsCSharp.CreateCfg(lambda, model, default); } }; a.ExecutionTime().Should().BeLessThan(1.Seconds()); // Takes roughly 0.4 sec on CI } [TestMethod] public void CreateCfg_SameNode_DifferentCompilation_DoesNotCache() { // https://github.com/SonarSource/sonar-dotnet/issues/5491 const string code = """ public class Sample { private void Method() { } } """; var compilation1 = TestCompiler.CompileCS(code).Model.Compilation; var compilation2 = compilation1.WithAssemblyName("Different-Compilation-Reusing-Same-Nodes"); var method1 = compilation1.SyntaxTrees.Single().Single(); var method2 = compilation2.SyntaxTrees.Single().Single(); var cfg1 = SyntaxNodeExtensionsCSharp.CreateCfg(method1, compilation1.GetSemanticModel(method1.SyntaxTree), default); var cfg2 = SyntaxNodeExtensionsCSharp.CreateCfg(method2, compilation2.GetSemanticModel(method2.SyntaxTree), default); ReferenceEquals(cfg1, cfg2).Should().BeFalse("Different compilations should not reuse cache. They do not share semantic model and symbols."); } [TestMethod] // Tuple. From right to left. [DataRow("(var a, (x, var b)) = (0, ($$x++, 1));", "x")] [DataRow("(var a, (x, var b)) = (0, $$(1, 2));", "(x, var b)")] [DataRow("(var a, (var b, var c, var d), var e) = (0, (1, 2, $$3), 4);", "var d")] // Tuple. From left to right [DataRow("(var a, (x, $$var b)) = (0, (x++, 1));", "1")] [DataRow("(var a, (var b, var c, $$var d), var e) = (0, (1, 2, 3), 4);", "3")] [DataRow("(var a, $$(var b, var c)) = (0, (1, 2));", "(1, 2)")] // Designation. From right to left. [DataRow("var (a, (b, c)) = (0, (1, $$2));", "c")] [DataRow("var (a, (b, c)) = (0, $$(1, 2));", "(b, c)")] [DataRow("var (a, (b, _)) = (0, (1, $$2));", "_")] [DataRow("var (a, _) = (0, ($$1, 2));", null)] [DataRow("var (a, _) = (0, $$(1, 2));", "_")] [DataRow("var _ = ($$0, (1, 2));", null)] [DataRow("_ = ($$0, (1, 2));", null)] // Designation. From left to right [DataRow("var (a, (b, $$c)) = (0, (1, 2));", "2")] [DataRow("var (a, $$(b, c)) = (0, (1, 2));", "(1, 2)")] [DataRow("var (a, (b, $$_)) = (0, (1, 2));", "2")] [DataRow("var (a, $$_) = (0, (1, 2));", "(1, 2)")] // Unaligned tuples. From left to right. [DataRow("(var a, var b) = ($$0, 1, 2);", null)] [DataRow("(var a, var b) = (0, 1, $$2);", null)] [DataRow("(var a, var b) = (0, (1, $$2));", null)] [DataRow("(var a, (var b, var c)) = (0, $$1);", "(var b, var c)")] // Syntacticly correct // Unaligned tuples. From right to left. [DataRow("(var a, var b, $$var c) = (0, (1, 2));", null)] [DataRow("(var a, (var b, $$var c)) = (0, 1, 2);", null)] // Unaligned designation. From right to left. [DataRow("var (a, (b, c)) = (0, (1, $$2, 3));", null)] [DataRow("var (a, (b, c)) = (0, (1, ($$2, 3)));", null)] [DataRow("var (a, (b, c, d)) = (0, (1, $$2));", null)] // Unaligned designation. From left to right . [DataRow("var (a, (b, $$c)) = (0, (1, 2, 3));", null)] [DataRow("var (a, (b, ($$c, d))) = (0, (1, 2));", null)] [DataRow("var (a, (b, $$c, d)) = (0, (1, 2));", null)] // Mixed. From right to left. [DataRow("(var a, var (b, c)) = (1, ($$2, 3));", "b")] [DataRow("(var a, var (b, (c, (d, e)))) = (1, (2, (3, (4, $$5))));", "e")] // Mixed. From left to right. [DataRow("(var a, var ($$b, c) )= (1, (2, 3));", "2")] [DataRow("(var a, var (b, (c, (d, $$e)))) = (1, (2, (3, (4, 5))));", "5")] [DataRow("(var $$a, var (b, c))= (a: 1, (b: (byte)2, 3));", "1")] [DataRow("(var a, $$var (b, c))= (a: 1, (b: (byte)2, 3));", "(b: (byte)2, 3)")] [DataRow("(var a, var ($$b, c))= (a: 1, (b: (byte)2, 3));", "(byte)2")] [DataRow("(var a, var (b, $$c))= (a: 1, (b: (byte)2, 3));", "3")] // Assignment to tuple variable. [DataRow("(int, int) t; t = (1, $$2);", null)] [DataRow("(int, int) t; (var a, t) = (1, ($$2, 3));", null)] [DataRow("(int, int) t; (var a, t) = (1, $$(2, 3));", "t")] // Start node is right side of assignment [DataRow("var (a, b) = $$(1, 2);", "var (a, b)")] [DataRow("var t = $$(1, 2);", null)] // Not an assignment [DataRow("(int, int) t; t = $$(1, 2);", "t")] [DataRow("(int, int) t; t = ($$1, 2);", null)] [DataRow("int a; a = $$1;", "a")] // Start node is left side of assignment [DataRow("var (a, b)$$ = (1, 2);", "(1, 2)")] [DataRow("$$var t = (1, 2);", null)] // Not an assignment [DataRow("(int, int) t; $$t = (1, 2);", "(1, 2)")] [DataRow("int a; $$a = 1;", "1")] public void FindAssignmentComplement_Tests(string code, string expectedNode) { code = $@" public class C {{ public void M() {{ var x = 0; {code} }} }}"; var nodePosition = code.IndexOf("$$"); code = code.Replace("$$", string.Empty); var tree = CSharpSyntaxTree.ParseText(code); var argument = tree.GetRoot().FindNode(new TextSpan(nodePosition, 0)); argument = argument.AncestorsAndSelf() .FirstOrDefault(x => x?.Kind() is SyntaxKind.Argument or SyntaxKindEx.DiscardDesignation or SyntaxKindEx.SingleVariableDesignation or SyntaxKindEx.ParenthesizedVariableDesignation or SyntaxKindEx.TupleExpression) ?? argument; tree.GetDiagnostics().Should().BeEmpty(); var target = SyntaxNodeExtensionsCSharp.FindAssignmentComplement(argument); if (expectedNode is null) { target.Should().BeNull(); } else { target.Should().NotBeNull(); target.ToString().Should().Be(expectedNode); } } [TestMethod] public void IsInExpressionTree_CS() { const string code = @" using System.Linq; public class Sample { public void Main(int[] arr) { var withNormalLambda = arr.Where(xNormal => xNormal == 42).OrderBy((xNormal) => xNormal).Select(xNormal => xNormal.ToString()); var withNormal = from xNormal in arr where xNormal == 42 orderby xNormal select xNormal.ToString(); var withExpressionLambda = arr.AsQueryable().Where(xExpres => xExpres == 42).OrderBy((xExpres) => xExpres).Select(xExpres => xExpres.ToString()); var withExpression = from xExpres in arr.AsQueryable() where xExpres == 42 orderby xExpres select xExpres.ToString(); } }"; var (tree, model) = TestCompiler.CompileCS(code); var allIdentifiers = tree.GetRoot().DescendantNodes().OfType().ToArray(); allIdentifiers.Where(x => x.Identifier.ValueText == "xNormal").Should().HaveCount(6).And.OnlyContain(x => !SyntaxNodeExtensionsCSharp.IsInExpressionTree(x, model)); allIdentifiers.Where(x => x.Identifier.ValueText == "xExpres").Should().HaveCount(6).And.OnlyContain(x => SyntaxNodeExtensionsCSharp.IsInExpressionTree(x, model)); } [TestMethod] public void IsInExpressionTree_VB() { const string code = @" Public Class Sample Public Sub Main(Arr() As Integer) Dim WithNormalLambda = Arr.Where(Function(xNormal) xNormal = 42).OrderBy(Function(xNormal) xNormal).Select(Function(xNormal) xNormal.ToString()) Dim WithNormal = From xNormal In Arr Where xNormal = 42 Order By xNormal Select Result = xNormal.ToString() Dim WithExpressionLambda = Arr.AsQueryable().Where(Function(xExpres) xExpres = 42).OrderBy(Function(xExpres) xExpres).Select(Function(xExpres) xExpres.ToString()) Dim WithExpression = From xExpres In Arr.AsQueryable() Where xExpres = 42 Order By xExpres Select Result = xExpres.ToString() End Sub End Class"; var (tree, model) = TestCompiler.CompileVB(code); var allIdentifiers = tree.GetRoot().DescendantNodes().OfType().ToArray(); allIdentifiers.Where(x => x.Identifier.ValueText == "xNormal").Should().HaveCount(6).And.OnlyContain(x => !SyntaxNodeExtensionsVisualBasic.IsInExpressionTree(x, model)); allIdentifiers.Where(x => x.Identifier.ValueText == "xExpres").Should().HaveCount(6).And.OnlyContain(x => SyntaxNodeExtensionsVisualBasic.IsInExpressionTree(x, model)); } [TestMethod] [DataRow("A?.$$M()", "A?.M()", "A")] [DataRow("A?.B?.$$M()", ".B?.M()", ".B")] [DataRow("A?.M()?.$$B", ".M()?.B", ".M()")] [DataRow("A?.$$M()?.B", "A?.M()?.B", "A")] [DataRow("A[0]?.M()?.$$B", ".M()?.B", ".M()")] [DataRow("A[0]?.M().B?.$$C", ".M().B?.C", ".M().B")] [DataRow("A[0]?.$$M().B?.C", "A[0]?.M().B?.C", "A[0]")] [DataRow("A?.$$B.C", "A?.B.C", "A")] [DataRow("A?.$$B?.C", "A?.B?.C", "A")] public void GetParentConditionalAccessExpression_CS(string expression, string parent, string parentExpression) { var code = $$""" public class X { public X A { get; } public X B { get; } public X C { get; } public X this[int i] => null; public X M() { var _ = {{expression}}; return null; } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var parentConditional = MicrosoftExtensionsCS.GetParentConditionalAccessExpression(node); parentConditional.ToString().Should().Be(parent); parentConditional.Expression.ToString().Should().Be(parentExpression); } [TestMethod] [DataRow("A?.$$M()", "A?.M()", "A")] [DataRow("A?.B?.$$M()", ".B?.M()", ".B")] [DataRow("A?.M()?.$$B", ".M()?.B", ".M()")] [DataRow("A?.$$M()?.B", "A?.M()?.B", "A")] [DataRow("A(0)?.M()?.$$B", ".M()?.B", ".M()")] [DataRow("A(0)?.M().B?.$$C", ".M().B?.C", ".M().B")] [DataRow("A(0)?.$$M().B?.C", "A(0)?.M().B?.C", "A(0)")] [DataRow("A?.$$B.C", "A?.B.C", "A")] [DataRow("A?.$$B?.C", "A?.B?.C", "A")] public void GetParentConditionalAccessExpression_VB(string expression, string parent, string parentExpression) { var code = $$""" Public Class X Public ReadOnly Property A As X Public ReadOnly Property B As X Public ReadOnly Property C As X Default Public ReadOnly Property Item(i As Integer) As X Get Return Nothing End Get End Property Public Function M() As X Dim __ = {{expression}} Return Nothing End Function End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code).Node; var parentConditional = SyntaxNodeExtensionsVisualBasic.GetParentConditionalAccessExpression(node); parentConditional.ToString().Should().Be(parent); parentConditional.Expression.ToString().Should().Be(parentExpression); } [TestMethod] [DataRow("A?.$$M()")] [DataRow("A?.B?.$$M()")] [DataRow("A?.M()?.$$B")] [DataRow("A?.$$M()?.B")] [DataRow("A[0]?.M()?.$$B")] [DataRow("A[0]?.M().B?.$$C")] [DataRow("A?.$$B.C")] [DataRow("A?.$$B?.C")] public void GetRootConditionalAccessExpression_CS(string expression) { var code = $$""" public class X { public X A { get; } public X B { get; } public X C { get; } public X this[int i] => null; public X M() { var _ = {{expression}}; return null; } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var parentConditional = MicrosoftExtensionsCS.GetRootConditionalAccessExpression(node); parentConditional.ToString().Should().Be(expression.Replace("$$", string.Empty)); } [TestMethod] public void Kind_Null_ReturnsNone() => ExtensionsCore.Kind(null).Should().Be(SyntaxKind.None); [TestMethod] [DataRow("class Test { }", DisplayName = "When there is no pragma, return default file name.")] [DataRow("#pragma checksum \"FileName.txt\" \"{guid}\" \"checksum bytes\"", "FileName.txt", DisplayName = "When pragma is present, return file name from pragma.")] public void GetMappedFilePath(string code, string expectedFileName = DefaultFileName) { var tree = GetSyntaxTree(code, DefaultFileName); tree.GetRoot().GetMappedFilePathFromRoot().Should().Be(expectedFileName); } [TestMethod] [DataRow("$$M(1)$$;")] [DataRow("_ = $$new C(1)$$;")] [DataRow("C c = $$new(1)$$;")] public void ArgumentList_CS_InvocationObjectCreation(string statement) { var code = $$""" public class C(int p) { public void M(int p) { {{statement}} } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var argumentList = SyntaxNodeExtensionsCSharp.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); } [TestMethod] [DataRow("base")] [DataRow("this")] public void ArgumentList_CS_ConstructorInitializer(string keyword) { var code = $$""" public class Base(int p); public class C: Base { public C(): $${{keyword}}(1)$$ { } public C(int p): base(p) { } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var argumentList = SyntaxNodeExtensionsCSharp.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); } [TestMethod] public void ArgumentList_CS_PrimaryConstructorBaseType() { var code = """ public class Base(int p); public class Derived(int p): $$Base(1)$$; """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var argumentList = SyntaxNodeExtensionsCSharp.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); } [TestMethod] [DataRow("_ = $$new System.Collections.Generic.List { 0 }$$;")] public void ArgumentList_CS_NoList(string statement) { var code = $$""" public class C { public void M() { {{statement}} } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; SyntaxNodeExtensionsCSharp.ArgumentList(node).Should().BeNull(); } [TestMethod] public void ArgumentList_CS_Null() => SyntaxNodeExtensionsCSharp.ArgumentList(null).Should().BeNull(); [TestMethod] [DataRow("_ = $$new int[] { 1 }$$;")] [DataRow("_ = $$new { A = 1 }$$;")] public void ArgumentList_CS_UnsupportedNodeKinds(string statement) { var code = $$""" public class C { public void M() { {{statement}} } } """; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var sut = () => SyntaxNodeExtensionsCSharp.ArgumentList(node); sut.Should().Throw(); } [TestMethod] [DataRow("$$M(1)$$")] [DataRow("Call $$M(1)$$")] [DataRow("Dim c = $$New C(1)$$")] [DataRow("$$RaiseEvent SomeEvent(1)$$")] public void ArgumentList_VB_Invocations(string statement) { var code = $$""" Imports System Public Class C Public Event SomeEvent As Action(Of Integer) Public Sub New(p As Integer) End Sub Public Sub M(p As Integer) Dim s As String = "Test" {{statement}} End Sub End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: true).Node; var argumentList = SyntaxNodeExtensionsVisualBasic.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); } [TestMethod] public void ArgumentList_VB_Mid() { var code = $$""" Public Class C Public Sub M() Dim s As String = "Test" $$Mid(s, 1)$$ = "Test" End Sub End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: true).Node; var argumentList = SyntaxNodeExtensionsVisualBasic.ArgumentList(node); argumentList.Arguments.Should().SatisfyRespectively( x => (x.GetExpression() is SyntaxVB.IdentifierNameSyntax { Identifier.ValueText: "s" }).Should().BeTrue(), x => (x.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue()); } [TestMethod] public void ArgumentList_VB_Attribute() { var code = """ <$$System.Obsolete("1")$$> Public Class C End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: true).Node; var argumentList = SyntaxNodeExtensionsVisualBasic.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); } [TestMethod] [DataRow("Dim $$i(1)$$ As Integer")] [DataRow("Dim sales()() As Double = $$New Double(1)() { }$$")] [DataRow("ReDim $$arr(1)$$")] public void ArgumentList_VB_ArrayBounds(string statement) { var code = $$""" Public Class C Public Sub M() Dim arr(0) As Integer {{statement}} End Sub End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: true).Node; var argumentList = SyntaxNodeExtensionsVisualBasic.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); } [TestMethod] public void ArgumentList_VB_Call() { var code = $$""" Public Class C Public Sub M() Call $$M$$ End Sub End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: false).Node; SyntaxNodeExtensionsVisualBasic.ArgumentList(node).Should().BeNull(); } [TestMethod] public void ArgumentList_VB_Null() => SyntaxNodeExtensionsVisualBasic.ArgumentList(null).Should().BeNull(); [TestMethod] [DataRow("$$Dim a = 1$$")] public void ArgumentList_VB_UnsupportedNodeKinds(string statement) { var code = $$""" Public Class C Public Sub M() {{statement}} End Sub End Class """; var node = TestCompiler.NodeBetweenMarkersVB(code, getInnermostNodeForTie: true).Node; var sut = () => SyntaxNodeExtensionsVisualBasic.ArgumentList(node); sut.Should().Throw(); } [TestMethod] [DataRow("""public C(int p) { }""")] [DataRow("""public void M(int p) { }""")] [DataRow("""public static C operator + (C p) => default;""")] [DataRow("""public static implicit operator C (int p) => default;""")] [DataRow("""public delegate void M(int p);""")] public void ParameterList_Methods(string declarations) { var node = TestCompiler.NodeBetweenMarkersCS($$""" public class C { $${{declarations}}$$ } """).Node; var actual = SyntaxNodeExtensionsCSharp.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; entry.Identifier.ValueText.Should().Be("p"); } [TestMethod] [DataRow("""$$void Local(int p) { }$$""")] [DataRow("""$$static void Local(int p) { }$$""")] [DataRow("""Func f = $$(int p) => 0$$;""")] [DataRow("""Func f = $$delegate (int p) { return 0; }$$;""")] public void ParameterList_NestedMethods(string declarations) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; public class C { public void M() { {{declarations}} } } """).Node; var actual = SyntaxNodeExtensionsCSharp.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; entry.Identifier.ValueText.Should().Be("p"); } [TestMethod] public void ParameterList_Destructor() { var node = TestCompiler.NodeBetweenMarkersCS(""" public class C { $$~C() { }$$ } """).Node; var actual = SyntaxNodeExtensionsCSharp.ParameterList(node); actual.Should().NotBeNull(); actual.Parameters.Should().BeEmpty(); } [TestMethod] [DataRow("class")] [DataRow("struct")] [DataRow("record struct")] [DataRow("readonly struct")] #if NET [DataRow("readonly record struct")] [DataRow("record")] [DataRow("record class")] #endif public void ParameterList_PrimaryConstructors(string type) { var node = TestCompiler.NodeBetweenMarkersCS($$""" $$public {{type}} C(int p) { }$$ """).Node; var actual = SyntaxNodeExtensionsCSharp.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; entry.Identifier.ValueText.Should().Be("p"); } [TestMethod] [DataRow("$$int i;$$")] [DataRow("$$class Nested { }$$")] public void ParameterList_ReturnsNull(string declaration) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System.Collections.Generic; public class C { {{declaration}} } """).Node; var actual = SyntaxNodeExtensionsCSharp.ParameterList(node); actual.Should().BeNull(); } [TestMethod] [DataRow("""$$global::System$$.Int32 i;""", "System")] // AliasQualifiedNameSyntax [DataRow("""$$global::System.Int32$$ i;""", "Int32")] // QualifiedNameSyntax (with AliasQualifiedNameSyntax in Left) [DataRow("""$$global::System.Collections.Generic.List$$ l;""", "List")] // QualifiedNameSyntax.Right -> GenericNameSyntax [DataRow("""int i = Math.Abs($$1$$);""", null)] // ArgumentSyntax [DataRow("""int i = Math.Abs($$value: 1$$);""", "value")] // ArgumentSyntax [DataRow("""$$int[]$$ i;""", "int")] // ArrayTypeSyntax [DataRow("""$$int[][]$$ i;""", "int")] // ArrayTypeSyntax [DataRow("""[DebuggerDisplay($$""$$)]int i;""", null)] // AttributeArgumentSyntax [DataRow("""[DebuggerDisplay($$value: ""$$)]int i;""", "value")] // AttributeArgumentSyntax [DataRow("""[DebuggerDisplay("", $$Name = ""$$)]int i;""", "Name")] // AttributeArgumentSyntax [DataRow("""[$$DebuggerDisplay("")$$]int i;""", "DebuggerDisplay")] // AttributeSyntax [DataRow("""class $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""struct $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""interface $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""record $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""record class $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""record struct $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""enum $$T$$ { }""", "T")] // BaseTypeDeclarationSyntax [DataRow("""void M() { try { } catch $$(Exception e)$$ { } }""", "e")] // CatchDeclarationSyntax [DataRow("""void M(string s) { var x = $$s?.Length$$; }""", "Length")] // ConditionalAccessExpressionSyntax [DataRow("""void M(string s) { $$s?.ToLower()?.ToUpper()$$; }""", "ToUpper")] // ConditionalAccessExpressionSyntax [DataRow("""void M(string s) { $$s.ToLower()?.ToUpper()$$; }""", "ToUpper")] // ConditionalAccessExpressionSyntax + MemberAccessExpressionSyntax [DataRow("""void M(string s) { $$s?.ToLower().ToUpper()$$; }""", "ToUpper")] // ConditionalAccessExpressionSyntax + MemberAccessExpressionSyntax [DataRow("""void M(string s) { $$s?.ToLower().ToUpper()?.PadLeft(42).Normalize()$$; }""", "Normalize")] // ConditionalAccessExpressionSyntax + MemberAccessExpressionSyntax [DataRow("""void M(string s) { $$s.ToLower().ToUpper().PadLeft(42).Normalize()$$; }""", "Normalize")] // MemberAccessExpressionSyntax [DataRow("""void M(string s) { $$s.ToLower().ToUpper()$$.PadLeft(42).Normalize(); }""", "ToUpper")] // MemberAccessExpressionSyntax [DataRow("""void M(string s) { $$s.ToLower().ToUpper()$$?.PadLeft(42).Normalize(); }""", "ToUpper")] // ConditionalAccessExpressionSyntax + MemberAccessExpressionSyntax [DataRow("""void M(string s) { s.ToLower()?.$$ToUpper()?.PadLeft(42).Normalize()$$; }""", "Normalize")] // MemberAccessExpressionSyntax [DataRow("""$$Test() { }$$""", "Test")] // ConstructorDeclarationSyntax [DataRow("""Test() : $$this(1)$$ { }""", "this")] // ConstructorInitializerSyntax [DataRow("""Test() : $$base()$$ { }""", "base")] // ConstructorInitializerSyntax [DataRow("""$$public static implicit operator int(Test t) => 1;$$""", "int")] // ConversionOperatorDeclarationSyntax [DataRow("""$$delegate void D();$$""", "D")] // DelegateDeclarationSyntax [DataRow("""$$~Test() { }$$""", "Test")] // DestructorDeclarationSyntax [DataRow("""enum E { $$M$$ }""", "M")] // EnumMemberDeclarationSyntax [DataRow("""enum E { $$M = 1$$, }""", "M")] // EnumMemberDeclarationSyntax [DataRow("""$$event Action E {add { } remove { } }$$""", "E")] // EventDeclarationSyntax [DataRow("""$$event Action E;$$""", null)] // EventFieldDeclarationSyntax [DataRow("""$$event Action E1, E2;$$""", null)] // EventFieldDeclarationSyntax [DataRow("""void M() { $$foreach (var x in new int[0]) { }$$ }""", "x")] // ForEachStatementSyntax [DataRow("""void M() { var q = $$from x in new int[0]$$ select x; }""", "x")] // FromClauseSyntax [DataRow("""$$Int32$$ i;""", "Int32")] // IdentifierNameSyntax [DataRow("""$$int this[int i] { get => 1; set { } }$$""", "this")] // IndexerDeclarationSyntax [DataRow("""int i = $$Math.Abs(1)$$;""", "Abs")] // InvocationExpressionSyntax [DataRow("""int i = $$Fun()()$$;""", null)] // InvocationExpressionSyntax [DataRow("""void M() { var q = from _ in new int[0] $$join x in new int[0] on _ equals x$$ select _; }""", "x")] // JoinClauseSyntax [DataRow("""void M() { var q = from _ in new int[0] join y in new int[0] on _ equals y $$into x$$ select _; }""", "x")] // JoinIntoClauseSyntax [DataRow("""void M() { var q = from _ in new int[0] $$let x = 0$$ select _; }""", "x")] // LetClauseSyntax [DataRow("""int i = $$int.MaxValue$$;""", "MaxValue")] // MemberAccessExpressionSyntax [DataRow("""string s = (new object())?$$.ToString$$();""", "ToString")] // MemberBindingExpressionSyntax [DataRow("""string s = (new object())?$$.ToString()$$;""", "ToString")] // MemberBindingExpressionSyntax [DataRow("""$$void M() { }$$""", "M")] // MethodDeclarationSyntax [DataRow("""$$int?$$ i;""", "int")] // NullableTypeSyntax [DataRow("""object o = $$new object()$$;""", "object")] // ObjectCreationExpressionSyntax [DataRow("""$$public static Test operator +(Test t) => default;$$""", "+")] // OperatorDeclarationSyntax [DataRow("""void M($$int i$$) { }""", "i")] // ParameterSyntax [DataRow("""int i = $$(int.MaxValue)$$;""", "MaxValue")] // ParenthesizedExpressionSyntax [DataRow("""$$int P { get; }$$""", "P")] // PropertyDeclarationSyntax [DataRow("""int i = $$-int.MaxValue$$;""", "MaxValue")] // PrefixUnaryExpressionSyntax [DataRow("""object o = $$new object()!$$;""", "object")] // PostfixUnaryExpressionSyntax [DataRow("""$$int*$$ i;""", "int")] // PointerTypeSyntax [DataRow("""$$System.Collections.ArrayList$$ l;""", "ArrayList")] // QualifiedNameSyntax [DataRow("""void M() { var q = from x in new int[0] group x by x $$into y$$ select y; }""", "y")] // QueryContinuationSyntax [DataRow("""$$List$$ l;""", "List")] // GenericNameSyntax [DataRow("""bool M() => (0, Name: "a") is (_, $$Name: null$$);""", "Name")] // SubpatternSyntax [DataRow("""bool M() => (0, Name: "a") is ($$_$$, Name: null);""", null)] // SubpatternSyntax [DataRow("""void M() where $$T : class$$ { }""", "T")] // TypeParameterConstraintClauseSyntax [DataRow("""void M<$$T$$>() { }""", "T")] // TypeParameterSyntax [DataRow("""int $$i$$;""", "i")] // VariableDeclaratorSyntax [DataRow("""object o = $$new()$$;""", "new")] // ImplicitObjectCreationExpressionSyntax [DataRow("""void M(int p) { $$ref int$$ i = ref p; }""", "int")] // RefTypeSyntax [DataRow("""void M() { int.TryParse("", out var $$x$$); }""", "x")] // SingleVariableDesignationSyntaxWrapper public void GetIdentifier_Members(string member, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; unsafe class Test { static Func Fun() => default; public Test(int i) { } {{member}} } """).Node; var actual = SyntaxNodeExtensionsCSharp.GetIdentifier(node); if (expected is null) { actual.Should().BeNull(); } else { actual.Should().NotBeNull(); actual.Value.ValueText.Should().Be(expected); } } [TestMethod] [DataRow("""$$Object$$ M() { return null; }""", "Object")] // TypeSyntax itself [DataRow("""$$Object M() { return null; }$$""", "Object")] // MethodDeclarationSyntax [DataRow("""$$void M() { }$$""", "void")] // MethodDeclarationSyntax — void return type [DataRow("""$$public static Object operator +(Test t) => default;$$""", "Object")] // OperatorDeclarationSyntax [DataRow("""$$public static implicit operator int(Test t) => 0;$$""", "int")] // ConversionOperatorDeclarationSyntax [DataRow("""$$delegate Object D();$$""", "Object")] // DelegateDeclarationSyntax [DataRow("""$$Object P { get; }$$""", "Object")] // PropertyDeclarationSyntax (BasePropertyDeclarationSyntax) [DataRow("""$$event Action E { add { } remove { } }$$""", "Action")] // EventDeclarationSyntax (BasePropertyDeclarationSyntax) [DataRow("""$$int this[int i] { get => 1; set { } }$$""", "int")] // IndexerDeclarationSyntax (BasePropertyDeclarationSyntax) [DataRow("""$$Object field;$$""", "Object")] // FieldDeclarationSyntax (BaseFieldDeclarationSyntax) [DataRow("""$$event Action E;$$""", "Action")] // EventFieldDeclarationSyntax (BaseFieldDeclarationSyntax) [DataRow("""void M() { $$Object x = null;$$ }""", "Object")] // LocalDeclarationStatementSyntax [DataRow("""void M() { $$using (IDisposable x = null) { }$$ }""", "IDisposable")] // UsingStatementSyntax (with declaration) [DataRow("""void M() { $$using (new System.IO.MemoryStream()) { }$$ }""", null)] // UsingStatementSyntax (expression form — no declaration) [DataRow("""void M() { $$for (Object x = null; ; ) { }$$ }""", "Object")] // ForStatementSyntax (with declaration) [DataRow("""void M() { int[] arr = new int[1]; $$fixed (int* p = arr) { }$$ }""", "int*")] // FixedStatementSyntax [DataRow("""void M() { foreach ($$Object x$$ in new Object[0]) { } }""", "Object")] // ForEachStatementSyntax [DataRow("""void M() { try { } catch ($$Exception e$$) { } }""", "Exception")] // CatchDeclarationSyntax [DataRow("""void M() { try { } $$catch (Exception e) { }$$ }""", "Exception")] // CatchClauseSyntax (with declaration) [DataRow("""void M() { try { } $$catch { }$$ }""", null)] // CatchClauseSyntax (without declaration) [DataRow("""void M($$Object p$$) { }""", "Object")] // ParameterSyntax [DataRow("""void M() { var _ = $$Object () => default$$; }""", "Object")] // ParenthesizedLambdaExpressionSyntax [DataRow("""void M() { Func _ = $$x => x$$; }""", null)] // SimpleLambdaExpressionSyntax — no type annotation [DataRow("""void M() { var q = $$from Object x in new Object[0]$$ select x; }""", "Object")] // FromClauseSyntax [DataRow("""void M() { var q = from Object x in new Object[0] $$join Object y in new Object[0] on x equals y$$ select x; }""", "Object")] // JoinClauseSyntax [DataRow("""void M() { _ = $$new Object()$$; }""", "Object")] // ObjectCreationExpressionSyntax [DataRow("""void M(object o) { _ = $$(Object)o$$; }""", "Object")] // CastExpressionSyntax [DataRow("""void M() { _ = $$typeof(Object)$$; }""", "Object")] // TypeOfExpressionSyntax [DataRow("""void M() { _ = $$default(Object)$$; }""", "Object")] // DefaultExpressionSyntax [DataRow("""void M() { _ = $$sizeof(int)$$; }""", "int")] // SizeOfExpressionSyntax [DataRow("""void M() { int* _ = $$stackalloc int[1]$$; }""", "int[1]")] // StackAllocArrayCreationExpressionSyntax [DataRow("""void M() { Object o = null; TypedReference tr = __makeref(o); _ = $$__refvalue(tr, Object)$$; }""", "Object")] // RefValueExpressionSyntax [DataRow("""void M(object o) { _ = $$o is Object$$; }""", "Object")] // BinaryExpressionSyntax IsExpression — type check [DataRow("""void M(object o) { _ = $$o is 1$$; }""", null)] // BinaryExpressionSyntax IsExpression — constant (not TypeSyntax) [DataRow("""void M(object o) { _ = $$o as Object$$; }""", "Object")] // BinaryExpressionSyntax AsExpression [DataRow("""class Inner : $$IDisposable$$ { public void Dispose() { } }""", "IDisposable")] // BaseTypeSyntax (SimpleBaseTypeSyntax) [DataRow("""void M() where T : $$IComparable$$ { }""", "IComparable")] // TypeConstraintSyntax [DataRow("""void M() { $$Object Local() => null;$$ }""", "Object")] // LocalFunctionStatementSyntax [DataRow("""void M(object o) { if (o is $$Object x$$) { } }""", "Object")] // DeclarationPatternSyntax [DataRow("""void M(object o) { _ = o is $$Object { }$$; }""", "Object")] // RecursivePatternSyntax [DataRow("""void M(object o) { _ = o switch { $$int$$ => 1, _ => 0 }; }""", "int")] // TypePatternSyntax [DataRow("""($$Object x$$, int y) M() => default;""", "Object")] // TupleElement [DataRow("""void M(ref Object p) { $$ref Object$$ x = ref p; }""", "Object")] // RefTypeSyntax [DataRow("""void M() { _ = int.TryParse("1", out $$int result$$); }""", "int")] // DeclarationExpression [DataRow("""unsafe void M() { delegate*<$$Object$$, void> _ = null; }""", "Object")] // FunctionPointerParameter [DataRow("""void M(ref Object p) { $$scoped ref Object$$ x = ref p; }""", "ref Object")] // ScopedType [DataRow("""[$$Obsolete("msg")$$] void M2() { }""", "Obsolete")] // AttributeSyntax [DataRow("""$$Object$$ field;""", "Object")] // TypeSyntax itself — identity [DataRow("""$$Test() { }$$""", null)] // ConstructorDeclarationSyntax — no type [DataRow("""$$~Test() { }$$""", null)] // DestructorDeclarationSyntax — no type public void TypeSyntax_Members(string member, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; using System.Collections.Generic; using System.Linq; unsafe class Test { public Test(int i) { } {{member}} } """).Node; var actual = node.TypeSyntax(); if (expected is null) { actual.Should().BeNull(); } else { actual.Should().NotBeNull(); actual.ToString().Should().Be(expected); } } [TestMethod] [DataRow("""$$using System.IO;$$""", "System.IO")] // regular namespace import [DataRow("""$$using IO = System.IO;$$""", "System.IO")] // alias public void TypeSyntax_UsingDirective(string snippet, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" {{snippet}} class Test { } """).Node; node.TypeSyntax().ToString().Should().Be(expected); } [TestMethod] [DataRow("""$$namespace A.B.C { }$$""", "C")] // NamespaceDeclarationSyntax [DataRow("""$$namespace A.B.C;$$""", "C")] // FileScopedNamespaceDeclarationSyntax [DataRow("""$$using A = System.Collections;$$""", "A")] // UsingDirectiveSyntax [DataRow("""$$using System.Collections;$$""", null)] // UsingDirectiveSyntax public void GetIdentifier_CompilationUnit(string member, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" {{member}} """).Node; var actual = SyntaxNodeExtensionsCSharp.GetIdentifier(node); if (expected is null) { actual.Should().BeNull(); } else { actual.Should().NotBeNull(); actual.Value.ValueText.Should().Be(expected); } } [TestMethod] [DataRow(""" : $$Base(i)$$""", "Base")] // NamespaceDeclarationSyntax [DataRow(""" : $$Test.Base(i)$$""", "Base")] // NamespaceDeclarationSyntax public void GetIdentifier_PrimaryConstructor(string baseType, string expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" namespace Test; public class Base(int i) { } public class Derived(int i) {{baseType}} { } """).Node; var actual = SyntaxNodeExtensionsCSharp.GetIdentifier(node); if (expected is null) { actual.Should().BeNull(); } else { actual.Should().NotBeNull(); actual.Value.ValueText.Should().Be(expected); } } [TestMethod] [DataRow("""event EventHandler SomeEvent { add { $$int x = 42;$$ } remove { int x = 42; } }""", SyntaxKind.AddAccessorDeclaration)] [DataRow("""int Method() { Func add = delegate (int a, int b) { return $$a + b$$; }; return add(1, 2); }""", SyntaxKind.AnonymousMethodExpression)] [DataRow("""Derived(int arg) : base($$arg$$) { }""", SyntaxKind.BaseConstructorInitializer)] [DataRow("""Derived() { $$var x = 42;$$ }""", SyntaxKind.ConstructorDeclaration)] [DataRow("""public static implicit operator int(Derived d) => $$42$$;""", SyntaxKind.ConversionOperatorDeclaration)] [DataRow("""~Derived() { $$var x = 42;$$ }""", SyntaxKind.DestructorDeclaration)] [DataRow("""int field = $$int.Parse("42")$$;""", SyntaxKind.FieldDeclaration)] [DataRow("""int Property { get; set; } = $$int.Parse("42")$$;""", SyntaxKind.PropertyDeclaration)] [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] [DataRow("""int Method() { return LocalFunction(); int LocalFunction() { $$return 42;$$ } }""", SyntaxKindEx.LocalFunctionStatement)] [DataRow("""int Method() { return LocalFunction(); int LocalFunction() => $$42$$; }""", SyntaxKindEx.LocalFunctionStatement)] [DataRow("""int Method() { $$return 42;$$ }""", SyntaxKind.MethodDeclaration)] [DataRow("""int Method() => $$42$$;""", SyntaxKind.MethodDeclaration)] [DataRow("""public static Derived operator +(Derived d) => $$d$$;""", SyntaxKind.OperatorDeclaration)] [DataRow("""int Method() { var lambda = () => $$42$$; return lambda(); }""", SyntaxKind.ParenthesizedLambdaExpression)] [DataRow("""int Method() { Func lambda = x => $$x + 1$$; return lambda(42); }""", SyntaxKind.SimpleLambdaExpression)] [DataRow("""event EventHandler SomeEvent { add { int x = 42; } remove { $$int x = 42;$$ } }""", SyntaxKind.RemoveAccessorDeclaration)] [DataRow("""Derived(int arg) : this($$arg.ToString()$$) { }""", SyntaxKind.ThisConstructorInitializer)] [DataRow("""enum E { A = $$1$$ }""", SyntaxKind.EnumMemberDeclaration)] [DataRow("""void M(int i = $$1$$) { }""", SyntaxKind.Parameter)] #if NET [DataRow("""int Property { init { $$_ = value;$$ } }""", SyntaxKindEx.InitAccessorDeclaration)] [DataRow("""record BaseRec(int I); record DerivedRec(int I): BaseRec($$I++$$);""", SyntaxKindEx.PrimaryConstructorBaseType)] #endif public void EnclosingScope_Members(string member, SyntaxKind expectedSyntaxKind) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; public class Base { public Base() { } public Base(int arg) { } } public class Derived: Base { Derived(string arg) { } {{member}} } """).Node; var actual = SyntaxNodeExtensionsCSharp.EnclosingScope(node)?.Kind() ?? SyntaxKind.None; actual.Should().Be(expectedSyntaxKind); } [TestMethod] public void EnclosingScope_TopLevelStatements() { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; $$Console.WriteLine("")$$; """, outputKind: OutputKind.ConsoleApplication).Node; var actual = SyntaxNodeExtensionsCSharp.EnclosingScope(node)?.Kind() ?? SyntaxKind.None; actual.Should().Be(SyntaxKind.CompilationUnit); } [TestMethod] [DataRow("from $$x$$ in qry select x", SyntaxKind.MethodDeclaration)] // Wrong. Should be FromClause [DataRow("from x in $$qry$$ select x", SyntaxKind.MethodDeclaration)] [DataRow("from x in qry from y in $$qry$$ select x", SyntaxKind.MethodDeclaration)] // Wrong. Should be FromClause [DataRow("from x in qry select $$x$$", SyntaxKind.SelectClause)] [DataRow("from x in qry orderby $$x$$ select x", SyntaxKind.OrderByClause)] [DataRow("from x in qry where x == $$string.Empty$$ select x", SyntaxKind.WhereClause)] [DataRow("from x in qry let y = $$x$$ select y", SyntaxKind.LetClause)] [DataRow("from x in qry join y in qry on $$x$$ equals y select x", SyntaxKind.JoinClause)] [DataRow("from x in qry join y in qry on x equals $$y$$ select x", SyntaxKind.JoinClause)] [DataRow("from x in qry join y in $$qry$$ on x equals y select x", SyntaxKind.JoinClause)] // Wrong. Should be MethodDeclaration [DataRow("from x in qry group x by $$x$$ into g select g", SyntaxKind.GroupClause)] [DataRow("from x in qry group $$x$$ by x into g select g", SyntaxKind.GroupClause)] // Wrong. Should be the FromClause [DataRow("from x in qry group x by x into $$g$$ select g", SyntaxKind.QueryContinuation)] [DataRow("from x in qry select x into $$y$$ select y", SyntaxKind.QueryContinuation)] public void EnclosingScope_QueryExpressionSyntax(string qry, SyntaxKind expected) { var node = TestCompiler.NodeBetweenMarkersCS($$""" using System; using System.Linq; class Test { public void Query(string[] qry) { _ = {{qry}}; } } """).Node; var actual = SyntaxNodeExtensionsCSharp.EnclosingScope(node)?.Kind(); actual.Should().Be(expected); } [TestMethod] public void Symbol_IsKnownType() { var snippet = new SnippetCompiler(""" using System.Collections.Generic; public class Sample { public void Method(List param1, List param2, List param3, IList param4) { } } """); var method = (MethodDeclarationSyntax)snippet.MethodDeclaration("Sample.Method"); ExtensionsCore.IsKnownType(method.ParameterList.Parameters[0].Type, KnownType.System_Collections_Generic_List_T, snippet.Model).Should().BeTrue(); ExtensionsCore.IsKnownType(method.ParameterList.Parameters[1].Type, KnownType.System_Collections_Generic_List_T, snippet.Model).Should().BeTrue(); ExtensionsCore.IsKnownType(method.ParameterList.Parameters[2].Type, KnownType.System_Collections_Generic_List_T, snippet.Model).Should().BeTrue(); ExtensionsCore.IsKnownType(method.ParameterList.Parameters[3].Type, KnownType.System_Collections_Generic_List_T, snippet.Model).Should().BeFalse(); } [TestMethod] public void ToSecondaryLocation_NullMessage() { var code = "public class $$C$$ {}"; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var secondaryLocation = node.ToSecondaryLocation(null); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(node.GetLocation()); secondaryLocation.Message.Should().BeNull(); } [TestMethod] [DataRow(null)] [DataRow([])] public void ToSecondaryLocation_MessageArgs(string[] messageArgs) { var code = "public class $$C$$ {}"; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var secondaryLocation = node.ToSecondaryLocation("Message", messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(node.GetLocation()); secondaryLocation.Message.Should().Be("Message"); } [TestMethod] [DataRow("Message {0}", "42")] [DataRow("{1} Message {0} ", "42", "21")] public void ToSecondaryLocation_MessageFormat(string format, params string[] messageArgs) { var code = "public class $$C$$ {}"; var node = TestCompiler.NodeBetweenMarkersCS(code).Node; var secondaryLocation = node.ToSecondaryLocation(format, messageArgs); secondaryLocation.Should().NotBeNull(); secondaryLocation.Location.Should().Be(node.GetLocation()); secondaryLocation.Message.Should().Be(string.Format(format, messageArgs)); } [TestMethod] [DataRow("public void M() { _ = $$42$$; }")] [DataRow("public void M() { int Local() => $$42$$; }")] [DataRow("public void M() { Func _ = () => $$42$$; }")] [DataRow("public string P { get { $$return P$$; } }")] [DataRow("public C() { _ = $$42$$; }")] [DataRow("private readonly int f = $$42$$;")] public void PerformanceSensitiveAttribute(string node) { var code = $$""" using System; [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] public sealed class PerformanceSensitiveAttribute(): Attribute; public class C { [PerformanceSensitive] {{node}} } """; var nodeAndModel = TestCompiler.NodeBetweenMarkersCS(code); nodeAndModel.Node.PerformanceSensitiveAttribute(nodeAndModel.Model).Should().NotBeNull(); } [TestMethod] public void PerformanceSensitiveAttribute_NullNode() => SyntaxNodeExtensionsCSharp.PerformanceSensitiveAttribute(null, null).Should().BeNull(); [TestMethod] [DataRow("Func f = [PerformanceSensitive] () => $$42$$;")] [DataRow("[PerformanceSensitive] int Local() => $$42$$;")] [DataRow("LM([PerformanceSensitive] () => $$42$$); int LM(Func f) => f();")] public void PerformanceSensitiveAttribute_AnonymousAndLocalMethods(string node) { var code = $$""" using System; [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] public sealed class PerformanceSensitiveAttribute(): Attribute; public class C { public void M() { {{node}} } } """; var nodeAndModel = TestCompiler.NodeBetweenMarkersCS(code); nodeAndModel.Node.PerformanceSensitiveAttribute(nodeAndModel.Model).Should().NotBeNull(); } [TestMethod] [DataRow("class Inner { public void M() { _ = $$42$$; } }")] [DataRow("interface Inner { void $$M$$(); }")] [DataRow("struct Inner { public void M() { _ = $$42$$; } }")] [DataRow("enum Inner { Value = $$1$$ }")] [DataRow("public event System.Action E = $$null$$;")] [DataRow("public delegate void MyDelegate(int $$param$$);")] public void PerformanceSensitiveAttribute_InvalidAttributeTarget(string node) { var code = $$""" [System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] public sealed class PerformanceSensitiveAttribute(): System.Attribute; public class C { [PerformanceSensitive] {{node}} } """; var nodeAndModel = TestCompiler.NodeBetweenMarkersCS(code); nodeAndModel.Node.PerformanceSensitiveAttribute(nodeAndModel.Model).Should().BeNull(); } [TestMethod] [DataRow("Func f = [PerformanceSensitive] x => $$x$$;")] // error CS8916: Attributes on lambda expressions require a parenthesized parameter list. [DataRow("Func f = [PerformanceSensitive] delegate(int x) { return $$x$$; };")] public void PerformanceSensitiveAttribute_DoNotCompile(string node) { var code = $$""" using System; [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] public sealed class PerformanceSensitiveAttribute(): Attribute; public class C { public void M() { {{node}} } } """; Action action = () => TestCompiler.NodeBetweenMarkersCS(code); action.Should().Throw().WithMessage("Test setup error: test code snippet did not compile. See output window for details."); } [TestMethod] [DataRow("public void M() { _ = $$42$$; }")] [DataRow("[Some] public void M() { Func f = () => $$42$$; }")] [DataRow("public void M() { int Local() => $$42$$; }")] [DataRow("public void M() { LM([Some] () => $$42$$); int LM(Func f) => f(); }")] [DataRow("public string P { get { return $$P$$; } }")] [DataRow("public C() { _ = $$42$$; }")] [DataRow("private readonly int f = $$42$$;")] public void PerformanceSensitiveAttribute_NoPerformanceSensitiveAttribute(string node) { var code = $$""" using System; [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] public sealed class SomeAttribute(): Attribute; public class C { {{node}} } """; var nodeAndModel = TestCompiler.NodeBetweenMarkersCS(code); nodeAndModel.Node.PerformanceSensitiveAttribute(nodeAndModel.Model).Should().BeNull(); } private static SyntaxToken GetFirstTokenOfKind(SyntaxTree tree, SyntaxKind kind) => tree.GetRoot().DescendantTokens().First(x => x.IsKind(kind)); private static SyntaxTree GetSyntaxTree(string content, string fileName = null) => SolutionBuilder .Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(content, fileName) .GetCompilation() .SyntaxTrees .First(); private static ControlFlowGraph CreateCfgCS(string code) where T : CSharpSyntaxNode { var (tree, model) = TestCompiler.CompileCS(code); return SyntaxNodeExtensionsCSharp.CreateCfg(tree.Single(), model, default); } private static ControlFlowGraph CreateCfgVB(string code) where T : Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode { var (tree, model) = TestCompiler.CompileVB(code); return SyntaxNodeExtensionsVisualBasic.CreateCfg(tree.Single(), model, default); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxTokenExtensionsSharedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using static SonarAnalyzer.CSharp.Core.Syntax.Extensions.SyntaxTokenExtensionsShared; namespace SonarAnalyzer.Test.Extensions; [TestClass] public class SyntaxTokenExtensionsSharedTest { [TestMethod] public void GetBindableParent_ForEmptyToken_ReturnsNull() { SyntaxToken empty = default; empty.GetBindableParent().Should().BeNull(); } [TestMethod] public void GetBindableParent_ForInterpolatedStringTextTokenInInterpolatedStringTextToken_ReturnsInterpolatedStringExpression() { const string code = @"string x = $""a"";"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var aToken = GetFirstTokenOfKind(syntaxTree, SyntaxKind.InterpolatedStringTextToken); var parent = aToken.GetBindableParent(); parent.Kind().Should().Be(SyntaxKind.InterpolatedStringExpression); } [TestMethod] public void GetBindableParent_ForOpenBraceInsideInterpolatedStringTextToken_ReturnsInterpolation() { const string code = @"string x = $""{1}"";"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var aToken = GetFirstTokenOfKind(syntaxTree, SyntaxKind.OpenBraceToken); var parent = aToken.GetBindableParent(); parent.Kind().Should().Be(SyntaxKind.Interpolation); } [TestMethod] public void GetBindableParent_ForMemberAccessExpressionSyntax_ReturnsTheExpression() { const string code = @"this.Value;"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var thisToken = GetFirstTokenOfKind(syntaxTree, SyntaxKind.ThisKeyword); var parent = thisToken.GetBindableParent(); parent.Kind().Should().Be(SyntaxKind.ThisExpression); } [TestMethod] public void GetBindableParent_ForObjectCreationExpressionSyntax_ReturnsArgumentList() { const string code = @"var s = new string();"; var syntaxTree = CSharpSyntaxTree.ParseText(code); var openParentToken = GetFirstTokenOfKind(syntaxTree, SyntaxKind.OpenParenToken); var parent = openParentToken.GetBindableParent(); parent.Kind().Should().Be(SyntaxKind.ArgumentList); } private static SyntaxToken GetFirstTokenOfKind(SyntaxTree syntaxTree, SyntaxKind kind) => syntaxTree.GetRoot().DescendantTokens().First(token => token.IsKind(kind)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.FlowCaptureOperation.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.LiveVariableAnalysis; public partial class RoslynLiveVariableAnalysisTest { [TestMethod] public void FlowCaptrure_NullCoalescingAssignment() { /* Block 1 * #0=param * | * Block 2 * #1=#0 * #1 is null --+ * | | * Block 3 | * #0=Hello | * | | * Exit <-----+ */ const string code = """param ??= "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string param"); context.ValidateEntry(LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[2], LiveIn("param")); context.Validate(context.Cfg.Blocks[3]); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_NullCoalescingOperator() { /* Block 1 * #0=param * #0 is null * / \ * F T * / \ * Block 2 Block 3 * #1=#0 #1="Hello" * \ / * Block 4 * result=#1 * | * Exit */ const string code = """var result = param ?? "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string param"); context.ValidateEntry(LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[2], LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[3], LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[4], LiveIn("param")); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_ConditionalAccess() { /* Block 1 * #1=param * #1 is null * / \ * F T * / \ * Block 2 Block 3 * #0=#1.Length #0=default * \ / * Block 4 * result=#1 * | * Exit */ const string code = """var result = param?.Length;"""; var context = CreateContextCS(code, additionalParameters: "string param"); context.ValidateEntry(LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[1], LiveIn("param"), LiveOut("param")); context.Validate(context.Cfg.Blocks[2], LiveIn("param")); context.Validate(context.Cfg.Blocks[3]); context.Validate(context.Cfg.Blocks[4]); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_Ternary() { /* Block 1 * boolParameter * / \ * F T * / \ * / \ * Block 2 Block 3 * #0=StringParam #0="Hello" * | | * Block 4 <--------+ * result=#0 * | * Exit */ const string code = """var result = boolParameter ? stringParam : "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string stringParam"); context.ValidateEntry(LiveIn("boolParameter", "stringParam"), LiveOut("boolParameter", "stringParam")); context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[2], LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[3], LiveIn("stringParam"), LiveOut("stringParam")); context.Validate(context.Cfg.Blocks[4], LiveIn("stringParam")); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_ReuseCaptures() { /* Block 1 * boolParameter * / \ * F T * / \ * Block 2 Block 3 * #0=st #0="Hello" * \ / * Block 4 * result1=#0 * | * Block5 * boolParameter * / \ * F T * / \ * Block 6 Block 7 * #1=st2 #1="Hello" * \ / * Block 8 * result2=#1 * | * Exit */ const string code = """ var result1 = boolParameter ? s1 : "Hello"; var result2 = boolParameter ? s2 : "Hi"; """; var context = CreateContextCS(code, additionalParameters: "string s1, string s2"); context.ValidateEntry(LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); context.Validate(context.Cfg.Blocks[2], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); context.Validate(context.Cfg.Blocks[3], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s1", "s2")); context.Validate(context.Cfg.Blocks[4], LiveIn("boolParameter", "s1", "s2"), LiveOut("boolParameter", "s2")); context.Validate(context.Cfg.Blocks[5], LiveIn("boolParameter", "s2"), LiveOut("s2")); context.Validate(context.Cfg.Blocks[6], LiveIn("s2"), LiveOut("s2")); context.Validate(context.Cfg.Blocks[7], LiveIn("s2"), LiveOut("s2")); context.Validate(context.Cfg.Blocks[8], LiveIn("s2")); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls() { /* Block 1 * #0=s1 * #0 is null * / \ * F T * / \ * / \ * Block 2 Block 3 * #1=#0 #2=s2 * | #2 is null * | / \ * | F T * | / \ * | Block 4 Block 5 * | #1=#2 #1="Hello" * | |___________| * | | * | | * +--------->Block 6 * result=#1 * | * Exit */ const string code = """var result = s1 ?? s2 ?? "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string s1, string s2"); context.ValidateEntry(LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[1], LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[3], LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[4], LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[5], LiveIn("s1", "s2"), LiveOut("s1", "s2")); context.Validate(context.Cfg.Blocks[6], LiveIn("s1", "s2")); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_NullCoalescingOperator_ConsequentCalls_Assignment() { const string code = """var result = s1 ??= s2 = s3 ??= s4 ?? "End";"""; var context = CreateContextCS(code, additionalParameters: "string s1, string s2, string s3, string s4"); context.ValidateEntry(LiveIn("s1", "s3", "s4"), LiveOut("s1", "s3", "s4")); context.Validate(context.Cfg.Blocks[1], LiveIn("s1", "s3", "s4"), LiveOut("s1", "s3", "s4")); // 1: #0=s1 context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s3", "s4"), LiveOut("s1", "s3", "s4")); // 2: #1=#0; if #1 is null context.Validate(context.Cfg.Blocks[3], LiveIn("s1"), LiveOut("s1")); // 3: F: #2=#1 context.Validate(context.Cfg.Blocks[4], LiveIn("s3", "s4"), LiveOut("s3", "s4")); // 4: T: #3=s2 context.Validate(context.Cfg.Blocks[5], LiveIn("s3", "s4"), LiveOut("s3", "s4")); // 5: #4=s3 context.Validate(context.Cfg.Blocks[6], LiveIn("s3", "s4"), LiveOut("s3", "s4")); // 6: #5=#4; if #5 is null context.Validate(context.Cfg.Blocks[7], LiveIn("s3"), LiveOut("s3")); // 7: F: #6=#5 context.Validate(context.Cfg.Blocks[8], LiveIn("s4"), LiveOut("s4")); // 8: T: #7=s4; if #7 is null context.Validate(context.Cfg.Blocks[9], LiveIn("s4"), LiveOut("s4")); // 9: F: #8=#7 context.Validate(context.Cfg.Blocks[10], LiveIn("s4"), LiveOut("s4")); // 10: #7=null; #8="End" context.Validate(context.Cfg.Blocks[11], LiveIn("s4"), LiveOut("s3")); // 11: #6= (#4=#8) context.Validate(context.Cfg.Blocks[12], LiveIn("s3"), LiveOut("s1")); // 12: #2= (#0 = (#3=#6) ) context.Validate(context.Cfg.Blocks[13], LiveIn("s1")); // 13: result=#2 context.ValidateExit(); } [TestMethod] public void FlowCaptrure_NullCoalescingOperator_Nested() { /* Block 1 * #0=s1 * #0 is null * / \ * F T * / \ * / \ * Block 2 Block 3 * #1=#0 s2 is null * | / \ * | T F * | / \ * | Block 4 Block 5 * | #2=s3 #2="NestedFalse" * | |___________| * | | * | | * | Block 6 * | #2 is null * | / \ * | F T * | / \ * | Block7 Block 8 * | #1=#2 #1="Hello" * | |___________| * | | * +---------->Block 9 * result=#1 * | * Exit */ const string code = """var result = s1 ?? (s2 is null ? s3 : "NestedFalse") ?? "Hello";"""; var context = CreateContextCS(code, additionalParameters: "string s1, string s2, string s3"); context.ValidateEntry(LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); context.Validate(context.Cfg.Blocks[1], LiveIn("s1", "s2", "s3"), LiveOut("s1", "s2", "s3")); context.Validate(context.Cfg.Blocks[2], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[3], LiveIn("s1", "s2", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[4], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[5], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[6], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[7], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[8], LiveIn("s1", "s3"), LiveOut("s1", "s3")); context.Validate(context.Cfg.Blocks[9], LiveIn("s1", "s3")); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_NullCoalescingOperator_Overwrite() { /* Block 1 * #0=s1 * | * Block 2 * #1=(s1="overwrite") * #1 is null * / \ * F T * / \ * Block3 Block4 * #2=#1 #2="value" * |___________| * | * Block5 * s1=#2 */ const string code = """s1 = (s1 = "overwrite") ?? "value";"""; var context = CreateContextCS(code, additionalParameters: "string s1"); // s1 is never read. The assignment returns its r-value, which is used for further calculation. context.ValidateEntry(); context.Validate(context.Cfg.Blocks[1]); context.Validate(context.Cfg.Blocks[2]); context.Validate(context.Cfg.Blocks[3]); context.Validate(context.Cfg.Blocks[4]); context.Validate(context.Cfg.Blocks[5]); context.ValidateExit(); } [TestMethod] public void FlowCaptrure_SwitchStatement() { const string code = """ var result = i switch { 0 => param, 1 => "Something", _ => "Everything" }; """; var context = CreateContextCS(code, additionalParameters: "int i, string param"); context.ValidateEntry(LiveIn("i", "param"), LiveOut("i", "param")); context.Validate(context.Cfg.Blocks[1], LiveIn("i", "param"), LiveOut("i", "param")); // #1 = i; if #1 == 0 context.Validate(context.Cfg.Blocks[2], LiveIn("param"), LiveOut("param")); // #0 = param context.Validate(context.Cfg.Blocks[3], LiveIn("i", "param"), LiveOut("i", "param")); // if #1 == 1 context.Validate(context.Cfg.Blocks[4], LiveIn("param"), LiveOut("param")); // #0 = "Something" context.Validate(context.Cfg.Blocks[5], LiveIn("i", "param"), LiveOut("param")); // if discard context.Validate(context.Cfg.Blocks[6], LiveIn("param"), LiveOut("param")); // #0 = "Everything" context.Validate(context.Cfg.Blocks[7]); // else, unreachable, throws context.Validate(context.Cfg.Blocks[8], LiveIn("param")); // result = #0 context.ValidateExit(); } [TestMethod] public void FlowCaptrure_ForEachCompundAssignment() { const string code = """ int sum = 0; foreach (var i in array) { sum += i; } """; var context = CreateContextCS(code, additionalParameters: "int[] array"); context.ValidateEntry(LiveIn("array"), LiveOut("array")); context.Validate(context.Cfg.Blocks[1], LiveIn("array"), LiveOut("array", "sum")); // sum = 0 context.Validate(context.Cfg.Blocks[2], LiveIn("array", "sum"), LiveOut("sum")); // #0 = array context.Validate(context.Cfg.Blocks[3], LiveIn("sum"), LiveOut("sum")); // If IEnumerator.MoveNext context.Validate(context.Cfg.Blocks[4], LiveIn("sum"), LiveOut("sum")); // sum += i context.Validate(context.Cfg.Blocks[5]); // Finally Region: #1=#0; if #1 is null, should have LiveIn/Liveout: array context.Validate(context.Cfg.Blocks[6]); // Finally Region: #1.Dispose, should have LiveIn: array context.Validate(context.Cfg.Blocks[7]); // Finally Region: Empty end of finally context.ValidateExit(); } [TestMethod] public void FlowCaptrure_ImplicDictionaryCreation() { const string code = """Dictionary dict = new() { ["Key"] = 0, ["Lorem"] = 1, [key] = value }; """; var context = CreateContextCS(code, additionalParameters: "string key, int value"); context.ValidateEntry(LiveIn("key", "value"), LiveOut("key", "value")); context.Validate(context.Cfg.Blocks[1], LiveIn("key", "value"), LiveOut("key", "value")); context.Validate(context.Cfg.Blocks[2], LiveIn("key", "value"), LiveOut("key", "value")); context.Validate(context.Cfg.Blocks[3], LiveIn("key", "value"), LiveOut("key", "value")); context.Validate(context.Cfg.Blocks[4], LiveIn("key", "value")); context.Validate(context.Cfg.Blocks[5]); context.ValidateExit(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.LocalFunction.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.LiveVariableAnalysis; public partial class RoslynLiveVariableAnalysisTest { [TestMethod] public void StaticLocalFunction_Expression_LiveIn() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) => a + 1; """; var context = CreateContextCS(code, "LocalFunction", "out int outParameter"); context.ValidateEntry(LiveIn("a"), LiveOut("a")); context.Validate("a + 1", LiveIn("a")); context.ValidateExit(); } [TestMethod] public void StaticLocalFunction_Expression_NotLiveIn_NotLiveOut() { var code = """ outParameter = LocalFunction(0); static int LocalFunction(int a) => 42; """; var context = CreateContextCS(code, "LocalFunction", "out int outParameter"); context.ValidateEntry(); context.Validate("42"); context.ValidateExit(); } [TestMethod] public void StaticLocalFunction_LiveIn() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) { return a + 1; } """; var context = CreateContextCS(code, "LocalFunction", "out int outParameter"); context.ValidateEntry(LiveIn("a"), LiveOut("a")); context.Validate("a + 1", LiveIn("a")); context.ValidateExit(); } [TestMethod] public void StaticLocalFunction_NotLiveIn_NotLivOut() { var code = """ outParameter = LocalFunction(0); static int LocalFunction(int a) { return 42; } """; var context = CreateContextCS(code, "LocalFunction", "out int outParameter"); context.ValidateEntry(); context.Validate("42"); context.ValidateExit(); } [TestMethod] public void StaticLocalFunction_Recursive() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) { if(a <= 0) return 0; else return LocalFunction(a - 1); }; """; var context = CreateContextCS(code, "LocalFunction", "out int outParameter"); context.ValidateEntry(LiveIn("a"), LiveOut("a")); context.Validate("0"); context.Validate("LocalFunction(a - 1)", LiveIn("a")); context.ValidateExit(); } [TestMethod] public void LocalFunctionInvocation_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() => variable; """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_FunctionDeclaredBeforeCode_LiveIn() { var code = """ var variable = 42; int LocalFunction() => variable; if (boolParameter) return; LocalFunction(); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_Generic_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() => variable; """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_NestedGeneric_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() { return Nested(); int Nested() => variable; } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_NotLiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); Method(variable); void LocalFunction() { variable = 0; } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter")); context.Validate("LocalFunction();"); } [TestMethod] public void LocalFunctionInvocation_NestedArgument_NotLiveIn() { var code = """ LocalFunction(40); int LocalFunction(int cnt) => cnt + 2; """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("LocalFunction(40);"); } [TestMethod] public void LocalFunctionInvocation_NestedVariable_NotLiveIn_NotCaptured() { var code = """ LocalFunction(); void LocalFunction() { var nested = 42; Func f = () => nested; } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("LocalFunction();"); } [TestMethod] public void LocalFunctionInvocation_Recursive_LiveIn_WithoutFlowCapture() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(10); int LocalFunction(int cnt) { if (cnt == 0) return 0; return variable + LocalFunction(cnt - 1); } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction(10);", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_Recursive_LiveIn_FlowCapture() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(10); int LocalFunction(int cnt) => variable + (cnt == 0 ? 0 : LocalFunction(cnt - 1)); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction(10);", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_Recursive_WhenAnalyzingLocalFunctionItself_LiveIn() { var code = """ var variable = 42; int LocalFunction(int arg) { return variable + (arg == 0 ? 0 : LocalFunction(arg - 1)); } """; // variable is not local, it's defined above the LocalFunction scope var context = CreateContextCS(code, "LocalFunction"); context.ValidateEntry(LiveIn("arg"), LiveOut("arg")); context.Validate("variable", LiveIn("arg"), LiveOut("arg")); context.Validate("LocalFunction(arg - 1)", LiveIn("arg")); context.Validate("0"); } [TestMethod] public void LocalFunctionInvocation_CrossReference_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() { int First() => Second(); int Second() => variable; return First(); } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_CrossReference_WhenAnalyzingLocalFunctionItself_LiveIn() { var code = """ int LocalFunction() { var variable = 42; if (boolParameter) return 0; int First() => Second(); int Second() => variable; return First(); } """; var context = CreateContextCS(code, "LocalFunction"); context.ValidateEntry(); context.Validate("boolParameter", LiveOut("variable")); context.Validate("First()", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_Nested_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() { return Nested(); int Nested() => variable; } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction();", LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_TryCatchFinally_LiveIn() { var code = """ var usedInTry = 42; var usedInCatch = 42; var usedInFinally = 42; var usedInUnreachable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() { try { Method(usedInTry); } catch { Method(usedInCatch); } finally { Method(usedInFinally); } return 42; Method(usedInUnreachable); } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); // usedInUnreachable is here only because of simplified processing inside local functions context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("usedInTry", "usedInCatch", "usedInFinally", "usedInUnreachable")); context.Validate("LocalFunction();", LiveIn("usedInTry", "usedInCatch", "usedInFinally", "usedInUnreachable")); } [TestMethod] public void LocalFunctionReference_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; Capturing(LocalFunction); int LocalFunction(int arg) => arg + variable; """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("Capturing(LocalFunction);", LiveIn("variable")); } [TestMethod] public void LocalFunctionReference_Recursive_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(42); void LocalFunction(int arg) { Enumerable.Empty().Where(IsTrue); bool IsTrue(object x) { arg--; return arg <= variable || new[] { x }.Any(IsTrue); } } """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("LocalFunction(42);", LiveIn("variable")); } [TestMethod] public void LocalFunctionReference_Recursive_WhenAnalyzingLocalFunctionItself_LiveIn() { var code = """ var variable = 42; int LocalFunction(int arg) { Capturing(LocalFunction); return variable + arg; } """; // variable is not local, it's defined above the LocalFunction scope var context = CreateContextCS(code, "LocalFunction"); context.ValidateEntry(LiveIn("arg"), LiveOut("arg")); context.Validate("Capturing(LocalFunction);", LiveIn("arg")); } [TestMethod] public void LocalFunctionCapture_CapturedLocalFunction_Captured() { // Local function using ?? (FlowCapture in CFG) is called from inside a lambda. // ResolveCaptures must be called for the local function CFG so that FlowCaptureReference resolves to "variable". var code = """ var variable = "value"; if (boolParameter) return; Capturing(() => LocalFunction()); string LocalFunction() => variable ?? "default"; """; var context = CreateContextCS(code); context.ValidateEntry(Captured("variable"), LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", Captured("variable"), LiveIn("boolParameter")); context.Validate("Capturing(() => LocalFunction());", Captured("variable")); } [TestMethod] public void LocalFunctionCapture_AnonymousFunction_FlowCapture_Captured() { // Lambda using ?? (FlowCapture in CFG) directly captures a variable. // ResolveCaptures must be called for the anonymous function CFG so that FlowCaptureReference resolves to "variable". var code = """ var variable = "value"; if (boolParameter) return; Capturing(() => variable ?? "default"); """; var context = CreateContextCS(code); context.ValidateEntry(Captured("variable"), LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", Captured("variable"), LiveIn("boolParameter")); context.Validate("Capturing(() => variable ?? \"default\");", Captured("variable")); } [TestMethod] public void LocalFunctionReference_FlowCapture_LiveIn() { // Local function using ?? (FlowCapture in CFG) is passed as a method reference. // ResolveCaptures must be called for the local function CFG so that FlowCaptureReference resolves to "variable". var code = """ var variable = "value"; if (boolParameter) return; Capturing(LocalFunction); string LocalFunction() => variable ?? "default"; """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("variable")); context.Validate("Capturing(LocalFunction);", LiveIn("variable")); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.TryCatchFinally.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Operations; using IIsNullOperation = Microsoft.CodeAnalysis.FlowAnalysis.IIsNullOperation; namespace SonarAnalyzer.Test.LiveVariableAnalysis; public partial class RoslynLiveVariableAnalysisTest { [TestMethod] [DataRow("using (var ms = new MemoryStream()) {", "}")] [DataRow("using var ms = new MemoryStream();", null)] public void Using_LiveInUntilTheEnd(string usingStatement, string suffix) { /* Block 1 Finally region: * ms = new Block 4 * | / \ * | Block5 \ * Block 2 ms.Dispose | * Method(ms.Length) \ / * / \ Block 6 * / \ | * Block 3 | (null) * Method(0) | * \ / * Exit */ var code = $""" {usingStatement} Method(ms.Capacity); if (boolParameter) Method(0); {suffix} """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("ms = new MemoryStream()", LiveIn("boolParameter"), LiveOut("boolParameter", "ms")); context.Validate("Method(ms.Capacity);", LiveIn("boolParameter", "ms"), LiveOut("ms")); context.Validate("Method(0);", LiveIn("ms"), LiveOut("ms")); context.ValidateExit(); // Finally region context.Validate("ms = new MemoryStream()", LiveIn("ms"), LiveOut("ms")); // Null check context.Validate("ms = new MemoryStream()", LiveIn("ms")); // Actual Dispose context.Validate(context.Cfg.Blocks[6]); } [TestMethod] public void Using_Nested_Block_LiveInUntilTheEnd() { const string code = """ using (var msOuter = new MemoryStream()) { if (boolParameter) { using (var msInner = new MemoryStream()) { Method(0); } Method(1); } } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(0);", LiveIn("msOuter", "msInner"), LiveOut("msOuter", "msInner")); context.Validate("Method(1);", LiveIn("msOuter"), LiveOut("msOuter")); context.Validate("Method(2);"); context.ValidateExit(); // Finally region context.Validate("msInner = new MemoryStream()", LiveIn("msInner", "msOuter"), LiveOut("msInner", "msOuter")); // Null check context.Validate("msInner = new MemoryStream()", LiveIn("msInner", "msOuter"), LiveOut("msOuter")); // Actual Dispose context.Validate("msOuter = new MemoryStream()", LiveIn("msOuter"), LiveOut("msOuter")); // Null check context.Validate("msOuter = new MemoryStream()", LiveIn("msOuter")); // Actual Dispose } [TestMethod] public void Using_Nested_Declaration_LiveInUntilTheEnd() { const string code = """ using var msOuter = new MemoryStream(); if (boolParameter) { using var msInner = new MemoryStream(); Method(0); if (boolParameter) { Method(1); } } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(0);", LiveIn("boolParameter", "msOuter", "msInner"), LiveOut("msOuter", "msInner")); context.Validate("Method(1);", LiveIn("msOuter", "msInner"), LiveOut("msOuter", "msInner")); context.Validate("Method(2);", LiveIn("msOuter"), LiveOut("msOuter")); context.ValidateExit(); // Finally region context.Validate("msInner = new MemoryStream()", LiveIn("msInner", "msOuter"), LiveOut("msInner", "msOuter")); // Null check context.Validate("msInner = new MemoryStream()", LiveIn("msInner", "msOuter"), LiveOut("msOuter")); // Actual Dispose context.Validate("msOuter = new MemoryStream()", LiveIn("msOuter"), LiveOut("msOuter")); // Null check context.Validate("msOuter = new MemoryStream()", LiveIn("msOuter")); // Actual Dispose } [TestMethod] public void Catch_LiveIn() { const string code = """ try { Method(0); } catch { Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void VariableBeforeTry_LiveOut() { const string code = """ var usedInCatch = 0; Method(0); try { usedInCatch = 1; Method(1); } catch { Method(usedInCatch); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveOut("usedInCatch")); context.Validate("Method(usedInCatch);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch() { const string code = """ var usedInCatch = 0; Method(0); try { Method(1); try { usedInCatch = 1; Method(2); } catch { Method(usedInCatch); // This can throw again Method(3); } } catch { Method(usedInCatch); Method(4); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(2);", LiveOut("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(4);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch_NestedTwice() { const string code = """ var usedInCatch = 0; Method(0); try { Method(1); try { Method(2); try { usedInCatch = 1; Method(3); } catch { Method(usedInCatch); // This can throw again Method(4); } } catch { Method(usedInCatch); Method(5); } } catch { Method(usedInCatch); Method(6); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(2);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(3);", LiveOut("usedInCatch")); context.Validate("Method(4);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(5);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(6);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch_ForEach() { const string code = """ var usedInCatch = 0; Method(0); try { Method(1); try { Method(2); var list = new List(); foreach(var i in list) { Method(i); Method(usedInCatch); } } catch { Method(usedInCatch); Method(3); } } catch { Method(usedInCatch); Method(4); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(2);", LiveIn("usedInCatch"), LiveOut("list", "usedInCatch")); context.Validate("Method(i);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(4);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterFilterHandler_FromInnerCatch() { const string code = """ int usedInCatch = 0; Method(0); try { try { usedInCatch = 2; Method(1); } catch { Method(usedInCatch); Method(2); } } catch when(usedInCatch > 10) { Method(usedInCatch); Method(3); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveOut("usedInCatch")); context.Validate("Method(2);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch_FromInnerFilterHandler() { const string code = """ int usedInCatch = 0; Method(0); try { try { usedInCatch = 2; Method(1); } catch when (true) { Method(usedInCatch); Method(2); } } catch { Method(usedInCatch); Method(3); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveOut("usedInCatch")); context.Validate("Method(2);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch_FromFilterHandler() { const string code = """ int usedInCatch = 0; Method(0); try { try { usedInCatch = 2; Method(1); } catch when(usedInCatch > 10 ) { usedInCatch = 3; Method(2); } } catch { Method(usedInCatch); Method(3); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveOut("usedInCatch")); context.Validate("Method(2);", LiveOut("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOutOuterCatch_CanThrowFromFilterHandler() { const string code = """ int usedInCatch = 0; Method(0); try { try { Method(1); } catch when(Method(2) == usedInCatch) { usedInCatch = 1; Method(3); } } catch { Method(usedInCatch); Method(4); } """; var context = CreateContextCS(code); context.Validate("Method(0);", LiveOut("usedInCatch")); context.Validate("Method(1);", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(2) == usedInCatch", LiveIn("usedInCatch"), LiveOut("usedInCatch")); context.Validate("Method(3);", LiveOut("usedInCatch")); context.Validate("Method(4);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void NestedCatch_LiveOut_ConsecutiveOuterCatch() { const string code = """ var usedInCatch = 100; try { try { Method(usedInCatch); Method(0); } catch { usedInCatch = 200; Method(1); } } catch (ArgumentNullException) { Method(2); Method(usedInCatch); } catch (IOException) { Method(3); Method(usedInCatch); } catch (NullReferenceException) { Method(4); Method(usedInCatch); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("usedInCatch")); // No LiveOut, because inner Catch catches all exceptions and reassigns value context.Validate("Method(1);", LiveOut("usedInCatch")); context.Validate("Method(2);", LiveIn("usedInCatch")); context.Validate("Method(3);", LiveIn("usedInCatch")); context.Validate("Method(4);", LiveIn("usedInCatch")); context.ValidateExit(); } [TestMethod] public void Catch_TryHasLocalLifetimeRegion_LiveIn() { const string code = """ try { Method(0); var t = true || true; // This causes LocalLivetimeRegion to be generated } catch { Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("t = true || true", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Catch_VariableUsedAfter_LiveIn_LiveOut() { const string code = """ try { Method(0); } catch { Method(1); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Catch_WithThrowStatement_LiveIn() { const string code = """ var usedInTry = 42; var usedInTryUnreachable = 42; var usedInCatch = 42; var usedInCatchUnreachable = 42; try { Method(usedInTry); throw new Exception(); Method(usedInTryUnreachable); // Unreachable } catch { Method(usedInCatch); throw new Exception(); Method(usedInCatchUnreachable); // Unreachable } Method(intParameter); // Unreachable """; var context = CreateContextCS(code); // LVA doesn't care if it's reachable. Blocks still should have LiveIn and LiveOut context.ValidateEntry(); // intParameter is used only in unreachable path => not visible here context.Validate("Method(usedInTry);", LiveIn("usedInTry", "usedInCatch"), LiveOut("usedInCatch")); // Doesn't see usedInTryUnreachable nor intParameter context.Validate("Method(usedInTryUnreachable);", LiveIn("intParameter", "usedInTryUnreachable", "usedInCatch"), LiveOut("intParameter", "usedInCatch")); context.Validate("Method(usedInCatch);", LiveIn("usedInCatch")); // Doesn't see usedInCatchUnreachable nor intParameter context.Validate("Method(usedInCatchUnreachable);", LiveIn("intParameter", "usedInCatchUnreachable"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Catch_WithThrowStatement_Conditional_LiveIn() { const string code = """ try { Method(0); } catch { Method(1); if (boolParameter) { throw new Exception(); } Method(2); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter", "boolParameter"), LiveOut("intParameter", "boolParameter")); context.Validate("Method(0);", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter", "boolParameter")); context.Validate("Method(1);", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter")); context.Validate("boolParameter", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter")); context.Validate("Method(2);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Catch_Rethrow_LiveIn() { const string code = """ try { Method(0); } catch { Method(1); throw; } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);"); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Catch_NotLiveIn_NotLiveOut() { const string code = """ try { Method(0); } catch { intParameter = 42; Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);"); context.Validate("Method(intParameter);"); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Catch_Nested_LiveIn() { const string code = """ var outer = 42; var inner = 42; try { try { Method(0); } catch { Method(inner); } Method(1); } catch { Method(outer); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("inner", "outer"), LiveOut("inner", "outer")); context.Validate("Method(inner);", LiveIn("inner", "outer"), LiveOut("outer")); context.Validate("Method(1);", LiveIn("outer"), LiveOut("outer")); context.Validate("Method(outer);", LiveIn("outer")); context.Validate("Method(2);"); context.ValidateExit(); } [TestMethod] public void Catch_InvalidSyntax_LiveIn() { /* Entry 0 * | * Block 1 * Method(0) * Method(intParameter) * | * Exit 2 */ const string code = """ // Error CS1003 Syntax error, 'try' expected // Error CS1514 { expected // Error CS1513 } expected catch { Method(0); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Catch_Loop_Propagates_LiveIn_LiveOut() { const string code = """ var variableUsedInCatch = 42; A: Method(intParameter); if (boolParameter) goto B; try { Method(0); } catch { Method(variableUsedInCatch); } goto A; B: Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter);", LiveIn("boolParameter", "intParameter", "variableUsedInCatch"), LiveOut("boolParameter", "intParameter", "variableUsedInCatch")); context.Validate("Method(0);", LiveIn("boolParameter", "intParameter", "variableUsedInCatch"), LiveOut("boolParameter", "intParameter", "variableUsedInCatch")); context.Validate("Method(variableUsedInCatch);", LiveIn("boolParameter", "intParameter", "variableUsedInCatch"), LiveOut("boolParameter", "intParameter", "variableUsedInCatch")); context.Validate("Method(1);"); } [TestMethod] public void Catch_ExVariable_LiveIn() { const string code = """ try { Method(0); } catch (Exception ex) { if (boolParameter) { Method(ex.HResult); } } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(0);", LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("ex")); // ex doesn't live in here, becase this blocks starts with SimpleAssignmentOperation: (Exception ex) context.Validate("Method(ex.HResult);", LiveIn("ex")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Catch_SingleType_LiveIn() { const string code = """ var usedAfter = 42; var usedInTry = 42; var usedInCatch = 42; try { Method(usedInTry); } catch (Exception ex) { Method(intParameter, usedInCatch, ex.HResult); } Method(usedAfter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(usedInTry);", LiveIn("usedInTry", "usedAfter", "usedInCatch", "intParameter"), LiveOut("usedAfter", "usedInCatch", "intParameter")); context.Validate("Method(intParameter, usedInCatch, ex.HResult);", LiveIn("intParameter", "usedInCatch", "usedAfter"), LiveOut("usedAfter")); context.Validate("Method(usedAfter);", LiveIn("usedAfter")); context.ValidateExit(); } [TestMethod] public void Catch_SingleTypeWhenCondition_LiveIn() { const string code = """ var usedAfter = 42; var usedInTry = 42; var usedInCatch = 42; try { Method(usedInTry); } catch (Exception ex) when (ex.InnerException == null) { Method(intParameter, usedInCatch, ex.HResult); } Method(usedAfter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(usedInTry);", LiveIn("usedInTry", "usedAfter", "usedInCatch", "intParameter"), LiveOut("usedAfter", "usedInCatch", "intParameter")); context.Validate("Method(intParameter, usedInCatch, ex.HResult);", LiveIn("intParameter", "usedInCatch", "usedAfter", "ex"), LiveOut("usedAfter")); context.Validate("Method(usedAfter);", LiveIn("usedAfter")); context.ValidateExit(); } [TestMethod] public void Catch_SingleTypeWhenConditionReferencingArgument_LiveIn() { const string code = """ try { Method(0); } catch (Exception ex) when (boolParameter) { Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(0);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Catch_MultipleTypes_LiveIn() { const string code = """ var usedAfter = 42; var usedInTry = 42; var usedInCatchA = 42; var usedInCatchB = 42; try { Method(usedInTry); } catch (FormatException ex) { Method(intParameter, usedInCatchA, ex.HResult); } catch (Exception ex) { Method(intParameter, usedInCatchB, ex.HResult); } Method(usedAfter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(usedInTry);", LiveIn("usedInTry", "usedAfter", "usedInCatchA", "usedInCatchB", "intParameter"), LiveOut("usedAfter", "usedInCatchA", "usedInCatchB", "intParameter")); // ex doesn't live in here, because the blocks starts with SimpleAssignmentOperation: (Exception ex) context.Validate("Method(intParameter, usedInCatchA, ex.HResult);", LiveIn("intParameter", "usedInCatchA", "usedAfter"), LiveOut("usedAfter")); context.Validate("Method(intParameter, usedInCatchB, ex.HResult);", LiveIn("intParameter", "usedInCatchB", "usedAfter"), LiveOut("usedAfter")); context.Validate("Method(usedAfter);", LiveIn("usedAfter")); context.ValidateExit(); } [TestMethod] public void Catch_AssignedInTry_LiveOut() { const string code = """ int variable = 42; Method(0); try { Method(1); // Can throw variable = 0; } catch { Method(variable); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveOut("variable")); context.Validate("Method(variable);", LiveIn("variable")); context.Validate("Method(2);"); } [TestMethod] public void Catch_When_AssignedInTry_LiveOut() { const string code = """ int variable = 42; Method(0); try { Method(1); // Can throw variable = 0; } catch when(variable == 0) { Method(2); } Method(3); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveOut("variable")); context.Validate("variable == 0", LiveIn("variable")); context.Validate("Method(2);"); context.Validate("Method(3);"); } [TestMethod] public void Catch_Loop_Propagates_LiveIn() { const string code = """ var variable = 0; Method(0); while (variable < 5) { variable++; Method(1); try { Method(2); // Can throw return; } catch (TimeoutException) { Method(3); // Continue loop to the next try } Method(4); } Method(5); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(2);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(3);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(4);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(5);"); } [TestMethod] public void Throw_NestedCatch_LiveOut() { const string code = """ var value = 100; try { try { Method(value); Method(0); } catch { value = 200; throw; } } catch { Method(value); Method(1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value")); context.Validate("value = 200;", LiveOut("value")); context.Validate("Method(1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Throw_NestedCatch_LiveInInConsecutiveOuterCatch() { const string code = """ var value = 100; try { try { Method(value); Method(0); } catch { value = 200; throw; } } catch (ArgumentNullException) { Method(value); Method(1); } catch (IOException) { Method(value); Method(2); } catch (NullReferenceException) { Method(value); Method(3); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value")); context.Validate("value = 200;", LiveOut("value")); context.Validate("Method(1);", LiveIn("value")); context.Validate("Method(2);", LiveIn("value")); context.Validate("Method(3);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Throw_NestedCatch_LiveInInConsecutiveOuterCatchNewThrow() { const string code = """ var value = 100; try { try { Method(value); Method(0); } catch (ArgumentNullException ex) { Method(value); Method(1); throw new Exception("Message", ex); } } catch (IOException) { Method(value); Method(2); } catch (NullReferenceException) { Method(value); Method(3); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); context.Validate("Method(1);", LiveIn("value"), LiveOut("value")); context.Validate("Method(2);", LiveIn("value")); context.Validate("Method(3);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Throw_NestedCatch_OuterCatchRethrows_LiveOutOuterCatch() { const string code = """ var value = 100; try { try { try { Method(value); Method(0); } catch { value = 200; throw; } } catch // Outer catch { Method(value); Method(1); throw; } } catch { Method(value); Method(2); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value")); context.Validate("value = 200;", LiveOut("value")); context.Validate("Method(1);", LiveIn("value"), LiveOut("value")); context.Validate("Method(2);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Finally_LiveIn() { const string code = """ try { Method(0); } finally { Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Finally_VariableUsedAfter_LiveIn_LiveOut() { const string code = """ try { Method(0); } finally { Method(1); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_VariableUsedAfter_FinallyHasLocalLifetimeRegion_LiveIn_LiveOut() { const string code = """ try { Method(0); } finally { Method(1); var t = true || true; // This causes LocalLivetimeRegion to be generated, but there's also one empty block outside if before the exit branch } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_WithThrowStatement_LiveIn() { const string code = """ try { Method(0); } finally { Method(1); throw new Exception(); Method(2); // Unreachable } Method(intParameter); // Unreachable """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);", LiveIn("intParameter"), LiveOut("intParameter")); // LVA doesn't care if it's reachable. Blocks still should have LiveIn and LiveOut context.Validate("Method(2);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_WithThrowStatementAsSingleExit_LiveIn() { const string code = """ try { Method(0); } finally { Method(1); throw new Exception(); } Method(intParameter); // Unreachable """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(1);", LiveIn("intParameter"), LiveOut("intParameter")); // LVA doesn't care if it's reachable. Blocks still should have LiveIn and LiveOut context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_WithThrowStatement_Conditional_LiveIn() { const string code = """ try { Method(0); } finally { Method(1); if (boolParameter) { throw new Exception(); } Method(2); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter", "boolParameter"), LiveOut("intParameter", "boolParameter")); context.Validate("Method(0);", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter", "boolParameter")); context.Validate("Method(1);", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter")); context.Validate("boolParameter", LiveIn("intParameter", "boolParameter"), LiveOut("intParameter")); context.Validate("Method(2);", LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_WithThrowStatementInTry_LiveOut() { const string code = """ int variable = 42; Method(0); try { Method(1); throw new Exception(); } finally { Method(variable); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(variable);", LiveIn("variable")); context.Validate("Method(2);"); } [TestMethod] public void Finally_WithThrowStatementInTry_LiveOut_FilteredCatch() { const string code = """ int variable = 42; Method(0); try { Method(1); throw new Exception(); } catch when (Property == 42) { } // FilterAndHandlerRegion catch (FormatException) { } catch (ArgumentException ex) { } finally { Method(variable); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveIn("variable"), LiveOut("variable")); context.Validate("Method(variable);", LiveIn("variable")); context.Validate("Method(2);"); } [TestMethod] [DataRow("catch")] [DataRow("catch (Exception)")] [DataRow("catch (Exception ex)")] public void Finally_WithThrowStatementInTry_LiveOut_CatchAll(string catchAll) { var code = $$""" int variable = 42; Method(0); try { Method(1); throw new Exception(); } {{catchAll}} { Method(2); variable = 0; } finally { Method(variable); } Method(3); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);"); context.Validate("Method(1);"); context.Validate("Method(2);", LiveOut("variable")); context.Validate("Method(variable);", LiveIn("variable")); context.Validate("Method(3);"); } [TestMethod] public void Finally_NotLiveIn_NotLiveOut() { const string code = """ try { Method(0); } finally { intParameter = 42; Method(intParameter); } Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);"); context.Validate("Method(intParameter);"); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Finally_Nested_LiveIn() { const string code = """ var outer = 42; var inner = 42; try { try { Method(0); } finally { Method(inner); } Method(1); } finally { Method(outer); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("inner", "outer"), LiveOut("inner", "outer")); context.Validate("Method(inner);", LiveIn("inner", "outer"), LiveOut("outer")); context.Validate("Method(1);", LiveIn("outer"), LiveOut("outer")); context.Validate("Method(outer);", LiveIn("outer")); context.Validate("Method(2);"); context.ValidateExit(); } [TestMethod] public void Finally_Nested_NoInstructionBetweenFinally_LiveIn() { const string code = """ var outer = 42; var inner = 42; try { try { Method(0); } finally { Method(inner); } // No action here, finally branch is crossing both regions } finally { Method(outer); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("inner", "outer"), LiveOut("inner", "outer")); context.Validate("Method(inner);", LiveIn("inner", "outer"), LiveOut("outer")); context.Validate("Method(outer);", LiveIn("outer")); context.Validate("Method(2);"); context.ValidateExit(); } [TestMethod] public void NestedFinally_LiveOut_OuterCatch() { const string code = """ var usedInOuterCatch = 0; Method(0); try { Method(1); try { Method(2); } finally { usedInOuterCatch = 2; Method(3); } } catch (Exception ex) { Method(usedInOuterCatch); Method(4); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("usedInOuterCatch")); context.Validate("Method(1);", LiveIn("usedInOuterCatch"), LiveOut("usedInOuterCatch")); context.Validate("Method(2);", LiveIn("usedInOuterCatch"), LiveOut("usedInOuterCatch")); context.Validate("Method(3);", LiveOut("usedInOuterCatch")); context.Validate("Method(4);", LiveIn("usedInOuterCatch")); context.ValidateExit(); } [TestMethod] public void NestedFinally_LiveOut_NestedTryInOuterCatch() { const string code = """ var usedInOuterTry = 0; Method(0); try { Method(1); try { Method(2); } finally { usedInOuterTry = 2; Method(3); } } catch { try { Method(usedInOuterTry); Method(4); } catch { Method(5); } } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("usedInOuterTry")); context.Validate("Method(1);", LiveIn("usedInOuterTry"), LiveOut("usedInOuterTry")); context.Validate("Method(2);", LiveIn("usedInOuterTry"), LiveOut("usedInOuterTry")); context.Validate("Method(3);", LiveOut("usedInOuterTry")); context.Validate("Method(4);", LiveIn("usedInOuterTry")); context.Validate("Method(5);"); context.ValidateExit(); } [TestMethod] public void NestedFinally_LiveOut_NestedFinallyInOuterCatch() { const string code = """ var usedInOuterFinally = 0; Method(0); try { Method(usedInOuterFinally); Method(1); try { Method(2); } finally { usedInOuterFinally = 2; // Compliant - used in outer finally Method(3); } } catch { try { Method(4); } finally { Method(usedInOuterFinally); Method(5); } } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("usedInOuterFinally")); context.Validate("Method(1);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(2);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(3);", LiveOut("usedInOuterFinally")); context.Validate("Method(4);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(5);", LiveIn("usedInOuterFinally")); context.ValidateExit(); } [TestMethod] public void NestedFinally_LiveOut_NestedFinallyInOuter_ConsecutiveCatch() { const string code = """ var usedInOuterFinally = 0; Method(0); try { Method(usedInOuterFinally); Method(1); try { Method(2); } finally { usedInOuterFinally = 2; // Compliant - used in outer finally Method(3); } } catch(NotImplementedException) { Method(4); } catch { try { Method(5); } finally { Method(6); Method(usedInOuterFinally); } } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("usedInOuterFinally")); context.Validate("Method(1);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(2);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(3);", LiveOut("usedInOuterFinally")); context.Validate("Method(4);"); context.Validate("Method(5);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(6);", LiveIn("usedInOuterFinally")); context.ValidateExit(); } [TestMethod] public void NestedFinally_LiveOutNestedFinallyInOuter_FinallyHasLocalLifetime() { const string code = """ var usedInOuterFinally = 0; Method(0); try { Method(usedInOuterFinally); Method(1); try { Method(2); } finally { var t = usedInOuterFinally > 1 ? 1 : 0; // This causes LocalLifetimeRegion to be generated } } catch { try { Method(4); } finally { Method(5); Method(usedInOuterFinally); } } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("usedInOuterFinally")); context.Validate("Method(1);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(2);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("t = usedInOuterFinally > 1 ? 1 : 0", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(4);", LiveIn("usedInOuterFinally"), LiveOut("usedInOuterFinally")); context.Validate("Method(5);", LiveIn("usedInOuterFinally")); context.ValidateExit(); } [TestMethod] public void Finally_ForEach_LiveIn() { const string code = """ var outer = 42; try { Method(0); foreach (var arg in args) // Produces implicit finally { } // No action here, finally branch is crossing both regions } finally { Method(outer); } Method(1); """; var context = CreateContextCS(code, null, "object[] args"); context.ValidateEntry(LiveIn("args"), LiveOut("args")); context.Validate("Method(0);", LiveIn("outer", "args"), LiveOut("outer", "args")); context.Validate("Method(outer);", LiveIn("outer")); context.Validate("Method(1);"); context.ValidateExit(); } [TestMethod] public void Finally_InvalidSyntax_LiveIn() { /* Entry 0 * | * Block 1 * Method(0) * Method(intParameter) * | * Exit 2 */ const string code = """ // Error CS1003 Syntax error, 'try' expected // Error CS1514 { expected // Error CS1513 } expected finally { Method(0); } Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(0);", LiveIn("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] public void Finally_Loop_Propagates_LiveIn_LiveOut() { const string code = """ A: Method(intParameter); if (boolParameter) goto B; try { Method(0); } finally { Method(1); } goto A; B: Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(0);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(1);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(2);"); } [TestMethod] public void Finally_Loop_Propagates_FinallyHasLocalLifetimeRegion_LiveIn_LiveOut() { const string code = """ A: Method(intParameter); if (boolParameter) goto B; try { Method(0); } finally { var t = true || true; // This causes LocalLivetimeRegion to be generated Method(1); } goto A; B: Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(0);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(1);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(2);"); } [TestMethod] public void Finally_AssignedInTry_LiveOut() { const string code = """ int variable = 42; Method(0); try { Method(1); // Can throw variable = 0; } finally { Method(variable); } Method(2); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveOut("variable")); context.Validate("Method(1);", LiveOut("variable")); context.Validate("Method(variable);", LiveIn("variable")); context.Validate("Method(2);"); } [TestMethod] public void TryCatchFinally_LiveIn() { const string code = """ var usedInTry = 42; var usedInCatch = 42; var usedInFinally = 42; var usedAfter = 42; try { Method(usedInTry); } catch { Method(usedInCatch); } finally { Method(usedInFinally); } Method(usedAfter); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(usedInTry);", LiveIn("usedInTry", "usedInCatch", "usedInFinally", "usedAfter"), LiveOut("usedInCatch", "usedInFinally", "usedAfter")); context.Validate("Method(usedInCatch);", LiveIn("usedInCatch", "usedInFinally", "usedAfter"), LiveOut("usedInFinally", "usedAfter")); context.Validate("Method(usedInFinally);", LiveIn("usedInFinally", "usedAfter"), LiveOut("usedAfter")); context.Validate("Method(usedAfter);", LiveIn("usedAfter")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_Rethrow_ValueLivesOut() { const string code = """ var value = 0; try { Method(0); value = 42; } catch { Method(value); value = 1; throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_Rethrow_ValueLivesOut_FinallyHasLocalLifetimeRegion() { const string code = """ var value = 0; try { } catch { value = 1; throw; } finally { var local = ""; // This causes LocalLifetimeRegion to be generated Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_RethrowOuterFinally() { const string code = """ var value = 0; try { Method(0); try { Method(0); value = 42; } catch { Method(value); value = 2; throw; } } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 2;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_RethrowOuterCatchFinally() { const string code = """ var value = 0; try { Method(0); try { Method(1); value = 42; } catch { value = 2; throw; } } catch { Method(2); } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 2;", LiveOut("value")); context.Validate("Method(2);", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_RethrowOuterCatchRethrowFinally() { const string code = """ var value = 0; try { Method(0); try { Method(1); value = 42; } catch { value = 2; throw; } } catch { Method(2); throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 2;", LiveOut("value")); context.Validate("Method(2);", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_RethrowCatchRethrowOuterFinally() { const string code = """ var value = 0; try { Method(0); try { Method(1); value = 42; } catch { value = 2; throw; } finally { Method(value + 1); } } finally { Method(value + 2); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 2;", LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 2);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_ConsecutiveCatchAllThrowRethrowFinally() { const string code = """ var value = 0; try { Method(0); value = 42; } catch (IOException) { Method(value); value = 1; throw; } catch { Method(value); value = 2; throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value"), LiveOut("value")); context.Validate("value = 2;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_ConsecutiveCatchRethrow() { const string code = """ var value = 0; try { Method(0); value = 42; } catch (IOException) { Method(value); value = 1; throw; } catch (ArgumentNullException) { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_FilteredCatchRethrowFinally() { const string code = """ var value = 0; try { Method(0); value = 42; } catch (IOException) { Method(value); value = 1; throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_MultipleFilteredCatchRethrowFinally() { const string code = """ var value = 0; try { Method(0); value = 42; } catch (IOException) { Method(value); value = 1; throw; } catch (ArgumentException) { Method(value); value = 2; throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value"), LiveOut("value")); context.Validate("value = 2;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void TryCatchFinally_MultipleFilteredCatchNewThrowFinally() { const string code = """ var value = 0; try { Method(0); value = 42; } catch (IOException ex) { Method(value); value = 1; throw new Exception("Message", ex); } catch (ArgumentException) { Method(value); value = 2; throw; } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 1;", LiveIn("value"), LiveOut("value")); context.Validate("value = 2;", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Throw_NestedCatch_LiveInInConsecutiveOuterCatchFinally() { const string code = """ var value = 100; try { try { Method(value); Method(0); } catch { value = 200; throw; } } catch (ArgumentNullException) { Method(value); Method(1); } catch (IOException) { Method(value); Method(2); } catch (NullReferenceException) { Method(value); Method(3); } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); context.Validate("value = 200;", LiveOut("value")); context.Validate("Method(1);", LiveIn("value"), LiveOut("value")); context.Validate("Method(2);", LiveIn("value"), LiveOut("value")); context.Validate("Method(3);", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } [TestMethod] public void Throw_NestedCatch_OuterCatchRethrows_LiveOutOuterCatchFinally() { const string code = """ var value = 100; try { try { try { Method(value); Method(0); } catch { value = 200; throw; } } catch // Outer catch { Method(value); Method(1); throw; } } catch { Method(value); Method(2); } finally { Method(value + 1); } """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("Method(0);", LiveIn("value"), LiveOut("value")); context.Validate("value = 200;", LiveOut("value")); context.Validate("Method(1);", LiveIn("value"), LiveOut("value")); context.Validate("Method(2);", LiveIn("value"), LiveOut("value")); context.Validate("Method(value + 1);", LiveIn("value")); context.ValidateExit(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/RoslynLiveVariableAnalysisTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CFG; using SonarAnalyzer.CFG.LiveVariableAnalysis; using SonarAnalyzer.CFG.Roslyn; using SonarAnalyzer.CFG.Syntax.Utilities; using SonarAnalyzer.CSharp.Core.Syntax.Extensions; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; namespace SonarAnalyzer.Test.LiveVariableAnalysis; [TestClass] public partial class RoslynLiveVariableAnalysisTest { private enum ExpectedKind { None, LiveIn, LiveOut, Captured } [TestMethod] public void WriteOnly() { var code = """ int a = 1; var b = Method(0); var c = 2 + 3; """; CreateContextCS(code).ValidateAllEmpty(); } [TestMethod] public void ProcessParameterReference_LiveIn() { var code = """ Method(intParameter); IsMethod(boolParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter", "boolParameter"), LiveOut("intParameter", "boolParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter", "boolParameter")); } [TestMethod] public void ProcessParameterReference_UsedAsOutArgument_NotLiveIn_NotLiveOut() { var code = "Main(true, 0, out outParameter, ref refParameter);"; var context = CreateContextCS(code, additionalParameters: "out int outParameter, ref int refParameter"); context.ValidateEntry(LiveIn("refParameter"), LiveOut("refParameter")); context.Validate(code, LiveIn("refParameter")); } [TestMethod] public void ProcessParameterReference_InNameOf_NotLiveIn_NotLiveOut() { var code = "Method(nameof(intParameter));"; CreateContextCS(code).ValidateAllEmpty(); } [TestMethod] public void ProcessParameterReference_Assigned_NotLiveIn_LiveOut() { var code = """ intParameter = 42; if (boolParameter) return; Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); } [TestMethod] public void ProcessParameterReference_MemberBinding_LiveIn() { var code = "Method(intParameter.ToString());"; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter.ToString());", LiveIn("intParameter")); } [TestMethod] public void ProcessParameterReference_MemberBindingByReference_LiveIn() { var code = "Capturing(intParameter.CompareTo);"; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Capturing(intParameter.CompareTo);", LiveIn("intParameter")); } [TestMethod] public void ProcessParameterReference_MemberBindingByReference_DifferentCfgOnNetFx_LiveIn() { // This specific char/string scenario produces different CFG shape under .NET Framework build. We have a syntax-based solution in place to support it. // https://github.com/dotnet/roslyn/issues/56644 var code = """ char[] charArray = null; var ret = false; var stringVariable = "Lorem Ipsum"; if (boolParameter) ret = charArray.Any(stringVariable.Contains); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("ret = charArray.Any(stringVariable.Contains);", LiveIn("charArray", "stringVariable")); } [TestMethod] public void ProcessParameterReference_Reassigned_LiveIn() { var code = """ intParameter = intParameter + 42; stringParameter = stringParameter.Replace('a', 'b'); Method(intParameter); Method(stringParameter); """; var context = CreateContextCS(code, additionalParameters: "string stringParameter"); context.ValidateEntry(LiveIn("intParameter", "stringParameter"), LiveOut("intParameter", "stringParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter", "stringParameter")); } [TestMethod] public void ProcessParameterReference_SelfAssigned_LiveIn() { var code = """ intParameter = intParameter; Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); } [TestMethod] public void UsedAfterBranch_LiveOut() { /* Binary * / \ * Jump Simple * return Method() * \ / * Exit */ var code = """ if (boolParameter) return; Method(intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("boolParameter", LiveIn("boolParameter", "intParameter"), LiveOut("intParameter")); context.Validate("Method(intParameter);", LiveIn("intParameter")); context.ValidateExit(); } [TestMethod] [DataRow("Capturing(x => field + variable + intParameter);", DisplayName = "SimpleLambda")] [DataRow("Capturing((x) => field + variable + intParameter);", DisplayName = "ParenthesizedLambda")] [DataRow("Capturing(x => { Func xxx = () => field + variable + intParameter; return xxx();});", DisplayName = "NestedLambda")] [DataRow("VoidDelegate d = delegate { Method(field + variable + intParameter);};", DisplayName = "AnonymousMethod")] [DataRow("var items = from xxx in new int[] { 42, 100 } where xxx > field + variable + intParameter select xxx;", DisplayName = "Query")] public void Captured_NotLiveIn_NotLiveOut(string capturingStatement) { /* Entry * | * Block 1 * / \ * | Block 2 * | Method() * \ / * Exit */ var code = $""" var variable = 42; {capturingStatement} if (boolParameter) return; Method(field, variable, intParameter); """; capturingStatement.Should().Contain("field + variable + intParameter"); var context = CreateContextCS(code); var expectedCaptured = Captured("variable", "intParameter"); context.ValidateEntry(expectedCaptured, LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", expectedCaptured, LiveIn("boolParameter")); context.Validate("Method(field, variable, intParameter);", expectedCaptured); context.ValidateExit(expectedCaptured); } [TestMethod] public void Captured_StaticLambda_NotLiveIn_NotLiveOut() { var code = """ Capturing(static x => x + 2); if (boolParameter) return; Method(0); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter")); context.Validate("Method(0);"); } [TestMethod] public void Assigned_NotLiveIn_NotLiveOut() { /* Entry * | * Block 1 * boolParameter * / \ * | Block 2 * | intParameter=0 * \ / * Exit */ var code = """ if (boolParameter) return; intParameter = 0; """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter")); context.Validate("intParameter = 0;"); context.ValidateExit(); } [TestMethod] public void LongPropagationChain_LiveIn_LiveOut() { /* Entry * | * Block 1 -------+ * declare | * | | * Block 2 ------+| * use & assign || * | || * Block 3 -----+|| * assign ||| * | vvv * Block 4 ---> Exit * use */ var code = """ var value = 0; if (boolParameter) return; Method(value); value = 1; if (boolParameter) return; value = 42; if (boolParameter) return; Method(intParameter, value); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("value = 0", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter", "value")); context.Validate("Method(value);", LiveIn("boolParameter", "intParameter", "value"), LiveOut("boolParameter", "intParameter")); context.Validate("value = 42;", LiveIn("boolParameter", "intParameter"), LiveOut("value", "intParameter")); context.Validate("Method(intParameter, value);", LiveIn("value", "intParameter")); context.ValidateExit(); } [TestMethod] public void BranchedPropagationChain_LiveIn_LiveOut_CS() { /* Binary * boolParameter * / \ * / \ * / \ * Binary Binary * firstBranch secondBranch * / \ / \ * / \ / \ * Simple Simple Simple Simple * firstTrue firstFalse secondTrue secondFalse * \ / \ / * \ / \ / * Simple Simple * first second * \ / * \ / * Simple * reassigned * everywhere */ var code = """ var everywhere = 42; var reassigned = 42; var first = 42; var firstTrue = 42; var firstFalse = 42; var second = 42; var secondTrue = 42; var secondFalse = 42; var firstCondition = boolParameter; var secondCondition = boolParameter; if (boolParameter) { if (firstCondition) { Method(firstTrue); } else { Method(firstFalse); } Method(first); } else { if (secondCondition) { Method(secondTrue); } else { Method(secondFalse); } Method(second); } reassigned = 0; Method(everywhere, reassigned); """; var context = CreateContextCS(code); context.Validate( "everywhere = 42", LiveIn("boolParameter"), LiveOut("everywhere", "firstCondition", "firstTrue", "firstFalse", "first", "secondCondition", "secondTrue", "secondFalse", "second")); // First block context.Validate( "firstCondition", LiveIn("everywhere", "firstCondition", "firstTrue", "firstFalse", "first"), LiveOut("everywhere", "firstTrue", "firstFalse", "first")); context.Validate( "Method(firstTrue);", LiveIn("everywhere", "firstTrue", "first"), LiveOut("everywhere", "first")); context.Validate( "Method(firstFalse);", LiveIn("everywhere", "firstFalse", "first"), LiveOut("everywhere", "first")); context.Validate( "Method(first);", LiveIn("everywhere", "first"), LiveOut("everywhere")); // Second block context.Validate( "secondCondition", LiveIn("everywhere", "secondCondition", "secondTrue", "secondFalse", "second"), LiveOut("everywhere", "secondTrue", "secondFalse", "second")); context.Validate( "Method(secondTrue);", LiveIn("everywhere", "secondTrue", "second"), LiveOut("everywhere", "second")); context.Validate( "Method(secondFalse);", LiveIn("everywhere", "secondFalse", "second"), LiveOut("everywhere", "second")); context.Validate( "Method(second);", LiveIn("everywhere", "second"), LiveOut("everywhere")); // Common end context.Validate("Method(everywhere, reassigned);", LiveIn("everywhere")); } [TestMethod] public void BranchedPropagationChain_LiveIn_LiveOut_VB() { /* Binary * boolParameter * / \ * / \ * / \ * Binary Binary * firstBranch secondBranch * / \ / \ * / \ / \ * Simple Simple Simple Simple * firstTrue firstFalse secondTrue secondFalse * \ / \ / * \ / \ / * Simple Simple * first second * \ / * \ / * Simple * reassigned * everywhere */ var code = """ Dim Everywhere As Integer = 42 Dim Reassigned As Integer = 42 Dim First As Integer = 42 Dim FirstTrue As Integer = 42 Dim FirstFalse As Integer = 42 Dim Second As Integer = 42 Dim SecondTrue As Integer = 42 Dim SecondFalse As Integer = 42 Dim FirstCondition As Boolean = BoolParameter Dim SecondCondition As Boolean = BoolParameter If BoolParameter Then If (FirstCondition) Then Method(FirstTrue) Else Method(FirstFalse) End If Method(First) Else If SecondCondition Then Method(SecondTrue) Else Method(SecondFalse) End If Method(Second) End If Reassigned = 0 Method(Everywhere, Reassigned) """; var context = CreateContextVB(code); context.Validate( "Everywhere As Integer = 42", LiveIn("BoolParameter"), LiveOut("Everywhere", "FirstCondition", "FirstTrue", "FirstFalse", "First", "SecondCondition", "SecondTrue", "SecondFalse", "Second")); // First block context.Validate( "FirstCondition", LiveIn("Everywhere", "FirstCondition", "FirstTrue", "FirstFalse", "First"), LiveOut("Everywhere", "FirstTrue", "FirstFalse", "First")); context.Validate( "Method(FirstTrue)", LiveIn("Everywhere", "FirstTrue", "First"), LiveOut("Everywhere", "First")); context.Validate( "Method(FirstFalse)", LiveIn("Everywhere", "FirstFalse", "First"), LiveOut("Everywhere", "First")); context.Validate( "Method(First)", LiveIn("Everywhere", "First"), LiveOut("Everywhere")); // Second block context.Validate( "SecondCondition", LiveIn("Everywhere", "SecondCondition", "SecondTrue", "SecondFalse", "Second"), LiveOut("Everywhere", "SecondTrue", "SecondFalse", "Second")); context.Validate( "Method(SecondTrue)", LiveIn("Everywhere", "SecondTrue", "Second"), LiveOut("Everywhere", "Second")); context.Validate( "Method(SecondFalse)", LiveIn("Everywhere", "SecondFalse", "Second"), LiveOut("Everywhere", "Second")); context.Validate( "Method(Second)", LiveIn("Everywhere", "Second"), LiveOut("Everywhere")); // Common end context.Validate("Method(Everywhere, Reassigned)", LiveIn("Everywhere")); } [TestMethod] public void ProcessBlockInternal_EvaluationOrder_UsedBeforeAssigned_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(variable, variable = 42); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(variable, variable = 42);", LiveIn("variable")); } [TestMethod] public void ProcessBlockInternal_EvaluationOrder_UsedBeforeAssignedInSubexpression_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(1 + 1 + Method(variable), variable = 42); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(1 + 1 + Method(variable), variable = 42);", LiveIn("variable")); } [TestMethod] public void ProcessBlockInternal_EvaluationOrder_AssignedBeforeUsed_NotLiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(variable = 42, variable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(variable = 42, variable);"); } [TestMethod] public void ProcessBlockInternal_EvaluationOrder_AssignedBeforeUsedInSubexpression_NotLiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(variable = 42, 1 + 1 + Method(variable)); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(variable = 42, 1 + 1 + Method(variable));"); } [TestMethod] public void ProcessLocalReference_InNameOf_NotLiveIn_NotLiveOut() { var code = """ var variable = 42; if (boolParameter) return; Method(nameof(variable)); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(nameof(variable));"); } [TestMethod] public void ProcessLocalReference_LocalScopeSymbol_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(intParameter, variable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter, variable);", LiveIn("intParameter", "variable")); } [TestMethod] public void ProcessLocalReference_ReassignedBeforeLastBlock_LiveIn() { var code = """ var variable = 0; variable = 42; if (boolParameter) return; Method(intParameter, variable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter, variable);", LiveIn("intParameter", "variable")); } [TestMethod] public void ProcessLocalReference_ReassignedInLastBlock_NotLiveIn() { var code = """ var variable = 0; if (boolParameter) return; variable = 42; Method(intParameter, variable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(intParameter, variable);", LiveIn("intParameter")); } [TestMethod] public void ProcessLocalReference_GlobalScopeSymbol_NotLiveIn_NotLiveOut() { var code = """ var s = new Sample(); Method(field, s.Property); """; CreateContextCS(code).ValidateAllEmpty(); } [TestMethod] public void ProcessLocalReference_UndefinedSymbol_NotLiveIn_NotLiveOut() { var code = """Method(undefined); // Error CS0103 The name 'undefined' does not exist in the current context"""; CreateContextCS(code).ValidateAllEmpty(); } [TestMethod] public void ProcessLocalReference_UsedAsOutArgument_NotLiveIn() { var code = """ var refVariable = 42; var outVariable = 42; outParameter = 0; if (boolParameter) return; Main(true, 0, out outVariable, ref refVariable); """; var context = CreateContextCS(code, additionalParameters: "out int outParameter, ref int refParameter"); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Main(true, 0, out outVariable, ref refVariable);", LiveIn("refVariable")); } [TestMethod] public void ProcessLocalReference_NotAssigned_LiveIn_LiveOut() { var code = """ var variable = intParameter; if (boolParameter) return; Method(variable, intParameter); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("variable = intParameter", LiveIn("intParameter", "boolParameter"), LiveOut("variable", "intParameter")); context.Validate("Method(variable, intParameter);", LiveIn("variable", "intParameter")); } [TestMethod] public void ProcessLocalReference_VariableDeclarator_NotLiveIn_LiveOut() { var code = """ int intValue = 42; var varValue = 42; if (intValue == 0) Method(intValue, varValue); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("intValue = 42", LiveOut("intValue", "varValue")); context.Validate("Method(intValue, varValue);", LiveIn("intValue", "varValue")); } [TestMethod] public void ProcessLocalReference_MemberBinding_LiveIn() { var code = """ var variable = 42; if (boolParameter) Method(variable.ToString()); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Method(variable.ToString());", LiveIn("variable")); } [TestMethod] public void ProcessLocalReference_MemberBindingByReference_LiveIn() { var code = """ var variable = 42; if (boolParameter) Capturing(variable.CompareTo); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("Capturing(variable.CompareTo);", LiveIn("variable")); } [TestMethod] public void ProcessLocalReference_Reassigned_LiveIn() { var code = """ var intVariable = 40; var stringVariable = "Lorem Ipsum"; if (boolParameter) return; intVariable = intVariable + 2; stringVariable = stringVariable.Replace('a', 'b'); Method(intVariable); Method(stringVariable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("intVariable", "stringVariable")); context.Validate("Method(intVariable);", LiveIn("intVariable", "stringVariable")); } [TestMethod] public void ProcessLocalReference_SelfAssigned_LiveIn() { var code = """ var intVariable = 42; if (boolParameter) return; intVariable = intVariable; Method(intVariable); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter"), LiveOut("boolParameter")); context.Validate("boolParameter", LiveIn("boolParameter"), LiveOut("intVariable")); context.Validate("Method(intVariable);", LiveIn("intVariable")); } [TestMethod] public void ProcessSimpleAssignment_Discard_NotLiveIn_NotLiveOut() { var code = """_ = intParameter;"""; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("_ = intParameter;", LiveIn("intParameter")); } [TestMethod] public void ProcessSimpleAssignment_UndefinedSymbol_NotLiveIn_NotLiveOut() { var code = """ undefined = intParameter; // Error CS0103 The name 'undefined' does not exist in the current context if (undefined == 0) // Error CS0103 The name 'undefined' does not exist in the current context Method(undefined); // Error CS0103 The name 'undefined' does not exist in the current context """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(undefined);"); } [TestMethod] public void ProcessSimpleAssignment_GlobalScoped_NotLiveIn_NotLiveOut() { var code = """ field = intParameter; if (field == 0) Method(field); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("intParameter"), LiveOut("intParameter")); context.Validate("Method(field);"); } [TestMethod] public void ProcessSimpleAssignment_LocalScoped_NotLiveIn_LiveOut() { var code = """ int value; value = 42; if (value == 0) Method(value); """; var context = CreateContextCS(code); context.ValidateEntry(); context.Validate("value = 42;", LiveOut("value")); context.Validate("Method(value);", LiveIn("value")); } [TestMethod] public void ProcessVariableInForeach_Declared_LiveIn_LiveOut() { /* * Entry * | * Block 1 * new[] {1, 2, 3} * | * Block 2 <------------------------+ * MoveNext branch | * F| \ Else | * v \ | * Exit Block 3 | * i=capture.Current | * Method(i, intParameter) -->+ * | A * Block 4 ------------------>+ * Method(i) */ var code = """ foreach(var i in new int[] {1, 2, 3}) { Method(i, intParameter); if (boolParameter) Method(i); } """; var context = CreateContextCS(code); context.Validate(context.Cfg.Blocks[1], LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate(context.Cfg.Blocks[2], LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(i, intParameter);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter", "i")); context.Validate("Method(i);", LiveIn("boolParameter", "intParameter", "i"), LiveOut("boolParameter", "intParameter")); context.ValidateExit(); } [TestMethod] public void NestedImplicitFinally_Lock_ForEach_LiveIn() { const string code = """ lock(args) { var value = 42; Method(0); foreach (var inner in args) { Method(1); if (inner == null) value = 0; } Method(value); } Method(2); """; var context = CreateContextCS(code, null, "string[] args"); context.Validate("Method(0);", LiveIn("args", null), LiveOut("args", "value", null)); // The null-named symbol is implicit `bool LockTaken` from the lock(args) statement context.Validate("Method(1);", LiveIn("value", null), LiveOut("value", null)); context.Validate("Method(value);", LiveIn(null, "value"), LiveOut([null])); context.Validate("Method(2);"); context.ValidateExit(); } [TestMethod] public void NestedImplicitFinally_ForEach_ForEach_LiveIn() { const string code = """ foreach (var outer in args) { var value = 42; Method(0); foreach (var inner in args) { Method(1); if (inner == null) value = 0; } Method(value); } Method(2); """; var context = CreateContextCS(code, null, "string[] args"); context.Validate("Method(0);", LiveIn("args"), LiveOut("args", "value")); context.Validate("Method(1);", LiveIn("args", "value"), LiveOut("args", "value")); context.Validate("Method(value);", LiveIn("args", "value"), LiveOut("args")); context.Validate("Method(2);"); context.ValidateExit(); } [TestMethod] public void Loop_Propagates_LiveIn_LiveOut() { var code = """ A: Method(intParameter); if (boolParameter) goto B; Method(0); goto A; B: Method(1); """; var context = CreateContextCS(code); context.ValidateEntry(LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("boolParameter", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(0);", LiveIn("boolParameter", "intParameter"), LiveOut("boolParameter", "intParameter")); context.Validate("Method(1);"); } [TestMethod] public void InvokedDelegate_LiveIn() { var code = "action();"; var context = CreateContextCS(code, additionalParameters: "Action action"); context.ValidateEntry(LiveIn("action"), LiveOut("action")); context.Validate("action();", LiveIn("action")); } [TestMethod] public void ReturningDelegate_LiveIn() { var code = """ using System; public class Sample { public Func Main(int intParameter) => () => intParameter; } """; var context = new Context(code, AnalyzerLanguage.CSharp); context.ValidateEntry(Captured("intParameter")); context.Validate("() => intParameter", Captured("intParameter")); } [TestMethod] public void ReturningDelegate_NestedByReference_LiveIn() { var code = """ using System; using System.Threading.Tasks; public class Sample { public Action Main(Func asyncHandler) { return x => { Task Wrap() => asyncHandler(x); RunTask(Wrap); }; } private void RunTask(Func f) { } } """; var context = new Context(code, AnalyzerLanguage.CSharp); context.ValidateEntry(Captured("asyncHandler")); } [TestMethod] public void ReturningDelegate_Nested_LiveIn() { var code = """ using System; public class Sample { public Func Main(int intParameter) => () => { int NestedLocalFunction() => intParameter; return NestedLocalFunction(); }; } """; var context = new Context(code, AnalyzerLanguage.CSharp); context.ValidateEntry(Captured("intParameter")); } [TestMethod] public void PropertyWithWriteOnly() { var code = """ public class Sample { public int Property => 42; } """; new Context(code, SyntaxKind.NumericLiteralExpression).ValidateAllEmpty(); } [TestMethod] public void AnonyomousFunctionWriteOnly() { var code = """ using System; public class Sample { public Func Method(int captureMe) => () => { return captureMe; }; } """; new Context(code, SyntaxKind.ParenthesizedLambdaExpression).ValidateAllEmpty(); } [TestMethod] public void ConstructorWriteOnly() { var code = """ using System; public class Sample { public Sample() { var variable = 42; } } """; new Context(code, SyntaxKind.Block).ValidateAllEmpty(); } private static Context CreateContextCS(string methodBody, string localFunctionName = null, string additionalParameters = null) { additionalParameters = additionalParameters is null ? null : ", " + additionalParameters; var code = $$""" using System; using System.Collections.Generic; using System.IO; using System.Linq; public class Sample { delegate void VoidDelegate(); private int field; public int Property { get; set; } public void Main(bool boolParameter, int intParameter{{additionalParameters}}) { {{methodBody}} } private int Method(params int[] args) => 42; private string Method(params string[] args) => null; private bool IsMethod(params bool[] args) => true; private void Capturing(Func f) { } private void Capturing(Func f) { } } """; return new Context(code, AnalyzerLanguage.CSharp, localFunctionName); } private static Context CreateContextVB(string methodBody) { var code = $""" Public Class Sample Public Delegate Sub VoidDelegate() Private Field As Integer Private Property Prop As Integer Public Sub Main(BoolParameter As Boolean, IntParameter As Integer) {methodBody} End Sub Private Function Method(ParamArray Args() As Integer) As Integer End Function Private Function Method(ParamArray Args() As String) As String End Function Private Function IsMethod(ParamArray Args() As Boolean) As Boolean End Function Private Sub Capturing(f As Func(Of Integer, Integer)) End Sub End Class """; return new Context(code, AnalyzerLanguage.VisualBasic); } private static Expected LiveIn(params string[] names) => new(names, ExpectedKind.LiveIn); private static Expected LiveOut(params string[] names) => new(names, ExpectedKind.LiveOut); private static Expected Captured(params string[] names) => new(names, ExpectedKind.Captured); private record Expected(string[] Names, ExpectedKind Kind); private class Context { public readonly RoslynLiveVariableAnalysis Lva; public readonly ControlFlowGraph Cfg; public Context(string code, AnalyzerLanguage language, string localFunctionName = null) { Cfg = TestCompiler.CompileCfg(code, language, code.Contains("// Error CS"), localFunctionName); SyntaxClassifierBase syntaxClassifier = language.LanguageName switch { LanguageNames.CSharp => CSharpSyntaxClassifier.Instance, LanguageNames.VisualBasic => VisualBasicSyntaxClassifier.Instance, _ => throw new UnexpectedLanguageException(language) }; Lva = new RoslynLiveVariableAnalysis(Cfg, syntaxClassifier, default); const string Separator = "----------"; Console.WriteLine(Separator); Console.WriteLine(CfgSerializer.Serialize(Lva)); Console.WriteLine(Separator); } public Context(string code, SyntaxKind syntaxKind) { var (tree, model) = TestCompiler.Compile(code, false, AnalyzerLanguage.CSharp); var node = tree.GetRoot().DescendantNodes().First(x => x.RawKind == (int)syntaxKind); Cfg = node.CreateCfg(model, default); Lva = new RoslynLiveVariableAnalysis(Cfg, CSharpSyntaxClassifier.Instance, default); // FIXME: null? } public void ValidateAllEmpty() { foreach (var block in Cfg.Blocks) { Validate(block, null, []); } } public void ValidateEntry(params Expected[] expected) => Validate(Cfg.EntryBlock, null, expected); public void ValidateExit(params Expected[] expected) { Array.TrueForAll(expected, x => x.Kind == ExpectedKind.Captured).Should().BeTrue("Exit block should expect only Captured variables."); Validate(Cfg.ExitBlock, null, expected); } public void Validate(string withSyntax, params Expected[] expected) { var block = Cfg.Blocks.Single(x => x.Kind == BasicBlockKind.Block && (withSyntax is null || x.OperationsAndBranchValue.Any(operation => operation.Syntax.ToString() == withSyntax))); Validate(block, withSyntax, expected); } public void Validate(string withSyntax, params Expected[] expected) where TOperation : IOperation { var block = Cfg.Blocks.Single(x => x.Kind == BasicBlockKind.Block && x.OperationsAndBranchValue.OfType().Any(operation => operation.Syntax.ToString() == withSyntax)); Validate(block, withSyntax, expected); } public void Validate(BasicBlock block, params Expected[] expected) => Validate(block, null, expected); public void Validate(BasicBlock block, string blockSuffix, params Expected[] expected) { var empty = new Expected([], ExpectedKind.None); var expectedLiveIn = expected.SingleOrDefault(x => x.Kind == ExpectedKind.LiveIn) ?? empty; var expectedLiveOut = expected.SingleOrDefault(x => x.Kind == ExpectedKind.LiveOut) ?? empty; var expectedCaptured = expected.SingleOrDefault(x => x.Kind == ExpectedKind.Captured) ?? empty; Lva.LiveIn(block).Select(x => x.Name).Should().BeEquivalentTo(expectedLiveIn.Names, $"{block.Kind} #{block.Ordinal} {blockSuffix}"); Lva.LiveOut(block).Select(x => x.Name).Should().BeEquivalentTo(expectedLiveOut.Names, $"{block.Kind} #{block.Ordinal} {blockSuffix}"); Lva.CapturedVariables.Select(x => x.Name).Should().BeEquivalentTo(expectedCaptured.Names, $"{block.Kind} #{block.Ordinal} {blockSuffix}"); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/LiveVariableAnalysis/SonarLiveVariableAnalysisTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CFG; using SonarAnalyzer.CFG.Sonar; using SonarAnalyzer.CFG.Sonar.Test; using SonarAnalyzer.CSharp.Core.LiveVariableAnalysis; using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.Test.LiveVariableAnalysis; [TestClass] public class SonarLiveVariableAnalysisTest { [TestMethod] public void WriteOnly() { var code = """ int a = 1; var b = Method(); var c = 2 + 3; """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void UsedBeforeAssigned_LiveIn() { var code = """ Method(intParameter); IsMethod(boolParameter); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("intParameter", "boolParameter")); } [TestMethod] public void UsedAfterBranch_LiveOut() { /* Binary * / \ * Jump Simple * return Method() * \ / * Exit */ var code = """ if (boolParameter) return; Method(intParameter); """; var context = new Context(code); var binary = context.Cfg.EntryBlock; var jump = context.Block(); var simple = context.Block(); var exit = context.Cfg.ExitBlock; context.Validate(binary, new LiveIn("boolParameter", "intParameter"), new LiveOut("intParameter")); context.Validate(jump); context.Validate(simple, new LiveIn("intParameter")); context.Validate(exit); } [TestMethod] public void Captured_NotLiveIn_NotLiveOut() { /* Binary * / \ * Jump Simple * return Method() * \ / * Exit */ var code = """ Capturing(() => intParameter); if (boolParameter) return; Method(intParameter); """; var context = new Context(code); var binary = context.Cfg.EntryBlock; var jump = context.Block(); var simple = context.Block(); var exit = context.Cfg.ExitBlock; context.Validate(binary, new Captured("intParameter"), new LiveIn("boolParameter")); context.Validate(jump, new Captured("intParameter")); context.Validate(simple, new Captured("intParameter")); context.Validate(exit, new Captured("intParameter")); } [TestMethod] public void Assigned_NotLiveIn_NotLiveOut() { /* Binary * / \ * Jump Simple * return intParameter=0 * \ / * Exit */ var code = """ if (boolParameter) return; intParameter = 0; """; var context = new Context(code); var binary = context.Cfg.EntryBlock; var jump = context.Block(); var simple = context.Block(); var exit = context.Cfg.ExitBlock; context.Validate(binary, new LiveIn("boolParameter")); context.Validate(jump); context.Validate(simple); context.Validate(exit); } [TestMethod] public void LongPropagationChain_LiveIn_LiveOut() { /* Binary -> Jump (return) ----+ * declare | * | | * Binary -> Jump (return) ---+| * use & assign || * | || * Binary -> Jump (return) --+|| * assign ||| * | vvv * Simple -----------------> Exit * use */ var code = """ var value = 0; if (boolParameter) return; Method(value); value = 42; if (boolParameter) return; value = 42; if (boolParameter) return; Method(intParameter, value); """; var context = new Context(code); var allBinary = context.Cfg.Blocks.OfType().ToArray(); var simple = context.Block(); context.Validate(allBinary[0], new LiveIn("boolParameter", "intParameter"), new LiveOut("boolParameter", "intParameter", "value")); context.Validate(allBinary[1], new LiveIn("boolParameter", "intParameter", "value"), new LiveOut("boolParameter", "intParameter")); context.Validate(allBinary[2], new LiveIn("boolParameter", "intParameter"), new LiveOut("value", "intParameter")); context.Validate(simple, new LiveIn("value", "intParameter")); context.Validate(context.Cfg.ExitBlock); } [TestMethod] public void BranchedPropagationChain_LiveIn_LiveOut() { /* Binary * boolParameter * / \ * / \ * / \ * Binary Binary * firstBranch secondBranch * / \ / \ * / \ / \ * Simple Simple Simple Simple * firstTrue firstFalse secondTrue secondFalse * \ / \ / * \ / \ / * Simple Simple * first second * \ / * \ / * Simple * reassigned * everywhere */ var code = """ var everywhere = 42; var reassigned = 42; var first = 42; var firstTrue = 42; var firstFalse = 42; var second = 42; var secondTrue = 42; var secondFalse = 42; var firstCondition = boolParameter; var secondCondition = boolParameter; if (boolParameter) { if (firstCondition) { Method(firstTrue); } else { Method(firstFalse); } Method(first); } else { if (secondCondition) { Method(secondTrue); } else { Method(secondFalse); } Method(second); } reassigned = 0; Method(everywhere, reassigned); """; var context = new Context(code); context.Validate( context.Block("boolParameter"), new LiveIn("boolParameter"), new LiveOut("everywhere", "firstCondition", "firstTrue", "firstFalse", "first", "secondCondition", "secondTrue", "secondFalse", "second")); // First block context.Validate( context.Block("firstCondition"), new LiveIn("everywhere", "firstCondition", "firstTrue", "firstFalse", "first"), new LiveOut("everywhere", "firstTrue", "firstFalse", "first")); context.Validate( context.Block("Method(firstTrue)"), new LiveIn("everywhere", "firstTrue", "first"), new LiveOut("everywhere", "first")); context.Validate( context.Block("Method(firstFalse)"), new LiveIn("everywhere", "firstFalse", "first"), new LiveOut("everywhere", "first")); context.Validate( context.Block("Method(first)"), new LiveIn("everywhere", "first"), new LiveOut("everywhere")); // Second block context.Validate( context.Block("secondCondition"), new LiveIn("everywhere", "secondCondition", "secondTrue", "secondFalse", "second"), new LiveOut("everywhere", "secondTrue", "secondFalse", "second")); context.Validate( context.Block("Method(secondTrue)"), new LiveIn("everywhere", "secondTrue", "second"), new LiveOut("everywhere", "second")); context.Validate( context.Block("Method(secondFalse)"), new LiveIn("everywhere", "secondFalse", "second"), new LiveOut("everywhere", "second")); context.Validate( context.Block("Method(second)"), new LiveIn("everywhere", "second"), new LiveOut("everywhere")); // Common end context.Validate(context.Block("Method(everywhere, reassigned)"), new LiveIn("everywhere")); } [TestMethod] public void ProcessIdentifier_InNameOf_NotLiveIn_NotLiveOut() { var code = """Method(nameof(intParameter));"""; var context = new Context(code); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void ProcessIdentifier_LocalScopeSymbol_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; Method(intParameter, variable); """; var context = new Context(code); context.Validate(context.Block(), new LiveIn("intParameter", "variable")); } [TestMethod] public void ProcessIdentifier_GlobalScopeSymbol_NotLiveIn_NotLiveOut() { var code = """ var s = new Sample(); Method(field, s.Property); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void ProcessIdentifier_UndefinedSymbol_NotLiveIn_NotLiveOut() { var code = """Method(undefined);"""; var context = new Context(code); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void ProcessIdentifier_RefOutArgument_NotLiveIn_LiveOut() { var code = """ outParameter = true; Method(outParameter, refParameter); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void ProcessIdentifier_Assigned_NotLiveIn_LiveOut() { // Jump (intParamter=42; goto) --> Jump (label) --> Simple (Method) --> Exit var code = """ intParameter = 42; goto A; A: Method(intParameter); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveOut("intParameter")); context.Validate(context.Block(), new LiveIn("intParameter")); } [TestMethod] public void ProcessIdentifier_NotAssigned_LiveIn_LiveOut() { // Jump (intParamter=42; goto) --> Jump (label) --> Simple (Method) --> Exit var code = """ var value = intParameter; goto A; A: Method(value, intParameter); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("intParameter"), new LiveOut("intParameter", "value")); context.Validate(context.Block(), new LiveIn("value", "intParameter")); } [TestMethod] public void ProcessSimpleAssignment_Discard_NotLiveIn_NotLiveOut() { var code = """_ = intParameter;"""; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("intParameter")); } [TestMethod] public void ProcessSimpleAssignment_UndefinedSymbol_NotLiveIn_NotLiveOut() { var code = """ undefined = intParameter; if (undefined == 0) Method(undefined); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("intParameter")); } [TestMethod] public void ProcessSimpleAssignment_GlobalScoped_NotLiveIn_NotLiveOut() { var code = """ field = intParameter; if (field == 0) Method(field); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("intParameter")); } [TestMethod] public void ProcessSimpleAssignment_LocalScoped_NotLiveIn_LiveOut() { var code = """ int value; value = 42; if (value == 0) Method(value); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveOut("value")); } [TestMethod] public void ProcessVariableDeclarator_NotLiveIn_LiveOut() { var code = """ int intValue = 42; var varValue = 42; if (intValue == 0) Method(intValue, varValue); """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveOut("intValue", "varValue")); } [TestMethod] public void ProcessVariableInForeach_Declared_LiveIn_LiveOut() { /* * ForEach * | * Binary <-----------+ * | \ true | * v \ | * Exit Simple | * Method(i) -->+ */ var code = """ foreach(var i in new int[] {1, 2, 3}) { Method(i, intParameter); } """; var context = new Context(code); context.Validate(context.Block(), new LiveIn("intParameter"), new LiveOut("intParameter")); context.Validate(context.Block(), new LiveIn("intParameter"), new LiveOut("intParameter", "i")); context.Validate(context.Block(), new LiveIn("intParameter", "i"), new LiveOut("intParameter")); } [TestMethod] public void ProcessVariableInForeach_Reused_LiveIn_LiveOut() { var code = """ int i = 42; foreach(i in new int[] {1, 2, 3}) { Method(i, intParameter); } """; var context = new Context(code); context.Validate(context.Block(), new LiveIn("intParameter"), new LiveOut("intParameter", "i")); context.Validate(context.Block(), new LiveIn("intParameter", "i"), new LiveOut("intParameter", "i")); context.Validate(context.Block(), new LiveIn("intParameter", "i"), new LiveOut("intParameter", "i")); } [TestMethod] public void StaticLocalFunction_ExpressionLiveIn() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) => a + 1; """; var context = new Context(code, "LocalFunction"); context.Validate(context.Cfg.EntryBlock, new LiveIn("a")); } [TestMethod] public void StaticLocalFunction_ExpressionNotLiveIn() { var code = """ outParameter = LocalFunction(0); static int LocalFunction(int a) => 42; """; var context = new Context(code, "LocalFunction"); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void StaticLocalFunction_LiveIn() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) { return a + 1; } """; var context = new Context(code, "LocalFunction"); context.Validate(context.Cfg.EntryBlock, new LiveIn("a")); } [TestMethod] public void StaticLocalFunction_NotLiveIn() { var code = """ outParameter = LocalFunction(0); static int LocalFunction(int a) { return 42; } """; var context = new Context(code, "LocalFunction"); context.Validate(context.Cfg.EntryBlock); } [TestMethod] public void StaticLocalFunction_Recursive() { var code = """ outParameter = LocalFunction(intParameter); static int LocalFunction(int a) { if(a <= 0) return 0; else return LocalFunction(a - 1); } """; var context = new Context(code, "LocalFunction"); context.Validate(context.Cfg.EntryBlock, new LiveIn("a"), new LiveOut("a")); } [TestMethod] public void UsingBlock_LiveInUntilTheEnd() { /* Jump * new ms * | * Binary * Method(ms.Length) * / \ * Simple \ * Method(0) | * \ / * Using * | * Exit */ var code = """ using (var ms = new System.IO.MemoryStream()) { Method(ms.Length); if (boolParameter) Method(0); } """; var context = new Context(code); context.Validate(context.Block(), new LiveIn("boolParameter"), new LiveOut("boolParameter", "ms")); context.Validate(context.Block(), new LiveIn("boolParameter", "ms"), new LiveOut("ms")); context.Validate(context.Block(), new LiveIn("ms"), new LiveOut("ms")); context.Validate(context.Block(), new LiveIn("ms")); context.Validate(context.Block()); } [TestMethod] public void UsingVar_NotSupported() { /* Bad CFG Shape * * Binary * ms = new * Method(ms.Length) * / \ * Simple \ * Method(0) | * \ / * Exit */ var code = """ using var ms = new System.IO.MemoryStream(); Method(ms.Length); if (boolParameter) Method(0); """; var context = new Context(code); context.Validate(context.Block(), new LiveIn("boolParameter")); context.Validate(context.Block()); context.Validate(context.Block()); } [TestMethod] public void LocalFunctionInvocation_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); int LocalFunction() => variable; """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("boolParameter"), new LiveOut("variable")); context.Validate(context.Block(), new LiveIn("boolParameter"), new LiveOut("variable")); // "variable" should also LiveIn context.Validate(context.Block(), new LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_Generic_LiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); T LocalFunction() => variable; """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("boolParameter"), new LiveOut("variable")); context.Validate(context.Block(), new LiveIn("boolParameter"), new LiveOut("variable")); context.Validate(context.Block(), new LiveIn("variable")); } [TestMethod] public void LocalFunctionInvocation_NotLiveIn() { var code = """ var variable = 42; if (boolParameter) return; LocalFunction(); Method(variable); void LocalFunction() { variable = 0; } """; var context = new Context(code); context.Validate(context.Cfg.EntryBlock, new LiveIn("boolParameter")); context.Validate(context.Block(), new LiveIn("boolParameter")); context.Validate(context.Block()); } private class Context { public readonly SonarCSharpLiveVariableAnalysis Lva; public readonly IControlFlowGraph Cfg; public Context(string methodBody, string localFunctionName = null) { var code = $$""" public class Sample { private int field; public int Property { get; set; } public void Main(bool boolParameter, int intParameter, out int outParameter, ref int refParameter) { {{methodBody}} } private int Method(params int[] args) => 42; private string Method(params string[] args) => null; private bool IsMethod(params bool[] args) => true; private void Capturing(System.Func f) { } } """; var (method, model) = SonarControlFlowGraphTest.CompileWithMethodBody(code); IMethodSymbol symbol; CSharpSyntaxNode body; if (localFunctionName is null) { symbol = model.GetDeclaredSymbol(method); body = method.Body; } else { var function = (LocalFunctionStatementSyntaxWrapper)method .DescendantNodes() .Single(x => x.IsKind(SyntaxKindEx.LocalFunctionStatement) && ((LocalFunctionStatementSyntaxWrapper)x).Identifier.Text == localFunctionName); symbol = model.GetDeclaredSymbol(function) as IMethodSymbol; body = (CSharpSyntaxNode)function.Body ?? function.ExpressionBody; } Cfg = CSharpControlFlowGraph.Create(body, model); Console.WriteLine(CfgSerializer.Serialize(Cfg)); Lva = new SonarCSharpLiveVariableAnalysis(Cfg, symbol, model, default); } public Block Block(string withInstruction = null) where TBlock : Block => Cfg.Blocks.Single(x => x.GetType().Equals(typeof(TBlock)) && (withInstruction is null || x.Instructions.Any(instruction => instruction.ToString() == withInstruction))); public void Validate(Block block, params Expected[] expected) { // This is not very nice from OOP perspective, but it makes UTs above easy to read. var expectedLiveIn = expected.OfType().SingleOrDefault() ?? new LiveIn(); var expectedLiveOut = expected.OfType().SingleOrDefault() ?? new LiveOut(); var expectedCaptured = expected.OfType().SingleOrDefault() ?? new Captured(); Lva.LiveIn(block).Select(x => x.Name).Should().BeEquivalentTo(expectedLiveIn.Names, block.GetType().Name); Lva.LiveOut(block).Select(x => x.Name).Should().BeEquivalentTo(expectedLiveOut.Names, block.GetType().Name); Lva.CapturedVariables.Select(x => x.Name).Should().BeEquivalentTo(expectedCaptured.Names, block.GetType().Name); } } private abstract class Expected { public readonly string[] Names; protected Expected(string[] names) => Names = names; } private class LiveIn : Expected { public LiveIn(params string[] names) : base(names) { } } private class LiveOut : Expected { public LiveOut(params string[] names) : base(names) { } } private class Captured : Expected { public Captured(params string[] names) : base(names) { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Metrics/CSharpExecutableLinesMetricTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Metrics; namespace SonarAnalyzer.Metrics.Test; [TestClass] public class CSharpExecutableLinesMetricTest { private const string RazorFile = "Test.razor"; [TestMethod] public void GetLineNumbers_NoExecutableLines() => AssertLineNumbersOfExecutableLines( @"using System; using System.Linq; namespace Test { class Program { static void Main(string[] args) { } } }"); [TestMethod] public void GetLineNumbers_Class() => AssertLineNumbersOfExecutableLines( @"class Program { }"); [TestMethod] public void GetLineNumbers_CheckedUnchecked() => AssertLineNumbersOfExecutableLines( @"class Program { static void Main(string[] args) { checked // +1 { unchecked // +1 { } } } }", 5, 7); [TestMethod] public void GetLineNumbers_Blocks() => AssertLineNumbersOfExecutableLines( @"class Program { unsafe static void Main(int[] arr, object obj) { lock (obj) { } // +1 fixed (int* p = arr) { } // +1 unsafe { } // +1 using ((System.IDisposable)obj) { } // +1 } }", 5, 6, 7, 8); [TestMethod] public void GetLineNumbers_Statements() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int i) { ; // +1 i++; // +1 } }", 5, 6); [TestMethod] public void GetLineNumbers_Loops() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int[] arr) { do {} // +1 while (true); foreach (var a in arr) { }// +1 for (;;) { } // +1 while // +1 (true) { } } }", 5, 7, 8, 9); [TestMethod] public void GetLineNumbers_Conditionals() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int? i, string s) { if (true) { } // +1 label: // +1 switch (i) // +1 { case 1: case 2: default: break; // +1 } var x = s?.Length; // +1 var xx = i == 1 ? 1 : 1; // +1 } }", 5, 6, 7, 12, 14, 15); [TestMethod] public void GetLineNumbers_Conditionals2() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(System.Exception ex) { goto home; // +1 throw ex; // +1 home: // +1 while (true) // +1 { continue; // +1 break; // +1 } return; // +1 } }", 5, 6, 7, 9, 11, 12, 14); [TestMethod] public void GetLineNumbers_Yields() => AssertLineNumbersOfExecutableLines( @"using System.Collections.Generic; namespace Test { class Program { IEnumerable Foo() { yield return """"; // +1 yield break; // +1 } } }", 9, 10); [TestMethod] public void GetLineNumbers_AccessAndInvocation() => AssertLineNumbersOfExecutableLines( @"class Program { static void Main(string[] args) { var x = args.Length; // +1 args.ToString(); // +1 } }", 5, 6); [TestMethod] public void GetLineNumbers_Initialization() => AssertLineNumbersOfExecutableLines( @"class Program { static string GetString() => """"; static void Main() { var arr = new object(); var arr2 = new int[] { 1 }; // +1 var ex = new System.Exception() { Source = GetString(), // +1 HelpLink = """" }; } }", 8, 12); [TestMethod] public void GetLineNumbers_PropertySet() => AssertLineNumbersOfExecutableLines( @"class Program { int Prop { get; set; } void Foo() { Prop = 1; // + 1 } }", 7); [TestMethod] public void GetLineNumbers_PropertyGet() => AssertLineNumbersOfExecutableLines( @"class Program { int Prop { get; set; } void Foo() { var x = Prop; } }"); [TestMethod] public void GetLineNumbers_Lambdas() => AssertLineNumbersOfExecutableLines( @"using System; using System.Linq; using System.Collections.Generic; class Program { static void Main(string[] args) { var x = args.Where(s => s != null) // +1 .Select(s => s.ToUpper()) // +1 .OrderBy(s => s) // +1 .ToList(); } }", 8, 9, 10); [TestMethod] public void GetLineNumbers_TryCatch() => AssertLineNumbersOfExecutableLines( @"using System; class Program { static void Main(string[] args) { try { Main(null); // +1 } catch (InvalidOperationException) { } catch (ArgumentNullException ane) when (ane.ToString() != null) // +1 { } finally { } } }", 8, 13); [TestMethod] public void GetLineNumbers_MultipleStatementsSameLine() => AssertLineNumbersOfExecutableLines( @"using System; class Program { public void Foo(int x) { int i = 0; if (i == 0) {i++;i--;} else // +1 { while(true){i--;} } // +1 } }", 5, 6); [TestMethod] public void GetLineNumbers_DoWhile() => AssertLineNumbersOfExecutableLines( @"class Program { static void Main(string[] args) { do // +1 { } while ( true ) ; } }", 5); [TestMethod] public void GetLineNumbers_ClassExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; public class ComplicatedCode { [ExcludeFromCodeCoverage] public string ComplexFoo() { string text = null; return text.ToLower(); string Method(string s) { return s.ToLower(); } } [SomeAttribute] public string ComplexFoo2() { string text = null; return text.ToLower(); // +1 string Method(string s) { return s.ToLower(); // +1 } } } public class SomeAttribute : System.Attribute { }", 19, 22); [TestMethod] public void GetLineNumbers_AttributeOnLocalFunctionExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; public class ComplicatedCode { [SomeAttribute] public string ComplexFoo2() { string text = null; return text.ToLower(); // +1 [ExcludeFromCodeCoverage] string ComplexFoo() { string text = null; return text.ToLower(); // +1 , FP } } } public class SomeAttribute : System.Attribute { }", 8, 14); [TestMethod] [DataRow("ExcludeFromCodeCoverage")] [DataRow("ExcludeFromCodeCoverage()")] [DataRow("ExcludeFromCodeCoverageAttribute")] [DataRow("ExcludeFromCodeCoverageAttribute()")] [DataRow("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute()")] public void GetLineNumbers_ExcludeFromCodeCoverage_AttributeVariants(string attribute) => AssertLineNumbersOfExecutableLines( @$"using System.Diagnostics.CodeAnalysis; public class ComplicatedCode {{ [{attribute}] public string ComplexFoo() {{ string text = null; return text.ToLower(); }} }}"); [TestMethod] public void GetLineNumbers_RecordExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; [ExcludeFromCodeCoverage] record Program { static void Main(string[] args) { Main(null); } }"); [TestMethod] public void GetLineNumbers_RecordStructExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; [ExcludeFromCodeCoverage] record struct Program { static void Main(string[] args) { Main(null); } }"); [TestMethod] public void GetLineNumbers_StructExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; namespace project_1 { [ExcludeFromCodeCoverage] struct Program { static void Foo() { Foo(); } } }"); [TestMethod] public void GetLineNumbers_ConstructorExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; class Program { int count; [ExcludeFromCodeCoverage] public Program() : this(1) { count = 123; // excluded } public Program(int initialCount) { count = initialCount; // +1 } }", 14); #if NET [TestMethod] public void GetLineNumbers_PropertyExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; class EventClass { private int _value; [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int Value1 { get { return _value; } // Excluded set { _value = value; } // Excluded } public int Value2 { get { return _value; } // +1 [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] set { _value = value; } // Excluded } public int Value3 { get { return _value; } // +1 set { _value = value; } // +1 } public int Value4 { get { return _value; } // +1 init { _value = value; } // +1 } public int Value5 { get { return _value; } // +1 [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] init { _value = value; } // Excluded } }", 15, 22, 23, 28, 29, 34); #endif [TestMethod] public void GetLineNumbers_EventExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; class EventClass { private System.EventHandler _explicitEvent; [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public event System.EventHandler ExplicitEvent1 { add { _explicitEvent += value; } // Excluded remove { _explicitEvent -= value; } // Excluded } public event System.EventHandler ExplicitEvent2 { add { _explicitEvent += value; } // +1 [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] remove { _explicitEvent -= value; } // excluded } public event System.EventHandler ExplicitEvent3 { add { _explicitEvent += value; } // +1 remove { _explicitEvent -= value; } // +1 } }", 15, 22, 23); [TestMethod] public void GetLineNumbers_PartialClassesExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; [ExcludeFromCodeCoverage] partial class Program { int FooProperty { get { return 1; } } // excluded } partial class Program { void Method1() { System.Console.WriteLine(); // other class partial is excluded -> excluded } } partial class AnotherClass { void Method2() { System.Console.WriteLine(); // +1 } } ", 20); [TestMethod] public void GetLineNumbers_PartialMethodsExcluded() => AssertLineNumbersOfExecutableLines( @"using System.Diagnostics.CodeAnalysis; partial class Program { [ExcludeFromCodeCoverage] partial void Method1(); } partial class Program { partial void Method1() { System.Console.WriteLine(); // other partial part of method is excluded -> excluded } } partial class AnotherClass { partial void Method2(); } partial class AnotherClass { [ExcludeFromCodeCoverage] partial void Method2() { System.Console.WriteLine(); // excluded } }"); [TestMethod] public void GetLineNumbers_AttributeAreIgnored() => AssertLineNumbersOfExecutableLines( @"using System.Reflection; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo(""FOO"")]"); [TestMethod] public void GetLineNumbers_OnlyAttributeAreIgnored() => AssertLineNumbersOfExecutableLines( @"[AnAttribute] public class Foo { [AnAttribute] void MyCode([AnAttribute] string foo) { System.Console.WriteLine(); // +1 } } public class AnAttribute : System.Attribute { }", 7); [TestMethod] public void GetLineNumbers_AttributeOnLambdaIsIgnored() => AssertLineNumbersOfExecutableLines( @"using System; using System.Linq; using System.Collections.Generic; class Test { static void Main(string[] args) { [AnAttribute] List LambdaWithAttribute(string[] args) => args.Where(s => s != null).ToList(); // +1 } } public class AnAttribute : System.Attribute { }", 10); [TestMethod] public void GetLineNumbers_ExpressionsAreCounted() => AssertLineNumbersOfExecutableLines( @"class Program { public void Foo(bool flag)     {         if (flag) // +1         {             flag = true; flag = false; flag = true; // +1         }     } }", 5, 7); [TestMethod] public void GetLineNumbers_MultiLineLoop() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int[] arr) { for // +1 ( // +0 int i=0; // +0 i < 10; // +0 i++ // +0 ) // +0 { // +0 } } }", 5); [TestMethod] public void GetLineNumbers_SwitchStatementWithMultipleCases() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int? i) { switch (i) // +1 { case 1: System.Console.WriteLine(4); // +1 break; // +1 case 2: System.Console.WriteLine(4); // +1 break; // +1 default: break; // +1 } } }", 5, 8, 9, 11, 12, 14); [TestMethod] public void GetLineNumbers_SwitchExpressionWithMultipleCases() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int? i, string s) { var x = s switch { ""a"" => true, ""b"" => false, _ => true }; var y = s switch { ""a"" => Foo(""b""), // +1 ""b"" => Foo(""a""), // +1 _ => false }; } bool Foo(string s) => true; }", 13, 14); [TestMethod] public void GetLineNumbers_MultiLineInterpolatedString() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int? i, string s) { string x = ""someString""; x += @$""This is a Multi Line interpolated string {i} {s}""; } }", 6); [TestMethod] public void GetLineNumbers_MultiLineInterpolatedStringWithMultipleLineExpressions() => AssertLineNumbersOfExecutableLines( @"public class C { public string M(int i) { return @$"" {(i == 1 ? Bar(1) : Bar(2))} {(i == 2 ? Bar(2) : Bar(3))} { Bar(5)} ""; } public string Bar(int i) => ""y"" + i; }", 5, 6, 7, 8); [TestMethod] public void GetLineNumbers_UsingDeclaration() => AssertLineNumbersOfExecutableLines( @"class Program { void Foo(int? i, string s) { using var file = new System.IO.StreamWriter(""WriteLines2.txt""); } }"); [TestMethod] public void GetLineNumbers_LocalFunctions() => AssertLineNumbersOfExecutableLines( @"class Program { int M1() { int y = MyMethod(); // +1 MyMethod(); // +1 return 1; // +1 int MyMethod() => 0; // Not counted } int M2() { int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; } }", 5, 6, 7, 15); #if NET [TestMethod] public void GetLineNumbers_IndicesAndRanges() => AssertLineNumbersOfExecutableLines( @"using System; class Program { void M() { string s = null; string[] subArray; var words = new string[] { ""The"", ""quick"", ""brown"", ""fox"", ""jumped"", ""over"", ""the"", ""lazy"", ""dog"" }; s = words[^1]; subArray = words[1..4]; } }", 9, 20, 21); #endif [TestMethod] public void GetLineNumbers_NullCoalescingAssignment() => AssertLineNumbersOfExecutableLines( @"using System; using System.Collections.Generic; class Program { void M() { List numbers = null; int? i = null; numbers ??= new List(); } }", 10); [TestMethod] public void GetLineNumbers_AssignmentAndDeclarationInTheSameDeconstruction() => AssertLineNumbersOfExecutableLines( @"using System; using System.Collections.Generic; class Program { void M() { List numbers = null; int? i = null; (i, int k) = (42, 42); } }", 10); [TestMethod] public void GetLineNumbers_NullCoalescingOperator() => AssertLineNumbersOfExecutableLines( @"using System; using System.Collections.Generic; using System.Linq; class Program { double SumNumbers(List setsOfNumbers, int indexOfSetToSum) { return setsOfNumbers?[indexOfSetToSum]?.Sum() // +1 ?? double.NaN; // +1 } }", 8, 9); [TestMethod] public void GetLineNumbers_SingleLinePatternMatching() => AssertLineNumbersOfExecutableLines( @"static class Program { public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z'; }"); [TestMethod] public void GetLineNumbers_MultiLinePatternMatching() => AssertLineNumbersOfExecutableLines( @"using System; using System.Collections.Generic; class Program { public static bool IsLetter(char c) { if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { return true; } return false; } }", 7, 12, 14); [TestMethod] public void GetLineNumbers_MultiLineInvocation() => AssertLineNumbersOfExecutableLines( @"using System; using System.Collections.Generic; class Program { public static bool Foo(int a, int b) { return Foo(1, Bar()); } public static int Bar() => 42; }", 7, 8); [TestMethod] public void GetLineNumbers_PartialProperties() => AssertLineNumbersOfExecutableLines( """ partial class Partial { public partial int Property { get; set; } } partial class Partial { public partial int Property { get => 1; set { value.ToString(); } // +1 } } """, 10); [TestMethod] public void GetLineNumbers_PartialProperties_Excluded() => AssertLineNumbersOfExecutableLines( """ partial class Partial { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public partial int Property { get; set; } } partial class Partial { public partial int Property { get => 1; set { value.ToString(); } // The setter is ignored because of ExcludeFromCodeCoverage in the partial property declaration } } """); [TestMethod] public void GetLineNumbers_PartialIndexers_Excluded() => AssertLineNumbersOfExecutableLines( """ partial class Partial { [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public partial int this[int index] { get; set; } } partial class Partial { public partial int this[int index] { get => 1; set { } // The setter is ignored because of ExcludeFromCodeCoverage in the partial indexer declaration } } """); #if NET [TestMethod] public void GetLineNumbers_Razor_NoExecutableLines() => AssertLineNumbersOfExecutableLinesRazor("""

Hello world!

@* Noncompliant *@ @code { private void SomeMethod() { } } """, RazorFile); [TestMethod] // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 public void GetLineNumbers_Razor_FieldReference() => AssertLineNumbersOfExecutableLinesRazor(""" @page "/razor" @using TestCases

Current count: @currentCount

@currentCount @code { private int currentCount = 0; } """, RazorFile, 4, 6); [TestMethod] // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 public void GetLineNumbers_Razor_MethodReferenceAndCall() => AssertLineNumbersOfExecutableLinesRazor("""

@(ShowAmount())

@code { [Parameter] public int IncrementAmount { get; set; } = 1; private void IncrementCount() { IncrementAmount += 1; // +1 } private string ShowAmount() { return $"Amount: {IncrementAmount}"; // +1 } } """, RazorFile, 2, 10, 15); [TestMethod] // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 public void GetLineNumbers_Razor_PropertyReference() => AssertLineNumbersOfExecutableLinesRazor(""" @IncrementAmount @code { [Parameter] public int IncrementAmount { get; set; } = 1; } """, RazorFile, 1); [TestMethod] // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 public void GetLineNumbers_Razor_Html() => AssertLineNumbersOfExecutableLinesRazor("""
    @foreach (var todo in todos) {
  • @todo.Title
  • }

Todo (@todos.Count(todo => !todo.IsDone))

""", RazorFile, 2, 4, 8); [TestMethod] public void GetLineNumbers_Razor_AssignmentAndDeclarationInTheSameDeconstruction() => AssertLineNumbersOfExecutableLinesRazor(""" @code { void Foo() { int? i = null; (i, int j) = (42, 42); // +1 } } """, RazorFile, 5); [TestMethod] public void GetLineNumbers_Razor_MultiLineInvocation() => AssertLineNumbersOfExecutableLinesRazor(""" @code { public static bool Foo(int a, int b) { return Foo(1, // +1 Bar()); // +1 } public static int Bar() => 42; } """, RazorFile, 4, 5); [TestMethod] public void GetLineNumbers_Razor_NullCoalescingAssignment() => AssertLineNumbersOfExecutableLinesRazor(""" @code { void Foo() { List numbers = null; numbers ??= new List(); // +1 } } """, RazorFile, 5); [TestMethod] public void GetLineNumbers_Razor_MultiLinePatternMatching() => AssertLineNumbersOfExecutableLinesRazor(""" @code { bool IsLetter(char c) { if (c is >= 'a' // +1 and <= 'z' or >= 'A' and <= 'Z') { return true; // +1 } return false; // +1 } } """, RazorFile, 4, 9, 11); [TestMethod] public void GetLineNumbers_Razor_NullCoalescingOperator() => AssertLineNumbersOfExecutableLinesRazor(""" @code { double SumNumbers(List setsOfNumbers, int indexOfSetToSum) { return setsOfNumbers?[indexOfSetToSum]?.Sum() // +1 ?? double.NaN; // +1 } } """, RazorFile, 4, 5); [TestMethod] public void GetLineNumbers_Razor_LocalFunctions() => AssertLineNumbersOfExecutableLinesRazor(""" @code { void Foo() { var x = LocalMethod(); // +1 LocalMethod(); // +1 int LocalMethod() => 42; } } """, RazorFile, 4, 5); #endif private static void AssertLineNumbersOfExecutableLines(string code, params int[] expectedExecutableLines) { var (syntaxTree, semanticModel) = TestCompiler.CompileCS(code); CSharpExecutableLinesMetric.GetLineNumbers(syntaxTree, semanticModel).Should().BeEquivalentTo(expectedExecutableLines); } private static void AssertLineNumbersOfExecutableLinesRazor(string code, string fileName, params int[] expectedExecutableLines) { var compilation = new VerifierBuilder() .AddSnippet(code, fileName) .WithLanguageVersion(LanguageVersion.Latest) .Compile() .Single(); var syntaxTree = compilation.SyntaxTrees.Single(x => x.ToString().Contains(fileName)); var semanticModel = compilation.GetSemanticModel(syntaxTree); var lineNumbers = CSharpExecutableLinesMetric.GetLineNumbers(syntaxTree, semanticModel); lineNumbers.Should().BeEquivalentTo(expectedExecutableLines); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Metrics/VisualBasicExecutableLinesMetricTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Metrics; namespace SonarAnalyzer.Metrics.Test; [TestClass] public class VisualBasicExecutableLinesMetricTest { [TestMethod] public void AttributeList() => AssertLinesOfCode( @" Public Class Sample Shared Sub SampleMethod() End Sub End Class"); [TestMethod] public void SyncLockStatement() => AssertLinesOfCode( @"Class simpleMessageList Private messagesLock As New Object Public Sub addAnotherMessage(ByVal newMessage As String) SyncLock messagesLock ' +1 End SyncLock End Sub End Class", 5); [TestMethod] public void UsingStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Using MS As New IO.MemoryStream End Using End Sub End Module", 3); [TestMethod] public void DoUntilStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim index As Integer = 0 Do Loop Until index > 10 End Sub End Module"); [TestMethod] public void DoWhileStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim index As Integer = 0 Do While index <= 10 Loop End Sub End Module", 4); [TestMethod] public void ForEachStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() For Each item As String In {""a"", ""b"", ""c""} ' +1 Next End Sub End Module", 3); [TestMethod] public void ForStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() For index As Integer = 1 To 5 Next End Sub End Module", 3); [TestMethod] public void WhileStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Index As Integer) While Index <= 10 End While End Sub End Module", 3); [TestMethod] public void IfStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Count As Integer) Dim Message As String If Count = 0 Then Message = ""There are no items."" ElseIf Count = 1 Then Message = ""There is 1 item."" Else Message = $""There are many items."" End If End Sub End Module", 4, 5, 6, 7, 9); [TestMethod] public void SelectStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim number As Integer = 8 Select Case number ' +1 Case 1 To 5 Debug.WriteLine(""f"") ' +1 Case Else Debug.WriteLine(""f"") ' +1 End Select End Sub End Module", 4, 6, 8); [TestMethod] public void ConditionalAccessExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Customers() As Integer) Dim length As Integer? = Customers?.Length End Sub End Module", 3); [TestMethod] public void BinaryConditionalExpression() => AssertLinesOfCode( @"Public Module Sample Public Function Go(First As String, Second As String) As String Return If(First, Second) End Function End Module", 3); [TestMethod] public void TernaryConditionalExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(A As String, B As String, C As String, D As String) Dim Ret As String = If(A = B, C, D) End Sub End Module", 3); [TestMethod] public void GoToStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() GoTo LastLine LastLine: End Sub End Module", 3, 4); [TestMethod] public void ThrowStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Throw New Exception() End Sub End Module", 3); [TestMethod] public void ReturnStatement() => AssertLinesOfCode( @"Public Module Sample Public Function getAgePhrase(ByVal age As Integer) As String Return ""Infant"" End Function End Module", 3); [TestMethod] public void ExitDoStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Index As Integer) Do While Index <= 100 If Index > 10 Then Exit Do End If Loop End Sub End Module", 3, 4, 5); [TestMethod] public void ExitForStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() For index As Integer = 1 To 100000 Exit For Next End Sub End Module", 3, 4); [TestMethod] public void ExitWhileStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Index As Integer) While Index < 100000 Exit While End While End Sub End Module", 3, 4); [TestMethod] public void ContinueDoStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go(Cnt As Integer) Do Continue Do Loop While Cnt < 20 End Sub End Module", 4); [TestMethod] public void ContinueForStatement() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() For index As Integer = 1 To 100000 Continue For Next End Sub End Module", 3, 4); [TestMethod] public void SimpleMemberAccessExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Console.WriteLine(""Found"") End Sub End Module", 3); [TestMethod] public void InvocationExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub foo() End Sub Public Sub bar() foo() End Sub End Module", 6); [TestMethod] public void SingleLineSubLambdaExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim writeline1 = Sub(x) Console.WriteLine(x) End Sub End Module", 3); [TestMethod] public void SingleLineFunctionLambdaExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim increment1 = Function(x) x + 1 End Sub End Module", 3); [TestMethod] public void MultiLineSubLambdaExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim writeline2 = Sub(x) Console.WriteLine(x) End Sub End Sub End Module", 3, 4); [TestMethod] public void MultiLineFunctionLambdaExpression() => AssertLinesOfCode( @"Public Module Sample Public Sub Go() Dim increment2 = Function(x) Return x + 2 End Function End Sub End Module", 3, 4); [TestMethod] public void StructureStatement() => AssertLinesOfCode( @"Structure foo End Structure"); [TestMethod] public void ClassStatement() => AssertLinesOfCode( @"Class foo End Class"); [TestMethod] public void ModuleStatement() => AssertLinesOfCode( @"Module foo End Module"); [TestMethod] public void FunctionStatement() => AssertLinesOfCode( @"Public Module Sample Function myFunction(ByVal j As Integer) As Double Return 3.87 * j End Function End Module", 3); [TestMethod] public void SubStatement() => AssertLinesOfCode( @"Public Module Sample Sub computeArea(ByVal length As Double, ByVal width As Double) End Sub End Module"); [TestMethod] public void SubNewStatement() => AssertLinesOfCode( @"Public Class Sample Public Sub New() End Sub End Class"); [TestMethod] public void PropertyStatement() => AssertLinesOfCode( @"Public Class Sample Public Property Name As String End Class"); [TestMethod] public void EventStatement() => AssertLinesOfCode( @"Public Class Sample Public Event LogonCompleted(ByVal UserName As String) End Class"); [TestMethod] public void AddHandlerAccessorStatement() => AssertLinesOfCode( @"Public Class Sample Public Event SomeEvent Sub TestEvents() Dim S As New Sample AddHandler S.SomeEvent, AddressOf OnSomeEvent End Sub Private Sub OnSomeEvent() End Sub End Class", 6); [TestMethod] public void RemoveHandlerAccessorStatement() => AssertLinesOfCode( @"Public Class Sample Public Event SomeEvent Sub TestEvents() Dim S As New Sample RemoveHandler S.SomeEvent, AddressOf OnSomeEvent End Sub Private Sub OnSomeEvent() End Sub End Class", 6); [TestMethod] public void SetAccessorStatement() => AssertLinesOfCode( @"Public Class Sample Private QuoteValue As String Public WriteOnly Property QuoteForTheDay() As String Set(ByVal Value As String) QuoteValue = Value End Set End Property End Class", 6); [TestMethod] public void GetAccessorStatement() => AssertLinesOfCode( @"Public Class Sample ReadOnly Property quoteForTheDay() As String Get End Get End Property End Class"); [TestMethod] public void Assignments() => AssertLinesOfCode( @"Public Module Sample Public Sub Foo(ByVal flag _ As Boolean) If flag _ Then flag = True : flag = False : flag = True End If End Sub End Module", 4, 6); [TestMethod] public void ExcludedFromCodeCoverage_Class() => AssertLinesOfCode( """ Imports System.Diagnostics.CodeAnalysis Public Module Class1 Public Sub TestMe() Console.WriteLine("Exclude me") ' +1, FP End Sub End Module """, 6); // There should be no executable lines in the module [TestMethod] public void ExcludedFromCodeCoverage_Methods() => AssertLinesOfCode( """ Imports System.Diagnostics.CodeAnalysis Public Module Class3 Public Sub Excluded() Console.WriteLine("Exclude me") ' +1, FP End Sub Public Sub NotCovered() Console.WriteLine("Not covered") ' +1 End Sub End Module """, 6, 9); private static void AssertLinesOfCode(string code, params int[] expectedExecutableLines) { var (syntaxTree, semanticModel) = TestCompiler.CompileVB(code); VisualBasicExecutableLinesMetric.GetLineNumbers(syntaxTree, semanticModel).Should().BeEquivalentTo(expectedExecutableLines); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Operations/Utilities/OperationExecutionOrderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CFG.Operations.Utilities; using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.Test.Operations.Utilities; [TestClass] public class OperationExecutionOrderTest { [TestMethod] [DataRow(false, DisplayName = "Execution order")] [DataRow(true, DisplayName = "Reversed execution order")] public void Linear(bool reverseOrder) { const string code = """ var a = 1; var b = 2; a = b + 1; Method(); """; var expected = new[] { "Literal: 1", "VariableInitializer: = 1", "VariableDeclarator: a = 1", "VariableDeclaration: var a = 1", "VariableDeclarationGroup: var a = 1;", "Literal: 2", "VariableInitializer: = 2", "VariableDeclarator: b = 2", "VariableDeclaration: var b = 2", "VariableDeclarationGroup: var b = 2;", "LocalReference: a", "LocalReference: b", "Literal: 1", "Binary: b + 1", "SimpleAssignment: a = b + 1", "ExpressionStatement: a = b + 1;", "Invocation: Method()", "ExpressionStatement: Method();" }; ValidateOrder(code, expected, reverseOrder); } [TestMethod] [DataRow(false, DisplayName = "Execution order")] [DataRow(true, DisplayName = "Reversed execution order")] public void Nested(bool reverseOrder) { const string code = """ Method(0); Method(1, Nested(40 + 2), 3); Method(4); """; var expected = new[] { "Literal: 0", "Invocation: Method(0)", "ExpressionStatement: Method(0);", "Literal: 1", "Literal: 40", "Literal: 2", "Binary: 40 + 2", "Invocation: Nested(40 + 2)", "Literal: 3", "Invocation: Method(1, Nested(40 + 2), 3)", "ExpressionStatement: Method(1, Nested(40 + 2), 3);", "Literal: 4", "Invocation: Method(4)", "ExpressionStatement: Method(4);" }; ValidateOrder(code, expected, reverseOrder); } [TestMethod] public void InterruptedEvaluation() { var enumerator = Compile("Method(0);", false).GetEnumerator(); enumerator.MoveNext().Should().BeTrue(); enumerator.Current.Should().NotBeNull(); enumerator.Current.Instance.Should().NotBeNull(); enumerator.Reset(); enumerator.MoveNext().Should().BeTrue(); enumerator.Current.Should().NotBeNull(); enumerator.Current.Instance.Should().NotBeNull(); enumerator.Dispose(); enumerator.MoveNext().Should().BeFalse(); enumerator.Current.Should().NotBeNull(); enumerator.Current.Instance.Should().BeNull(); } [TestMethod] public void AsIEnumerable() { var enumerator = ((IEnumerable)Compile("Method(0);", false)).GetEnumerator(); enumerator.MoveNext().Should().BeTrue(); enumerator.Current.Should().NotBeNull(); } private static void ValidateOrder(string code, string[] expected, bool reverseOrder) { var sut = Compile(code, reverseOrder); var list = new List(); if (reverseOrder) { expected = Enumerable.Reverse(expected).ToArray(); } foreach (var operation in sut) // Act { if (!operation.IsImplicit) { list.Add(operation.Instance.Kind + ": " + operation.Instance.Syntax); } } list.Should().Equal(expected); } private static OperationExecutionOrder Compile(string methodBody, bool reverseOrder) { var code = $$""" public class Sample { public void Main() { {{methodBody}} } public int Method(params int[] values) => 0; public int Nested(params int[] values) => 0; } """; var (tree, semanticModel) = TestCompiler.CompileCS(code); var body = tree.First(); var rootOperation = new IOperationWrapperSonar(semanticModel.GetOperation(body)); return new OperationExecutionOrder(rootOperation.Children, reverseOrder); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Operations/Utilities/OperationFinderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.Operations; using SonarAnalyzer.CFG.Operations.Utilities; using StyleCop.Analyzers.Lightup; namespace SonarAnalyzer.Test.Operations.Utilities; [TestClass] public class OperationFinderTest { [TestMethod] public void ValidateFinder() { const string code = @" public class Sample { int field; public void Method(bool condition) { if (condition) field = 42 + 43; } }"; var cfg = TestCompiler.CompileCfgCS(code); var assign = cfg.Blocks[2]; var finder = new FirstNumericLiteralFinder(); finder.TryFind(cfg.EntryBlock, out var result).Should().BeFalse(); result.Should().Be(default); finder.TryFind(assign, out result).Should().BeTrue(); result.Should().Be(42); } private class FirstNumericLiteralFinder : OperationFinder { protected override bool TryFindOperation(IOperationWrapperSonar operation, out int result) { if (operation.Instance is ILiteralOperation) { result = (int)operation.Instance.ConstantValue.Value; return true; } result = default; return false; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AbstractClassToInterfaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AbstractClassToInterfaceTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ClassShouldNotBeAbstract() => builder.AddPaths("AbstractClassToInterface.cs").Verify(); [TestMethod] public void ClassShouldNotBeAbstract_CS_Latest() => builder .AddPaths("AbstractClassToInterface.Latest.cs") .AddPaths("AbstractClassToInterface.Latest.Partial.cs") .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AbstractTypesShouldNotHaveConstructorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AbstractTypesShouldNotHaveConstructorsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void AbstractTypesShouldNotHaveConstructors() => builder.AddPaths("AbstractTypesShouldNotHaveConstructors.cs").Verify(); [TestMethod] public void AbstractTypesShouldNotHaveConstructors_TopLevelStatements() => builder.AddPaths("AbstractTypesShouldNotHaveConstructors.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void AbstractTypesShouldNotHaveConstructors_Latest() => builder.AddPaths("AbstractTypesShouldNotHaveConstructors.Latest.cs") .AddPaths("AbstractTypesShouldNotHaveConstructors.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AllBranchesShouldNotHaveSameImplementationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AllBranchesShouldNotHaveSameImplementationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void AllBranchesShouldNotHaveSameImplementation_CSharp8() => builderCS.AddPaths("AllBranchesShouldNotHaveSameImplementation.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void AllBranchesShouldNotHaveSameImplementation_CSharp9() => builderCS.AddPaths("AllBranchesShouldNotHaveSameImplementation.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void AllBranchesShouldNotHaveSameImplementation_VB() => new VerifierBuilder().AddPaths("AllBranchesShouldNotHaveSameImplementation.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AlwaysSetDateTimeKindTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AlwaysSetDateTimeKindTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void AlwaysSetDateTimeKind_CS() => builderCS.AddPaths("AlwaysSetDateTimeKind.cs").Verify(); [TestMethod] public void AlwaysSetDateTimeKind_VB() => new VerifierBuilder().AddPaths("AlwaysSetDateTimeKind.vb").Verify(); [TestMethod] public void AlwaysSetDateTimeKind_CSharp9() => builderCS.AddSnippet( """ using System; DateTime dateTime = new(1994, 07, 05, 16, 23, 00, 42, 42); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dateTime = new(1623, DateTimeKind.Unspecified); // Compliant """) .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AnonymousDelegateEventUnsubscribeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AnonymousDelegateEventUnsubscribeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void AnonymousDelegateEventUnsubscribe() => builder.AddPaths("AnonymousDelegateEventUnsubscribe.cs").Verify(); [TestMethod] public void AnonymousDelegateEventUnsubscribe_CS_Latest() => builder.AddPaths("AnonymousDelegateEventUnsubscribe.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArgumentSpecifiedForCallerInfoParameterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ArgumentSpecifiedForCallerInfoParameterTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ArgumentSpecifiedForCallerInfoParameter() => builder.AddPaths("ArgumentSpecifiedForCallerInfoParameter.cs").Verify(); [TestMethod] public void ArgumentSpecifiedForCallerInfoParameter_CS_Latest() => builder.AddPaths("ArgumentSpecifiedForCallerInfoParameter.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArrayCovarianceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ArrayCovarianceTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ArrayCovariance() => builder.AddPaths("ArrayCovariance.cs").Verify(); [TestMethod] public void ArrayCovariance_CSharp9() => builder.AddPaths("ArrayCovariance.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArrayCreationLongSyntaxTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ArrayCreationLongSyntaxTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ArrayCreationLongSyntax() => builder.AddPaths("ArrayCreationLongSyntax.vb").Verify(); [TestMethod] public void ArrayCreationLongSyntax_CodeFix() => builder.AddPaths("ArrayCreationLongSyntax.vb").WithCodeFix().WithCodeFixedPaths("ArrayCreationLongSyntax.Fixed.vb").VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArrayDesignatorOnVariableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ArrayDesignatorOnVariableTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ArrayDesignatorOnVariable() => builder.AddPaths("ArrayDesignatorOnVariable.vb").Verify(); [TestMethod] public void ArrayDesignatorOnVariable_CodeFix() => builder.AddPaths("ArrayDesignatorOnVariable.vb") .WithCodeFix() .WithCodeFixedPaths("ArrayDesignatorOnVariable.Fixed.vb") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArrayInitializationMultipleStatementsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ArrayInitializationMultipleStatementsTest { [TestMethod] public void ArrayInitializationMultipleStatements() => new VerifierBuilder().AddPaths("ArrayInitializationMultipleStatements.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ArrayPassedAsParamsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ArrayPassedAsParamsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ArrayPassedAsParams_CS() => builderCS.AddPaths("ArrayPassedAsParams.cs").Verify(); [TestMethod] public void ArrayPassedAsParams_CS_Latest() => builderCS.AddPaths("ArrayPassedAsParams.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void ArrayPassedAsParams_VB() => builderVB.AddPaths("ArrayPassedAsParams.vb").Verify(); [TestMethod] [DataRow("{ }", false)] [DataRow("""{ "s", "s", "s", "s" }""", false)] [DataRow("New String(2) { }", true)] [DataRow("""New String(2) { "s", "s", "s" }""", false)] [DataRow("New String() { }", false)] [DataRow("""New String() { "s" }""", false)] public void ArrayPassedAsParams_VBCollectionInitializerSyntaxTests(string arrayCreation, bool compliant) { var code = $$""" Public Class C Public Sub M() ParamsMethod({{arrayCreation}}) ' {{(compliant ? "Compliant" : "Noncompliant")}} End Sub Public Sub ParamsMethod(ParamArray p As String()) End Sub End Class """; var builder = builderVB.AddSnippet(code); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/AnnotateApiActionsWithHttpVerbTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules.AspNet; [TestClass] public class AnnotateApiActionsWithHttpVerbTest { private static readonly VerifierBuilder Builder = new VerifierBuilder() .WithBasePath("AspNet") .AddReferences( [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, // ControllerBase, ApiController, etc AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, // Controller AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, // StatusCodes ]); [TestMethod] public void AnnotateApiActionsWithHttpVerb_CS() => Builder .AddPaths("AnnotateApiActionsWithHttpVerb.cs") .Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ApiControllersShouldNotDeriveDirectlyFromControllerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ApiControllersShouldNotDeriveDirectlyFromControllerTest { #if NET private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(AspNetCoreReferences) .WithBasePath("AspNet"); private static IEnumerable AspNetCoreReferences => [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, ]; [TestMethod] public void ApiControllersShouldNotDeriveDirectlyFromController_CS() => builder.AddPaths("ApiControllersShouldNotDeriveDirectlyFromController.cs").Verify(); [DataRow("""View()""")] [DataRow("""View("viewName")""")] [DataRow("""View(model)""")] [DataRow("""View("viewName", model)""")] [DataRow("""PartialView()""")] [DataRow("""PartialView("viewName")""")] [DataRow("""PartialView(model)""")] [DataRow("""PartialView("viewName", model)""")] [DataRow("""ViewComponent("foo")""")] [DataRow("""ViewComponent("foo", model)""")] [DataRow("""ViewComponent(typeof(object))""")] [DataRow("""ViewComponent(typeof(object), model)""")] [DataRow("""Json(model)""")] [DataRow("""Json(model, model)""")] [DataRow("""OnActionExecutionAsync(default(ActionExecutingContext), default(ActionExecutionDelegate))""")] [DataRow("""ViewData""")] [DataRow("""ViewBag""")] [DataRow("""TempData""")] [DataRow("""ViewData["foo"]""")] [DataRow("""ViewBag["foo"]""")] [DataRow("""TempData["foo"]""")] [TestMethod] public void ApiControllersShouldNotDeriveDirectlyFromController_DoesNotRaiseWithViewFunctionality(string invocation) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; [ApiController] public class Invocations : Controller // Compliant { object model = null; public object Foo() => {{invocation}}; } """).VerifyNoIssues(); [DataRow("""OnActionExecuted(default(ActionExecutedContext))""")] [DataRow("""OnActionExecuting(default(ActionExecutingContext))""")] [TestMethod] public void ApiControllersShouldNotDeriveDirectlyFromController_DoesNotRaiseWithVoidInvocations(string assignment) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; [ApiController] public class VoidInvocations : Controller // Compliant { public void Foo() => {{assignment}}; } """).VerifyNoIssues(); [DataRow("public Test() => foo = View();", DisplayName = "Constructor")] [DataRow("~Test() => foo = View();", DisplayName = "Destructor")] [DataRow("object prop => View();", DisplayName = "PropertyGet")] [DataRow("object prop { set => _ = View(); }", DisplayName = "PropertySet")] [DataRow("object this[int index] => View();", DisplayName = "Indexer")] [TestMethod] public void ApiControllersShouldNotDeriveDirectlyFromController_DoesNotRaiseInDifferentConstructs(string code) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; [ApiController] public class Test : Controller // Compliant { object foo; {{code}} } """).VerifyNoIssues(); [TestMethod] public void ApiControllersShouldNotDeriveDirectlyFromController_CodeFix() => builder .WithCodeFix() .AddPaths("ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.cs") .WithCodeFixedPaths("ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.Fixed.cs") .VerifyCodeFix(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/AvoidUnderPostingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AvoidUnderPostingTest { private static readonly IEnumerable AspNetReferences = [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, ..NuGetMetadataReference.SystemComponentModelAnnotations(TestConstants.NuGetLatestVersion)]; private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("AspNet") .AddReferences([.. AspNetReferences, .. NuGetMetadataReference.SystemTextJson("7.0.4"), .. NuGetMetadataReference.NewtonsoftJson("13.0.3")]); [TestMethod] public void AvoidUnderPosting_CSharp() => builder.AddPaths("AvoidUnderPosting.cs", "AvoidUnderPosting.AutogeneratedModel.cs").Verify(); [TestMethod] public void AvoidUnderPosting_Latest() => builder.AddPaths("AvoidUnderPosting.Latest.cs", "AvoidUnderPosting.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] [DataRow("class")] [DataRow("struct")] [DataRow("record")] [DataRow("record struct")] public void AvoidUnderPosting_EnclosingTypes_CSharp(string enclosingType) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; public {{enclosingType}} Model { public int ValueProperty { get; set; } // Noncompliant public int? NullableValueProperty { get; set; } // Compliant public required int RequiredValueProperty { get; set; } // Compliant } public class ControllerClass : Controller { [HttpPost] public IActionResult Create(Model model) => View(model); } """) .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] [DataRow("HttpDelete")] [DataRow("HttpGet")] [DataRow("HttpPost")] [DataRow("HttpPut")] public void AvoidUnderPosting_HttpHandlers_CSharp(string attribute) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; public class Model { public int ValueProperty { get; set; } // Noncompliant } public class ControllerClass : Controller { [{{attribute}}] public IActionResult Create(Model model) => View(model); } """).Verify(); [TestMethod] public void AvoidUnderPosting_ModelInDifferentProject_CSharp() { const string modelCode = """ namespace Models { public class Person { public int Age { get; set; } // FN - Roslyn can't raise an issue when the location is in different project than the one being analyzed } } """; const string controllerCode = """ using Microsoft.AspNetCore.Mvc; using Models; namespace Controllers { public class PersonController : Controller { [HttpPost] public IActionResult Post(Person person) => View(person); } } """; var solution = SolutionBuilder.Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(modelCode) .Solution .AddProject(AnalyzerLanguage.CSharp) .AddProjectReference(x => x.ProjectIds[0]) .AddReferences(AspNetReferences) .AddSnippet(controllerCode) .Solution; var compiledAspNetProject = solution.Compile()[1]; DiagnosticVerifier.Verify(compiledAspNetProject, [new AvoidUnderPosting()], CompilationErrorBehavior.Default, null, [], []); } } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/BackslashShouldBeAvoidedInAspNetRoutesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Reflection; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; #pragma warning disable T0008 // Internal Styling Rule T0008 #pragma warning disable T0009 // Internal Styling Rule T0009 [TestClass] public class BackslashShouldBeAvoidedInAspNetRoutesTest { private const string AttributePlaceholder = ""; private const string SlashAndBackslashConstants = """ private const string ASlash = "/"; private const string ABackSlash = @"\"; private const string AConstStringIncludingABackslash = $"A{ABackSlash}"; private const string AConstStringNotIncludingABackslash = $"A{ASlash}"; """; private static readonly object[][] AttributesWithAllTypesOfStrings = [ [$"[{AttributePlaceholder}(AConstStringIncludingABackslash)]", false, "ConstStringIncludingABackslash"], [$"[{AttributePlaceholder}(AConstStringNotIncludingABackslash)]", true, "ConstStringNotIncludingABackslash"], [$"""[{AttributePlaceholder}("\u002f[action]")]""", true, "EscapeCodeOfSlash"], [$"""[{AttributePlaceholder}("\u005c[action]")]""", false, "EscapeCodeOfBackslash"], [$$"""[{{AttributePlaceholder}}($"A{ASlash}[action]")]""", true, "InterpolatedString"], [$$"""[{{AttributePlaceholder}}($@"A{ABackSlash}[action]")]""", false, "InterpolatedVerbatimString"], [$""""[{AttributePlaceholder}("""\[action]""")]"""", false, "RawStringLiteralsTriple"], [$"""""[{AttributePlaceholder}(""""\[action]"""")]""""", false, "RawStringLiteralsQuadruple"], [$$$""""[{{{AttributePlaceholder}}}($$"""{{ABackSlash}}/[action]""")]"""", false, "InterpolatedRawStringLiteralsIncludingABackslash"], [$$$""""[{{{AttributePlaceholder}}}($$"""{{ASlash}}/[action]""")]"""", true, "InterpolatedRawStringLiteralsNotIncludingABackslash"], ]; private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("AspNet"); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("AspNet"); private static IEnumerable RouteAttributesWithAllTypesOfStrings => AttributesWithAllTypesOfStrings.Select(x => new object[] { ((string)x[0]).Replace(AttributePlaceholder, "Route"), x[1], x[2] }); public static string AttributesWithAllTypesOfStringsDisplayNameProvider(MethodInfo methodInfo, object[] values) => $"{methodInfo.Name}_{(string)values[2]}"; #if NETFRAMEWORK // ASP.NET 4x MVC 3 and 4 don't support attribute routing, nor MapControllerRoute and similar public static IEnumerable AspNet4xMvcVersionsUnderTest => [["5.2.7"] /* Most used */, [TestConstants.NuGetLatestVersion]]; private static IEnumerable AspNet4xReferences(string aspNetMvcVersion) => MetadataReferenceFacade.SystemWeb // For HttpAttributeMethod and derived attributes .Concat(NuGetMetadataReference.MicrosoftAspNetMvc(aspNetMvcVersion)); // For Controller [TestMethod] [DynamicData(nameof(AspNet4xMvcVersionsUnderTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNet4x_CS(string aspNetMvcVersion) => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.cs") .AddReferences(AspNet4xReferences(aspNetMvcVersion)) .Verify(); [TestMethod] [DynamicData( nameof(RouteAttributesWithAllTypesOfStrings), DynamicDataDisplayName = nameof(AttributesWithAllTypesOfStringsDisplayNameProvider), DynamicDataDisplayNameDeclaringType = typeof(BackslashShouldBeAvoidedInAspNetRoutesTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNet4x_CS_Latest(string actionDeclaration, bool compliant, string displayName) { actionDeclaration = actionDeclaration.Replace(AttributePlaceholder, "Route"); var builder = builderCS.AddReferences(AspNet4xReferences("5.2.7")).WithOptions(LanguageOptions.CSharpLatest).AddSnippet($$""" using System.Web.Mvc; public class WithAllTypesOfStringsController : Controller { {{SlashAndBackslashConstants}} {{(compliant ? actionDeclaration : actionDeclaration + " // Noncompliant")}} public ActionResult Index() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] [DynamicData(nameof(AspNet4xMvcVersionsUnderTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNet4x_VB(string aspNetMvcVersion) => builderVB .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.vb") .AddReferences(AspNet4xReferences(aspNetMvcVersion)) .Verify(); #endif #if NET private static IEnumerable HttpMethodAttributesWithAllTypesOfStrings => AttributesWithAllTypesOfStrings.Zip( ["HttpGet", "HttpPost", "HttpPatch", "HttpHead", "HttpDelete", "HttpOptions", "HttpGet", "HttpPost", "HttpPatch", "HttpHead"], (attribute, httpMethod) => new object[] { ((string)attribute[0]).Replace(AttributePlaceholder, httpMethod), attribute[1], attribute[2] }); public static IEnumerable AspNetCore2xVersionsUnderTest => [["2.0.4"] /* Latest 2.0.x */, ["2.2.0"] /* 2nd most used */, [TestConstants.NuGetLatestVersion]]; private static IEnumerable AspNetCore2xReferences(string aspNetCoreVersion) => NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetCoreVersion) // For Controller .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcViewFeatures(aspNetCoreVersion)) // For View .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcAbstractions(aspNetCoreVersion)); // For IActionResult private static IEnumerable AspNetCore3AndAboveReferences => [ AspNetCoreMetadataReference.MicrosoftAspNetCore, // For WebApplication AspNetCoreMetadataReference.MicrosoftExtensionsHostingAbstractions, // For IHost AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, // For HttpContext, RouteValueDictionary AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcRazorPages, // For RazorPagesEndpointRouteBuilderExtensions.MapFallbackToPage AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreRouting, // For IEndpointRouteBuilder ]; [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_CrossFilePartialClass_DoesNotThrow() => builderCS .AddReferences(AspNetCore3AndAboveReferences) .AddSnippet(""" using System; public partial class SomeComponent { private TimeSpan _elapsedTime = TimeSpan.Zero; } """) .AddSnippet(""" using Microsoft.AspNetCore.Mvc; using System; public class MyController : Controller { public IActionResult Index() => View(); } public partial class SomeComponent { public void Process() { Use(_elapsedTime); } private void Use(object value) { } } """) .VerifyNoIssues(); [TestMethod] [DynamicData(nameof(AspNetCore2xVersionsUnderTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore2x_CS(string aspNetCoreVersion) => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2AndAbove.cs") .AddReferences(AspNetCore2xReferences(aspNetCoreVersion)) .Verify(); [TestMethod] [DynamicData( nameof(RouteAttributesWithAllTypesOfStrings), DynamicDataDisplayName = nameof(AttributesWithAllTypesOfStringsDisplayNameProvider), DynamicDataDisplayNameDeclaringType = typeof(BackslashShouldBeAvoidedInAspNetRoutesTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore2x_Route_CS_Latest(string actionDeclaration, bool compliant, string displayName) => TestAspNetCoreAttributeDeclaration( builderCS.AddReferences(AspNetCore2xReferences("2.2.0")).WithOptions(LanguageOptions.CSharpLatest), actionDeclaration, compliant); [TestMethod] [DynamicData( nameof(HttpMethodAttributesWithAllTypesOfStrings), DynamicDataDisplayName = nameof(AttributesWithAllTypesOfStringsDisplayNameProvider), DynamicDataDisplayNameDeclaringType = typeof(BackslashShouldBeAvoidedInAspNetRoutesTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore2x_HttpMethods_CS_Latest(string actionDeclaration, bool compliant, string displayName) => TestAspNetCoreAttributeDeclaration( builderCS.AddReferences(AspNetCore2xReferences("2.2.0")).WithOptions(LanguageOptions.CSharpLatest), actionDeclaration, compliant); [TestMethod] [DynamicData( nameof(RouteAttributesWithAllTypesOfStrings), DynamicDataDisplayName = nameof(AttributesWithAllTypesOfStringsDisplayNameProvider), DynamicDataDisplayNameDeclaringType = typeof(BackslashShouldBeAvoidedInAspNetRoutesTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore3AndAbove_Route_CS_Latest(string actionDeclaration, bool compliant, string displayName) => TestAspNetCoreAttributeDeclaration( builderCS.AddReferences(AspNetCore3AndAboveReferences).WithOptions(LanguageOptions.CSharpLatest), actionDeclaration, compliant); [TestMethod] [DynamicData( nameof(HttpMethodAttributesWithAllTypesOfStrings), DynamicDataDisplayName = nameof(AttributesWithAllTypesOfStringsDisplayNameProvider), DynamicDataDisplayNameDeclaringType = typeof(BackslashShouldBeAvoidedInAspNetRoutesTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore3AndAbove_HttpMethods_CS_Latest(string actionDeclaration, bool compliant, string displayName) => TestAspNetCoreAttributeDeclaration( builderCS.AddReferences(AspNetCore3AndAboveReferences).WithOptions(LanguageOptions.CSharpLatest), actionDeclaration, compliant); [TestMethod] [DynamicData(nameof(AspNetCore2xVersionsUnderTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore2x_VB(string aspNetCoreVersion) => builderVB .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.vb") .AddReferences(AspNetCore2xReferences(aspNetCoreVersion)) .Verify(); [TestMethod] [DynamicData(nameof(AspNetCore2xVersionsUnderTest))] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore2x_CS_Latest(string aspNetCoreVersion) => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.Latest.cs") .AddReferences(AspNetCore2xReferences(aspNetCoreVersion)) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore3AndAbove_CS() => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2AndAbove.cs") .AddReferences(AspNetCore3AndAboveReferences) .Verify(); [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore3AndAbove_CS_Latest() => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.Latest.cs") .AddReferences(AspNetCore3AndAboveReferences) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore3AndAbove_VB() => builderVB .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.vb") .AddReferences(AspNetCore3AndAboveReferences) .Verify(); [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore8AndAbove_CS_Latest() => builderCS .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.Latest.cs") .AddReferences(AspNetCore3AndAboveReferences) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void BackslashShouldBeAvoidedInAspNetRoutes_AspNetCore8AndAbove_VB() => builderVB .AddPaths("BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.vb") .AddReferences(AspNetCore3AndAboveReferences) .Verify(); private static void TestAspNetCoreAttributeDeclaration(VerifierBuilder builder, string attributeDeclaration, bool compliant) { builder = builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; public class WithAllTypesOfStringsController : Controller { {{SlashAndBackslashConstants}} {{(compliant ? attributeDeclaration : attributeDeclaration + " // Noncompliant")}} public IActionResult Index() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/CallModelStateIsValidTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CallModelStateIsValidTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("AspNet") .AddReferences([ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, ..NuGetMetadataReference.SystemComponentModelAnnotations(TestConstants.NuGetLatestVersion) ]); [TestMethod] public void CallModelStateIsValid_CS() => builder.AddPaths("CallModelStateIsValid.cs", "CallModelStateIsValid.AutogeneratedController.cs").Verify(); [TestMethod] public void CallModelStateIsValid_CS_Latest() => builder.AddPaths("CallModelStateIsValid.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void CallModelStateIsValid_AssemblyLevelControllerAttribute_CS() => builder.AddSnippet(""" using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; [assembly: ApiController] // causes the ApiController attribute to be applied on every Controller class in the assembly public class Movie { [Required] public string Title { get; set; } [Range(1900, 2200)] public int Year { get; set; } } public class ControllerWithApiAttributeAtTheAssemblyLevel : ControllerBase { [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant - ApiController attribute is applied at the assembly level. { return "Hello!"; } } """).VerifyNoIssues(); [TestMethod] public void CallModelStateIsValid_FluentValidation_CS() => builder.AddSnippet(""" using Microsoft.AspNetCore.Mvc; using FluentValidation; public class Movie { public string Title { get; set; } public int Year { get; set; } } public class MovieValidator : AbstractValidator { public MovieValidator() { RuleFor(x => x.Title).NotNull(); RuleFor(x => x.Year).InclusiveBetween(1900, 2100); } } public class MovieController : ControllerBase { private IValidator _validator; public MovieController(IValidator validator) { _validator = validator; } [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant - uses FluentValidation { if (!_validator.Validate(movie).IsValid) { return ""; } return "Hello!"; } } [Controller] public class PocoMovieController { private IValidator _validator; public PocoMovieController(IValidator validator) { _validator = validator; } [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant - uses FluentValidation { if (!_validator.Validate(movie).IsValid) { return ""; } return "Hello!"; } } public class NonValidatingMovieController : ControllerBase { [HttpPost("/[controller]")] public string Add(Movie movie) // FN - the project references FluentValidation, but doesn't use it { return "Hello!"; } } """).AddReferences(NuGetMetadataReference.FluentValidation()).VerifyNoIssues(); [TestMethod] [DataRow("!ModelState.IsValid")] [DataRow("ModelState?.IsValid is false")] [DataRow("!ModelState!.IsValid")] [DataRow("ModelState!?.IsValid is false")] [DataRow("ModelState?.IsValid! is false")] [DataRow("ModelState is { IsValid: false }")] [DataRow("this is { ModelState.IsValid: false }")] [DataRow("this is { ModelState: { IsValid: false } }")] [DataRow("this is { ModelState: { IsValid: not true } }")] [DataRow("ModelState.ValidationState != ModelValidationState.Valid")] [DataRow("ModelState.ValidationState == ModelValidationState.Invalid")] [DataRow("ModelState is { ValidationState: ModelValidationState.Invalid }")] public void CallModelStateIsValid_ValidatingState_CS(string condition) => builder.AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using System.ComponentModel.DataAnnotations; public class Movie { [Required] public string Title { get; set; } [Range(1900, 2200)] public int Year { get; set; } } public class MovieController : ControllerBase { [HttpPost("/[controller]")] public IActionResult Add(Movie movie) // Compliant { if ({{condition}}) { return BadRequest(); } return Ok(); } } """).WithOptions(LanguageOptions.FromCSharp10).VerifyNoIssues(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersHaveMixedResponsibilitiesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; #if NET [TestClass] public class ControllersHaveMixedResponsibilitiesTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(References).WithBasePath("AspNet"); private static IEnumerable References => [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, CoreMetadataReference.SystemComponentModel, // For IServiceProvider .. NuGetMetadataReference.MicrosoftExtensionsDependencyInjectionAbstractions("8.0.1"), // For IServiceProvider extensions ]; [TestMethod] public void ControllersHaveMixedResponsibilities_CS() => builder .AddPaths("ControllersHaveMixedResponsibilities.Latest.cs", "ControllersHaveMixedResponsibilities.Latest.Partial.cs") .WithLanguageVersion(LanguageVersion.Latest) .Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/ControllersReuseClientTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; #if NET [TestClass] public class ControllersReuseClientTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("AspNet") .AddReferences( [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, .. MetadataReferenceFacade.SystemThreadingTasks, .. NuGetMetadataReference.SystemNetHttp(), .. NuGetMetadataReference.MicrosoftExtensionsHttp() ]); [TestMethod] public void ControllersReuseClient_CS() => builder .AddPaths("ControllersReuseClient.cs") .Verify(); [TestMethod] public void ControllersReuseClient_CS8() => builder .AddPaths("ControllersReuseClient.CSharp8.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void ControllersReuseClient_CS9() => builder .AddPaths("ControllersReuseClient.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void ControllersReuseClient_CS12() => builder .AddPaths("ControllerReuseClient.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12).Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/RouteTemplateShouldNotStartWithSlashTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RouteTemplateShouldNotStartWithSlashTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("AspNet"); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("AspNet"); #if NET [TestMethod] public void RouteTemplateShouldNotStartWithSlash_CS() => builderCS .AddPaths("RouteTemplateShouldNotStartWithSlash.AspNetCore.cs", "RouteTemplateShouldNotStartWithSlash.AspNetCore.PartialAutogenerated.cs") .WithConcurrentAnalysis(false) .AddReferences(AspNetCoreMetadataReference.BasicReferences) .Verify(); [DataRow("""[HttpGet("/Index1")]""", "", false)] [DataRow("""[Route("/Index2")]""", "", false)] [DataRow("""[HttpGet("\\Index1")]""", "", true)] [DataRow("""[Route("\\Index2")]""", "", true)] [DataRow("""[HttpGet("Index1/SubPath")]""", "", true)] [DataRow("""[Route("Index2/SubPath")]""", "", true)] [DataRow("""[Route("/IndexA")]""", """[Route("/IndexB")]""", false)] [DataRow("""[Route("/IndexC")]""", """[HttpGet("/IndexD")]""", false)] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_RouteAttributes(string firstAttribute, string secondAttribute, bool compliant) { var builder = builderCS.AddReferences(AspNetCoreMetadataReference.BasicReferences) .AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; [Route("[controller]")] public class BasicsController : Controller {{(compliant ? string.Empty : " // Noncompliant")}} { {{firstAttribute}}{{(compliant ? string.Empty : " // Secondary")}} {{secondAttribute}}{{(compliant || string.IsNullOrEmpty(secondAttribute) ? string.Empty : " // Secondary")}} public ActionResult SomeAction() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [DataRow("""[Route(template: @"/[action]", Name = "a", Order = 42)]""")] [DataRow("""[RouteAttribute(@"/[action]")]""")] [DataRow("""[Microsoft.AspNetCore.Mvc.RouteAttribute(@"/[action]")]""")] [DataRow("""[method:Route(@"/[action]")]""")] [DataRow("""[HttpGet("/IndexGet")]""")] [DataRow("""[HttpPost("/IndexPost")]""")] [DataRow("""[HttpPut("/IndexPut")]""")] [DataRow("""[HttpDelete("/IndexDelete")]""")] [DataRow("""[HttpPatch("/IndexPatch")]""")] [DataRow("""[HttpHead("/IndexHead")]""")] [DataRow("""[HttpOptions("/IndexOptions")]""")] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_Attributes(string attribute) { var builder = builderCS.AddReferences(AspNetCoreMetadataReference.BasicReferences) .AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; public class BasicsController : Controller // Noncompliant { {{attribute}} // Secondary public ActionResult SomeAction() => View(); } """); builder.Verify(); } [DataRow("""(@"/[action]")""", false)] [DataRow("""("/[action]")""", false)] [DataRow("""("\u002f[action]")""", false)] [DataRow("""($"/{ConstA}/[action]")""", false)] [DataRow(""""("""/[action]""")"""", false)] [DataRow(""""""(""""/[action]"""")"""""", false)] [DataRow(""""($$"""/{{ConstA}}/[action]""")"""", false)] [DataRow("""(@"/[action]", Name = "a", Order = 42)""", false)] [DataRow("""($"{ConstA}/[action]")""", true)] [DataRow("""($"{ConstSlash}[action]")""", false)] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_WithAllTypesOfStrings(string attributeParameter, bool compliant) { var builder = builderCS .AddReferences(AspNetCoreMetadataReference.BasicReferences) .WithOptions(LanguageOptions.FromCSharp11) .AddSnippet($$""" using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; public class BasicsController : Controller {{(compliant ? string.Empty : " // Noncompliant")}} { private const string ConstA = "A"; private const string ConstSlash = "/"; [Route{{attributeParameter}}] {{(compliant ? string.Empty : " // Secondary")}} public ActionResult SomeAction() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] public void RouteTemplateShouldNotStartWithSlash_VB() => builderVB .AddPaths("RouteTemplateShouldNotStartWithSlash.AspNetCore.vb") .AddReferences(AspNetCoreMetadataReference.BasicReferences) .Verify(); [TestMethod] public void RouteTemplateShouldNotStartWithSlash_CSharp12() => builderCS .AddPaths("RouteTemplateShouldNotStartWithSlash.AspNetCore.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12) .AddReferences(AspNetCoreMetadataReference.BasicReferences) .Verify(); #endif #if NETFRAMEWORK // ASP.NET 4x MVC 3 and 4 don't support attribute routing, nor MapControllerRoute and similar public static IEnumerable AspNet4xMvcVersionsUnderTest => [["5.2.7"] /* Most used */, [TestConstants.NuGetLatestVersion]]; private static IEnumerable AspNet4xReferences(string aspNetMvcVersion) => MetadataReferenceFacade.SystemWeb .Concat(NuGetMetadataReference.MicrosoftAspNetMvc(aspNetMvcVersion)); [TestMethod] [DynamicData(nameof(AspNet4xMvcVersionsUnderTest))] public void RouteTemplateShouldNotStartWithSlash_CS(string aspNetMvcVersion) => builderCS .AddPaths("RouteTemplateShouldNotStartWithSlash.AspNet4x.cs", "RouteTemplateShouldNotStartWithSlash.AspNet4x.PartialAutogenerated.cs") .WithConcurrentAnalysis(false) .AddReferences(AspNet4xReferences(aspNetMvcVersion)) .Verify(); [TestMethod] [DynamicData(nameof(AspNet4xMvcVersionsUnderTest))] public void RouteTemplateShouldNotStartWithSlash_VB(string aspNetMvcVersion) => builderVB .AddPaths("RouteTemplateShouldNotStartWithSlash.AspNet4x.vb") .AddReferences(AspNet4xReferences(aspNetMvcVersion)) .Verify(); [DataRow("/Index2", false)] [DataRow(@"\Index2", true)] [DataRow("Index1/SubPath", true)] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_WithHttpAttribute(string attributeParameter, bool compliant) { var builder = builderCS.AddReferences(AspNet4xReferences("5.2.7")).AddSnippet($$""" using System.Web.Mvc; [Route("[controller]")] public class BasicsController : Controller {{(compliant ? string.Empty : " // Noncompliant")}} { [HttpGet] [Route(@"{{attributeParameter}}")] {{(compliant ? string.Empty : " // Secondary")}} public ActionResult SomeAction() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [DataRow("""[Route(template: @"/[action]", Name = "a", Order = 42)]""", false)] [DataRow("""[Route(template: @"[action]", Name = "/a", Order = 42)]""", true)] [DataRow("""[RouteAttribute(@"/[action]")]""", false)] [DataRow("""[RouteAttribute(@"/[action]")]""", false)] [DataRow("""[System.Web.Mvc.RouteAttribute(@"/[action]")]""", false)] [DataRow("""[method:Route(@"/[action]")]""", false)] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_WithAttributeSyntaxVariations(string attribute, bool compliant) { var builder = builderCS.AddReferences(AspNet4xReferences("5.2.7")) .WithOptions(LanguageOptions.FromCSharp11) .AddSnippet($$""" using System.Web.Mvc; [Route("[controller]")] public class BasicsController : Controller {{(compliant ? string.Empty : " // Noncompliant")}} { {{attribute}} {{(compliant ? string.Empty : " // Secondary")}} public ActionResult SomeAction() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [DataRow("""(@"/[action]")""", false)] [DataRow("""("/[action]")""", false)] [DataRow("""("\u002f[action]")""", false)] [DataRow("""($"/{ConstA}/[action]")""", false)] [DataRow(""""("""/[action]""")"""", false)] [DataRow(""""""(""""/[action]"""")"""""", false)] [DataRow(""""($$"""/{{ConstA}}/[action]""")"""", false)] [DataRow("""(@"/[action]", Name = "a", Order = 42)""", false)] [DataRow("""($"{ConstA}/[action]")""", true)] [DataRow("""($"{ConstSlash}[action]")""", false)] [TestMethod] public void RouteTemplateShouldNotStartWithSlash_WithAllTypesOfStrings(string attributeParameter, bool compliant) { var builder = builderCS .AddReferences(AspNet4xReferences("5.2.7")) .WithOptions(LanguageOptions.FromCSharp11) .AddSnippet($$""" using System.Web.Mvc; [Route("[controller]")] public class BasicsController : Controller {{(compliant ? string.Empty : " // Noncompliant")}} { private const string ConstA = "A"; private const string ConstSlash = "/"; [Route{{attributeParameter}}] {{(compliant ? string.Empty : " // Secondary")}} public ActionResult SomeAction() => View(); } """); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/SpecifyRouteAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SpecifyRouteAttributeTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("AspNet") .WithOptions(LanguageOptions.FromCSharp12) .AddReferences([ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions ]); [TestMethod] public void SpecifyRouteAttribute_CSharp12() => builder.AddPaths("SpecifyRouteAttribute.CSharp12.cs").Verify(); [TestMethod] public void SpecifyRouteAttribute_PartialClasses_CSharp12() => builder .AddSnippet(""" using Microsoft.AspNetCore.Mvc; public partial class HomeController : Controller // Noncompliant [first] { [HttpGet("Test")] public IActionResult Index() => View(); // Secondary [first, second] } """) .AddSnippet(""" using Microsoft.AspNetCore.Mvc; public partial class HomeController : Controller { } // Noncompliant [second] """) .Verify(); [TestMethod] public void SpecifyRouteAttribute_PartialClasses_OneGenerated_CSharp12() => builder .AddSnippet(""" // using Microsoft.AspNetCore.Mvc; public partial class HomeController : Controller { [HttpGet("Test")] public IActionResult ActionInGeneratedCode() => View(); // Secondary } """) .AddSnippet(""" using Microsoft.AspNetCore.Mvc; public partial class HomeController : Controller // Noncompliant { [HttpGet("Test")] public IActionResult Index() => View(); // Secondary } """) .Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AspNet/UseAspNetModelBindingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; #if NET [TestClass] public class UseAspNetModelBindingTest { private readonly VerifierBuilder builderAspNetCore = new VerifierBuilder() .WithBasePath("AspNet") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences([ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpFeatures, AspNetCoreMetadataReference.MicrosoftExtensionsPrimitives, ]); [TestMethod] public void UseAspNetModelBinding_NoRegistrationIfNotAspNet() => new VerifierBuilder().AddSnippet(string.Empty).VerifyNoIssues(); [TestMethod] public void UseAspNetModelBinding_AspNetCore_CS_Latest() => builderAspNetCore.AddPaths("UseAspNetModelBinding_AspNetCore_Latest.cs").Verify(); [TestMethod] [DataRow("form.Files", true)] // "form" is a parameter and as such IFormCollection binding was (probably) used which is an alternative to direct IFormFileCollection binding [DataRow("form?.Files", true)] [DataRow("form!.Files", true)] [DataRow("request.Form.Files", false)] [DataRow("request.Form?.Files", true)] // FN [DataRow("request.Form!.Files", false)] [DataRow("Request.Form.Files", false)] [DataRow("Request.Form!.Files", false)] [DataRow("Request.Form?.Files", true)] // FN public void UseAspNetModelBinding_AspNetCore_FormFileAccess(string filesAccess, bool compliant) { var builder = builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; public class TestController : Controller { async Task NoncompliantKeyVariations(IFormCollection form, HttpRequest request) { _ = {{filesAccess}}; // {{(compliant ? string.Empty : "//Noncompliant")}} } } """"); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] [DataRow("Form")] [DataRow("Query")] [DataRow("RouteValues")] [DataRow("Headers")] public void UseAspNetModelBinding_NonCompliantAccess(string property) => builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; public class TestController : Controller { async Task NoncompliantKeyVariations() { _ = Request.{{property}}[@"key"]; // Noncompliant _ = Request.{{property}}.TryGetValue(@"key", out _); // Noncompliant _ = Request.{{property}}["""key"""]; // Noncompliant _ = Request.{{property}}.TryGetValue("""key""", out _); // Noncompliant const string key = "id"; _ = Request.{{property}}[key]; // Noncompliant _ = Request.{{property}}.TryGetValue(key, out _); // Noncompliant _ = Request.{{property}}[$"prefix.{key}"]; // Noncompliant _ = Request.{{property}}.TryGetValue($"prefix.{key}", out _); // Noncompliant _ = Request.{{property}}[$"""prefix.{key}"""]; // Noncompliant _ = Request.{{property}}.TryGetValue($"""prefix.{key}""", out _); // Noncompliant _ = Request.{{property}}[key: "id"]; // Noncompliant _ = Request.{{property}}.TryGetValue(value: out _, key: "id"); // Noncompliant } private static void HandleRequest(HttpRequest request) { _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller void LocalFunction() { _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller } static void StaticLocalFunction(HttpRequest request) { _ = request.{{property}}["id"]; // Noncompliant: Containing type is a controller } } } """").Verify(); [TestMethod] [CombinatorialData] public void UseAspNetModelBinding_CompliantAccess( [CombinatorialValues( "_ = {0}.Keys", "_ = {0}.Count", "foreach (var kvp in {0}) {{ }}", "_ = {0}.Select(x => x);", "_ = {0}[key]; // Compliant: The accessed key is not a compile time constant")] string statementFormat, [CombinatorialValues("Request", "this.Request", "ControllerContext.HttpContext.Request", "request")] string request, [CombinatorialValues("Form", "Headers", "Query", "RouteValues")] string property, [CombinatorialValues("[FromForm]", "[FromQuery]", "[FromRoute]", "[FromHeader]")] string attribute) => builderAspNetCore.AddSnippet($$""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; public class TestController : Controller { async Task Compliant({{attribute}} string key, HttpRequest request) { {{string.Format(statementFormat, $"{request}.{property}")}}; } } """).VerifyNoIssues(); [TestMethod] [DataRow("Form")] [DataRow("Headers")] [DataRow("Query")] [DataRow("RouteValues")] public void UseAspNetModelBinding_DottedExpressions(string property) => builderAspNetCore.AddSnippet($$""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using System; using System.Linq; using System.Threading.Tasks; public class TestController : Controller { HttpRequest ValidRequest => Request; IFormCollection Form => Request.Form; IHeaderDictionary Headers => Request.Headers; IQueryCollection Query => Request.Query; RouteValueDictionary RouteValues => Request.RouteValues; async Task DottedExpressions() { _ = (true ? Request : Request).{{property}}["id"]; // Noncompliant _ = ValidatedRequest().{{property}}["id"]; // Noncompliant _ = ValidRequest.{{property}}["id"]; // Noncompliant _ = {{property}}["id"]; // Noncompliant _ = this.{{property}}["id"]; // Noncompliant _ = new TestController().{{property}}["id"]; // Noncompliant _ = this.Request.{{property}}["id"]; // Noncompliant _ = Request?.{{property}}?["id"]; // Noncompliant _ = Request?.{{property}}?.TryGetValue("id", out _); // Noncompliant _ = Request.{{property}}?.TryGetValue("id", out _); // Noncompliant _ = Request.{{property}}?.TryGetValue("id", out _).ToString(); // Noncompliant _ = HttpContext.Request.{{property}}["id"]; // Noncompliant _ = Request.HttpContext.Request.{{property}}["id"]; // Noncompliant _ = this.ControllerContext.HttpContext.Request.{{property}}["id"]; // Noncompliant var r1 = HttpContext.Request; _ = r1.{{property}}["id"]; // Noncompliant var r2 = ControllerContext; _ = r2.HttpContext.Request.{{property}}["id"]; // Noncompliant HttpRequest ValidatedRequest() => Request; } } """).Verify(); [TestMethod] [DataRow("public class MyController: Controller")] [DataRow("public class MyController: ControllerBase")] [DataRow("[Controller] public class My: Controller")] // [DataRow("public class MyController")] FN: Poco controller are not detected public void UseAspNetModelBinding_PocoController(string classDeclaration) => builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; {{classDeclaration}} { public async Task Action([FromServices]IHttpContextAccessor httpContextAccessor) { _ = httpContextAccessor.HttpContext.Request.Form["id"]; // Noncompliant } } """").Verify(); [TestMethod] [DataRow("public class My")] [DataRow("[NonController] public class My: Controller")] [DataRow("[NonController] public class MyController: Controller")] public void UseAspNetModelBinding_NoController(string classDeclaration) => builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; {{classDeclaration}} { public async Task Action([FromServices]IHttpContextAccessor httpContextAccessor) { _ = httpContextAccessor.HttpContext.Request.Form["id"]; // Compliant } } """").VerifyNoIssues(); [TestMethod] [DataRow("Form")] [DataRow("Headers")] [DataRow("Query")] [DataRow("RouteValues")] public void UseAspNetModelBinding_NoController_Properties(string property) => builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; static class HttpRequestExtensions { public static void Ext(this HttpRequest request) { _ = request.{{property}}["id"]; // Compliant: Not in a controller } } class RequestService { public HttpRequest Request { get; } public void HandleRequest(HttpRequest request) { _ = Request.{{property}}["id"]; // Compliant: Not in a controller _ = request.{{property}}["id"]; // Compliant: Not in a controller } } """").VerifyNoIssues(); [TestMethod] [CombinatorialData] public void UseAspNetModelBinding_InheritanceAccess( [CombinatorialValues( ": Controller", ": ControllerBase", ": MyBaseController", ": MyBaseBaseController")]string baseList, [CombinatorialValues( """_ = Request.Form["id"]""", """_ = Request.Form.TryGetValue("id", out var _)""", """_ = Request.Headers["id"]""", """_ = Request.Query["id"]""", """_ = Request.RouteValues["id"]""")]string nonCompliantStatement) => builderAspNetCore.AddSnippet($$"""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; public class MyBaseController : ControllerBase { } public class MyBaseBaseController : MyBaseController { } public class MyTestController {{baseList}} { public void Action() { {{nonCompliantStatement}}; // Noncompliant } } """").Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AssertionArgsShouldBePassedInCorrectOrderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Test.Common; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AssertionArgsShouldBePassedInCorrectOrderTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(TestConstants.NuGetLatestVersion)] public void AssertionArgsShouldBePassedInCorrectOrder_MsTest(string testFwkVersion) => builder.AddPaths("AssertionArgsShouldBePassedInCorrectOrder.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_MsTest_Static() => builder.WithTopLevelStatements().AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)).AddSnippet(""" using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert; var str = ""; AreEqual(str, ""); // Noncompliant """).Verify(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(NUnit.Ver3Latest)] // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 public void AssertionArgsShouldBePassedInCorrectOrder_NUnit(string testFwkVersion) => builder.AddPaths("AssertionArgsShouldBePassedInCorrectOrder.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_NUnit4() => builder.AddPaths("AssertionArgsShouldBePassedInCorrectOrder.NUnit4.cs") .AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)) .Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_NUnit_Static() => // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 builder.WithTopLevelStatements().AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver3Latest)).AddSnippet(""" using static NUnit.Framework.Assert; var str = ""; AreEqual(str, ""); // Noncompliant """).Verify(); [TestMethod] [DataRow(XUnitVersions.Ver2)] [DataRow(XUnitVersions.Ver253)] public void AssertionArgsShouldBePassedInCorrectOrder_XUnit(string testFwkVersion) => builder.AddPaths("AssertionArgsShouldBePassedInCorrectOrder.Xunit.cs") .AddReferences(NuGetMetadataReference.XunitFramework(testFwkVersion) .Concat(MetadataReferenceFacade.NetStandard)) .Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_XUnitV3() => builder .AddPaths("AssertionArgsShouldBePassedInCorrectOrder.Xunit.cs") .AddPaths("AssertionArgsShouldBePassedInCorrectOrder.XunitV3.cs") .AddReferences(NuGetMetadataReference.XunitFrameworkV3()) .AddReferences(NuGetMetadataReference.SystemMemory(TestConstants.NuGetLatestVersion)) .AddReferences(MetadataReferenceFacade.NetStandard) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_XUnit_Static() => builder.WithTopLevelStatements() .AddReferences(NuGetMetadataReference.XunitFramework(XUnitVersions.Ver253)) .AddSnippet(""" using static Xunit.Assert; var str = ""; Equal(str, ""); // Noncompliant """).Verify(); [TestMethod] public void AssertionArgsShouldBePassedInCorrectOrder_NUnit4_AliasedNamespace() => builder.AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)).AddSnippet(""" using NUnit.Framework; namespace Aliased { using Assert = NUnit.Framework.Legacy.ClassicAssert; [TestFixture] class Program { [Test] public void Test(string str) { Assert.AreEqual("", str); // Compliant Assert.AreEqual(str, ""); // Noncompliant } } } """).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AssertionsShouldBeCompleteTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AssertionsShouldBeCompleteTest { private readonly VerifierBuilder fluentAssertions = new VerifierBuilder() .AddReferences(NuGetMetadataReference.FluentAssertions("6.10.0")) .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemXmlLinq) .AddReferences(MetadataReferenceFacade.SystemNetHttp) .AddReferences(MetadataReferenceFacade.SystemData); private readonly VerifierBuilder nfluent = new VerifierBuilder() .AddReferences(NuGetMetadataReference.NFluent("2.8.0")); private readonly VerifierBuilder nsubstitute = new VerifierBuilder() .AddReferences(NuGetMetadataReference.NSubstitute("5.0.0")); private readonly VerifierBuilder allFrameworks; public AssertionsShouldBeCompleteTest() => allFrameworks = new VerifierBuilder() .AddReferences(fluentAssertions.References) .AddReferences(nfluent.References) .AddReferences(nsubstitute.References); [TestMethod] public void AssertionsShouldBeComplete_FluentAssertions_CSharp7() => fluentAssertions // The overload resolution errors for s[0].Should() and collection.Should() are fixed in CSharp 7.3. .WithOptions(ImmutableArray.Create(new CSharpParseOptions[] { new(LanguageVersion.CSharp7), new(LanguageVersion.CSharp7_1), new(LanguageVersion.CSharp7_2) })) .AddPaths("AssertionsShouldBeComplete.FluentAssertions.CSharp7.cs") .Verify(); [TestMethod] public void AssertionsShouldBeComplete_FluentAssertions_MissingParen() => fluentAssertions .AddSnippet(""" using FluentAssertions; public class Test { public void MissingParen() { var s = "Test"; s.Should(; // Error [CS1026] } } """) .Verify(); [TestMethod] public void AssertionsShouldBeComplete_FluentAssertions_CS_Latest() => fluentAssertions .WithOptions(LanguageOptions.CSharpLatest) .AddPaths("AssertionsShouldBeComplete.FluentAssertions.Latest.cs") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void AssertionsShouldBeComplete_NFluent_CSharp() => nfluent.AddTestReference().AddPaths("AssertionsShouldBeComplete.NFluent.cs").Verify(); [TestMethod] public void AssertionsShouldBeComplete_NFluent_CS_Latest() => nfluent.WithOptions(LanguageOptions.CSharpLatest).AddTestReference().AddPaths("AssertionsShouldBeComplete.NFluent.Latest.cs").Verify(); [TestMethod] public void AssertionsShouldBeComplete_NSubstitute_CS() => nsubstitute.AddPaths("AssertionsShouldBeComplete.NSubstitute.cs").Verify(); [TestMethod] public void AssertionsShouldBeComplete_AllFrameworks_CS() => allFrameworks.AddPaths("AssertionsShouldBeComplete.AllFrameworks.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AssignmentInsideSubExpressionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AssignmentInsideSubExpressionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void AssignmentInsideSubExpression() => builder.AddPaths("AssignmentInsideSubExpression.cs").Verify(); [TestMethod] public void AssignmentInsideSubExpression_TopLevelStatements() => builder.AddPaths("AssignmentInsideSubExpression.TopLevelStatements.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void AssignmentInsideSubExpression_Latest() => builder.AddPaths("AssignmentInsideSubExpression.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AsyncAwaitIdentifierTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AsyncAwaitIdentifierTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void AsyncAwaitIdentifier() => builder.AddPaths("AsyncAwaitIdentifier.cs").Verify(); [TestMethod] public void AsyncAwaitIdentifier_CS_Latest() => builder.AddPaths("AsyncAwaitIdentifier.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AsyncVoidMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AsyncVoidMethodTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void AsyncVoidMethod() => builder.AddPaths("AsyncVoidMethod.cs") .Verify(); [TestMethod] public void AsyncVoidMethod_CS_Latest() => builder.AddPaths("AsyncVoidMethod.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MicrosoftVisualStudioQualityToolsUnitTestFramework) .Verify(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver12)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void AsyncVoidMethod_MsTestTestFramework(string testFwkVersion) => builder.AddPaths("AsyncVoidMethod.MsTestTestFramework.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void AsyncVoidMethod_VsUtFramework() => builder.AddPaths("AsyncVoidMethod.VsUtFramework.cs") // MicrosoftVisualStudioQualityToolsUnitTestFramework is not compatible with Net7/C#11 so max is C#10 .WithOptions(LanguageOptions.FromCSharp10) .AddReferences(NuGetMetadataReference.MicrosoftVisualStudioQualityToolsUnitTestFramework) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AvoidDateTimeNowForBenchmarkingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AvoidDateTimeNowForBenchmarkingTest { [TestMethod] public void AvoidDateTimeNowForBenchmarking_CS() => new VerifierBuilder().AddPaths("AvoidDateTimeNowForBenchmarking.cs").Verify(); [TestMethod] public void AvoidDateTimeNowForBenchmarking_VB() => new VerifierBuilder().AddPaths("AvoidDateTimeNowForBenchmarking.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AvoidExcessiveClassCouplingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AvoidExcessiveClassCouplingTest { private readonly VerifierBuilder withThreshold0 = new VerifierBuilder().AddAnalyzer(() => new AvoidExcessiveClassCoupling { Threshold = 0 }); private readonly VerifierBuilder withThreshold1 = new VerifierBuilder().AddAnalyzer(() => new AvoidExcessiveClassCoupling { Threshold = 1 }); [TestMethod] public void AvoidExcessiveClassCoupling() => withThreshold1.AddPaths("AvoidExcessiveClassCoupling.cs").Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Generic_No_Constraints() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Generics1 // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { public void Foo(IDictionary dictionary) { } // +1 for dictionary } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Generic_With_Constraints() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Generics1 // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 4 to the maximum authorized 0 or less.}} { public void Foo1(IDictionary dictionary) // +1 for IDictionary where T : IEnumerable // +1 for IEnumerable, +1 for IDisposable where V : ICloneable // +1 for ICloneable { } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Generic_Bounded() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Generics1 // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 0 or less.}} { public void Foo(IDictionary dictionary) // +1 for IDictionary, +1 for IDisposable, +1 for ICloneable { } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Generic_Bounded_Deep_Nesting() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Generics1 // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 0 or less.}} { public void Foo(IList>>>>> dictionary) // +1 for IList, +1 for ICollection, +1 for IEnumerable, +1 for IComparer, +1 for Stack, +1 for Queue { } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Task_Not_Counted() => withThreshold0.AddSnippet(""" using System.Threading.Tasks; public class Tasks // Compliant, Task types are not counted { public void Foo(Task task1, Task task2) { } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Action_Not_Counted() => withThreshold0.AddSnippet(""" using System; public class Actions // Compliant, Action types are not counted { public void Foo(Action action1, Action action2, Action action3, Action action4, Action action5) { } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Func_Not_Counted() => withThreshold0.AddSnippet(""" using System; public class Functions // Compliant, Func types are not counted { public void Foo(Func func1, Func func2, Func func3, Func func4) { } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Pointers_Not_Counted() => withThreshold0.AddSnippet(""" using System; public class Pointers // Compliant, pointers are not counted { public void Foo(int* pointer) { } // Error [CS0214] } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Enums_Not_Counted() => withThreshold0.AddSnippet(""" using System; public class Pointers // Compliant, enums are not counted { public ConsoleColor Foo(ConsoleColor c) { return ConsoleColor.Black; } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Lazy_Not_Counted() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Lazyness // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { public void Foo(Lazy> lazy) { } // +1 IEnumerable } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Fields_Are_Counted() => withThreshold0.AddSnippet(""" using System.Collections.Generic; public class Fields // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 5 to the maximum authorized 0 or less.}} { // accessibility private IList c1; public ICollection c2; internal IEnumerable c3; protected IEnumerator c4; // static public static IEqualityComparer c5; } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Properties_Are_Counted() => withThreshold0.AddSnippet(""" using System.Collections.Generic; public class Properties // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 9 to the maximum authorized 0 or less.}} { // accessibility private IList C1 { get; set; } public ICollection C2 { get; set; } internal IEnumerable C3 { get; set; } protected IEnumerator C4 { get; set; } // static public static IEqualityComparer C5 { get; set; } // accessor bodies public Stack C6 { get { Queue q; return null; } set { List l; } } // expression body public object C7 => new Dictionary(); } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Indexers_Are_Counted() => withThreshold0.AddSnippet(""" using System.Collections.Generic; public class Indexers // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 0 or less.}} { // accessibility public IList this[int i] { get { return null; } } // +1 IList private ICollection this[int i, int j] { get { return null; } } // +1 ICollection protected IEnumerable this[int i, int j, int k] { get { return null; } } // +1 IEnumerable internal IEnumerator this[int i, int j, int k, int l] { get { return null; } } // +1 IEnumerator // parameters public int this[IEqualityComparer i, Stack j] { get { return 0; } } // +1 IEqualityComparer, +1 Stack } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Events_Are_Counted() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Events // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 9 to the maximum authorized 0 or less.}} { // accessibility private event EventHandler> e1; // +1 EventHandler, +1 IList public event EventHandler> e2; // +1 ICollection internal event EventHandler> e3; // +1 IEnumerable protected event EventHandler> e4; // +1 IEnumerator // static public static event EventHandler> e5; // +1 IEqualityComparer // accessor bodies public event EventHandler> E6 // +1 Stack { add { Queue q; // +1 Queue } remove { List l; // +1 List } } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Methods_Are_Counted() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; using System.Diagnostics; public class Methods // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 10 to the maximum authorized 0 or less.}} { // accessibility private void M1(IList l1) { } // +1 IList public void M2(ICollection l1) { } // +1 ICollection internal void M3(IEnumerable l1) { } // +1 IEnumerable protected void M4(IEnumerator l1) { } // +1 IEnumerator // return type private IEqualityComparer M5() { return null; } // +1 IEqualityComparer // generic constraints private void M6() where T : Stack { return; } // +1 Stack // method body private void M8() { Queue q; // +1 Queue Console.Clear(); // +1 Console } // expression body private object M9() => new List(); // +1 List private void M10() => Debug.Write(1); // +1 Debug } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Inner_Classes_And_Structs_Are_Not_Counted() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class OuterClass // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { private void M1(IList l1) { } // +1 IList public class InnerClass // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { private void M1(ICollection l1) { } // +1 ICollection } } public struct OuterStruct // Noncompliant {{Split this struct into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { private void M1(IList l1) { } // +1 IList public struct InnerStruct // Noncompliant {{Split this struct into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { private void M1(ICollection l1) { } // +1 ICollection } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Interface_Declaration() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public interface I // Noncompliant {{Split this interface into smaller and more specialized ones to reduce its dependencies on other types from 1 to the maximum authorized 0 or less.}} { void M1(IList l1); // +1 IList // interfaces cannot contain other types } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Self_Reference() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Self // Compliant, self references are not counted { void M1(Self other) { } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Base_Classes_Interfaces_NotCounted() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Base {} public class Self // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 0 or less.}} : Base, ICloneable { public object Clone() { return null; } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Catch_Statements() => withThreshold0.AddSnippet(""" using System; using System.Collections.Generic; public class Self // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 0 or less.}} { void M1() { try { } catch (Exception) { } // +1 Exception try { } catch (Exception e) when (e is InvalidOperationException) { } // +1 InvalidOperationException } } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Attributes() => withThreshold0.AddSnippet(""" using System; [Serializable] public class Self // Compliant, attributes are not counted { [Obsolete] void M1() { } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Nameof() => withThreshold0.AddSnippet(""" public class A // Compliant, types referenced by the nameof expression are not counted { public A() { var s1 = nameof(System.Type); var s2 = nameof(System.Action); } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_InRecord_Enums_Not_Counted() => withThreshold0.AddSnippet(""" using System; public record Pointers // Compliant, enums are not counted { public ConsoleColor Foo(ConsoleColor c) { return ConsoleColor.Black; } } """).WithOptions(LanguageOptions.FromCSharp9).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Base_Records_Interfaces_NotCounted() => withThreshold0.AddSnippet(""" using System; using System.Runtime.Serialization; public record Base {} public record Self // Noncompliant : Base, ISerializable { public void GetObjectData(SerializationInfo info, StreamingContext context) { } } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Primitive_Types_Not_Counted() => withThreshold0.AddSnippet(""" using System; public class Types // Compliant, pointers are not counted { public void Foo(bool b1, byte b2, sbyte b3, int i1, uint i2, long i3, ulong i4, nint ni1, nuint ni2, IntPtr p1, UIntPtr p2, char c1, float d1, double d2, string s1, object o1) { } } """).WithOptions(LanguageOptions.FromCSharp9).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Implicit_Explicit_Operators() => withThreshold0.AddSnippet(""" using System.IO; public class WithConversionOperators // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 0 or less.}} { public static implicit operator Stream(WithConversionOperators x) => null; // +1 Stream (return type); self not counted public static explicit operator MemoryStream(WithConversionOperators x) => null; // +1 MemoryStream (return type); self not counted public static explicit operator WithConversionOperators(FileStream x) => null; // self (not counted); +1 FileStream (parameter type) } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_UserDefined_Delegates() => withThreshold0.AddSnippet(""" using System.IO; public delegate void MyCallback(Stream s); public class WithUserDefinedDelegate // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 0 or less.}} { private MyCallback _cb; // +1 MyCallback (user-defined delegate) private FileStream _fs; // +1 FileStream } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Extension_Methods() => withThreshold0.AddSnippet(""" using System.IO; public static class StreamExtensions // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 0 or less.}} { public static void Foo(this Stream s, FileStream fs) { } // +1 Stream (this-param), +1 FileStream (param) } """).Verify(); [TestMethod] public void AvoidExcessiveClassCoupling_Extension_Method_Container_Not_Counted_On_Invocation() => withThreshold1.AddSnippet(""" using System.IO; public static class StreamExtensions // Compliant: 1 dep (Stream, from this-param) { public static void Foo(this Stream s) { } } public class InvokesExtensionMethod // Compliant: 1 dep (Stream, from param) — StreamExtensions not counted, otherwise 2 { void M(Stream s) { s.Foo(); } } """).VerifyNoIssues(); [TestMethod] public void AvoidExcessiveClassCoupling_Latest() => withThreshold1.AddPaths("AvoidExcessiveClassCoupling.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AvoidExcessiveInheritanceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AvoidExcessiveInheritanceTest { private readonly VerifierBuilder defaultBuilder = new VerifierBuilder(); [TestMethod] public void AvoidExcessiveInheritance_DefaultValues() => defaultBuilder.AddPaths( "AvoidExcessiveInheritance_DefaultValues.cs", // The test cases are duplicated to make sure the rules can execute in a concurrent manner. "AvoidExcessiveInheritance_DefaultValues.Concurrent.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void AvoidExcessiveInheritance_DefaultValues_Records() => defaultBuilder.AddPaths( "AvoidExcessiveInheritance_DefaultValues.Records.cs", // The test cases are duplicated to make sure the rules can execute in a concurrent manner. "AvoidExcessiveInheritance_DefaultValues.Records.Concurrent.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void AvoidExcessiveInheritance_DefaultValues_FileScopedTypes() => defaultBuilder.AddPaths("AvoidExcessiveInheritance_DefaultValues.FileScopedTypes.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void AvoidExcessiveInheritance_CustomValuesFullyNamedFilteredClass() => new VerifierBuilder() .AddAnalyzer(() => CreateAnalyzerWithFilter("Tests.Diagnostics.SecondSubClass")) .AddPaths("AvoidExcessiveInheritance_CustomValues.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void AvoidExcessiveInheritance_CustomValuesWildcardFilteredClass() => new VerifierBuilder() .AddAnalyzer(() => CreateAnalyzerWithFilter("Tests.Diagnostics.*SubClass")) .AddPaths("AvoidExcessiveInheritance_CustomValues.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void AvoidExcessiveInheritance_CustomValuesWildcardFilteredRecord() => new VerifierBuilder() .AddAnalyzer(() => CreateAnalyzerWithFilter("Tests.Diagnostics.*SubRecord")) .AddPaths("AvoidExcessiveInheritance_CustomValues.Records.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void FilteredClasses_ByDefault_ShouldBeEmpty() => new AvoidExcessiveInheritance().FilteredClasses.Should().BeEmpty(); private static AvoidExcessiveInheritance CreateAnalyzerWithFilter(string filter) => new() { MaximumDepth = 2, FilteredClasses = filter }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AvoidLambdaExpressionInLoopsInBlazorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AvoidLambdaExpressionInLoopsInBlazorTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void AvoidLambdaExpressionInLoopsInBlazor_Blazor() => builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void AvoidLambdaExpressionInLoopsInBlazor_BlazorLoopsWithNoBody() => builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void AvoidLambdaExpressionInLoopsInBlazor_UsingRenderFragment() => builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.RenderFragment.razor", "AvoidLambdaExpressionInLoopsInBlazor.RenderFragmentConsumer.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void AvoidLambdaExpressionInLoopsInBlazor_CS() => builder.AddPaths("AvoidLambdaExpressionInLoopsInBlazor.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreComponents("7.0.13")) .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreComponentsWeb("7.0.13")) .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/AvoidUnsealedAttributesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AvoidUnsealedAttributesTest { [TestMethod] public void AvoidUnsealedAttributes_CS() => new VerifierBuilder().AddPaths("AvoidUnsealedAttributes.cs").Verify(); [TestMethod] public void AvoidUnsealedAttributes_VB() => new VerifierBuilder().AddPaths("AvoidUnsealedAttributes.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BeginInvokePairedWithEndInvokeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class BeginInvokePairedWithEndInvokeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void BeginInvokePairedWithEndInvoke_CS() => builderCS.AddPaths("BeginInvokePairedWithEndInvoke.cs").Verify(); [TestMethod] public void BeginInvokePairedWithEndInvoke_CS_Latest() => builderCS.AddPaths("BeginInvokePairedWithEndInvoke.Latest.cs", "BeginInvokePairedWithEndInvoke.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void BeginInvokePairedWithEndInvoke_VB() => builderVB.AddPaths("BeginInvokePairedWithEndInvoke.vb", "BeginInvokePairedWithEndInvoke.Partial.vb") .WithOptions(LanguageOptions.FromVisualBasic14) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BinaryOperationWithIdenticalExpressionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class BinaryOperationWithIdenticalExpressionsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void BinaryOperationWithIdenticalExpressions_CS() => builderCS.AddPaths("BinaryOperationWithIdenticalExpressions.cs").Verify(); [TestMethod] public void BinaryOperationWithIdenticalExpressions_TestProject_CS() => builderCS.AddPaths("BinaryOperationWithIdenticalExpressions.cs") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void BinaryOperationWithIdenticalExpressions_VB() => builderVB.AddPaths("BinaryOperationWithIdenticalExpressions.vb").Verify(); [TestMethod] public void BinaryOperationWithIdenticalExpressions_TestProject_VB() => builderVB.AddPaths("BinaryOperationWithIdenticalExpressions.vb") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void BinaryOperationWithIdenticalExpressions_CS_Latest() => builderCS.AddPaths("BinaryOperationWithIdenticalExpressions.CSharpLatest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BlazorQueryParameterRoutableComponentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class BlazorQueryParameterRoutableComponentTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void BlazorQueryParameterRoutableComponent_Blazor() => builder.AddPaths("BlazorQueryParameterRoutableComponent.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void BlazorQueryParameterRoutableComponent_BlazorNoRoute() => builder.AddPaths("BlazorQueryParameterRoutableComponent.NoRoute.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void BlazorQueryParameterRoutableComponent_Partial() => builder.WithOptions(LanguageOptions.CSharpLatest) .AddPaths( "BlazorQueryParameterRoutableComponent.Latest.Partial.razor", "BlazorQueryParameterRoutableComponent.Latest.Partial.1.razor.cs", "BlazorQueryParameterRoutableComponent.Latest.Partial.2.razor.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void BlazorQueryParameterRoutableComponent_CS() => builder.AddPaths("BlazorQueryParameterRoutableComponent.Compliant.cs", "BlazorQueryParameterRoutableComponent.Noncompliant.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreComponents("7.0.13")) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BooleanCheckInvertedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class BooleanCheckInvertedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void BooleanCheckInverted_CS() => builderCS.AddPaths("BooleanCheckInverted.cs").Verify(); [TestMethod] public void BooleanCheckInverted_CS_CodeFix() => builderCS.AddPaths("BooleanCheckInverted.cs") .WithCodeFix() .WithCodeFixedPaths("BooleanCheckInverted.Fixed.cs", "BooleanCheckInverted.Fixed.Batch.cs") .VerifyCodeFix(); [TestMethod] public void BooleanCheckInverted_VB() => new VerifierBuilder().AddPaths("BooleanCheckInverted.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void BooleanCheckInverted_CS_Latest() => builderCS .WithOptions(LanguageOptions.CSharpLatest) .WithConcurrentAnalysis(false) .AddPaths("BooleanCheckInverted.Latest.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BooleanLiteralUnnecessaryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class BooleanLiteralUnnecessaryTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void BooleanLiteralUnnecessary_CS() => builderCS.AddPaths("BooleanLiteralUnnecessary.cs").Verify(); [TestMethod] public void BooleanLiteralUnnecessary_CodeFix_CS() => builderCS.AddPaths("BooleanLiteralUnnecessary.cs") .WithCodeFix() .WithCodeFixedPaths("BooleanLiteralUnnecessary.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void BooleanLiteralUnnecessary_Latest() => builderCS.AddPaths("BooleanLiteralUnnecessary.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void BooleanLiteralUnnecessary_CodeFix_Latest() => builderCS.AddPaths("BooleanLiteralUnnecessary.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("BooleanLiteralUnnecessary.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); [TestMethod] public void BooleanLiteralUnnecessary_VB() => new VerifierBuilder().AddPaths("BooleanLiteralUnnecessary.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BreakOutsideSwitchTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class BreakOutsideSwitchTest { private readonly VerifierBuilder verifier = new VerifierBuilder(); [TestMethod] public void BreakOutsideSwitch() => verifier.AddPaths("BreakOutsideSwitch.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/BypassingAccessibilityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class BypassingAccessibilityTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void BypassingAccessibility_CS() => builderCS.AddPaths("BypassingAccessibility.cs").Verify(); [TestMethod] public void BypassingAccessibility_CSharp_Latest() => builderCS.AddPaths("BypassingAccessibility.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void BypassingAccessibility_VB() => builderVB.AddPaths("BypassingAccessibility.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CallToAsyncMethodShouldNotBeBlockingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CallToAsyncMethodShouldNotBeBlockingTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CallToAsyncMethodShouldNotBeBlocking() => builder.AddPaths("CallToAsyncMethodShouldNotBeBlocking.cs").AddReferences(NuGetMetadataReference.MicrosoftNetSdkFunctions()).Verify(); [TestMethod] public void CallToAsyncMethodShouldNotBeBlocking_TopLevelStatements() => builder.AddPaths("CallToAsyncMethodShouldNotBeBlocking.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void CallToAsyncMethodShouldNotBeBlocking_CS_Latest() => builder.AddPaths("CallToAsyncMethodShouldNotBeBlocking.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CallerInformationParametersShouldBeLastTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CallerInformationParametersShouldBeLastTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CallerInformationParametersShouldBeLast() => builder.AddPaths("CallerInformationParametersShouldBeLast.cs").Verify(); [TestMethod] public void CallerInformationParametersShouldBeLast_CS_Latest() => builder.AddPaths("CallerInformationParametersShouldBeLast.Latest.cs", "CallerInformationParametersShouldBeLast.Latest.Partial.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void CallerInformationParametersShouldBeLastInvalidSyntax() => builder.AddPaths("CallerInformationParametersShouldBeLastInvalidSyntax.cs").WithLanguageVersion(LanguageVersion.CSharp7).WithConcurrentAnalysis(false).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CastConcreteTypeToInterfaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CastConcreteTypeToInterfaceTest { [TestMethod] public void CastConcreteTypeToInterface() => new VerifierBuilder().AddPaths("CastConcreteTypeToInterface.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CastShouldNotBeDuplicatedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CastShouldNotBeDuplicatedTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void CastShouldNotBeDuplicated() => Builder.AddPaths("CastShouldNotBeDuplicated.cs").Verify(); [TestMethod] public void CastShouldNotBeDuplicated_CS_Latest() => Builder.AddPaths("CastShouldNotBeDuplicated.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #if NET [TestMethod] public void CastShouldNotBeDuplicated_MvcView() => Builder .AddSnippet(""" public class Base {} public class Derived: Base { public int Prop { get; set; } } """) .AddPaths("CastShouldNotBeDuplicated.cshtml") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .VerifyNoIssues(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CatchEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CatchEmptyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CatchEmpty() => builder.AddPaths("CatchEmpty.cs").Verify(); [TestMethod] public void CatchEmpty_InTest() => builder.AddPaths("CatchEmpty.cs").AddTestReference().VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CatchRethrowTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CatchRethrowTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void CatchRethrow() => builderCS.AddPaths("CatchRethrow.cs").Verify(); [TestMethod] public void CatchRethrow_CodeFix() => builderCS.AddPaths("CatchRethrow.cs") .WithCodeFix() .WithCodeFixedPaths("CatchRethrow.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CatchRethrow_VB() => new VerifierBuilder().AddPaths("CatchRethrow.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CertificateValidationCheckTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CertificateValidationCheckTest { private static readonly VerifierBuilder WithReferences = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemNetHttp) .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .AddReferences(MetadataReferenceFacade.NetStandard); private readonly VerifierBuilder builderCS = WithReferences.AddAnalyzer(() => new CS.CertificateValidationCheck()); private readonly VerifierBuilder builderVB = WithReferences.AddAnalyzer(() => new VB.CertificateValidationCheck()); [TestMethod] public void CertificateValidationCheck_CS() => builderCS.AddPaths("CertificateValidationCheck.cs").Verify(); [TestMethod] public void CertificateValidationCheck_CS_Latest() => builderCS.AddPaths("CertificateValidationCheck.Latest.cs", "CertificateValidationCheck.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void CertificateValidationCheck_CS_TopLevelStatements() => builderCS.AddPaths("CertificateValidationCheck.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void CertificateValidationCheck_VB() => builderVB.AddPaths("CertificateValidationCheck.vb").Verify(); [TestMethod] public void CreateParameterLookup_CS_ThrowsException() { var analyzer = new CS.CertificateValidationCheck(); Action a = () => analyzer.CreateParameterLookup(null, null); a.Should().Throw(); } [TestMethod] public void CreateParameterLookup_VB_ThrowsException() { var analyzer = new VB.CertificateValidationCheck(); Action a = () => analyzer.CreateParameterLookup(null, null); a.Should().Throw(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CheckArgumentExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CheckArgumentExceptionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CheckArgumentException() => builder.AddPaths("CheckArgumentException.cs").Verify(); [TestMethod] public void CheckArgumentException_TopLevelStatements() => builder.AddPaths("CheckArgumentException.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void CheckArgumentException_CS_Latest() => builder.AddPaths("CheckArgumentException.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CheckFileLicenseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CheckFileLicenseTest { private const string SingleLineHeader = "// Copyright (c) SonarSource. All Rights Reserved. Licensed under the LGPL License. See License.txt in the project root for license information."; private const string MultiLineHeader = @"/* * SonarQube, open source software quality management tool. * Copyright (C) 2008-2013 SonarSource * mailto:contact AT sonarsource DOT com * * SonarQube 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. * * SonarQube 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. */"; private const string MultiSingleLineCommentHeader = @"//----- // MyHeader //-----"; private const string HeaderForcingLineBreak = @"//--- "; private const string SingleLineRegexHeader = @"// Copyright \(c\) \w*\. All Rights Reserved\. " + @"Licensed under the LGPL License\. See License\.txt in the project root for license information\."; private const string MultiLineRegexHeader = @"/\* \* SonarQube, open source software quality management tool\. \* Copyright \(C\) \d\d\d\d-\d\d\d\d SonarSource \* mailto:contact AT sonarsource DOT com \* \* SonarQube 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\. \* \* SonarQube 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\. \*/"; private const string MultiLineRegexWithNewLine = "//-{5}\r\n// MyHeader\r\n//-{5}"; private const string MultiLineRegexWithDot = "//-{5}.+// MyHeader.+//-{5}"; private const string FailingSingleLineRegexHeader = "["; [TestMethod] public void CheckFileLicense_WhenUnlicensedFileStartingWithUsing_ShouldBeNoncompliant_CS() => Builder(SingleLineHeader).AddPaths("CheckFileLicense_NoLicenseStartWithUsing.cs").Verify(); [TestMethod] public void CheckFileLicense_WhenLicensedFileStartingWithUsing_ShouldBeCompliant_CS() => Builder(SingleLineHeader).AddPaths("CheckFileLicense_SingleLineLicenseStartWithUsing.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedFileStartingWithUsingAndUsingCustomValues_ShouldBeCompliant_CS() => Builder(SingleLineRegexHeader, true).AddPaths("CheckFileLicense_SingleLineLicenseStartWithUsing.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultilineCommentStartingWithUsing_ShouldBeCompliant_CS() => Builder(MultiLineHeader).AddPaths("CheckFileLicense_MultiLineLicenseStartWithUsing.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultilineCommentStartingWithUsingWithCustomValues_ShouldBeCompliant_CS() => Builder(MultiLineRegexHeader, true).AddPaths("CheckFileLicense_MultiLineLicenseStartWithUsing.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenNoLicenseStartingWithNamespace_ShouldBeNonCompliant_CS() => Builder(SingleLineHeader).AddPaths("CheckFileLicense_NoLicenseStartWithNamespace.cs").Verify(); [TestMethod] public void CheckFileLicense_WhenLicensedWithSingleLineCommentStartingWithNamespace_ShouldBeCompliant_CS() => Builder(SingleLineHeader).AddPaths("CheckFileLicense_SingleLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithSingleLineCommentStartingWithNamespaceAndUsingCustomValues_ShouldBeCompliant_CS() => Builder(SingleLineRegexHeader, true).AddPaths("CheckFileLicense_SingleLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultilineCommentStartingWithNamespace_ShouldBeCompliant_CS() => Builder(MultiLineHeader).AddPaths("CheckFileLicense_MultiLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultilineCommentStartingWithNamespaceAndUsingCustomValues_ShouldBeCompliant_CS() => Builder(MultiLineRegexHeader, true).AddPaths("CheckFileLicense_MultiLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultiSingleLineCommentStartingWithNamespaceAndNoRegex_ShouldBeCompliant_CS() => Builder(MultiSingleLineCommentHeader).AddPaths("CheckFileLicense_MultiSingleLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultiSingleLineCommentStartingWithAdditionalComments_ShouldBeCompliant_CS() => Builder(MultiSingleLineCommentHeader).AddPaths("CheckFileLicense_MultiSingleLineLicenseStartWithAdditionalComment.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultiSingleLineCommentStartingWithAdditionalCommentOnSameLine_ShouldBeNonCompliant_CS() => Builder(MultiSingleLineCommentHeader).AddPaths("CheckFileLicense_MultiSingleLineLicenseStartWithAdditionalCommentOnSameLine.cs").Verify(); [TestMethod] public void CheckFileLicense_WithForcingEmptyLines_ShouldBeNonCompliant_CS() => Builder(HeaderForcingLineBreak).AddPaths("CheckFileLicense_ForcingEmptyLinesKo.cs").Verify(); [TestMethod] public void CheckFileLicense_WithForcingEmptyLines_ShouldBeCompliant_CS() => Builder(HeaderForcingLineBreak).AddPaths("CheckFileLicense_ForcingEmptyLinesOk.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultiSingleLineCommentStartingWithNamespaceAndMultiLineRegexWithNewLine_ShouldBeCompliant_CS() => Builder(MultiLineRegexWithNewLine, true).AddPaths("CheckFileLicense_MultiSingleLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenLicensedWithMultiSingleLineCommentStartingWithNamespaceAndMultiLineRegexWithDot_ShouldBeCompliant_CS() => Builder(MultiLineRegexWithDot, true).AddPaths("CheckFileLicense_MultiSingleLineLicenseStartWithNamespace.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenEmptyFile_ShouldBeNonCompliant_CS() => // While we put Noncompliant annotation on 1st line of any file, in this case, the file needs to be empty Builder(SingleLineHeader).AddPaths("CheckFileLicense_EmptyFile.cs").Invoking(x => x.Verify()).Should().Throw().WithMessage(""" There are differences for CSharp7 CheckFileLicense_EmptyFile.cs: Line 1: Unexpected issue 'Add or update the header of this file.' Rule S1451 There is 1 more difference in CheckFileLicense_EmptyFile.Concurrent.cs """); [TestMethod] public void CheckFileLicenseCodeFix_WhenThereIsAYearDifference_ShouldBeNonCompliant_CS() => Builder(MultiLineHeader).AddPaths("CheckFileLicense_YearDifference.cs").Verify(); [TestMethod] public void CheckFileLicense_WhenProvidingAnInvalidRegex_ShouldThrowException_CS() { var compilation = SolutionBuilder.CreateSolutionFromPath(@"TestCases\CheckFileLicense_NoLicenseStartWithUsing.cs").Compile(LanguageOptions.CSharpLatest.ToArray()).Single(); var errors = DiagnosticVerifier.AnalyzerExceptions(compilation, new CS.CheckFileLicense { HeaderFormat = FailingSingleLineRegexHeader, IsRegularExpression = true }); errors.Should().ContainSingle().Which.GetMessage().Should() .Contain("System.InvalidOperationException") .And.Contain("Invalid regular expression: " + FailingSingleLineRegexHeader); } [TestMethod] public void CheckFileLicense_WhenUsingComplexRegex_ShouldBeCompliant_CS() => Builder(@"// \r\n// Copyright \(c\) 2012 All Rights Reserved\r\n// \r\n// .*\r\n// .*\r\n// .*\r\n", true) .AddPaths("CheckFileLicense_ComplexRegex.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicense_WhenUsingMultilinesHeaderAsSingleLineString_ShouldBeCompliant_CS() => Builder(@"// \r\n// Copyright (c) 2012 All Rights Reserved\r\n// \r\n// Name of the Author\r\n// 08/22/2017 12:39:58 AM \r\n// Class representing a Sample entity\r\n", false) .AddPaths("CheckFileLicense_ComplexRegex.cs") .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void CheckFileLicenseCodeFix_WhenNoLicenseStartWithNamespaceAndUsesDefaultValues_ShouldBeNoncompliant_CS() => new VerifierBuilder() .WithCodeFix() .AddPaths("CheckFileLicense_DefaultValues.cs") .WithCodeFixedPaths("CheckFileLicense_DefaultValues.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_CSharp9_ShouldBeNoncompliant_CS() => new VerifierBuilder() .WithCodeFix() .AddPaths("CheckFileLicense_CSharp9.cs") .WithCodeFixedPaths("CheckFileLicense_CSharp9.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp9) .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_WhenNoLicenseStartingWithUsing_ShouldBeFixedAsExpected_CS() => Builder(SingleLineHeader) .WithCodeFix() .AddPaths("CheckFileLicense_NoLicenseStartWithUsing.cs") .WithCodeFixedPaths("CheckFileLicense_NoLicenseStartWithUsing.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_WhenNoLicenseStartingWithNamespace_ShouldBeFixedAsExpected_CS() => Builder(SingleLineHeader) .WithCodeFix() .AddPaths("CheckFileLicense_NoLicenseStartWithNamespace.cs") .WithCodeFixedPaths("CheckFileLicense_NoLicenseStartWithNamespace.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_WhenThereIsAYearDifference_ShouldBeFixedAsExpected_CS() => Builder(MultiLineHeader) .WithCodeFix() .AddPaths("CheckFileLicense_YearDifference.cs") .WithCodeFixedPaths("CheckFileLicense_YearDifference.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_WhenOutdatedLicenseStartingWithUsing_ShouldBeFixedAsExpected_CS() => Builder(MultiLineHeader) .WithCodeFix() .AddPaths("CheckFileLicense_OutdatedLicenseStartWithUsing.cs") .WithCodeFixedPaths("CheckFileLicense_OutdatedLicenseStartWithUsing.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicenseCodeFix_WhenOutdatedLicenseStartingWithNamespace_ShouldBeFixedAsExpected_CS() => Builder(MultiLineHeader) .WithCodeFix() .AddPaths("CheckFileLicense_OutdatedLicenseStartWithNamespace.cs") .WithCodeFixedPaths("CheckFileLicense_OutdatedLicenseStartWithNamespace.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CheckFileLicense_NullHeader_NoIssueReported_CS() => Builder(null).AddPaths("CheckFileLicense_NoLicenseStartWithNamespace.cs").VerifyNoIssues(); // No need to duplicate all test cases from C#, because we are sharing the implementation [TestMethod] public void CheckFileLicense_NonCompliant_VB() => new VerifierBuilder().AddPaths("CheckFileLicense_NonCompliant.vb").Verify(); [TestMethod] public void CheckFileLicense_Compliant_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.CheckFileLicense { HeaderFormat = @"Copyright \(c\) [0-9]+ All Rights Reserved ", IsRegularExpression = true }) .AddPaths("CheckFileLicense_Compliant.vb") .VerifyNoIssues(); private static VerifierBuilder Builder(string headerFormat, bool isRegularExpression = false) => new VerifierBuilder().AddAnalyzer(() => new CS.CheckFileLicense { HeaderFormat = headerFormat, IsRegularExpression = isRegularExpression }); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassAndMethodNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassAndMethodNameTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ClassAndMethodName_CS() => builderCS.AddPaths("ClassAndMethodName.cs", "ClassAndMethodName.Partial.cs") .AddReferences(MetadataReferenceFacade.NetStandard21) .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void ClassAndMethodName_InTestProject_CS() => builderCS.AddPaths("ClassAndMethodName.Tests.cs").AddTestReference().WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void ClassAndMethodName_TopLevelStatement_CS() => builderCS.AddPaths("ClassAndMethodName.TopLevelStatement.cs").WithTopLevelStatements().Verify(); [TestMethod] public void ClassAndMethodName_TopLevelStatement_InTestProject_CS() => builderCS.AddPaths("ClassAndMethodName.TopLevelStatement.Test.cs").AddTestReference().WithTopLevelStatements().Verify(); [TestMethod] public void ClassAndMethodName_MethodName_CS_Latest() => builderCS.AddPaths("ClassAndMethodName.MethodName.Latest.cs", "ClassAndMethodName.MethodName.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ClassAndMethodName_MethodName_InTestProject_CS_Latest() => builderCS.AddPaths("ClassAndMethodName.MethodName.Latest.cs", "ClassAndMethodName.MethodName.Latest.Partial.cs").AddTestReference().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void ClassAndMethodName_VB(ProjectType projectType) => new VerifierBuilder().AddPaths("ClassAndMethodName.vb").AddReferences(TestCompiler.ProjectTypeReference(projectType)).Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void ClassAndMethodName_MethodName(ProjectType projectType) => builderCS.AddPaths("ClassAndMethodName.MethodName.cs", "ClassAndMethodName.MethodName.Partial.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] [DataRow("foo", "foo")] [DataRow("Foo", "Foo")] [DataRow("FFF", "FFF")] [DataRow("FfF", "Ff", "F")] [DataRow("Ff9F", "Ff", "9", "F")] [DataRow("你好", "你", "好")] [DataRow("FFf", "F", "Ff")] [DataRow("")] [DataRow("FF9d", "FF", "9", "d")] [DataRow("y2x5__w7", "y", "2", "x", "5", "_", "_", "w", "7")] [DataRow("3%c#account", "3", "%", "c", "#", "account")] public void TestSplitToParts(string name, params string[] expectedParts) => CS.ClassAndMethodName.SplitToParts(name).Should().BeEquivalentTo(expectedParts); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassNamedExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassNamedExceptionTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ClassNamedException_CS() => builderCS .AddPaths("ClassNamedException.cs") .Verify(); [TestMethod] public void ClassNamedException_CS_Latest() => builderCS .AddPaths("ClassNamedException.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyNoIssues(); [TestMethod] public void ClassNamedException_VB() => builderVB .AddPaths("ClassNamedException.vb") .Verify(); #if NETFRAMEWORK [TestMethod] public void ClassNamedException_Interop_CS() => builderCS .AddPaths("ClassNamedException.Interop.cs") .VerifyNoIssues(); [TestMethod] public void ClassNamedException_Interop_VB() => builderVB .AddPaths("ClassNamedException.Interop.vb") .VerifyNoIssues(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassNotInstantiatableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassNotInstantiatableTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ClassNotInstantiatable_CS() => builderCS.AddPaths("ClassNotInstantiatable.cs").Verify(); [TestMethod] public void ClassNotInstantiatable_CS_Latest() => builderCS.AddPaths("ClassNotInstantiatable.Latest.cs", "ClassNotInstantiatable.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ClassNotInstantiatable_VB() => new VerifierBuilder().AddPaths("ClassNotInstantiatable.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassShouldNotBeEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassShouldNotBeEmptyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ClassShouldNotBeEmpty_CS() => builderCS .AddPaths("ClassShouldNotBeEmpty.cs") .Verify(); [TestMethod] public void ClassShouldNotBeEmpty_VB() => builderVB .AddPaths("ClassShouldNotBeEmpty.vb") .Verify(); [TestMethod] public void ClassShouldNotBeEmpty_CS_Latest() => builderCS .AddPaths("ClassShouldNotBeEmpty.Latest.cs", "ClassShouldNotBeEmpty.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #if NET [TestMethod] public void ClassShouldNotBeEmpty_Inheritance_CS() => builderCS .AddPaths("ClassShouldNotBeEmpty.Inheritance.cs") .AddReferences(AdditionalReferences()) .Verify(); [TestMethod] public void ClassShouldNotBeEmpty_Inheritance_VB() => builderVB .AddPaths("ClassShouldNotBeEmpty.Inheritance.vb") .AddReferences(AdditionalReferences()) .Verify(); private static MetadataReference[] AdditionalReferences() => [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcRazorPages ]; #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassWithEqualityShouldImplementIEquatableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassWithEqualityShouldImplementIEquatableTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ClassWithEqualityShouldImplementIEquatable() => builder.AddPaths("ClassWithEqualityShouldImplementIEquatable.cs").Verify(); [TestMethod] public void ClassWithEqualityShouldImplementIEquatable_CS_Latest() => builder.AddPaths("ClassWithEqualityShouldImplementIEquatable.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ClassWithOnlyStaticMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClassWithOnlyStaticMemberTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ClassWithOnlyStaticMember() => builder.AddPaths("ClassWithOnlyStaticMember.cs").Verify(); [TestMethod] public void ClassWithOnlyStaticMember_TopLevelStatements() => builder.AddPaths("ClassWithOnlyStaticMember.TopLevelStatements.cs") .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void ClassWithOnlyStaticMember_Latest() => builder.AddPaths("ClassWithOnlyStaticMember.Latest.cs", "ClassWithOnlyStaticMember.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CloudNative/AzureFunctionsCatchExceptionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AzureFunctionsCatchExceptionsTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("CloudNative").AddReferences(NuGetMetadataReference.MicrosoftNetSdkFunctions()); [TestMethod] public void AzureFunctionsCatchExceptions_CS() => builder.AddPaths("AzureFunctionsCatchExceptions.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CloudNative/AzureFunctionsLogFailuresTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AzureFunctionsLogFailuresTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("CloudNative").AddReferences(NuGetMetadataReference.MicrosoftNetSdkFunctions()); [TestMethod] public void AzureFunctionsLogFailures_CS() => builder.AddPaths("AzureFunctionsLogFailures.cs").Verify(); [TestMethod] // Calls to LoggerExtensions.LogSomething extension methods [DataRow(true, "log.LogError(ex, string.Empty);")] [DataRow(true, "log.LogCritical(ex, string.Empty);")] [DataRow(true, "log.LogWarning(ex, string.Empty);")] [DataRow(true, "log.LogInformation(ex, string.Empty);")] [DataRow(false, "log.LogDebug(ex, string.Empty);")] [DataRow(false, "log.LogTrace(ex, string.Empty);")] // LoggerExtensions.Log with LogLevel parameter [DataRow(true, "log.Log(LogLevel.Information, ex, string.Empty);")] [DataRow(true, "log.Log(LogLevel.Warning, ex, string.Empty);")] [DataRow(true, "log.Log(LogLevel.Error, ex, string.Empty);")] [DataRow(true, "log.Log(LogLevel.Critical, ex, string.Empty);")] [DataRow(false, "log.Log(LogLevel.Trace, ex, string.Empty);")] [DataRow(false, "log.Log(LogLevel.Debug, ex, string.Empty);")] [DataRow(false, "log.Log(LogLevel.None, ex, string.Empty);")] // Calls with complications in it [DataRow(true, "log.Log(LogLevel.Critical, string.Empty);")] // It is not required to pass the exception to the log call [DataRow(true, "log.Log(exception: ex, message: string.Empty, logLevel: LogLevel.Error);")] // Out of order named args [DataRow(true, "log.Log(message: string.Empty, logLevel: LogLevel.Error);")] [DataRow(true, @"log.Log((LogLevel)Enum.Parse(typeof(LogLevel), ""Trace""), string.Empty);")] // call is compliant, if LogLevel is not known at compile time // Calls to ILogger.Log [DataRow(true, "log.Log(LogLevel.Error, new EventId(), (object)null, ex, (s, e) => string.Empty);")] [DataRow(true, "log.Log(eventId: new EventId(), state: (object)null, exception: ex, formatter: (s, e) => string.Empty, logLevel: LogLevel.Error);")] [DataRow(false, "log.Log(eventId: new EventId(), state: (object)null, exception: ex, formatter: (s, e) => string.Empty, logLevel: LogLevel.Trace);")] // Receiver is complicated expression [DataRow(true, "((ILogger)log).Log(LogLevel.Critical, string.Empty);")] [DataRow(false, "((ILogger)log).Log(LogLevel.Trace, string.Empty);")] [DataRow(true, "new Func(()=>log)().Log(LogLevel.Critical, string.Empty);")] [DataRow(true, "new Func(()=>log)().Log(LogLevel.Error, new EventId(), (object)null, ex, (s, e) => string.Empty);")] // Non logging methods [DataRow(false, "log.BeginScope(string.Empty, string.Empty);")] [DataRow(false, "log.BeginScope(null);")] [DataRow(false, "log.IsEnabled(LogLevel.Warning);")] public void AzureFunctionsLogFailures_VerifyLoggerCalls(bool isCompliant, string loggerInvocation) { var snippet = builder.AddSnippet($$""" using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using System; public static class Function1 { [FunctionName("Function1")] public static void Run(ILogger log) { try { } catch(Exception ex) // {{(isCompliant ? "Compliant" : "Noncompliant")}} { {{loggerInvocation}} // {{(isCompliant ? string.Empty : "Secondary")}} } } } """); if (isCompliant) { snippet.VerifyNoIssues(); } else { snippet.Verify(); } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CloudNative/AzureFunctionsReuseClientsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules.CloudNative { [TestClass] public class AzureFunctionsReuseClientsTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("CloudNative") .AddReferences(NuGetMetadataReference.MicrosoftNetSdkFunctions()) .AddReferences(MetadataReferenceFacade.SystemThreadingTasks) .AddReferences(NuGetMetadataReference.SystemNetHttp()) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsHttp()); [TestMethod] public void AzureFunctionsReuseClients_HttpClient_CS() => builder.AddPaths("AzureFunctionsReuseClients.HttpClient.cs").Verify(); [TestMethod] public void AzureFunctionsReuseClients_HttpClient_CSharp9() => builder.AddPaths("AzureFunctionsReuseClients.HttpClient.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void AzureFunctionsReuseClients_DocumentClient_CS() => builder.AddPaths("AzureFunctionsReuseClients.DocumentClient.cs") .AddReferences(NuGetMetadataReference.MicrosoftAzureDocumentDB()) .Verify(); [TestMethod] public void AzureFunctionsReuseClients_CosmosClient_CS() => builder.AddPaths("AzureFunctionsReuseClients.CosmosClient.cs") .AddReferences(NuGetMetadataReference.MicrosoftAzureCosmos()) .Verify(); [TestMethod] public void AzureFunctionsReuseClients_ServiceBusV5_CS() => builder.AddPaths("AzureFunctionsReuseClients.ServiceBusV5.cs") .AddReferences(NuGetMetadataReference.MicrosoftAzureServiceBus()) .Verify(); [TestMethod] public void AzureFunctionsReuseClients_ServiceBusV7_CS() => builder.AddPaths("AzureFunctionsReuseClients.ServiceBusV7.cs") .AddReferences(NuGetMetadataReference.AzureMessagingServiceBus()) .Verify(); [TestMethod] public void AzureFunctionsReuseClients_Storage_CS() => builder.AddPaths("AzureFunctionsReuseClients.Storage.cs") .AddReferences(NuGetMetadataReference.AzureCore()) .AddReferences(NuGetMetadataReference.AzureStorageCommon()) .AddReferences(NuGetMetadataReference.AzureStorageBlobs()) .AddReferences(NuGetMetadataReference.AzureStorageQueues()) .AddReferences(NuGetMetadataReference.AzureStorageFilesShares()) .AddReferences(NuGetMetadataReference.AzureStorageFilesDataLake()) .Verify(); [TestMethod] public void AzureFunctionsReuseClients_ArmClient_CS() => builder.AddPaths("AzureFunctionsReuseClients.ArmClient.cs") .AddReferences(NuGetMetadataReference.AzureCore()) .AddReferences(NuGetMetadataReference.AzureIdentity()) .AddReferences(NuGetMetadataReference.AzureResourceManager()) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CloudNative/AzureFunctionsStatelessTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class AzureFunctionsStatelessTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("CloudNative").AddReferences(NuGetMetadataReference.MicrosoftNetSdkFunctions()); [TestMethod] public void AzureFunctionsStateless_CSharp8() => builder.AddPaths("AzureFunctionsStateless.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void AzureFunctionsStateless_Latest() => builder.AddPaths("AzureFunctionsStateless.Latest.cs", "AzureFunctionsStateless.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CloudNative/DurableEntityInterfaceRestrictionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DurableEntityInterfaceRestrictionsTest { #if NET private readonly VerifierBuilder builder = new VerifierBuilder() .WithBasePath("CloudNative") .AddReferences(NuGetMetadataReference.MicrosoftAzureWebJobsExtensionsDurableTask()); [TestMethod] public void DurableEntityInterfaceRestrictions_CSharp11() => builder.AddPaths("DurableEntityInterfaceRestrictions.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void DurableEntityInterfaceRestrictions_CS_NET() => builder.AddPaths("DurableEntityInterfaceRestrictions.cs").Verify(); #endif [TestMethod] public void DurableEntityInterfaceRestrictions_CS() => new VerifierBuilder() .AddReferences(NuGetMetadataReference.MicrosoftAzureWebJobsExtensionsDurableTask("2.13.7")) .WithBasePath("CloudNative") .AddPaths("DurableEntityInterfaceRestrictions.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CognitiveComplexityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CognitiveComplexityTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.CognitiveComplexity { Threshold = 0, PropertyThreshold = 0 }); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.CognitiveComplexity { Threshold = 0, PropertyThreshold = 0 }); [TestMethod] public void CognitiveComplexity_CS() => builderCS.AddPaths("CognitiveComplexity.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void CognitiveComplexity_CS_Latest() => builderCS .AddPaths("CognitiveComplexity.Latest.cs") .AddPaths("CognitiveComplexity.Latest.Partial.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void CognitiveComplexity_VB() => builderVB.AddPaths("CognitiveComplexity.vb").Verify(); [TestMethod] public void CognitiveComplexity_StackOverflow_CS() { if (!TestEnvironment.IsAzureDevOpsContext) // ToDo: Test throws OOM on Azure DevOps { builderCS.AddPaths("SyntaxWalker_InsufficientExecutionStackException.cs").VerifyNoIssues(); } } [TestMethod] public void CognitiveComplexity_StackOverflow_VB() { if (!TestEnvironment.IsAzureDevOpsContext) // ToDO: Test throws OOM on Azure DevOps { builderVB.AddPaths("SyntaxWalker_InsufficientExecutionStackException.vb").VerifyNoIssues(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CollectionEmptinessCheckingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CollectionEmptinessCheckingTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void CollectionEmptinessChecking_CS() => builderCS .AddPaths("CollectionEmptinessChecking.cs") .Verify(); [TestMethod] public void CollectionEmptinessChecking_VB() => builderVB .AddPaths("CollectionEmptinessChecking.vb") .Verify(); [TestMethod] public void CollectionEmptinessChecking_CodeFix_CS() => builderCS .AddPaths("CollectionEmptinessChecking.cs") .WithCodeFix() .WithCodeFixedPaths("CollectionEmptinessChecking.Fixed.cs") .VerifyCodeFix(); #if NET [TestMethod] public void CollectionEmptinessChecking_CS_Latest() => builderCS .AddPaths("CollectionEmptinessChecking.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CollectionPropertiesShouldBeReadOnlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CollectionPropertiesShouldBeReadOnlyTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemRuntimeSerialization); [TestMethod] public void CollectionPropertiesShouldBeReadOnly() => builder.AddPaths("CollectionPropertiesShouldBeReadOnly.cs") .Verify(); [TestMethod] public void CollectionPropertiesShouldBeReadOnly_CS_Latest() => builder.AddPaths("CollectionPropertiesShouldBeReadOnly.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void CollectionPropertiesShouldBeReadOnly_Razor() => builder.AddPaths("CollectionPropertiesShouldBeReadOnly.razor", "CollectionPropertiesShouldBeReadOnly.razor.cs") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CollectionQuerySimplificationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CollectionQuerySimplificationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CollectionQuerySimplification() => builder.AddPaths("CollectionQuerySimplification.cs") .Verify(); #if NETFRAMEWORK [TestMethod] public void CollectionQuerySimplification_NetFx() => builder.AddPaths("CollectionQuerySimplification.NetFx.cs") .AddReferences(FrameworkMetadataReference.SystemDataLinq) .Verify(); #endif #if NET [TestMethod] public void CollectionQuerySimplification_TopLevelStatements() => builder.AddPaths("CollectionQuerySimplification.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void CollectionQuerySimplification_Latest() => builder.AddPaths("CollectionQuerySimplification.Latest.cs") .AddReferences(EntityFrameworkNetReferences()) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); private static IEnumerable EntityFrameworkNetReferences() => Enumerable.Empty() .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore("2.2.6")) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational("2.2.6")) .Concat(NuGetMetadataReference.EntityFramework("6.2.0")) .Concat(NuGetMetadataReference.SystemComponentModelTypeConverter()); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CollectionsShouldImplementGenericInterfaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CollectionsShouldImplementGenericInterfaceTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemCollections) .WithErrorBehavior(CompilationErrorBehavior.Ignore); // It would be too tedious to implement all those interfaces [TestMethod] public void CollectionsShouldImplementGenericInterface() => builder.AddPaths("CollectionsShouldImplementGenericInterface.cs").Verify(); [TestMethod] public void CollectionsShouldImplementGenericInterface_Csharp9() => builder.AddPaths("CollectionsShouldImplementGenericInterface.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void CollectionsShouldImplementGenericInterface_Csharp10() => builder.AddPaths("CollectionsShouldImplementGenericInterface.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CommentKeywordTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CommentKeywordTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void CommentTodo_CS(ProjectType projectType) => builderCS.AddPaths("CommentTodo.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void CommentFixme_CS(ProjectType projectType) => builderCS.AddPaths("CommentFixme.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void CommentTodo_VB(ProjectType projectType) => builderVB.AddPaths("CommentTodo.vb") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void CommentFixme_VB(ProjectType projectType) => builderVB.AddPaths("CommentFixme.vb") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CommentLineEndTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CommentLineEndTest { [TestMethod] public void CommentLineEnd() => new VerifierBuilder().AddPaths("CommentLineEnd.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CommentedOutCodeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CommentedOutCodeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CommentedOutCode_Nonconcurrent() => builder.AddPaths("CommentedOutCode_Nonconcurrent.cs").WithConcurrentAnalysis(false).Verify(); [TestMethod] public void CommentedOutCode() => builder.AddPaths("CommentedOutCode.cs").Verify(); [TestMethod] public void CommentedOutCode_NoDocumentation() => builder.AddPaths("CommentedOutCode.cs") .WithConcurrentAnalysis(false) .WithOptions(ImmutableArray.Create(new CSharpParseOptions(documentationMode: DocumentationMode.None))) .Verify(); [TestMethod] public void CommentedOutCode_CodeFix_SingleLine() => builder.AddPaths("CommentedOutCode.SingleLine.ToFix.cs") .WithCodeFix() .WithCodeFixedPaths("CommentedOutCode.SingleLine.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void CommentedOutCode_CodeFix_MultiLine() => builder.AddPaths("CommentedOutCode.MultiLine.ToFix.cs") .WithCodeFix() .WithCodeFixedPaths("CommentedOutCode.MultiLine.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CommentsShouldNotBeEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CommentsShouldNotBeEmptyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void CommentsShouldNotBeEmpty_CS() => builderCS.AddPaths("CommentsShouldNotBeEmpty.cs").Verify(); [TestMethod] public void CommentsShouldNotBeEmpty_CS1() => builderCS .AddTestReference() .AddPaths("CommentsShouldNotBeEmpty.cs").VerifyNoIssues(); [TestMethod] public void CommentsShouldNotBeEmpty_VB() => builderVB.AddPaths("CommentsShouldNotBeEmpty.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ComparableInterfaceImplementationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ComparableInterfaceImplementationTest { [TestMethod] public void IComparableImplementation() => new VerifierBuilder().AddPaths("ComparableInterfaceImplementation.cs").Verify(); [TestMethod] public void IComparableImplementation_Latest() => new VerifierBuilder().AddPaths("ComparableInterfaceImplementation.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CompareNaNTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class CompareNaNTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void CompareNaN() => builder.AddPaths("CompareNaN.cs").Verify(); [TestMethod] public void CompareNaN_CS_Latest() => builder.AddPaths("CompareNaN.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConditionalSimplificationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConditionalSimplificationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFix = new VerifierBuilder().WithCodeFix(); [TestMethod] public void ConditionalSimplification_BeforeCSharp8() => builder.AddPaths("ConditionalSimplification.BeforeCSharp8.cs").WithOptions(LanguageOptions.BeforeCSharp8).Verify(); [TestMethod] public void ConditionalSimplification_CSharp8() => builder.AddPaths("ConditionalSimplification.CSharp8.cs").WithLanguageVersion(LanguageVersion.CSharp8).Verify(); [TestMethod] public void ConditionalSimplification_CSharp8_CodeFix() => codeFix.AddPaths("ConditionalSimplification.CSharp8.cs").WithLanguageVersion(LanguageVersion.CSharp8).WithCodeFixedPaths("ConditionalSimplification.CSharp8.Fixed.cs").VerifyCodeFix(); [TestMethod] public void ConditionalSimplification_FromCSharp8() => builder.AddPaths("ConditionalSimplification.FromCSharp8.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void ConditionalSimplification_BeforeCSharp8_CodeFix() => codeFix.AddPaths("ConditionalSimplification.BeforeCSharp8.cs") .WithCodeFixedPaths("ConditionalSimplification.BeforeCSharp8.Fixed.cs") .WithOptions(LanguageOptions.BeforeCSharp8).VerifyCodeFix(); [TestMethod] public void ConditionalSimplification_FromCSharp8_CodeFix() => codeFix.AddPaths("ConditionalSimplification.FromCSharp8.cs") .WithCodeFixedPaths("ConditionalSimplification.FromCSharp8.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp8) .VerifyCodeFix(); [TestMethod] public void ConditionalSimplification_FromCSharp9() => builder.AddPaths("ConditionalSimplification.FromCSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void ConditionalSimplification_FromCSharp10() => builder.AddPaths("ConditionalSimplification.FromCSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .AddReferences(NuGetMetadataReference.FluentAssertions(NugetPackageVersions.FluentAssertionsVersions.Ver7)) .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemNetHttp) .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemXmlLinq) .Verify(); [TestMethod] public void ConditionalSimplification_FromCSharp9_CodeFix() => codeFix.AddPaths("ConditionalSimplification.FromCSharp9.cs") .WithCodeFixedPaths("ConditionalSimplification.FromCSharp9.Fixed.cs") .WithTopLevelStatements() .VerifyCodeFix(); [TestMethod] public void ConditionalSimplification_FromCSharp10_CodeFix() => codeFix.AddPaths("ConditionalSimplification.FromCSharp10.cs") .WithCodeFixedPaths("ConditionalSimplification.FromCSharp10.Fixed.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .AddReferences(NuGetMetadataReference.FluentAssertions(NugetPackageVersions.FluentAssertionsVersions.Ver7)) .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemNetHttp) .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemXmlLinq) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConditionalStructureSameConditionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ConditionalStructureSameConditionTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void ConditionalStructureSameCondition_CS() => builderCS.AddPaths("ConditionalStructureSameCondition.cs").Verify(); [TestMethod] public void ConditionalStructureSameCondition_CS_CSharp9() => builderCS.AddPaths("ConditionalStructureSameCondition.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void ConditionalStructureSameCondition_CS_CSharp10() => builderCS.AddPaths("ConditionalStructureSameCondition.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void ConditionalStructureSameCondition_RazorFile_CorrectMessage() => builderCS.AddSnippet( """ @code { public bool condition { get; set; } public void Method() { var b = true; if (b && condition) // ^^^^^^^^^^^^^^ Secondary { } else if (b && condition) // Noncompliant {{This branch duplicates the one on line 8.}} // ^^^^^^^^^^^^^^ { } } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void ConditionalStructureSameCondition_VisualBasic() => new VerifierBuilder().AddPaths("ConditionalStructureSameCondition.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConditionalStructureSameImplementationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConditionalStructureSameImplementationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void ConditionalStructureSameImplementation_If_CSharp() => builderCS.AddPaths("ConditionalStructureSameImplementation_If.cs").Verify(); [TestMethod] public void ConditionalStructureSameImplementation_Switch_CSharp() => builderCS.AddPaths("ConditionalStructureSameImplementation_Switch.cs").Verify(); // https://github.com/SonarSource/sonar-dotnet/issues/9637 [TestMethod] [DataRow("nameof(first)", "nameof(first)", false)] [DataRow("nameof ( first ) ", "nameof(first)", false)] [DataRow("nameof(first)", "nameof(second)", true)] [DataRow("first.ToLower()", "nameof(second)", true)] [DataRow("nameof(first)", "first.ToLower()", true)] public void ConditionalStructureSameImplementation_NameOf_CSharp(string firstExpression, string secondExpression, bool isCompliant) { var compliantComment = isCompliant ? string.Empty : "// Noncompliant"; var secondaryComment = isCompliant ? string.Empty : "// Secondary"; var builder = builderCS.AddSnippet($$""" public class NameOfExpressions { public string Method(string first, string second) { if (first.Length == 42) { {{secondaryComment}} var ret = {{firstExpression}}; return ret; } else if (second.Length == 42) { {{compliantComment}} var ret = {{secondExpression}}; return ret; } return ""; } } """); if (isCompliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] [DataRow("NameOf(first)", "NameOf(first)", false)] [DataRow("NameOf ( first ) ", "NameOf(first)", false)] [DataRow("NameOf(first)", "NameOf(second)", true)] [DataRow("first.ToLower()", "NameOf(second)", true)] [DataRow("NameOf(first)", "first.ToLower()", true)] public void ConditionalStructureSameImplementation_SymbolCannotBeResolved_VB(string firstExpression, string secondExpression, bool isCompliant) { var compliantComment = isCompliant ? "' Compliant" : "' Noncompliant"; var builder = builderVB.AddSnippet($""" Public Class NameOfExpressions Public Function Method(first as String, second as String) If first.Length = 42 Then Dim ret = {firstExpression} Return ret ElseIf second.Length = 42 Then Dim ret = {secondExpression} {compliantComment} Return ret End If Return "" End Function End Class """); if (isCompliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] public void ConditionalStructureSameImplementation_If_VisualBasic() => builderVB.AddPaths("ConditionalStructureSameImplementation_If.vb").Verify(); [TestMethod] public void ConditionalStructureSameImplementation_Switch_VisualBasic() => builderVB.AddPaths("ConditionalStructureSameImplementation_Switch.vb").Verify(); [TestMethod] public void ConditionalStructureSameImplementation_If_CSharp_Latest() => builderCS.AddPaths("ConditionalStructureSameImplementation_If.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ConditionalStructureSameImplementation_Switch_CSharp_Latest() => builderCS.AddPaths("ConditionalStructureSameImplementation_Switch.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ConditionalStructureSameImplementation_RazorFile_CorrectMessage() => builderCS.AddSnippet( """ @code { public bool someCondition1 { get; set; } public void DoSomething1() { } public void Method() { if (someCondition1) { // Secondary DoSomething1(); DoSomething1(); } else { // Noncompliant {{Either merge this branch with the identical one on line 9 or change one of the implementations.}} DoSomething1(); DoSomething1(); } } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConditionalsShouldStartOnNewLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConditionalsShouldStartOnNewLineTest { [TestMethod] public void ConditionalsShouldStartOnNewLine() => new VerifierBuilder().AddPaths("ConditionalsShouldStartOnNewLine.cs").Verify(); #if NET [TestMethod] public void ConditionalsShouldStartOnNewLine_CS_Latest() => new VerifierBuilder().AddPaths("ConditionalsShouldStartOnNewLine.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).VerifyNoIssues(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConditionalsWithSameConditionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ConditionalsWithSameConditionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void ConditionalsWithSameCondition() => builder.AddPaths("ConditionalsWithSameCondition.cs").Verify(); [TestMethod] public void ConditionalsWithSameCondition_CSharp9() => builder.AddPaths("ConditionalsWithSameCondition.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void ConditionalsWithSameCondition_CSharp9_TopLevelStatements() => builder.AddPaths("ConditionalsWithSameCondition.CSharp9.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void ConditionalsWithSameCondition_CSharp10() => builder.AddPaths("ConditionalsWithSameCondition.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void ConditionalsWithSameCondition_RazorFile_CorrectMessage() => builder.AddSnippet( """ @code { public void doTheThing(object o) { } public void Method(int a, int b) { if (a == b) { doTheThing(b); } if (a == b) // Noncompliant {{This condition was just checked on line 7.}} // ^^^^^^ { doTheThing(b); } } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConstructorArgumentValueShouldExistTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConstructorArgumentValueShouldExistTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ConstructorArgumentValueShouldExist_CS_Latest_Net() => builderCS.AddPaths("ConstructorArgumentValueShouldExist.Latest.cs", "ConstructorArgumentValueShouldExist.Latest.Partial.cs") .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.CSharpLatest) .WithNetOnly() .Verify(); [TestMethod] public void ConstructorArgumentValueShouldExist_CS_NetFx() => builderCS.AddPaths("ConstructorArgumentValueShouldExist.cs") .AddReferences(MetadataReferenceFacade.SystemXaml) .WithNetFrameworkOnly() .Verify(); [TestMethod] public void ConstructorArgumentValueShouldExist_VB_NetFx() => new VerifierBuilder().AddPaths("ConstructorArgumentValueShouldExist.vb") .AddReferences(MetadataReferenceFacade.SystemXaml) .WithNetFrameworkOnly() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConstructorOverridableCallTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConstructorOverridableCallTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ConstructorOverridableCall() => builder.AddPaths("ConstructorOverridableCall.cs").Verify(); [TestMethod] public void ConstructorOverridableCall_Latest() => builder.AddPaths("ConstructorOverridableCall.Latest.cs", "ConstructorOverridableCall.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ConstructorOverridableCall_Nancy() => builder.AddPaths("ConstructorOverridableCall.Nancy.cs").WithConcurrentAnalysis(false).VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ConsumeValueTaskCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ConsumeValueTaskCorrectlyTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemThreadingTasks) .AddReferences(MetadataReferenceFacade.SystemMemory); [TestMethod] public void ConsumeValueTaskCorrectly() => builder.AddPaths("ConsumeValueTaskCorrectly.cs").Verify(); [TestMethod] public void ConsumeValueTaskCorrectly_CSharp_Latest() => builder.AddPaths("ConsumeValueTaskCorrectly.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ControlCharacterInStringTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ControlCharacterInStringTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ControlCharacterInString_CSharp8() => builder.AddPaths("ControlCharacterInString.cs").Verify(); [TestMethod] public void ControlCharacterInString_Latest() => builder.AddPaths("ControlCharacterInString.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/CryptographicKeyShouldNotBeTooShortTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CryptographicKeyShouldNotBeTooShortTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(AdditionalReferences()); [TestMethod] public void CryptographicKeyShouldNotBeTooShort() => builder.AddPaths("CryptographicKeyShouldNotBeTooShort.cs").Verify(); #if NETFRAMEWORK [TestMethod] public void CryptographicKeyShouldNotBeTooShort_NetFramework() => builder.AddPaths("CryptographicKeyShouldNotBeTooShort.BeforeNet7.cs").Verify(); #else [TestMethod] public void CryptographicKeyShouldNotBeTooShort_CS_Latest() => builder.AddPaths("CryptographicKeyShouldNotBeTooShort.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); #endif private static IEnumerable AdditionalReferences() => MetadataReferenceFacade.SystemSecurityCryptography #if NETFRAMEWORK .Concat(NuGetMetadataReference.SystemSecurityCryptographyOpenSsl()) #endif .Concat(NuGetMetadataReference.BouncyCastle()); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DangerousGetHandleShouldNotBeCalledTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DangerousGetHandleShouldNotBeCalledTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DangerousGetHandleShouldNotBeCalled_CS() => builderCS.AddPaths("DangerousGetHandleShouldNotBeCalled.cs") .AddReferences(MetadataReferenceFacade.MicrosoftWin32Registry) .Verify(); [TestMethod] public void DangerousGetHandleShouldNotBeCalled_CS_CSharp9() => builderCS.AddPaths("DangerousGetHandleShouldNotBeCalled.CSharp9.cs") .AddReferences(MetadataReferenceFacade.MicrosoftWin32Registry) .WithOptions(LanguageOptions.FromCSharp9) .WithTopLevelStatements() .Verify(); [TestMethod] public void DangerousGetHandleShouldNotBeCalled_VB() => builderVB.AddPaths("DangerousGetHandleShouldNotBeCalled.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DatabasePasswordsShouldBeSecureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using System.IO; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DatabasePasswordsShouldBeSecureTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] [DataRow("3.1.11", "3.19.80")] [DataRow("5.0.2", "5.21.1")] public void DatabasePasswordsShouldBeSecure_CS(string entityFrameworkCoreVersion, string oracleVersion) => builder.AddPaths("DatabasePasswordsShouldBeSecure.cs") .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(GetReferences(entityFrameworkCoreVersion, oracleVersion)) .Verify(); [TestMethod] [DataRow("3.1.11", "3.19.80")] [DataRow("5.0.2", "5.21.1")] public void DatabasePasswordsShouldBeSecure_CS_Latest(string entityFrameworkCoreVersion, string oracleVersion) => builder.AddPaths("DatabasePasswordsShouldBeSecure.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(GetReferences(entityFrameworkCoreVersion, oracleVersion)) .Verify(); [TestMethod] public void DatabasePasswordsShouldBeSecure_Net5_CS() => builder.AddPaths("DatabasePasswordsShouldBeSecure.Net5.cs") .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(GetReferences("5.0.2", "5.21.1")) .Verify(); [TestMethod] public void DatabasePasswordsShouldBeSecure_NetCore3_CS() => builder.AddPaths("DatabasePasswordsShouldBeSecure.NetCore31.cs") .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(GetReferences("3.1.11", "3.19.80")) .Verify(); [TestMethod] [DataRow(true, @"TestCases\WebConfig\DatabasePasswordsShouldBeSecure\Values")] [DataRow(false, @"TestCases\WebConfig\DatabasePasswordsShouldBeSecure\UnexpectedContent")] public void DatabasePasswordsShouldBeSecure_CS_WebConfig(bool expectIssues, string root) { var webConfigPath = GetWebConfigPath(root); VerifyAdditionalFiles(expectIssues, webConfigPath); } [TestMethod] public void DatabasePasswordsShouldBeSecure_CS_ExternalConnection() { var root = @"TestCases\WebConfig\DatabasePasswordsShouldBeSecure\ExternalConfig"; var webConfigPath = GetWebConfigPath(root); var externalConfigPath = Path.Combine(root, "external.config"); VerifyAdditionalFiles(false, webConfigPath, externalConfigPath); } [TestMethod] public void DatabasePasswordsShouldBeSecure_CS_CorruptAndNonExistingWebConfigs_ShouldNotFail() { var root = @"TestCases\WebConfig\DatabasePasswordsShouldBeSecure\Corrupt"; var missingDirectory = @"TestCases\WebConfig\DatabasePasswordsShouldBeSecure\NonExistingDirectory"; var corruptFilePath = GetWebConfigPath(root); var nonExistentFilePath = GetWebConfigPath(missingDirectory); VerifyAdditionalFiles(false, corruptFilePath, nonExistentFilePath); } [TestMethod] [DataRow(true, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\Values")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\ArrayInside")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\EmptyArray")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\EmptyFile")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\WrongStructure")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\ConnectionStringComment")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\ValueKind")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\PropertyKinds")] [DataRow(false, @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\UnexpectedContent\Null")] public void DatabasePasswordsShouldBeSecure_CS_AppSettings(bool expectIssues, string root) { var appSettingsPath = GetAppSettingsPath(root); VerifyAdditionalFiles(expectIssues, appSettingsPath); } [TestMethod] public void DatabasePasswordsShouldBeSecure_CS_CorruptAndNonExistingAppSettings_ShouldNotFail() { var root = @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\Corrupt"; var missingDirectory = @"TestCases\AppSettings\DatabasePasswordsShouldBeSecure\NonExistingDirectory"; var corruptFilePath = GetAppSettingsPath(root); var nonExistentFilePath = GetAppSettingsPath(missingDirectory); VerifyAdditionalFiles(false, corruptFilePath, nonExistentFilePath); } private static string GetWebConfigPath(string rootFolder) => Path.Combine(rootFolder, "Web.config"); private static string GetAppSettingsPath(string rootFolder) => Path.Combine(rootFolder, "appsettings.json"); private void VerifyAdditionalFiles(bool expectIssues, string additionalSourceFile, params string[] additionalFilesToAnalyze) { var withAdditionalSourceFiles = builder .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, additionalFilesToAnalyze.Append(additionalSourceFile).ToArray())) .AddAdditionalSourceFiles(additionalSourceFile); if (expectIssues) { withAdditionalSourceFiles.Verify(); } else { withAdditionalSourceFiles.VerifyNoIssues(); } } private static IEnumerable GetReferences(string entityFrameworkCoreVersion, string oracleVersion) => Enumerable.Empty() .Concat(MetadataReferenceFacade.SystemData) .Concat(MetadataReferenceFacade.SystemComponentModelPrimitives) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore(entityFrameworkCoreVersion)) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqliteCore(entityFrameworkCoreVersion)) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqlServer(entityFrameworkCoreVersion)) .Concat(NuGetMetadataReference.OracleEntityFrameworkCore(oracleVersion)) .Concat(NuGetMetadataReference.MySqlDataEntityFrameworkCore()) .Concat(NuGetMetadataReference.NpgsqlEntityFrameworkCorePostgreSQL(entityFrameworkCoreVersion)); } } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DateAndTimeShouldNotBeUsedasTypeForPrimaryKeyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DateAndTimeShouldNotBeUsedAsTypeForPrimaryKeyTest { private readonly VerifierBuilder verifierCS = CreateVerifier(); private readonly VerifierBuilder verifierVB = CreateVerifier(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_CS() => verifierCS.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.cs").Verify(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_VB() => verifierVB.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.vb").Verify(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_NoReferenceToEntityFramework_CS() => new VerifierBuilder() .AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.NoReferenceToEntityFramework.cs") .VerifyNoIssues(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_NoReferenceToEntityFramework_VB() => new VerifierBuilder() .AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.NoReferenceToEntityFramework.vb") .VerifyNoIssues(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_Latest() => verifierCS .AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #if NET [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_EntityFrameworkCore_CS() => verifierCS.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.EntityFrameworkCore.cs").Verify(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_EntityFrameworkCore_VB() => verifierVB.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.EntityFrameworkCore.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_FluentApi_CS() => verifierCS.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.FluentApi.cs").VerifyNoIssues(); [TestMethod] public void DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey_FluentApi_VB() => verifierVB.AddPaths("DateAndTimeShouldNotBeUsedAsTypeForPrimaryKey.FluentApi.vb").VerifyNoIssues(); #endif private static VerifierBuilder CreateVerifier() where TAnalyzer : DiagnosticAnalyzer, new() => new VerifierBuilder() .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()) #if NET .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore("7.0.0")) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreAbstractions("7.0.0")); #else .AddReferences(NuGetMetadataReference.MicrosoftEntityFramework("6.0.0")); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DateTimeFormatShouldNotBeHardcodedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DateTimeFormatShouldNotBeHardcodedTest { private readonly VerifierBuilder builderCS = new(); private readonly VerifierBuilder builderVB = new(); [TestMethod] public void DateTimeFormatShouldNotBeHardcoded_CS() => builderCS.AddPaths("DateTimeFormatShouldNotBeHardcoded.cs").Verify(); [TestMethod] public void DateTimeFormatShouldNotBeHardcoded_VB() => builderVB.AddPaths("DateTimeFormatShouldNotBeHardcoded.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); #if NET [TestMethod] public void DateTimeFormatShouldNotBeHardcoded_CS_Latest() => builderCS .AddPaths("DateTimeFormatShouldNotBeHardcoded.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DateTimeFormatShouldNotBeHardcoded_NET_VB() => builderVB.AddPaths("DateTimeFormatShouldNotBeHardcoded.Net.vb").Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DeadStoresTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DeadStoresTest { private readonly VerifierBuilder sonarCfg = new VerifierBuilder() .AddAnalyzer(() => new DeadStores(AnalyzerConfiguration.AlwaysEnabledWithSonarCfg)) .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(MetadataReferenceFacade.NetStandard21); private readonly VerifierBuilder roslynCfg = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.NetStandard21); [TestMethod] public void DeadStores_SonarCfg() => sonarCfg.AddPaths("DeadStores.SonarCfg.cs").Verify(); [TestMethod] public void DeadStores_RoslynCfg() => roslynCfg.AddPaths("DeadStores.RoslynCfg.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void DeadStores_CS_Latest() => roslynCfg.AddPaths("DeadStores.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DebugAssertHasNoSideEffectsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DebugAssertHasNoSideEffectsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DebugAssertHasNoSideEffects() => builder.AddPaths("DebugAssertHasNoSideEffects.cs").Verify(); [TestMethod] public void DebugAssertHasNoSideEffects_Latest() => builder.AddPaths("DebugAssertHasNoSideEffects.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DebuggerDisplayUsesExistingMembersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DebuggerDisplayUsesExistingMembersTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DebuggerDisplayUsesExistingMembers_CS() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.cs").Verify(); [TestMethod] public void DebuggerDisplayUsesExistingMembers_CSharp_Latest() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DebuggerDisplayUsesExistingMembers_VB() => builderVB.AddPaths("DebuggerDisplayUsesExistingMembers.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DeclareEventHandlersCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DeclareEventHandlersCorrectlyTest { [TestMethod] public void DeclareEventHandlersCorrectly() => new VerifierBuilder().AddPaths("DeclareEventHandlersCorrectly.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DeclareTypesInNamespacesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DeclareTypesInNamespacesTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithAutogenerateConcurrentFiles(false); [TestMethod] public void DeclareTypesInNamespaces_CS() => builder.AddPaths("DeclareTypesInNamespaces.cs", "DeclareTypesInNamespaces2.cs").Verify(); [TestMethod] public void DeclareTypesInNamespaces_CS_Latest() => builder .AddPaths("DeclareTypesInNamespaces.Latest.cs", "DeclareTypesInNamespaces.FileScopedNamespace.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DeclareTypesInNamespaces_CS_TopLevelStatements() => builder .AddPaths("DeclareTypesInNamespaces.TopLevelStatements.cs", "DeclareTypesInNamespaces.TopLevelStatements.Partial.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void DeclareTypesInNamespaces_VB() => new VerifierBuilder() .AddPaths("DeclareTypesInNamespaces.vb", "DeclareTypesInNamespaces2.vb") .WithAutogenerateConcurrentFiles(false) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DefaultSectionShouldBeFirstOrLastTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DefaultSectionShouldBeFirstOrLastTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DefaultSectionShouldBeFirstOrLast() => builder.AddPaths("DefaultSectionShouldBeFirstOrLast.cs").Verify(); [TestMethod] public void DefaultSectionShouldBeFirstOrLast_CSharp9() => builder.AddPaths("DefaultSectionShouldBeFirstOrLast.CSharp9.cs") .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DelegateSubtractionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DelegateSubtractionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DelegateSubtraction() => builder.AddPaths("DelegateSubtraction.cs").Verify(); [TestMethod] public void DelegateSubtraction_CS_Latest() => builder.AddPaths("DelegateSubtraction.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DelegateSubtraction_TopLevelStatements() => builder.AddPaths("DelegateSubtraction.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposableMemberInNonDisposableClassTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DisposableMemberInNonDisposableClassTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposableMemberInNonDisposableClass() => builder.AddPaths("DisposableMemberInNonDisposableClass.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); #if NET [TestMethod] public void DisposableMemberInNonDisposableClass_CSharp9() => builder.AddPaths("DisposableMemberInNonDisposableClass.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void DisposableMemberInNonDisposableClass_IAsyncDisposable() => // IAsyncDisposable is available only on .Net Core builder.AddPaths("DisposableMemberInNonDisposableClass.NetCore.cs").Verify(); #endif } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposableNotDisposedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DisposableNotDisposedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposableNotDisposed() => builder.AddPaths("DisposableNotDisposed.cs") .AddReferences(MetadataReferenceFacade.SystemNetHttp) .Verify(); [TestMethod] public void DisposableNotDisposed_ILogger() => builder.AddPaths("DisposableNotDisposed.ILogger.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(TestConstants.NuGetLatestVersion).ToArray()) .VerifyNoIssues(); [TestMethod] public void DisposableNotDisposed_TopLevelStatements() => builder.AddPaths("DisposableNotDisposed.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void DisposableNotDisposed_Latest() => builder.AddPaths("DisposableNotDisposed.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.FluentAssertions(NugetPackageVersions.FluentAssertionsVersions.Ver5)) .AddReferences(MetadataReferenceFacade.SystemNetHttp) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposableReturnedFromUsingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DisposableReturnedFromUsingTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposableReturnedFromUsing() => builder.AddPaths("DisposableReturnedFromUsing.cs") .Verify(); [TestMethod] public void DisposableReturnedFromUsing_TopLevelStatements() => builder.AddPaths("DisposableReturnedFromUsing.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void DisposableReturnedFromUsing_Latest() => builder.AddPaths("DisposableReturnedFromUsing.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposableTypesNeedFinalizersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DisposableTypesNeedFinalizersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposableTypesNeedFinalizers() => builder.AddPaths("DisposableTypesNeedFinalizers.cs").Verify(); [TestMethod] public void DisposableTypesNeedFinalizers_CSharp9() => builder.AddPaths("DisposableTypesNeedFinalizers.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void DisposableTypesNeedFinalizers_CSharp11() => builder.AddPaths("DisposableTypesNeedFinalizers.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); [TestMethod] public void DisposableTypesNeedFinalizers_InvalidCode() => builder.AddSnippet(""" public class Foo_05 : IDisposable { private HandleRef; } """).VerifyNoIssuesIgnoreErrors(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposeFromDisposeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DisposeFromDisposeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposeFromDispose_CSharp7_2() => // Readonly structs have been introduced in C# 7.2. // In C# 8, readonly structs can be disposed of, and the behavior is different. new VerifierBuilder() .AddPaths("DisposeFromDispose.CSharp7_2.cs") .WithLanguageVersion(LanguageVersion.CSharp7_2) .Verify(); [TestMethod] public void DisposeFromDispose_CSharp8() => builder.AddPaths("DisposeFromDispose.CSharp8.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void DisposeFromDispose_CSharp9() => builder.AddPaths("DisposeFromDispose.CSharp9.Part1.cs", "DisposeFromDispose.CSharp9.Part2.cs").WithTopLevelStatements().Verify(); [TestMethod] public void DisposeFromDispose_CSharp10() => builder.AddPaths("DisposeFromDispose.CSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DisposeNotImplementingDisposeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DisposeNotImplementingDisposeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DisposeNotImplementingDispose() => builder.AddPaths("DisposeNotImplementingDispose.cs").Verify(); [TestMethod] public void DisposeNotImplementingDispose_TopLevelStatements() => builder.AddPaths("DisposeNotImplementingDispose.TopLevelStatements.cs") .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void DisposeNotImplementingDispose_Latest() => builder.AddPaths("DisposeNotImplementingDispose.Latest.cs", "DisposeNotImplementingDispose.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCallAssemblyGetExecutingAssemblyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotCallAssemblyGetExecutingAssemblyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCallAssemblyGetExecutingAssembly() => builder.AddPaths("DoNotCallAssemblyGetExecutingAssembly.cs") .Verify(); [TestMethod] public void DoNotCallAssemblyGetExecutingAssembly_CSharp9() => builder.AddPaths("DoNotCallAssemblyGetExecutingAssembly.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCallAssemblyLoadInvalidMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotCallAssemblyLoadInvalidMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCallAssemblyLoadInvalidMethods() => builder.AddPaths("DoNotCallAssemblyLoadInvalidMethods.cs") .AddReferences(MetadataReferenceFacade.SystemSecurityPermissions) .Verify(); [TestMethod] public void DoNotCallAssemblyLoadInvalidMethods_CSharp9() => builder.AddPaths("DoNotCallAssemblyLoadInvalidMethods.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithTopLevelStatements() .AddReferences(MetadataReferenceFacade.SystemSecurityPermissions) .Verify(); #if NETFRAMEWORK // The overloads with Evidence are obsolete on .Net Framework 4.8 and not available on .Net Core [TestMethod] public void DoNotCallAssemblyLoadInvalidMethods_EvidenceParameter() => builder.AddPaths("DoNotCallAssemblyLoadInvalidMethods.Evidence.cs") .Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCallExitMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotCallExitMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCallExitMethods() => builder.AddPaths("DoNotCallExitMethods.cs") .AddReferences(MetadataReferenceFacade.SystemWindowsForms) .Verify(); [TestMethod] public void DoNotCallExitMethods_CSharp9() => builder.AddPaths("DoNotCallExitMethods.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCallGCCollectMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotCallGCCollectMethodTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCallGCCollectMethod() => builder.AddPaths("DoNotCallGCCollectMethod.cs") .Verify(); [TestMethod] public void DoNotCallGCCollectMethod_Latest() => builder.AddPaths("DoNotCallGCCollectMethod.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void DoNotCallGCCollectMethod_AD0001() => builder.AddPaths("DoNotCallGCCollectMethodAD0001.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithConcurrentAnalysis(false) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCallGCSuppressFinalizeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotCallGCSuppressFinalizeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCallGCSuppressFinalize() => builder.AddPaths("DoNotCallGCSuppressFinalize.cs") .Verify(); #if NET [TestMethod] public void DoNotCallGCSuppressFinalize_NetCore() => builder.AddPaths("DoNotCallGCSuppressFinalize.NetCore.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void DoNotCallGCSuppressFinalize_Net5() => builder.AddPaths("DoNotCallGCSuppressFinalize.Net5.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); #endif } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCatchNullReferenceExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotCatchNullReferenceExceptionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCatchNullReferenceException() => builder.AddPaths("DoNotCatchNullReferenceException.cs").Verify(); [TestMethod] public void DoNotCatchNullReferenceException_CSharp9() => builder.AddPaths("DoNotCatchNullReferenceException.CSharp9.cs") .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCatchSystemExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotCatchSystemExceptionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCatchSystemException() => builder.AddReferences(NuGetMetadataReference.MicrosoftAzureWebJobsCore()).AddPaths("DoNotCatchSystemException.cs").Verify(); [TestMethod] public void DoNotCatchSystemException_CSharp9() => builder.AddPaths("DoNotCatchSystemException.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void DoNotCatchSystemException_CSharp10() => builder.AddPaths("DoNotCatchSystemException.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).WithTopLevelStatements().Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCheckZeroSizeCollectionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotCheckZeroSizeCollectionTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DoNotCheckZeroSizeCollection_CS() => builderCS.AddPaths("DoNotCheckZeroSizeCollection.cs").Verify(); [TestMethod] public void DoNotCheckZeroSizeCollection_CS_Latest() => builderCS.AddPaths("DoNotCheckZeroSizeCollection.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DoNotCheckZeroSizeCollection_VB() => builderVB.AddPaths("DoNotCheckZeroSizeCollection.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotCopyArraysInPropertiesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotCopyArraysInPropertiesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotCopyArraysInProperties() => builder.AddPaths("DoNotCopyArraysInProperties.cs").Verify(); [TestMethod] public void DoNotCopyArraysInProperties_CS_Latest() => builder.AddPaths("DoNotCopyArraysInProperties.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotDecreaseMemberVisibilityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotDecreaseMemberVisibilityTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithAutogenerateConcurrentFiles(false); [TestMethod] public void DoNotDecreaseMemberVisibility() => builder.AddPaths("DoNotDecreaseMemberVisibility.cs", "DoNotDecreaseMemberVisibility.Concurrent.cs").Verify(); [TestMethod] public void DoNotDecreaseMemberVisibility_CS_Latest() => builder.AddPaths("DoNotDecreaseMemberVisibility.Latest.cs", "DoNotDecreaseMemberVisibility.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotExposeListTTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotExposeListTTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotExposeListT() => builder.AddPaths("DoNotExposeListT.cs") .AddReferences(MetadataReferenceFacade.SystemXml) .Verify(); [TestMethod] public void DoNotExposeListT_Latest() => builder.AddPaths("DoNotExposeListT.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void DoNotExposeListT_InvalidCode() => builder.AddSnippet(""" public class InvalidCode { public List () => null; public List { get; set; } public List Method() => null; public InvalidType Method2() => null; } """) .VerifyNoIssuesIgnoreErrors(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotHardcodeCredentialsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotHardcodeCredentialsTest { private readonly VerifierBuilder builderCS = CreateVerifierCS(); private readonly VerifierBuilder builderVB = CreateVerifierVB(); public TestContext TestContext { get; set; } internal static IEnumerable AdditionalReferences => MetadataReferenceFacade.SystemSecurityCryptography.Concat(MetadataReferenceFacade.SystemNetHttp); [TestMethod] public void DoNotHardcodeCredentials_CS_DefaultValues() => builderCS.AddPaths("DoNotHardcodeCredentials.DefaultValues.cs").Verify(); [TestMethod] public void DoNotHardcodeCredentials_CS_SecureString() => builderCS.AddPaths("DoNotHardcodeCredentials.SecureString.cs").Verify(); [TestMethod] public void DoNotHardcodeCredentials_VB_SecureString() => builderVB.AddPaths("DoNotHardcodeCredentials.SecureString.vb").Verify(); [TestMethod] public void DoNotHardcodeCredentials_CS_DefaultValues_Latest() => builderCS.AddPaths("DoNotHardcodeCredentials.DefaultValues.Latest.cs").AddReferences(AdditionalReferences).WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void DoNotHardcodeCredentials_CS_CustomValues() => CreateVerifierCS(@"kode,facal-faire,*,x\*+?|}{][)(^$.# ").AddPaths("DoNotHardcodeCredentials.CustomValues.cs").Verify(); [TestMethod] public void DoNotHardcodeCredentials_CS_CustomValues_CaseInsensitive() => CreateVerifierCS(@"KODE ,,,, FaCaL-FaIrE,*,x\*+?|}{][)(^$.# ").AddPaths("DoNotHardcodeCredentials.CustomValues.cs").Verify(); [TestMethod] public void DoNotHardcodeCredentials_CS_WebConfig() => DoNotHardcodeCredentials_ExternalFiles(new CS.DoNotHardcodeCredentials(), "WebConfig", "*.config"); [TestMethod] public void DoNotHardcodeCredentials_CS_LaunchSettings() => DoNotHardcodeCredentials_ExternalFiles(new CS.DoNotHardcodeCredentials(), "LaunchSettings", "*.json"); [TestMethod] public void DoNotHardcodeCredentials_CS_AppSettings() => DoNotHardcodeCredentials_ExternalFiles(new CS.DoNotHardcodeCredentials(), "AppSettings", "*.json"); [TestMethod] public void DoNotHardcodeCredentials_VB_DefaultValues() => builderVB.AddPaths("DoNotHardcodeCredentials.DefaultValues.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void DoNotHardcodeCredentials_VB_CustomValues() => CreateVerifierVB(@"kode,facal-faire,*,x\*+?|}{][)(^$.# ").AddPaths("DoNotHardcodeCredentials.CustomValues.vb").Verify(); [TestMethod] public void DoNotHardcodeCredentials_VB_CustomValues_CaseInsensitive() => CreateVerifierVB(@"KODE ,,,, FaCaL-FaIrE,*,x\*+?|}{][)(^$.# ").AddPaths("DoNotHardcodeCredentials.CustomValues.vb").Verify(); [TestMethod] public void DoNotHardcodeCredentials_VB_WebConfig() => DoNotHardcodeCredentials_ExternalFiles(new VB.DoNotHardcodeCredentials(), "WebConfig", "*.config"); [TestMethod] public void DoNotHardcodeCredentials_VB_LaunchSettings() => DoNotHardcodeCredentials_ExternalFiles(new VB.DoNotHardcodeCredentials(), "LaunchSettings", "*.json"); [TestMethod] public void DoNotHardcodeCredentials_VB_AppSettings() => DoNotHardcodeCredentials_ExternalFiles(new VB.DoNotHardcodeCredentials(), "AppSettings", "*.json"); [TestMethod] public void DoNotHardcodeCredentials_ConfiguredCredentialsAreRead() { var cs = new CS.DoNotHardcodeCredentials { CredentialWords = "Lorem, ipsum" }; cs.CredentialWords.Should().Be("Lorem, ipsum"); var vb = new CS.DoNotHardcodeCredentials { CredentialWords = "Lorem, ipsum" }; vb.CredentialWords.Should().Be("Lorem, ipsum"); } private static VerifierBuilder CreateVerifierCS(string credentialWords = null) => new VerifierBuilder().AddAnalyzer(() => credentialWords is null ? new CS.DoNotHardcodeCredentials() : new CS.DoNotHardcodeCredentials { CredentialWords = credentialWords }) .AddReferences(AdditionalReferences); private static VerifierBuilder CreateVerifierVB(string credentialWords = null) => new VerifierBuilder().AddAnalyzer(() => credentialWords is null ? new VB.DoNotHardcodeCredentials() : new VB.DoNotHardcodeCredentials { CredentialWords = credentialWords }) .AddReferences(AdditionalReferences); private void DoNotHardcodeCredentials_ExternalFiles(DiagnosticAnalyzer analyzer, string testDirectory, string pattern) { var paths = Directory.GetFiles(@$"TestCases\{testDirectory}\DoNotHardcodeCredentials", pattern, SearchOption.AllDirectories); paths.Should().NotBeEmpty(); new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddSnippet(string.Empty) // Nothing to see here, C# and VB .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, paths)) .AddAdditionalSourceFiles(paths) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotHardcodeSecretsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotHardcodeSecretsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.DoNotHardcodeSecrets()); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.DoNotHardcodeSecrets()); public TestContext TestContext { get; set; } [TestMethod] public void DoNotHardcodeSecrets_DefaultValues_CS() => builderCS.AddPaths("DoNotHardcodeSecrets.cs").Verify(); [TestMethod] public void DoNotHardcodeSecrets_DefaultValues_CS_Latest() => builderCS.AddPaths("DoNotHardcodeSecrets.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void DoNotHardcodeSecrets_DefaultValues_VB() => builderVB.AddPaths("DoNotHardcodeSecrets.vb").Verify(); [TestMethod] public void DoNotHardcodeSecrets_WebConfig_CS() => DoNotHardcodeCredentials_ExternalFiles(builderCS, "WebConfig", "*.config"); [TestMethod] public void DoNotHardcodeSecrets_WebConfig_VB() => DoNotHardcodeCredentials_ExternalFiles(builderVB, "WebConfig", "*.config"); [TestMethod] public void DoNotHardcodeSecrets_AppSettings_CS() => DoNotHardcodeCredentials_ExternalFiles(builderCS, "AppSettings", "*.json"); [TestMethod] public void DoNotHardcodeSecrets_AppSettings_VB() => DoNotHardcodeCredentials_ExternalFiles(builderVB, "AppSettings", "*.json"); [TestMethod] public void DoNotHardcodeSecrets_LaunchSettings_CS() => DoNotHardcodeCredentials_ExternalFiles(builderCS, "LaunchSettings", "*.json"); [TestMethod] public void DoNotHardcodeSecrets_LaunchSettings_VB() => DoNotHardcodeCredentials_ExternalFiles(builderVB, "LaunchSettings", "*.json"); private void DoNotHardcodeCredentials_ExternalFiles(VerifierBuilder builder, string testDirectory, string pattern) { var root = @$"TestCases\{testDirectory}\DoNotHardcodeSecrets"; var paths = Directory.GetFiles(root, pattern, SearchOption.AllDirectories); paths.Should().NotBeEmpty(); builder .AddSnippet(string.Empty) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, paths)) .AddAdditionalSourceFiles(paths) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotHideBaseClassMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotHideBaseClassMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotHideBaseClassMethods() => builder.AddPaths("DoNotHideBaseClassMethods.cs", "DoNotHideBaseClassMethods.Concurrent.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void DoNotHideBaseClassMethods_CS_Latest() => builder.AddPaths("DoNotHideBaseClassMethods.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithConcurrentAnalysis(false) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotInstantiateSharedClassesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotInstantiateSharedClassesTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemComponentModelComposition); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemComponentModelComposition); [TestMethod] public void DoNotInstantiateSharedClasses_CS() => builderCS.AddPaths("DoNotInstantiateSharedClasses.cs").Verify(); [TestMethod] public void DoNotInstantiateSharedClasses_CS_InTest() => builderCS.AddPaths("DoNotInstantiateSharedClasses.cs") .AddTestReference() .VerifyNoIssuesIgnoreErrors(); [TestMethod] public void DoNotInstantiateSharedClasses_VB() => builderVB.AddPaths("DoNotInstantiateSharedClasses.vb").Verify(); [TestMethod] public void DoNotInstantiateSharedClasses_VB_InTest() => builderVB.AddPaths("DoNotInstantiateSharedClasses.vb") .AddTestReference() .VerifyNoIssuesIgnoreErrors(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotLockOnSharedResourceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotLockOnSharedResourceTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DoNotLockOnSharedResource_CS() => builderCS.AddPaths("DoNotLockOnSharedResource.cs").Verify(); [TestMethod] public void DoNotLockOnSharedResource_VB() => builderVB.AddPaths("DoNotLockOnSharedResource.vb").Verify(); [TestMethod] public void DoNotLockOnSharedResource_CS_Latest() => builderCS.AddPaths("DoNotLockOnSharedResource.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotLockWeakIdentityObjectsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotLockWeakIdentityObjectsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotLockWeakIdentityObjects_CS() => builder.AddPaths("DoNotLockWeakIdentityObjects.cs").Verify(); [TestMethod] public void DoNotLockWeakIdentityObjects_Latest() => builder.AddPaths("DoNotLockWeakIdentityObjects.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void DoNotLockWeakIdentityObjects_VB() => new VerifierBuilder().AddPaths("DoNotLockWeakIdentityObjects.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotMarkEnumsWithFlagsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotMarkEnumsWithFlagsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotMarkEnumsWithFlags() => builder.AddPaths("DoNotMarkEnumsWithFlags.cs").Verify(); [TestMethod] public void DoNotMarkEnumsWithFlags_InvalidEnumType() => builder.AddSnippet( """ [System.Flags] public enum InvalidStringEnum : string // Noncompliant { MyValue = "toto" // Secondary } """) .WithErrorBehavior(CompilationErrorBehavior.Ignore) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotNestTernaryOperatorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotNestTernaryOperatorsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DoNotNestTernaryOperators_CS() => builderCS.AddPaths("DoNotNestTernaryOperators.cs").Verify(); [TestMethod] public void DoNotNestTernaryOperators_VB() => builderVB.AddPaths("DoNotNestTernaryOperators.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotNestTypesInArgumentsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotNestTypesInArgumentsTest { [TestMethod] public void DoNotNestTypesInArguments_CS() => new VerifierBuilder() .AddPaths("DoNotNestTypesInArguments.cs") .Verify(); [TestMethod] public void DoNotNestTypesInArguments_CS_Latest() => new VerifierBuilder() .AddPaths("DoNotNestTypesInArguments.Latest.cs", "DoNotNestTypesInArguments.Latest.partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotOverloadOperatorEqualTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotOverloadOperatorEqualTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotOverloadOperatorEqual() => builder.AddPaths("DoNotOverloadOperatorEqual.cs").Verify(); [TestMethod] public void DoNotOverloadOperatorEqual_Latest() => builder.AddPaths("DoNotOverloadOperatorEqual.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotOverwriteCollectionElementsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotOverwriteCollectionElementsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DoNotOverwriteCollectionElements_CS() => builderCS.AddPaths("DoNotOverwriteCollectionElements.cs").Verify(); [TestMethod] public void DoNotOverwriteCollectionElements_VB() => builderVB.AddPaths("DoNotOverwriteCollectionElements.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void DoNotOverwriteCollectionElements_CS_Latest() => builderCS.AddPaths("DoNotOverwriteCollectionElements.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotShiftByZeroOrIntSizeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotShiftByZeroOrIntSizeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void DoNotShiftByZeroOrIntSize() => builder.AddPaths("DoNotShiftByZeroOrIntSize.cs").Verify(); [TestMethod] public void DoNotShiftByZeroOrIntSize_CSharp9() => builder.AddPaths("DoNotShiftByZeroOrIntSize.CSharp9.cs") .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void DoNotShiftByZeroOrIntSize_CSharp10() => builder.AddPaths("DoNotShiftByZeroOrIntSize.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void DoNotShiftByZeroOrIntSize_CSharp11() => builder.AddPaths("DoNotShiftByZeroOrIntSize.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void DoNotShiftByZeroOrIntSize_RazorFile_CorrectMessage() => builder.AddSnippet( """ @code { public void Method() { byte b = 1; b = (byte)(b << 10); b = (byte)(b << 10); b = 1 << 0; sbyte sb = 1; sb = (sbyte)(sb << 10); int i = 1 << 10; i = i << 32; // Noncompliant } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotTestThisWithIsOperatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotTestThisWithIsOperatorTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotTestThisWithIsOperator() => builder.AddPaths("DoNotTestThisWithIsOperator.cs").Verify(); [TestMethod] public void DoNotTestThisWithIsOperator_CSharp9() => builder.AddPaths("DoNotTestThisWithIsOperator.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void DoNotTestThisWithIsOperator_CSharp10() => builder.AddPaths("DoNotTestThisWithIsOperator.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void DoNotTestThisWithIsOperator_CSharp11() => builder.AddPaths("DoNotTestThisWithIsOperator.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotThrowFromDestructorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotThrowFromDestructorsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void DoNotThrowFromDestructors_CS() => builderCS.AddPaths("DoNotThrowFromDestructors.cs").Verify(); [TestMethod] public void DoNotThrowFromDestructors_CS_CSharp9() => builderCS.AddPaths("DoNotThrowFromDestructors.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void DoNotThrowFromDestructors_VB() => new VerifierBuilder().AddPaths("DoNotThrowFromDestructors.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseByValTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotUseByValTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotUseByVal() => builder.AddPaths("DoNotUseByVal.vb").Verify(); [TestMethod] public void DoNotUseByValCodeFix() => builder.AddPaths("DoNotUseByVal.vb") .WithCodeFix() .WithCodeFixedPaths("DoNotUseByVal.Fixed.vb") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseCollectionInItsOwnMethodCallsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotUseCollectionInItsOwnMethodCallsTest { [TestMethod] public void DoNotUseCollectionInItsOwnMethodCalls() => new VerifierBuilder().AddPaths("DoNotUseCollectionInItsOwnMethodCalls.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseDateTimeNowTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotUseDateTimeNowTest { [TestMethod] public void DoNotUseDateTimeNow_CS() => new VerifierBuilder().AddPaths("DoNotUseDateTimeNow.cs").Verify(); [TestMethod] public void DoNotUseDateTimeNow_VB() => new VerifierBuilder().AddPaths("DoNotUseDateTimeNow.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseIIfTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotUseIIfTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotUseIif() => builder.AddPaths("DoNotUseIIf.vb").Verify(); [TestMethod] public void DoNotUseIif_CodeFix() => builder.AddPaths("DoNotUseIIf.vb") .WithCodeFix() .WithCodeFixedPaths("DoNotUseIIf.Fixed.vb") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseLiteralBoolInAssertionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Test.Common; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotUseLiteralBoolInAssertionsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(TestConstants.NuGetLatestVersion)] public void DoNotUseLiteralBoolInAssertions_MsTest(string testFwkVersion) => builder.AddPaths("DoNotUseLiteralBoolInAssertions.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(NUnit.Ver3Latest)] // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 public void DoNotUseLiteralBoolInAssertions_NUnit(string testFwkVersion) => builder.AddPaths("DoNotUseLiteralBoolInAssertions.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] public void DoNotUseLiteralBoolInAssertions_NUnit4() => builder.AddPaths("DoNotUseLiteralBoolInAssertions.NUnit4.cs") .AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)) .Verify(); [TestMethod] [DataRow("2.0.0")] [DataRow(XUnitVersions.Ver253)] public void DoNotUseLiteralBoolInAssertions_Xunit(string testFwkVersion) => builder.AddPaths("DoNotUseLiteralBoolInAssertions.Xunit.cs") .AddReferences(NuGetMetadataReference.XunitFramework(testFwkVersion)) .Verify(); [TestMethod] public void DoNotUseLiteralBoolInAssertions_XunitV3() => builder .AddPaths("DoNotUseLiteralBoolInAssertions.Xunit.cs") .AddPaths("DoNotUseLiteralBoolInAssertions.XunitV3.cs") .AddReferences(NuGetMetadataReference.XunitFrameworkV3(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.SystemMemory(TestConstants.NuGetLatestVersion)) .AddReferences(MetadataReferenceFacade.NetStandard) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] public void DoNotUseLiteralBoolInAssertions_NUnit4_AliasedNamespace() => builder.AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)).AddSnippet(""" namespace Aliased { using Assert = NUnit.Framework.Legacy.ClassicAssert; class Foo { public void Test() { bool b = true; Assert.AreEqual(true, b); // Noncompliant Assert.AreEqual(b, b); // Compliant } } } """).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotUseOutRefParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotUseOutRefParametersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotUseOutRefParameters() => builder.AddPaths("DoNotUseOutRefParameters.cs").Verify(); [TestMethod] public void DoNotUseOutRefParameters_CSharp9() => builder.AddPaths("DoNotUseOutRefParameters.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void DoNotUseOutRefParameters_CSharp11() => builder.AddPaths("DoNotUseOutRefParameters.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DoNotWriteToStandardOutputTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class DoNotWriteToStandardOutputTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DoNotWriteToStandardOutput() => builder.AddPaths("DoNotWriteToStandardOutput.cs").Verify(); [TestMethod] public void DoNotWriteToStandardOutput_ConditionalDirectives1() => builder.AddPaths("DoNotWriteToStandardOutput_Conditionals1.cs") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void DoNotWriteToStandardOutput_ConditionalDirectives2() => builder.AddPaths("DoNotWriteToStandardOutput_Conditionals2.cs") .WithConcurrentAnalysis(false) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DontMixIncrementOrDecrementWithOtherOperatorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DontMixIncrementOrDecrementWithOtherOperatorsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DontMixIncrementOrDecrementWithOtherOperators() => builder.AddPaths("DontMixIncrementOrDecrementWithOtherOperators.cs").Verify(); [TestMethod] public void DontMixIncrementOrDecrementWithOtherOperators_Latest() => builder .AddPaths("DontMixIncrementOrDecrementWithOtherOperators.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DontUseTraceSwitchLevelsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DontUseTraceSwitchLevelsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DontUseTraceSwitchLevels_CS() => builder.AddPaths("DontUseTraceSwitchLevels.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/DontUseTraceWriteTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DontUseTraceWriteTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void DontUseTraceWrite_CS() => builder.AddPaths("DontUseTraceWrite.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EmptyMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EmptyMethodTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void EmptyMethod() => builderCS.AddPaths("EmptyMethod.cs") .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(MetadataReferenceFacade.NetStandard21) .Verify(); [TestMethod] public void EmptyMethod_CSharp9() => builderCS.AddPaths("EmptyMethod.CSharp9.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void EmptyMethod_CSharp9_CodeFix_Throw() => builderCS.WithCodeFix() .AddPaths("EmptyMethod.CSharp9.cs") .WithTopLevelStatements() .WithCodeFixedPaths("EmptyMethod.CSharp9.Throw.Fixed.cs") .WithCodeFixTitle(CS.EmptyMethodCodeFix.TitleThrow) .VerifyCodeFix(); [TestMethod] public void EmptyMethod_CSharp9_CodeFix_Comment() => builderCS.WithCodeFix() .AddPaths("EmptyMethod.CSharp9.cs") .WithTopLevelStatements() .WithCodeFixedPaths("EmptyMethod.CSharp9.Comment.Fixed.cs") .WithCodeFixTitle(CS.EmptyMethodCodeFix.TitleComment) .VerifyCodeFix(); [TestMethod] public void EmptyMethod_CS_Latest() => builderCS.AddPaths("EmptyMethod.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void EmptyMethod_CodeFix_Throw() => builderCS.WithCodeFix() .AddPaths("EmptyMethod.cs") .WithCodeFixedPaths("EmptyMethod.Throw.Fixed.cs") .WithCodeFixTitle(CS.EmptyMethodCodeFix.TitleThrow) .VerifyCodeFix(); [TestMethod] public void EmptyMethod_CodeFix_Comment() => builderCS.WithCodeFix() .AddPaths("EmptyMethod.cs") .WithCodeFixedPaths("EmptyMethod.Comment.Fixed.cs") .WithCodeFixTitle(CS.EmptyMethodCodeFix.TitleComment) .VerifyCodeFix(); [TestMethod] public void EmptyMethod_WithoutClosingBracket_CodeFix_Comment() => builderCS.WithCodeFix() .AddPaths("EmptyMethod.WithoutClosingBracket.cs") .WithCodeFixedPaths("EmptyMethod.WithoutClosingBracket.Comment.Fixed.cs") .WithCodeFixTitle(CS.EmptyMethodCodeFix.TitleComment) .VerifyCodeFix(); [TestMethod] public void EmptyMethod_VB() => builderVB.AddPaths("EmptyMethod.vb").Verify(); [TestMethod] public void EmptyMethod_WithVirtualOverride_RaisesIssueForMainProject_CS() => builderCS.AddPaths("EmptyMethod.OverrideVirtual.cs").Verify(); [TestMethod] public void EmptyMethod_WithVirtualOverride_DoesNotRaiseIssuesForTestProject_CS() => builderCS.AddPaths("EmptyMethod.OverrideVirtual.cs").AddTestReference().VerifyNoIssues(); [TestMethod] public void EmptyMethod_WithVirtualOverride_RaisesIssueForMainProject_VB() => builderVB.AddPaths("EmptyMethod.OverrideVirtual.vb").Verify(); [TestMethod] public void EmptyMethod_WithVirtualOverride_DoesNotRaiseIssuesForTestProject_VB() => builderVB.AddPaths("EmptyMethod.OverrideVirtual.vb").AddTestReference().VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EmptyNamespaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EmptyNamespaceTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EmptyNamespace() => builder.AddPaths("EmptyNamespace.cs").Verify(); [TestMethod] public void EmptyNamespace_CSharp10() => builder.AddPaths("EmptyNamespace.CSharp10.Empty.cs", "EmptyNamespace.CSharp10.NotEmpty.cs") .WithOptions(LanguageOptions.FromCSharp10) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void EmptyNamespace_CSharp10_CodeFix() => builder.AddPaths("EmptyNamespace.CSharp10.Empty.cs") .WithCodeFix() .WithOptions(LanguageOptions.FromCSharp10) .WithAutogenerateConcurrentFiles(false) .WithCodeFixedPaths("EmptyNamespace.CSharp10.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void EmptyNamespace_CodeFix() => builder.AddPaths("EmptyNamespace.cs") .WithCodeFix() .WithCodeFixedPaths("EmptyNamespace.Fixed.cs", "EmptyNamespace.Fixed.Batch.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EmptyNestedBlockTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EmptyNestedBlockTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void EmptyNestedBlock_CS() => builderCS.AddPaths("EmptyNestedBlock.cs", "EmptyNestedBlock2.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); [TestMethod] public void EmptyNestedBlock_CS_Latest() => builderCS.AddPaths("EmptyNestedBlock.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void EmptyNestedBlock_VB() => new VerifierBuilder().AddPaths("EmptyNestedBlock.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EmptyStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EmptyStatementTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFix = new VerifierBuilder().WithCodeFix(); [TestMethod] public void EmptyStatement() => builder.AddPaths("EmptyStatement.cs").Verify(); [TestMethod] public void EmptyStatement_CS_TopLevelStatements() => builder.AddPaths("EmptyStatement.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void EmptyStatement_CS_Latest() => builder.AddPaths("EmptyStatement.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void EmptyStatement_CodeFix() => codeFix.AddPaths("EmptyStatement.cs") .WithCodeFixedPaths("EmptyStatement.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void EmptyStatement_CodeFix_CS_TopLevelStatements() => codeFix.AddPaths("EmptyStatement.TopLevelStatements.cs") .WithTopLevelStatements() .WithCodeFixedPaths("EmptyStatement.TopLevelStatements.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EncryptionAlgorithmsShouldBeSecureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EncryptionAlgorithmsShouldBeSecureTest { private readonly VerifierBuilder builderCS = new(); private readonly VerifierBuilder builderVB = new(); [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_CS() => builderCS.AddPaths(@"EncryptionAlgorithmsShouldBeSecure.cs").AddReferences(GetAdditionalReferences()).Verify(); [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_CS_NetStandard21() => builderCS.AddPaths(@"EncryptionAlgorithmsShouldBeSecure_NetStandard21.cs").AddReferences(MetadataReferenceFacade.NetStandard21.Concat(GetAdditionalReferences())).Verify(); [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_VB() => builderVB.AddPaths(@"EncryptionAlgorithmsShouldBeSecure.vb").AddReferences(GetAdditionalReferences()).Verify(); [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_VB_NetStandard21() => builderVB.AddPaths(@"EncryptionAlgorithmsShouldBeSecure_NetStandard21.vb") .AddReferences(MetadataReferenceFacade.NetStandard21.Concat(GetAdditionalReferences())) .Verify(); #if NET [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_VB_NetStandard21_Net7() => builderVB.AddPaths(@"EncryptionAlgorithmsShouldBeSecure_NetStandard21.Net7.vb") .WithOptions(LanguageOptions.VisualBasicLatest) .AddReferences(MetadataReferenceFacade.NetStandard21.Concat(GetAdditionalReferences())) .Verify(); #else [TestMethod] public void EncryptionAlgorithmsShouldBeSecure_VB_NetStandard21_Net48() => builderVB.AddPaths(@"EncryptionAlgorithmsShouldBeSecure_NetStandard21.Net48.vb") .WithOptions(LanguageOptions.VisualBasicLatest) .AddReferences(MetadataReferenceFacade.NetStandard21.Concat(GetAdditionalReferences())) .Verify(); #endif private static IEnumerable GetAdditionalReferences() => MetadataReferenceFacade.SystemSecurityCryptography; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EndStatementUsageTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EndStatementUsageTest { [TestMethod] public void EndStatementUsage() => new VerifierBuilder().AddPaths("EndStatementUsage.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumNameHasEnumSuffixTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EnumNameHasEnumSuffixTest { [TestMethod] public void EnumNameHasEnumSuffix_CS() => new VerifierBuilder().AddPaths("EnumNameHasEnumSuffix.cs").Verify(); [TestMethod] public void EnumNameHasEnumSuffix_VB() => new VerifierBuilder().AddPaths("EnumNameHasEnumSuffix.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumNameShouldFollowRegexTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EnumNameShouldFollowRegexTest { [TestMethod] public void EnumNameShouldFollowRegex_CS() => new VerifierBuilder().AddPaths("EnumNameShouldFollowRegex.cs").Verify(); [TestMethod] public void EnumNameShouldFollowRegex_VB() => new VerifierBuilder().AddPaths("EnumNameShouldFollowRegex.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumStorageNeedsToBeInt32Test.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EnumStorageNeedsToBeInt32Test { [TestMethod] public void EnumStorageNeedsToBeInt32() => new VerifierBuilder().AddPaths("EnumStorageNeedsToBeInt32.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumerableSumInUncheckedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EnumerableSumInUncheckedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EnumerableSumInUnchecked() => builder.AddPaths("EnumerableSumInUnchecked.cs").Verify(); [TestMethod] public void EnumerableSumInUnchecked_CSharp9() => builder.AddPaths("EnumerableSumInUnchecked.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).WithTopLevelStatements().Verify(); [TestMethod] public void EnumerableSumInUnchecked_CSharp11() => builder.AddPaths("EnumerableSumInUnchecked.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).WithTopLevelStatements().Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumerationValueNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EnumerationValueNameTest { [TestMethod] public void EnumerationValueName() => new VerifierBuilder().AddPaths("EnumerationValueName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EnumsShouldNotBeNamedReservedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EnumsShouldNotBeNamedReservedTest { [TestMethod] public void EnumsShouldNotBeNamedReserved() => new VerifierBuilder().AddPaths("EnumsShouldNotBeNamedReserved.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EqualityOnFloatingPointTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EqualityOnFloatingPointTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EqualityOnFloatingPoint() => builder.AddPaths("EqualityOnFloatingPoint.cs").Verify(); #if NET [TestMethod] public void EqualityOnFloatingPoint_CSharp11() => builder.AddPaths("EqualityOnFloatingPoint.CSharp11.cs") .AddReferences(new[] { CoreMetadataReference.SystemRuntime }) .WithOptions(LanguageOptions.FromCSharp11) .Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EqualityOnModulusTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EqualityOnModulusTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EqualityOnModulus() => builder.AddPaths("EqualityOnModulus.cs").Verify(); [TestMethod] public void EqualityOnModulus_CSharp9() => builder.AddPaths("EqualityOnModulus.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).WithTopLevelStatements().Verify(); [TestMethod] public void EqualityOnModulus_CSharp11() => builder.AddPaths("EqualityOnModulus.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).WithTopLevelStatements().Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EquatableClassShouldBeSealedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EquatableClassShouldBeSealedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EquatableClassShouldBeSealed() => builder.AddPaths("EquatableClassShouldBeSealed.cs").Verify(); [TestMethod] public void EquatableClassShouldBeSealed_CSharp9() => builder.AddPaths("EquatableClassShouldBeSealed.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EscapeLambdaParameterTypeNamedScopedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EscapeLambdaParameterTypeNamedScopedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EscapeLambdaParameterTypeNamedScoped_TopLevelStatements_CSharp11_13() => builder.AddPaths("EscapeLambdaParameterTypeNamedScoped.TopLevelStatements.CSharp11-13.cs") .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp11, LanguageVersion.CSharp13)) .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void EscapeLambdaParameterTypeNamedScoped_CSharp11_13() => builder.AddPaths("EscapeLambdaParameterTypeNamedScoped.CSharp11-13.cs") .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp11, LanguageVersion.CSharp13)) .Verify(); [TestMethod] public void EscapeLambdaParameterTypeNamedScoped_TopLevelStatements_Latest() => builder.AddPaths("EscapeLambdaParameterTypeNamedScoped.TopLevelStatements.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void EscapeLambdaParameterTypeNamedScoped_Latest() => builder.AddPaths("EscapeLambdaParameterTypeNamedScoped.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EventHandlerDelegateShouldHaveProperArgumentsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class EventHandlerDelegateShouldHaveProperArgumentsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void EventHandlerDelegateShouldHaveProperArguments() => builder.AddPaths("EventHandlerDelegateShouldHaveProperArguments.cs").Verify(); [TestMethod] public void EventHandlerDelegateShouldHaveProperArguments_Latest() => builder.AddPaths("EventHandlerDelegateShouldHaveProperArguments.Latest.cs", "EventHandlerDelegateShouldHaveProperArguments.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EventHandlerNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EventHandlerNameTest { [TestMethod] public void EventHandlerName() => new VerifierBuilder().AddPaths("EventHandlerName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EventNameContainsBeforeOrAfterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EventNameContainsBeforeOrAfterTest { [TestMethod] public void EventNameContainsBeforeOrAfter() => new VerifierBuilder().AddPaths("EventNameContainsBeforeOrAfter.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/EventNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class EventNameTest { [TestMethod] public void EventName() => new VerifierBuilder().AddPaths("EventName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionRethrowTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ExceptionRethrowTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExceptionRethrow() => builder.AddPaths("ExceptionRethrow.cs").Verify(); [TestMethod] public void ExceptionRethrow_CodeFix() => builder.AddPaths("ExceptionRethrow.cs") .WithCodeFix() .WithCodeFixedPaths("ExceptionRethrow.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionShouldNotBeThrownFromUnexpectedMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExceptionShouldNotBeThrownFromUnexpectedMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExceptionShouldNotBeThrownFromUnexpectedMethods() => builder.AddPaths("ExceptionShouldNotBeThrownFromUnexpectedMethods.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void ExceptionShouldNotBeThrownFromUnexpectedMethods_Latest() => builder.AddPaths("ExceptionShouldNotBeThrownFromUnexpectedMethods.Latest.cs", "ExceptionShouldNotBeThrownFromUnexpectedMethods.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionsNeedStandardConstructorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ExceptionsNeedStandardConstructorsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExceptionsNeedStandardConstructors() => builder.AddPaths("ExceptionsNeedStandardConstructors.cs").Verify(); [TestMethod] public void ExceptionsNeedStandardConstructors_InvalidCode() => builder.AddSnippet(""" public class : Exception { My_07_Exception() {} My_07_Exception(string message) { } My_07_Exception(string message, Exception innerException) {} My_07_Exception(SerializationInfo info, StreamingContext context) {} } """) .VerifyNoIssuesIgnoreErrors(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionsShouldBeLoggedOrThrownTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExceptionsShouldBeLoggedOrThrownTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_CS() => builder .AddPaths("ExceptionsShouldBeLoggedOrThrown.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_Coalesce_CS() => builder .AddSnippet(""" using System; using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, object x) { try { } catch (SystemException e) { logger.LogError(e, "Message!"); x = x ?? throw e; } } } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .WithOptions(LanguageOptions.FromCSharp8) .VerifyNoIssues(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_Log4net_CS() => builder .AddSnippet(""" using System; using log4net; using log4net.Util; public class Program { public void Method(ILog logger, string message) { try { } catch (AggregateException e) // Noncompliant { ILogExtensions.DebugExt(logger, "Message", e); // Secondary throw; // Secondary } catch (Exception e) // Noncompliant { logger.Debug(message, e); // Secondary throw; // Secondary } } } """) .AddReferences(NuGetMetadataReference.Log4Net("2.0.8", "net45-full")) .Verify(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_NLog_CS() => builder .AddSnippet(""" using System; using NLog; public class Program { public void Method(ILogger logger, string message) { try { } catch (ArgumentException e) // Noncompliant { logger.Debug(e, message); // Secondary throw; // Secondary } catch (Exception e) // Noncompliant { ILoggerExtensions.Warn(logger, e, null); // Secondary throw; // Secondary } } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_CastleCore_CS() => builder .AddSnippet(""" using System; using Castle.Core.Logging; public class Program { public void Method(ILogger logger, string message) { try { } catch (Exception e) // Noncompliant { logger.Debug(message, e); // Secondary throw; // Secondary } } } """) .AddReferences(NuGetMetadataReference.CastleCore()) .Verify(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_CommonLogging_CS() => builder .AddSnippet(""" using System; using Common.Logging; public class Program { public void Method(ILog logger, string message) { try { } catch (Exception e) // Noncompliant { logger.Debug(message, e); // Secondary throw; // Secondary } } } """) .AddReferences(NuGetMetadataReference.CommonLoggingCore()) .Verify(); [TestMethod] public void ExceptionsShouldBeLoggedOrThrown_Serilog_CS() => builder .AddSnippet(""" using System; using Serilog; public class Program { public void Method(ILogger logger, string message) { try { } catch (AggregateException e) // Noncompliant { Log.Debug(e, message); // Secondary throw; // Secondary } catch (Exception e) // Noncompliant { logger.Debug(e, message); // Secondary throw; // Secondary } } } """) .AddReferences(NuGetMetadataReference.Serilog()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionsShouldBeLoggedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExceptionsShouldBeLoggedTest { private const string EventIdParameter = "new EventId(1),"; private const string LogLevelParameter = "LogLevel.Warning,"; private const string NoParameter = ""; private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExceptionsShouldBeLogged_CS() => builder .AddPaths("ExceptionsShouldBeLogged.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] [DataRow("Log", LogLevelParameter)] [DataRow("Log", LogLevelParameter, EventIdParameter)] [DataRow("LogCritical")] [DataRow("LogCritical", NoParameter, EventIdParameter)] [DataRow("LogDebug")] [DataRow("LogDebug", NoParameter, EventIdParameter)] [DataRow("LogError")] [DataRow("LogError", NoParameter, EventIdParameter)] [DataRow("LogInformation")] [DataRow("LogInformation", NoParameter, EventIdParameter)] [DataRow("LogTrace")] [DataRow("LogTrace", NoParameter, EventIdParameter)] [DataRow("LogWarning")] [DataRow("LogWarning", NoParameter, EventIdParameter)] public void ExceptionsShouldBeLogged_MicrosoftExtensionsLogging_NonCompliant_CS(string methodName, string logLevel = "", string eventId = "") => builder.AddSnippet($$""" using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; public class Program { public void Method(ILogger logger, string message) { try { } catch (Exception e) { logger.{{methodName}}({{logLevel}} {{eventId}} message); // Noncompliant } try { } catch (Exception e) { logger.{{methodName}}({{logLevel}} {{eventId}} e, message); // Compliant } try { } catch (Exception e) { LoggerExtensions.{{methodName}}(logger, {{logLevel}} {{eventId}} message); // Noncompliant } try { } catch (Exception e) { LoggerExtensions.{{methodName}}(logger, {{logLevel}}{{eventId}} e, message); // Compliant } } } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] [DataRow("Error")] [DataRow("Debug")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("Warn")] // https://github.com/castleproject/Core/blob/dca4ed09df545dd7512c82778127219795668d30/src/Castle.Core/Core/Logging/ILogger.cs public void ExceptionsShouldBeLogged_CastleCore_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using Castle.Core.Logging; public class Program { public void Method(ILogger logger, string message) { try { } catch (Exception e) { logger.{{methodName}}(message); // Noncompliant logger.{{methodName}}Format(message); // Secondary logger.{{methodName}}Format(CultureInfo.CurrentCulture, message); // Secondary } try { } catch (Exception e) { logger.{{methodName}}(message, e); // Compliant logger.{{methodName}}Format(e, message); // Compliant logger.{{methodName}}Format(e, CultureInfo.CurrentCulture, message); // Compliant } } } """) .AddReferences(NuGetMetadataReference.CastleCore()) .Verify(); [TestMethod] [DataRow("Error")] [DataRow("Debug")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("Warn")] // https://www.fuget.org/packages/Common.Logging.Core/3.4.1/lib/netstandard1.0/Common.Logging.Core.dll/Common.Logging/ILog public void ExceptionsShouldBeLogged_CommonLoggingCore_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using Common.Logging; public class Program { public void Method(ILog logger, string message) { try { } catch (Exception e) { logger.{{methodName}}("Message"); // Noncompliant logger.{{methodName}}(_ => { }); // Secondary logger.{{methodName}}(CultureInfo.CurrentCulture, _ => { }); // Secondary logger.{{methodName}}Format("Message"); // Secondary logger.{{methodName}}Format(CultureInfo.CurrentCulture, "Message"); // Secondary } try { } catch (Exception e) { logger.{{methodName}}("Message", e); logger.{{methodName}}(_ => { }, e); logger.{{methodName}}(CultureInfo.CurrentCulture, _ => { }, e); logger.{{methodName}}Format("Message", e); logger.{{methodName}}Format(CultureInfo.CurrentCulture, "Message", e); } } } """) .AddReferences(NuGetMetadataReference.CommonLoggingCore()) .Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Warn")] // https://logging.apache.org/log4net/release/sdk/html/T_log4net_ILog.htm public void ExceptionsShouldBeLogged_Log4net_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using log4net; using log4net.Util; public class Program { public void Method(ILog logger, string message) { try { } catch (Exception e) { logger.{{methodName}}(message); // Noncompliant logger.{{methodName}}Ext(message); // Secondary ILogExtensions.{{methodName}}Ext(logger, message); // Secondary logger.{{methodName}}Format(message); // Compliant - Format overloads do not take an exception. logger.{{methodName}}Format(CultureInfo.CurrentCulture, message); // Compliant - Format overloads do not take an exception. } try { } catch (Exception e) { logger.{{methodName}}(message, e); // Compliant logger.{{methodName}}Ext(message, e); // Compliant ILogExtensions.{{methodName}}Ext(logger, message, e); // Compliant } } } """) .AddReferences(NuGetMetadataReference.Log4Net("3.0.1", "netstandard2.0")) // After 3.0.2 they removed ILogExtensions. .Verify(); [TestMethod] [DataRow("ILogger", "Debug")] [DataRow("ILogger", "Error")] [DataRow("ILogger", "Fatal")] [DataRow("ILogger", "Info")] [DataRow("ILogger", "Trace")] [DataRow("ILogger", "Warn")] [DataRow("Logger", "Debug")] [DataRow("Logger", "Error")] [DataRow("Logger", "Fatal")] [DataRow("Logger", "Info")] [DataRow("Logger", "Trace")] [DataRow("Logger", "Warn")] // https://nlog-project.org/documentation/v5.0.0/html/Methods_T_NLog_Logger.htm public void ExceptionsShouldBeLogged_NLog_CS(string type, string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using NLog; public class Program { public void Method({{type}} logger, string message, Object[] parameters) { try { } catch (Exception e) { logger.{{methodName}}("Message"); // Noncompliant logger.{{methodName}}("Message", parameters); // Secondary logger.{{methodName}}(CultureInfo.CurrentCulture, "Message"); // Secondary ILoggerExtensions.{{methodName}}(logger, null, null); // Secondary } try { } catch (Exception e) { logger.{{methodName}}(e, "Message"); logger.{{methodName}}(e, "Message", parameters); logger.{{methodName}}(e, CultureInfo.CurrentCulture, "Message"); ILoggerExtensions.{{methodName}}(logger, null, null); } } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] public void ExceptionsShouldBeLogged_NLog_ILoggerBase_CS() => builder.AddSnippet(""" using System; using System.Globalization; using NLog; public class Program { public void Method(ILoggerBase logger, string message, Object[] parameters) { try { } catch (Exception e) { logger.Log(LogLevel.Debug, "Message"); // Noncompliant logger.Log(LogLevel.Debug, CultureInfo.CurrentCulture, "Message"); // Secondary logger.Log(LogLevel.Debug, "Message"); // Secondary logger.Log(LogLevel.Debug, CultureInfo.CurrentCulture, "Message"); // Secondary } try { } catch (Exception e) { logger.Log(LogLevel.Debug, e, "Message"); logger.Log(LogLevel.Debug, CultureInfo.CurrentCulture, "Message"); logger.Log(LogLevel.Debug, "Message"); logger.Log(LogLevel.Debug, CultureInfo.CurrentCulture, "Message"); } } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] [DataRow("ConditionalDebug")] [DataRow("ConditionalTrace")] // https://nlog-project.org/documentation/v5.0.0/html/Methods_T_NLog_Logger.htm public void ExceptionsShouldBeLogged_NLog_Conditional_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using NLog; public class Program { public void Method(Logger logger, string message, Object[] parameters) { try { } catch (Exception e) { logger.{{methodName}}("Message"); // Noncompliant logger.{{methodName}}(CultureInfo.CurrentCulture, "Message"); // Secondary ILoggerExtensions.{{methodName}}(logger, "Message"); // Secondary } try { } catch (Exception e) { logger.{{methodName}}(e, "Message"); logger.{{methodName}}(e, CultureInfo.CurrentCulture, "Message"); ILoggerExtensions.{{methodName}}(logger, e, "Message"); } } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionsShouldBePublicTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExceptionsShouldBePublicTest { [TestMethod] public void ExceptionsShouldBePublic_CS() => new VerifierBuilder() .AddPaths("ExceptionsShouldBePublic.cs") .Verify(); [TestMethod] public void ExceptionsShouldBePublic_VB() => new VerifierBuilder() .AddPaths("ExceptionsShouldBePublic.vb") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExceptionsShouldBeUsedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ExceptionsShouldBeUsedTest { [TestMethod] public void ExceptionsShouldBeUsed() => new VerifierBuilder().AddPaths("ExceptionsShouldBeUsed.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExcludeFromCodeCoverageAttributesNeedJustificationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExcludeFromCodeCoverageAttributesNeedJustificationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); #if NET private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_OnAssembly_CS() => builderCS.AddSnippet("[assembly:System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // Noncompliant").Verify(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_OnAssembly_VB() => builderVB.AddSnippet(" ' Noncompliant").Verify(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_CS() => builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.cs").Verify(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_CSharp9() => builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_CSharp10() => builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_VB() => builderVB.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.vb").Verify(); #else [TestMethod] public void ExcludeFromCodeCoverageAttributesNeedJustification_IgnoredForNet48() => builderCS.AddPaths("ExcludeFromCodeCoverageAttributesNeedJustification.Net48.cs").Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExitStatementUsageTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExitStatementUsageTest { [TestMethod] public void ExitStatementUsage() => new VerifierBuilder().AddPaths("ExitStatementUsage.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExpectedExceptionAttributeShouldNotBeUsedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExpectedExceptionAttributeShouldNotBeUsedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] // Removed in V4 https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-migration-v3-v4#expectedexceptionattribute-api-is-removed public void ExpectedExceptionAttributeShouldNotBeUsed_MsTest_CS(string testFwkVersion) => builderCS.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] [DataRow(NUnit.Ver25)] // Lowest NUnit version available [DataRow(NUnit.Ver27)] // Latest version of NUnit that contains the attribute public void ExpectedExceptionAttributeShouldNotBeUsed_NUnit_CS(string testFwkVersion) => builderCS.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] [DataRow("3.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] [Description("Starting with version 3.0.0 the attribute was removed.")] public void ExpectedExceptionAttributeShouldNotBeUsed_NUnit_NoIssue_CS(string testFwkVersion) => builderCS.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] public void ExpectedExceptionAttributeShouldNotBeUsed_MsTest_VB(string testFwkVersion) => builderVB.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.MsTest.vb") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] [DataRow("2.5.7.10213")] [DataRow("2.6.7")] public void ExpectedExceptionAttributeShouldNotBeUsed_NUnit_VB(string testFwkVersion) => builderVB.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.NUnit.vb") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] [DataRow("3.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] [Description("Starting with version 3.0.0 the attribute was removed.")] public void ExpectedExceptionAttributeShouldNotBeUsed_NUnit_NoIssue_VB(string testFwkVersion) => builderVB.AddPaths("ExpectedExceptionAttributeShouldNotBeUsed.NUnit.vb") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExpressionComplexityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExpressionComplexityTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.ExpressionComplexity { Maximum = 3 }); [TestMethod] public void ExpressionComplexity_CSharp8() => builderCS.AddPaths("ExpressionComplexity.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] [DataRow("==")] [DataRow("!=")] [DataRow("<")] [DataRow("<=")] [DataRow(">")] [DataRow(">=")] public void ExpressionComplexity_TransparentComparissionOperators(string @operator) => builderCS.AddSnippet($$$""" class C { public void M() { var x = true && true && (1 {{{@operator}}} (true ? 1 : 1)); // Compliant (Make sure, the @operator is not increasing complexity) var y = true && true && true && (1 {{{@operator}}} (true ? 1 : 1)); // Noncompliant {{Reduce the number of conditional operators (4) used in the expression (maximum allowed 3).}} } } """) .Verify(); [TestMethod] [DataRow("o", "??")] [DataRow("i", "|")] [DataRow("i", "^")] [DataRow("i", "&")] [DataRow("i", ">>")] [DataRow("i", ">>>")] [DataRow("i", "<<")] [DataRow("i", "+")] [DataRow("i", "-")] [DataRow("i", "*")] [DataRow("i", "/")] [DataRow("i", "%")] public void ExpressionComplexity_TransparentBinaryOperators(string parameter, string @operator) => builderCS.AddSnippet($$""" class C { public void M(int i, object o) { var x = true && true && (({{parameter}} {{@operator}} (true ? {{parameter}} : {{parameter}})) == {{parameter}}); // Compliant var y = true && true && true && (({{parameter}} {{@operator}} (true ? {{parameter}} : {{parameter}})) == {{parameter}}); // Noncompliant } } """) .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void ExpressionComplexity_CSharp9() => builderCS.AddPaths("ExpressionComplexity.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void ExpressionComplexity_CSharp10() => builderCS.AddPaths("ExpressionComplexity.CSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void ExpressionComplexity_CSharp11() => builderCS.AddPaths("ExpressionComplexity.CSharp11.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void ExpressionComplexity_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.ExpressionComplexity { Maximum = 3 }).AddPaths("ExpressionComplexity.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExtensionMethodShouldBeInSeparateNamespaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ExtensionMethodShouldBeInSeparateNamespaceTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ExtensionMethodShouldBeInSeparateNamespace() => builder .AddPaths("ExtensionMethodShouldBeInSeparateNamespace.cs", "ExtensionMethodShouldBeInSeparateNamespace.GeneratedCode.cs") .Verify(); [TestMethod] public void ExtensionMethodShouldBeInSeparateNamespace_CSharp9() => builder.AddPaths("ExtensionMethodShouldBeInSeparateNamespace.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void ExtensionMethodShouldBeInSeparateNamespace_CSharp10() => builder .AddPaths("ExtensionMethodShouldBeInSeparateNamespace.CSharp10.cs") .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ExtensionMethodShouldNotExtendObjectTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExtensionMethodShouldNotExtendObjectTest { [TestMethod] public void ExtensionMethodShouldNotExtendObject_CS() => new VerifierBuilder() .AddPaths("ExtensionMethodShouldNotExtendObject.cs") .Verify(); [TestMethod] public void ExtensionMethodShouldNotExtendObject_VB() => new VerifierBuilder() .AddPaths("ExtensionMethodShouldNotExtendObject.vb") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FieldShadowsParentFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FieldShadowsParentFieldTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void FieldShadowsParentField_CS() => builderCS.AddPaths("FieldShadowsParentField.cs").Verify(); [TestMethod] public void FieldShadowsParentField_VB() => builderVB.AddPaths("FieldShadowsParentField.vb").Verify(); [TestMethod] public void FieldShadowsParentField_DoesNotRaiseIssuesForTestProject_CS() => builderCS.AddPaths("FieldShadowsParentField.cs") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void FieldShadowsParentField_DoesNotRaiseIssuesForTestProject_VB() => builderVB.AddPaths("FieldShadowsParentField.vb") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void FieldShadowsParentField_CSharp9() => builderCS.AddPaths("FieldShadowsParentField.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void FieldsShouldNotDifferByCapitalization_CShar9() => builderCS.AddPaths("FieldsShouldNotDifferByCapitalization.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void FieldsShouldNotDifferByCapitalization_CS(ProjectType projectType) => builderCS.AddPaths("FieldsShouldNotDifferByCapitalization.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void FieldsShouldNotDifferByCapitalization_VB(ProjectType projectType) => builderVB.AddPaths("FieldsShouldNotDifferByCapitalization.vb") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FieldShouldBeReadonlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FieldShouldBeReadonlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void FieldShouldBeReadonly() => builder.AddPaths("FieldShouldBeReadonly.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void FieldShouldBeReadonly_Latest() => builder.AddPaths("FieldShouldBeReadonly.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void FieldShouldBeReadonly_CodeFix() => builder.WithOptions(LanguageOptions.FromCSharp8) .AddPaths("FieldShouldBeReadonly.cs") .WithCodeFixedPaths("FieldShouldBeReadonly.Fixed.cs") .WithCodeFix() .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FieldShouldNotBePublicTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FieldShouldNotBePublicTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void FieldShouldNotBePublic_CS() => builderCS.AddPaths("FieldShouldNotBePublic.cs").Verify(); [TestMethod] public void FieldShouldNotBePublic_CS_CSharp9() => builderCS.AddPaths("FieldShouldNotBePublic.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void FieldShouldNotBePublic_VB() => builderVB.AddPaths("FieldShouldNotBePublic.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FieldsShouldBeEncapsulatedInPropertiesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class FieldsShouldBeEncapsulatedInPropertiesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void FieldsShouldBeEncapsulatedInProperties() => builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.cs").Verify(); [TestMethod] public void FieldsShouldBeEncapsulatedInProperties_Unity3D_Ignored() => builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.Unity3D.cs") // Concurrent analysis puts fake Unity3D class into the Concurrent namespace .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void FieldsShouldBeEncapsulatedInProperties_CSharp9() => builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void FieldsShouldBeEncapsulatedInProperties_CSharp12() => builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FileLinesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FileLinesTest { [TestMethod] public void FileLines_CS() => new VerifierBuilder().AddAnalyzer(() => new CS.FileLines { Maximum = 10 }).AddPaths("FileLines20.cs", "FileLines9.cs").WithAutogenerateConcurrentFiles(false).Verify(); [TestMethod] public void FileLines_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.FileLines { Maximum = 10 }) .AddPaths("FileLines20.vb", "FileLines9.vb") .WithAutogenerateConcurrentFiles(false) .WithOptions(LanguageOptions.FromVisualBasic14) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FileShouldEndWithEmptyNewLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FileShouldEndWithEmptyNewLineTest { [TestMethod] public void FileShouldEndWithEmptyNewLine() => new VerifierBuilder().AddPaths( "FileShouldEndWithEmptyNewLine_EmptyLine.cs", "FileShouldEndWithEmptyNewLine_NoEmptyLine.cs", "FileShouldEndWithEmptyNewLine_EmptyFile.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FinalizerShouldNotBeEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FinalizerShouldNotBeEmptyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void FinalizerShouldNotBeEmpty() => builder.AddPaths("FinalizerShouldNotBeEmpty.cs").Verify(); [TestMethod] public void FinalizerShouldNotBeEmpty_CSharp9() => builder.AddPaths("FinalizerShouldNotBeEmpty.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void FinalizerShouldNotBeEmpty_InvalidCode() => builder.AddSnippet(""" class Program4 { ~Program4() => } """) .VerifyNoIssuesIgnoreErrors(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FindInsteadOfFirstOrDefaultTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class FindInsteadOfFirstOrDefaultTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithConcurrentAnalysis(false); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void FindInsteadOfFirstOrDefault_CS() => builderCS.AddPaths("FindInsteadOfFirstOrDefault.cs").AddReferences(GetReferencesEntityFrameworkNetCore("7.0.5")).Verify(); internal static IEnumerable GetReferencesEntityFrameworkNetCore(string entityFrameworkVersion) => Enumerable.Empty() .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore(entityFrameworkVersion)) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(entityFrameworkVersion)); #if NET [TestMethod] public void FindInsteadOfFirstOrDefault_Immutable_CS() => builderCS.AddPaths("FindInsteadOfFirstOrDefault.Immutable.cs").AddReferences(MetadataReferenceFacade.SystemCollections).Verify(); [TestMethod] public void FindInsteadOfFirstOrDefault_Net_CS() => builderCS.AddPaths("FindInsteadOfFirstOrDefault.Net.cs").Verify(); [TestMethod] public void FindInsteadOfFirstOrDefault_Net_VB() => builderVB.AddPaths("FindInsteadOfFirstOrDefault.Net.vb").Verify(); #endif [TestMethod] public void FindInsteadOfFirstOrDefault_Array_CS() => builderCS.AddPaths("FindInsteadOfFirstOrDefault.Array.cs").Verify(); [TestMethod] public void FindInsteadOfFirstOrDefault_VB() => builderVB.AddPaths("FindInsteadOfFirstOrDefault.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FlagsEnumWithoutInitializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FlagsEnumWithoutInitializerTest { [TestMethod] public void FlagsEnumWithoutInitializer_CS() => new VerifierBuilder().AddPaths("FlagsEnumWithoutInitializer.cs").Verify(); [TestMethod] public void FlagsEnumWithoutInitializer_VB() => new VerifierBuilder().AddPaths("FlagsEnumWithoutInitializer.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FlagsEnumZeroMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FlagsEnumZeroMemberTest { [TestMethod] public void FlagsEnumZeroMember_CS() => new VerifierBuilder().AddPaths("FlagsEnumZeroMember.cs").Verify(); [TestMethod] public void FlagsEnumZeroMember_VB() => new VerifierBuilder().AddPaths("FlagsEnumZeroMember.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ForLoopConditionAlwaysFalseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ForLoopConditionAlwaysFalseTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ForLoopConditionAlwaysFalse() => builder.AddPaths("ForLoopConditionAlwaysFalse.cs").Verify(); [TestMethod] public void ForLoopConditionAlwaysFalse_CSharp9() => builder.AddPaths("ForLoopConditionAlwaysFalse.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void ForLoopConditionAlwaysFalse_CSharp11() => builder.AddPaths("ForLoopConditionAlwaysFalse.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ForLoopCounterChangedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ForLoopCounterChangedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ForLoopCounterChanged() => builder.AddPaths("ForLoopCounterChanged.cs").Verify(); [TestMethod] public void ForLoopCounterChanged_CS_Latest() => builder.AddPaths("ForLoopCounterChanged.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] [CombinatorialData] public void ForLoopCounterChanged_VariableUsage( [CombinatorialValues(true, false)] bool inInitializer, [CombinatorialValues(true, false)] bool inCondition, [CombinatorialValues(true, false)] bool inIncrementor) { var initializer = inInitializer ? "i = 0" : string.Empty; var condition = inCondition ? "i < 10" : string.Empty; var incrementor = inIncrementor ? "i++" : string.Empty; var expectedRaise = inCondition && inIncrementor; var noncompliant = expectedRaise ? " // Noncompliant" : string.Empty; var declaration = inInitializer ? string.Empty : "int i = 0;"; var verifier = builder.AddSnippet($$""" class C { void M() { {{declaration}} for ({{(inInitializer ? "int i = 0" : initializer)}}; {{condition}}; {{incrementor}}) { i = 5;{{noncompliant}} } } } """); if (expectedRaise) { verifier.Verify(); } else { verifier.VerifyNoIssues(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ForLoopCounterConditionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ForLoopCounterConditionTest { [TestMethod] public void ForLoopCounterCondition() => new VerifierBuilder().AddPaths("ForLoopCounterCondition.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ForLoopIncrementSignTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ForLoopIncrementSignTest { [TestMethod] public void ForLoopIncrementSign() => new VerifierBuilder().AddPaths("ForLoopIncrementSign.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ForeachLoopExplicitConversionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ForeachLoopExplicitConversionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ForeachLoopExplicitConversion() => builder.AddPaths("ForeachLoopExplicitConversion.cs").Verify(); [TestMethod] public void ForeachLoopExplicitConversion_CodeFix() => builder.WithCodeFix() .AddPaths("ForeachLoopExplicitConversion.cs") .WithCodeFixedPaths("ForeachLoopExplicitConversion.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void ForeachLoopExplicitConversion_CS_Latest() => builder.AddPaths("ForeachLoopExplicitConversion.Latest.cs") .WithAutogenerateConcurrentFiles(false) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ForeachLoopExplicitConversion_CS_Latest_CodeFix() => builder.WithCodeFix() .AddPaths("ForeachLoopExplicitConversion.Latest.cs") .WithCodeFixedPaths("ForeachLoopExplicitConversion.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FrameworkTypeNamingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FrameworkTypeNamingTest { [TestMethod] public void FrameworkTypeNaming() => new VerifierBuilder().AddPaths("FrameworkTypeNaming.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FunctionComplexityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FunctionComplexityTest { [TestMethod] public void FunctionComplexity_CS() => CreateCSBuilder(3).AddPaths("FunctionComplexity.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void FunctionComplexity_CS_Latest() => CreateCSBuilder(3).AddPaths("FunctionComplexity.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void FunctionComplexity_InsufficientExecutionStack_CS() { if (!TestEnvironment.IsAzureDevOpsContext) // ToDo: Test doesn't work on Azure DevOps { CreateCSBuilder(3).AddPaths("SyntaxWalker_InsufficientExecutionStackException.cs").VerifyNoIssues(); } } [TestMethod] public void FunctionComplexity_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.FunctionComplexity { Maximum = 3 }) .AddPaths("FunctionComplexity.vb") .Verify(); private static VerifierBuilder CreateCSBuilder(int maxComplexityScore) => new VerifierBuilder().AddAnalyzer(() => new CS.FunctionComplexity { Maximum = maxComplexityScore }); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FunctionNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FunctionNameTest { [TestMethod] public void FunctionName() => new VerifierBuilder().AddPaths("FunctionName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/FunctionNestingDepthTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class FunctionNestingDepthTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.FunctionNestingDepth { Maximum = 3 }); [TestMethod] public void FunctionNestingDepth_CS() => builderCS.AddPaths("FunctionNestingDepth.cs").Verify(); [TestMethod] public void FunctionNestingDepth_CS_CSharp9() => builderCS.AddPaths("FunctionNestingDepth.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void FunctionNestingDepth_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.FunctionNestingDepth { Maximum = 3 }).AddPaths("FunctionNestingDepth.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericInheritanceShouldNotBeRecursiveTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class GenericInheritanceShouldNotBeRecursiveTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void GenericInheritanceShouldNotBeRecursive_CS() => builderCS.AddPaths("GenericInheritanceShouldNotBeRecursive.cs").Verify(); [TestMethod] public void GenericInheritanceShouldNotBeRecursive_CSharp9() => builderCS.AddPaths("GenericInheritanceShouldNotBeRecursive.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void GenericInheritanceShouldNotBeRecursive_VB() => builderVB.AddPaths("GenericInheritanceShouldNotBeRecursive.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericLoggerInjectionShouldMatchEnclosingTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GenericLoggerInjectionShouldMatchEnclosingTypeTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()); [TestMethod] public void GenericLoggerInjectionShouldMatchEnclosingTypeTest_CS() => builder.AddPaths("GenericLoggerInjectionShouldMatchEnclosingType.cs").Verify(); #if NET [TestMethod] public void GenericLoggerInjectionShouldMatchEnclosingTypeTest_CS_Latest() => builder.AddPaths("GenericLoggerInjectionShouldMatchEnclosingType.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericReadonlyFieldPropertyAssignmentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GenericReadonlyFieldPropertyAssignmentTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFix = new VerifierBuilder().WithCodeFix(); [TestMethod] public void GenericReadonlyFieldPropertyAssignment() => builder.AddPaths("GenericReadonlyFieldPropertyAssignment.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void GenericReadonlyFieldPropertyAssignment_CS_Latest() => builder.AddPaths("GenericReadonlyFieldPropertyAssignment.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void GenericReadonlyFieldPropertyAssignment_CS_Latest_CodeFix_Remove_Statement() => codeFix.AddPaths("GenericReadonlyFieldPropertyAssignment.Latest.cs") .WithCodeFixedPaths("GenericReadonlyFieldPropertyAssignment.Latest.Remove.Fixed.cs") .WithCodeFixTitle(GenericReadonlyFieldPropertyAssignmentCodeFix.TitleRemove) .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); [TestMethod] public void GenericReadonlyFieldPropertyAssignment_CodeFix_Remove_Statement() => codeFix.AddPaths("GenericReadonlyFieldPropertyAssignment.cs") .WithCodeFixedPaths("GenericReadonlyFieldPropertyAssignment.Remove.Fixed.cs") .WithCodeFixTitle(GenericReadonlyFieldPropertyAssignmentCodeFix.TitleRemove) .VerifyCodeFix(); [TestMethod] public void GenericReadonlyFieldPropertyAssignment_CodeFix_Add_Generic_Type_Constraint() => codeFix.AddPaths("GenericReadonlyFieldPropertyAssignment.cs") .WithCodeFixedPaths("GenericReadonlyFieldPropertyAssignment.AddConstraint.Fixed.cs") .WithCodeFixTitle(GenericReadonlyFieldPropertyAssignmentCodeFix.TitleAddClassConstraint) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericTypeParameterEmptinessCheckingTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GenericTypeParameterEmptinessCheckingTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemCollections); [TestMethod] public void GenericTypeParameterEmptinessChecking() => builder.AddPaths("GenericTypeParameterEmptinessChecking.cs").Verify(); [TestMethod] public void GenericTypeParameterEmptinessChecking_CS_Latest() => builder.AddPaths("GenericTypeParameterEmptinessChecking.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void GenericTypeParameterEmptinessChecking_CodeFix() => builder.AddPaths("GenericTypeParameterEmptinessChecking.cs") .WithCodeFix() .WithCodeFixedPaths("GenericTypeParameterEmptinessChecking.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericTypeParameterInOutTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GenericTypeParameterInOutTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void GenericTypeParameterInOut() => builder.AddPaths("GenericTypeParameterInOut.cs").Verify(); [TestMethod] public void GenericTypeParameterInOut_CS_Latest() => builder.AddPaths("GenericTypeParameterInOut.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericTypeParameterUnusedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GenericTypeParameterUnusedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void GenericTypeParameterUnused() => builder.AddPaths("GenericTypeParameterUnused.cs", "GenericTypeParameterUnused.Partial.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void GenericTypeParameterUnused_CS_Latest() => builder.AddPaths("GenericTypeParameterUnused.Latest.cs", "GenericTypeParameterUnused.Latest.Partial.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GenericTypeParametersRequiredTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class GenericTypeParametersRequiredTest { [TestMethod] public void GenericTypeParametersRequired() => new VerifierBuilder().AddPaths("GenericTypeParametersRequired.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GetHashCodeEqualsOverrideTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GetHashCodeEqualsOverrideTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives); [TestMethod] public void GetHashCodeEqualsOverride() => builder.AddPaths("GetHashCodeEqualsOverride.cs").Verify(); [TestMethod] public void GetHashCodeEqualsOverride_CS_Latest() => builder.AddPaths("GetHashCodeEqualsOverride.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GetHashCodeMutableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GetHashCodeMutableTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void GetHashCodeMutable() => builder.AddPaths("GetHashCodeMutable.cs").Verify(); [TestMethod] public void GetHashCodeMutable_CS_Latest() => builder.AddPaths("GetHashCodeMutable.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void GetHashCodeMutable_CodeFix() => builder.WithCodeFix() .AddPaths("GetHashCodeMutable.cs") .WithCodeFixedPaths("GetHashCodeMutable.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GetTypeWithIsAssignableFromTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GetTypeWithIsAssignableFromTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void GetTypeWithIsAssignableFrom() => builder.AddPaths("GetTypeWithIsAssignableFrom.cs").Verify(); [TestMethod] public void GetTypeWithIsAssignableFrom_CS_Latest() => builder.AddPaths("GetTypeWithIsAssignableFrom.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void GetTypeWithIsAssignableFrom_CS_Latest_CodeFix() => builder.AddPaths("GetTypeWithIsAssignableFrom.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("GetTypeWithIsAssignableFrom.Latest.Fixed.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); [TestMethod] public void GetTypeWithIsAssignableFrom_CodeFix() => builder.AddPaths("GetTypeWithIsAssignableFrom.cs") .WithCodeFix() .WithCodeFixedPaths("GetTypeWithIsAssignableFrom.Fixed.cs", "GetTypeWithIsAssignableFrom.Fixed.Batch.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GotoStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GotoStatementTest { [TestMethod] public void GotoStatement_CS() => new VerifierBuilder().AddPaths("GotoStatement.cs").Verify(); [TestMethod] public void GotoStatement_VB() => new VerifierBuilder().AddPaths("GotoStatement.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/GuardConditionOnEqualsOverrideTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class GuardConditionOnEqualsOverrideTest { [TestMethod] public void GuardConditionOnEqualsOverride() => new VerifierBuilder().AddPaths("GuardConditionOnEqualsOverride.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/ClearTextProtocolsAreSensitiveTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ClearTextProtocolsAreSensitiveTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new ClearTextProtocolsAreSensitive(AnalyzerConfiguration.AlwaysEnabled)); internal static IEnumerable AdditionalReferences => MetadataReferenceFacade.SystemNetHttp .Concat(MetadataReferenceFacade.SystemComponentModelPrimitives) .Concat(MetadataReferenceFacade.SystemXml) .Concat(MetadataReferenceFacade.SystemXmlLinq) .Concat(MetadataReferenceFacade.SystemWeb); [TestMethod] public void ClearTextProtocolsAreSensitive() => builder.AddPaths("ClearTextProtocolsAreSensitive.cs") .AddReferences(AdditionalReferences) .WithConcurrentAnalysis(false) .Verify(); #if NETFRAMEWORK [TestMethod] public void ClearTextProtocolsAreSensitive_NetFx() => builder.AddPaths("ClearTextProtocolsAreSensitive.NetFramework.cs") .AddReferences(FrameworkMetadataReference.SystemWebServices) .Verify(); #endif [TestMethod] public void ClearTextProtocolsAreSensitive_CS_Latest() => builder.AddPaths("ClearTextProtocolsAreSensitive.Latest.cs") .AddReferences(AdditionalReferences) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ClearTextProtocolsAreSensitive_CS_TopLevelStatements() => builder.AddPaths("ClearTextProtocolsAreSensitive.TopLevelStatements.cs") .AddReferences(AdditionalReferences) .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/CommandPathTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CommandPathTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.SystemDiagnosticsProcess) .AddAnalyzer(() => new CS.CommandPath(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.SystemDiagnosticsProcess) .AddAnalyzer(() => new VB.CommandPath(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void CommandPath_CS() => builderCS.AddPaths("CommandPath.cs").Verify(); [TestMethod] public void CommandPath_CS_Latest() => builderCS.AddPaths("CommandPath.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void CommandPath_VB() => builderVB.AddPaths("CommandPath.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/ConfiguringLoggersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ConfiguringLoggersTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CS.ConfiguringLoggers(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new VB.ConfiguringLoggers(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void ConfiguringLoggers_Log4Net_CS() => builderCS.AddPaths("ConfiguringLoggers_Log4Net.cs") .AddReferences(Log4NetReferences) .Verify(); [TestMethod] public void ConfiguringLoggers_Log4Net_VB() => builderVB.AddPaths("ConfiguringLoggers_Log4Net.vb") .AddReferences(Log4NetReferences) .Verify(); [TestMethod] public void ConfiguringLoggers_NLog_CS() => builderCS.AddPaths("ConfiguringLoggers_NLog.cs") .AddReferences(NLogReferences) .Verify(); [TestMethod] public void ConfiguringLoggers_NLog_VB() => builderVB.AddPaths("ConfiguringLoggers_NLog.vb") .AddReferences(NLogReferences) .Verify(); [TestMethod] public void ConfiguringLoggers_Serilog_CS() => builderCS.AddPaths("ConfiguringLoggers_Serilog.cs") .AddReferences(SeriLogReferences) .Verify(); [TestMethod] public void ConfiguringLoggers_Serilog_VB() => builderVB.AddPaths("ConfiguringLoggers_Serilog.vb") .AddReferences(SeriLogReferences) .Verify(); #if NET [TestMethod] public void ConfiguringLoggers_AspNetCore2_CS() => builderCS.AddPaths("ConfiguringLoggers_AspNetCore.cs") .AddReferences(AspNetCore2LoggingReferences) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void ConfiguringLoggers_AspNetCoreLatest_CS() => builderCS.AddPaths("ConfiguringLoggers_AspNetCore6.cs") .AddReferences(AspNetCoreLoggingReferences(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void ConfiguringLoggers_AspNetCore_VB() => builderVB.AddPaths("ConfiguringLoggers_AspNetCore.vb") .AddReferences(AspNetCore2LoggingReferences) .Verify(); #endif internal static IEnumerable Log4NetReferences => // See: https://github.com/SonarSource/sonar-dotnet/issues/3548 NuGetMetadataReference.Log4Net("2.0.8", "net45-full").Concat(MetadataReferenceFacade.SystemXml); private static IEnumerable NLogReferences => NuGetMetadataReference.NLog(); private static IEnumerable SeriLogReferences => Enumerable.Empty() .Concat(NuGetMetadataReference.Serilog("2.11.0")) .Concat(NuGetMetadataReference.SerilogSinksConsole("4.1.0")); #if NET private static IEnumerable AspNetCore2LoggingReferences => Enumerable.Empty() .Concat(NuGetMetadataReference.MicrosoftAspNetCore(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreHosting(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreHostingAbstractions(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreHttpAbstractions(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsConfigurationAbstractions(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsOptions(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(TestConstants.DotNetCore220Version)) .Concat(new[] { AspNetCoreMetadataReference.MicrosoftExtensionsDependencyInjectionAbstractions }); private static IEnumerable AspNetCoreLoggingReferences(string version) => new[] { AspNetCoreMetadataReference.MicrosoftAspNetCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreHosting, AspNetCoreMetadataReference.MicrosoftAspNetCoreHostingAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftExtensionsLoggingEventSource } .Concat(NuGetMetadataReference.MicrosoftExtensionsConfigurationAbstractions(version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsOptions(version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(version)) .Concat(NuGetMetadataReference.MicrosoftExtensionsDependencyInjectionAbstractions(version)); #endif } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/CookieShouldBeHttpOnlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CookieShouldBeHttpOnlyTest { private const string WebConfig = "Web.config"; private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CookieShouldBeHttpOnly(AnalyzerConfiguration.AlwaysEnabled)); public TestContext TestContext { get; set; } internal static IEnumerable AdditionalReferences => NuGetMetadataReference.Nancy(); [TestMethod] public void CookieShouldBeHttpOnly_Nancy() => builder.AddPaths("CookieShouldBeHttpOnly_Nancy.cs").AddReferences(AdditionalReferences).Verify(); [TestMethod] public void CookieShouldBeHttpOnly_Latest() => builder.AddPaths("CookieShouldBeHttpOnly.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreHttpFeatures(TestConstants.NuGetLatestVersion)) .AddReferences(AdditionalReferences) .Verify(); [TestMethod] public void CookieShouldBeHttpOnly_TopLevelStatements() => builder.AddPaths("CookieShouldBeHttpOnly.TopLevelStatements.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreHttpFeatures(TestConstants.NuGetLatestVersion)) .AddReferences(AdditionalReferences) .Verify(); [TestMethod] public void CookieShouldBeHttpOnly() => builder.AddPaths("CookieShouldBeHttpOnly.cs").AddReferences(MetadataReferenceFacade.SystemWeb).WithNetFrameworkOnly().Verify(); [TestMethod] [DataRow(@"TestCases\WebConfig\CookieShouldBeHttpOnly\HttpOnlyCookiesConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeHttpOnly\Formatting")] public void CookieShouldBeHttpOnly_WithWebConfigValueSetToTrue(string root) => builder.AddPaths("CookieShouldBeHttpOnly_WithWebConfig.cs") .AddReferences(MetadataReferenceFacade.SystemWeb) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, Path.Combine(root, WebConfig))) .WithNetFrameworkOnly() .Verify(); [TestMethod] [DataRow(@"TestCases\WebConfig\CookieShouldBeHttpOnly\NonHttpOnlyCookiesConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeHttpOnly\UnrelatedConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeHttpOnly\ConfigWithoutAttribute")] public void CookieShouldBeHttpOnly_WithWebConfigValueSetToFalse(string root) => builder.AddPaths("CookieShouldBeHttpOnly.cs") .AddReferences(MetadataReferenceFacade.SystemWeb) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, Path.Combine(root, WebConfig))) .WithNetFrameworkOnly() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/CookieShouldBeSecureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CookieShouldBeSecureTest { private const string WebConfig = "Web.config"; private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CookieShouldBeSecure(AnalyzerConfiguration.AlwaysEnabled)); public TestContext TestContext { get; set; } internal static IEnumerable AdditionalReferences => NuGetMetadataReference.Nancy(); [TestMethod] public void CookieShouldBeSecure_Nancy() => builder.AddPaths("CookieShouldBeSecure_Nancy.cs").AddReferences(AdditionalReferences).Verify(); [TestMethod] public void CookieShouldBeSecure_Latest() => builder.AddPaths("CookieShouldBeSecure.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreHttpFeatures(TestConstants.NuGetLatestVersion)) .AddReferences(AdditionalReferences) .Verify(); [TestMethod] public void CookieShouldBeSecure_TopLevelStatements() => builder.AddPaths("CookieShouldBeSecure.TopLevelStatements.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreHttpFeatures(TestConstants.NuGetLatestVersion)) .AddReferences(AdditionalReferences) .Verify(); [TestMethod] public void CookieShouldBeSecure_NetFx() => builder.AddPaths("CookieShouldBeSecure.cs") .AddReferences(MetadataReferenceFacade.SystemWeb) .WithNetFrameworkOnly() .Verify(); [TestMethod] [DataRow(@"TestCases\WebConfig\CookieShouldBeSecure\SecureCookieConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeSecure\Formatting")] public void CookieShouldBeSecure_WithWebConfigValueSetToTrue(string root) { var webConfigPath = Path.Combine(root, WebConfig); builder.AddPaths("CookieShouldBeSecure_WithWebConfig.cs") .AddReferences(MetadataReferenceFacade.SystemWeb) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, webConfigPath)) .WithNetFrameworkOnly() .Verify(); } [TestMethod] [DataRow(@"TestCases\WebConfig\CookieShouldBeSecure\NonSecureCookieConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeSecure\UnrelatedConfig")] [DataRow(@"TestCases\WebConfig\CookieShouldBeSecure\ConfigWithoutAttribute")] public void CookieShouldBeSecure_WithWebConfigValueSetToFalse(string root) { var webConfigPath = Path.Combine(root, WebConfig); builder.AddPaths("CookieShouldBeSecure.cs") .AddReferences(MetadataReferenceFacade.SystemWeb) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, webConfigPath)) .WithNetFrameworkOnly() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/CreatingHashAlgorithmsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CreatingHashAlgorithmsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .AddAnalyzer(() => new CS.CreatingHashAlgorithms(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .AddAnalyzer(() => new VB.CreatingHashAlgorithms(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void CreatingHashAlgorithms_CS() => builderCS.AddPaths("CreatingHashAlgorithms.cs").Verify(); [TestMethod] public void CreatingHashAlgorithms_CS_Latest() => builderCS.AddPaths("CreatingHashAlgorithms.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void CreatingHashAlgorithms_CS_NetFx() => builderCS.AddPaths("CreatingHashAlgorithms.NetFramework.cs").WithNetFrameworkOnly().Verify(); [TestMethod] public void CreatingHashAlgorithms_VB() => builderVB.AddPaths("CreatingHashAlgorithms.vb").Verify(); [TestMethod] public void CreatingHashAlgorithms_VB_NET() => builderVB.AddPaths("CreatingHashAlgorithms.NET.vb").WithNetOnly().Verify(); [TestMethod] public void CreatingHashAlgorithms_VB_NetFx() => builderVB.AddPaths("CreatingHashAlgorithms.NetFramework.vb").WithNetFrameworkOnly().Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/DeliveringDebugFeaturesInProductionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DeliveringDebugFeaturesInProductionTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CS.DeliveringDebugFeaturesInProduction(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new VB.DeliveringDebugFeaturesInProduction(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void DeliveringDebugFeaturesInProduction_NetCore2_CS() => builderCS.AddPaths("DeliveringDebugFeaturesInProduction.NetCore2.cs") .AddReferences(AdditionalReferencesForAspNetCore2) .Verify(); [TestMethod] public void DeliveringDebugFeaturesInProduction_NetCore2_VB() => builderVB.AddPaths("DeliveringDebugFeaturesInProduction.NetCore2.vb") .AddReferences(AdditionalReferencesForAspNetCore2) .Verify(); #if NET [TestMethod] public void DeliveringDebugFeaturesInProduction_NetCore3_CS() => builderCS.AddPaths("DeliveringDebugFeaturesInProduction.NetCore3.cs") .AddReferences(AdditionalReferencesForAspNetCore3AndLater) .Verify(); [TestMethod] public void DeliveringDebugFeaturesInProduction_NetCore3_VB() => builderVB.AddPaths("DeliveringDebugFeaturesInProduction.NetCore3.vb") .AddReferences(AdditionalReferencesForAspNetCore3AndLater) .Verify(); [TestMethod] public void DeliveringDebugFeaturesInProduction_Net7_CS() => builderCS.AddPaths("DeliveringDebugFeaturesInProduction.Net7.cs") .WithTopLevelStatements() .AddReferences([ AspNetCoreMetadataReference.MicrosoftAspNetCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreRouting, AspNetCoreMetadataReference.MicrosoftAspNetCoreDiagnostics, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHostingAbstractions, AspNetCoreMetadataReference.MicrosoftExtensionsHostingAbstractions]) .VerifyNoIssues(); private static IEnumerable AdditionalReferencesForAspNetCore3AndLater => new[] { AspNetCoreMetadataReference.MicrosoftAspNetCoreDiagnostics, AspNetCoreMetadataReference.MicrosoftAspNetCoreHostingAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftExtensionsHostingAbstractions }; #endif internal static IEnumerable AdditionalReferencesForAspNetCore2 => Enumerable.Empty() .Concat(MetadataReferenceFacade.NetStandard) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreDiagnostics(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreDiagnosticsEntityFrameworkCore(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreHttpAbstractions(TestConstants.DotNetCore220Version)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreHostingAbstractions(TestConstants.DotNetCore220Version)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/DisablingCsrfProtectionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DisablingCsrfProtectionTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots") .AddAnalyzer(() => new DisablingCsrfProtection(AnalyzerConfiguration.AlwaysEnabled)) .AddReferences(AdditionalReferences()); [TestMethod] public void DisablingCsrfProtection_Latest() => builder.AddPaths("DisablingCsrfProtection.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); internal static IEnumerable AdditionalReferences() => [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvc, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftExtensionsDependencyInjectionAbstractions ]; } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/DisablingRequestValidationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using System.IO; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DisablingRequestValidationTest { private const string AspNetMvcVersion = "5.2.7"; private const string WebConfig = "Web.config"; public TestContext TestContext { get; set; } [TestMethod] public void DisablingRequestValidation_CS() => CreateBuilderCS(AnalyzerConfiguration.AlwaysEnabled) .AddPaths("DisablingRequestValidation.cs") .Verify(); [TestMethod] public void DisablingRequestValidation_CS_Disabled() => CreateBuilderCS(AnalyzerConfiguration.Hotspot) .AddPaths("DisablingRequestValidation.cs") .VerifyNoIssuesIgnoreErrors(); [TestMethod] public void DisablingRequestValidation_CS_NoIssuesInTestCode() => CreateBuilderCS(AnalyzerConfiguration.AlwaysEnabled) .AddPaths("DisablingRequestValidation.cs") .AddTestReference() .VerifyNoIssuesIgnoreErrors(); [TestMethod] [DataRow(true, @"TestCases\WebConfig\DisablingRequestValidation\Values")] [DataRow(true, @"TestCases\WebConfig\DisablingRequestValidation\Formatting")] [DataRow(false, @"TestCases\WebConfig\DisablingRequestValidation\UnexpectedContent")] public void DisablingRequestValidation_CS_WebConfig(bool expectIssues, string root) { var webConfigPath = Path.Combine(root, WebConfig); VerifyAdditionalFiles(expectIssues, webConfigPath); } [TestMethod] public void DisablingRequestValidation_CS_CorruptAndNonExistingWebConfigs() { var root = @"TestCases\WebConfig\DisablingRequestValidation\Corrupt"; var nonexisting = @"TestCases\WebConfig\DisablingRequestValidation\NonExsitingDirectory"; var corruptFilePath = Path.Combine(root, WebConfig); var nonExistentFilePath = Path.Combine(nonexisting, WebConfig); VerifyAdditionalFiles(false, corruptFilePath, nonExistentFilePath); } [TestMethod] [DataRow(@"TestCases\WebConfig\DisablingRequestValidation\MultipleFiles", "SubFolder")] [DataRow(@"TestCases\WebConfig\DisablingRequestValidation\EdgeValues", "3.9", "5.6")] public void DisablingRequestValidation_CS_WebConfig_SubFolders(string rootDirectory, params string[] subFolders) { var newCulture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone(); // decimal.TryParse() from the implementation might not recognize "1.2" under different culture newCulture.NumberFormat.NumberDecimalSeparator = ","; using var scope = new CurrentCultureScope(newCulture); 42.42.ToString().Should().Be("42,42"); var rootFile = Path.Combine(rootDirectory, WebConfig); var filesToAnalyze = new List { rootFile }; foreach (var subFolder in subFolders) { filesToAnalyze.Add(Path.Combine(rootDirectory, subFolder, WebConfig)); } filesToAnalyze.Add(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, filesToAnalyze.ToArray())); CreateBuilderCS(AnalyzerConfiguration.AlwaysEnabled) .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, filesToAnalyze.ToArray())) .AddAdditionalSourceFiles(filesToAnalyze.ToArray()) .Verify(); } [TestMethod] public void DisablingRequestValidation_CS_WebConfig_LowerCase() { var root = @"TestCases\WebConfig\DisablingRequestValidation\LowerCase"; var webConfigPath = Path.Combine(root, "web.config"); VerifyAdditionalFiles(true, webConfigPath); } [TestMethod] [DataRow(true, @"TestCases\WebConfig\DisablingRequestValidation\TransformCustom\Web.Custom.config")] [DataRow(false, @"TestCases\WebConfig\DisablingRequestValidation\TransformDebug\Web.Debug.config")] [DataRow(true, @"TestCases\WebConfig\DisablingRequestValidation\TransformRelease\Web.Release.config")] public void DisablingRequestValidation_CS_WebConfig_Transformation(bool expectIssues, string configPath) => VerifyAdditionalFiles(expectIssues, configPath); [TestMethod] public void DisablingRequestValidation_VB() => CreateBuilderVB(AnalyzerConfiguration.AlwaysEnabled) .AddPaths("DisablingRequestValidation.vb") .WithOptions(LanguageOptions.FromVisualBasic14) .Verify(); [TestMethod] public void DisablingRequestValidation_VB_Disabled() => CreateBuilderVB(AnalyzerConfiguration.Hotspot) .AddPaths("DisablingRequestValidation.vb") .VerifyNoIssuesIgnoreErrors(); [TestMethod] public void DisablingRequestValidation_VB_WebConfig() { var root = @"TestCases\WebConfig\DisablingRequestValidation\Values"; var webConfigPath = Path.Combine(root, WebConfig); CreateBuilderCS(AnalyzerConfiguration.AlwaysEnabled) .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, webConfigPath)) .AddAdditionalSourceFiles(webConfigPath) .Verify(); } private static VerifierBuilder CreateBuilderCS(IAnalyzerConfiguration configuration) => new VerifierBuilder() .WithBasePath("Hotspots") .AddAnalyzer(() => new CS.DisablingRequestValidation(configuration)) .AddReferences(NuGetMetadataReference.MicrosoftAspNetMvc(AspNetMvcVersion)); private static VerifierBuilder CreateBuilderVB(IAnalyzerConfiguration configuration) => new VerifierBuilder() .WithBasePath("Hotspots") .AddAnalyzer(() => new VB.DisablingRequestValidation(configuration)) .AddReferences(NuGetMetadataReference.MicrosoftAspNetMvc(AspNetMvcVersion)); private void VerifyAdditionalFiles(bool expectIssues, string additionalSourceFile, params string[] additionalFilesToAnalyze) { var withAdditionalSourceFiles = CreateBuilderCS(AnalyzerConfiguration.AlwaysEnabled) .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, additionalFilesToAnalyze.Append(additionalSourceFile).ToArray())) .AddAdditionalSourceFiles(additionalSourceFile); if (expectIssues) { withAdditionalSourceFiles.Verify(); } else { withAdditionalSourceFiles.VerifyNoIssues(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/DoNotUseRandomTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class DoNotUseRandomTest { [TestMethod] public void DoNotUseRandom() => new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new DoNotUseRandom(AnalyzerConfiguration.AlwaysEnabled)) .AddPaths("DoNotUseRandom.cs") .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/ExecutingSqlQueriesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExecutingSqlQueriesTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled)).WithBasePath("Hotspots"); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.ExecutingSqlQueries(AnalyzerConfiguration.AlwaysEnabled)).WithBasePath("Hotspots"); [TestMethod] public void ExecutingSqlQueries_CS_Dapper() => builderCS .AddPaths("ExecutingSqlQueries.Dapper.cs") .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(NuGetMetadataReference.Dapper()) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_MySqlData() => builderCS .AddPaths("ExecutingSqlQueries.MySqlData.cs") .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives) .AddReferences(NuGetMetadataReference.Dapper("2.1.35")) .AddReferences(NuGetMetadataReference.MySqlData("9.0.0")) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_EF6() => builderCS.AddPaths("ExecutingSqlQueries.EF6.cs") .AddReferences(NuGetMetadataReference.EntityFramework()) .Verify(); [TestMethod] public void ExecutingSqlQueries_OrmLite_CS_810() => builderCS .AddPaths(@"ExecutingSqlQueries.OrmLite.cs") .AddReferences(MetadataReferenceFacade.SystemData) // after 8.10 netfx breaks .AddReferences(NuGetMetadataReference.ServiceStackOrmLite("8.10")) .Verify(); [TestMethod] public void ExecutingSqlQueries_OrmLite_CS() => builderCS .AddPaths(@"ExecutingSqlQueries.OrmLite.cs") .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(NuGetMetadataReference.ServiceStackOrmLite(TestConstants.NuGetLatestVersion)) .WithNetOnly() .Verify(); [TestMethod] public void ExecutingSqlQueries_NHibernate_CS() => builderCS .AddPaths("ExecutingSqlQueries.NHibernate.cs") .AddReferences(NuGetMetadataReference.NHibernate(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_AzureCosmos() => builderCS .AddPaths("ExecutingSqlQueries.AzureCosmos.cs") .AddReferences(NuGetMetadataReference.MicrosoftAzureCosmos()) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_MicrosoftDataSqlClient() => builderCS .AddPaths("ExecutingSqlQueries.MicrosoftDataSqlClient.cs") .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives) .AddReferences(NuGetMetadataReference.MicrosoftDataSqlClient()) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_OracleManagedDataAccess() => builderCS .AddPaths("ExecutingSqlQueries.OracleManagedDataAccess.cs") .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives) .AddReferences(NuGetMetadataReference.OracleManagedDataAccessCore()) .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_EntityFrameworkCore2() => builderCS .AddPaths("ExecutingSqlQueries.EntityFrameworkCore2.cs") .AddReferences(ReferencesEntityFrameworkNetCore("2.2.6").Concat(NuGetMetadataReference.SystemComponentModelTypeConverter())) .WithNetOnly() .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_EntityFrameworkCore7() => builderCS .AddPaths("ExecutingSqlQueries.EntityFrameworkCoreLatest.cs") .AddReferences(ReferencesEntityFrameworkNetCore("7.0.14")) .WithNetOnly() .Verify(); [TestMethod] public void ExecutingSqlQueries_CS_Latest() => builderCS.AddPaths("ExecutingSqlQueries.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(ReferencesEntityFrameworkNetCore(TestConstants.DotNetCore220Version).Concat(NuGetMetadataReference.MicrosoftDataSqliteCore())) .Verify(); [TestMethod] public void ExecutingSqlQueries_VB_EntityFrameworkCore2() => builderVB .AddPaths(@"ExecutingSqlQueries.EntityFrameworkCore2.vb") .WithOptions(LanguageOptions.FromVisualBasic15) .AddReferences(ReferencesEntityFrameworkNetCore(TestConstants.DotNetCore220Version)) .Verify(); [TestMethod] public void ExecutingSqlQueries_VB_EntityFrameworkCore7() => builderVB .AddPaths(@"ExecutingSqlQueries.EntityFrameworkCoreLatest.vb") .WithOptions(LanguageOptions.FromVisualBasic15) .AddReferences(ReferencesEntityFrameworkNetCore("7.0.14")) .Verify(); #if NETFRAMEWORK // System.Data.OracleClient.dll is not available on .Net Core [TestMethod] public void ExecutingSqlQueries_CS_Net46() => builderCS .AddPaths("ExecutingSqlQueries.Net46.cs") .AddReferences(ReferencesNet46(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void ExecutingSqlQueries_VB_Net46() => builderVB .AddPaths("ExecutingSqlQueries.Net46.vb") .WithOptions(LanguageOptions.FromVisualBasic15) .AddReferences(ReferencesNet46(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void ExecutingSqlQueries_MonoSqlLite_Net46_CS() => builderCS .AddPaths("ExecutingSqlQueries.Net46.MonoSqlLite.cs") .AddReferences(FrameworkMetadataReference.SystemData) .AddReferences(NuGetMetadataReference.MonoDataSqlite()) .Verify(); [TestMethod] public void ExecutingSqlQueries_MonoSqlLite_Net46_VB() => builderVB .AddPaths("ExecutingSqlQueries.Net46.MonoSqlLite.vb") .AddReferences(FrameworkMetadataReference.SystemData) .AddReferences(NuGetMetadataReference.MonoDataSqlite()) .WithOptions(LanguageOptions.FromVisualBasic14) .Verify(); internal static IEnumerable ReferencesNet46(string sqlServerCeVersion) => MetadataReferenceFacade.NetStandard .Concat(FrameworkMetadataReference.SystemData) .Concat(FrameworkMetadataReference.SystemDataOracleClient) .Concat(NuGetMetadataReference.SystemDataSqlServerCe(sqlServerCeVersion)) .Concat(NuGetMetadataReference.MySqlData("8.0.22")) .Concat(NuGetMetadataReference.MicrosoftDataSqliteCore()) .Concat(NuGetMetadataReference.SystemDataSQLiteCore()); #endif internal static IEnumerable ReferencesEntityFrameworkNetCore(string entityFrameworkVersion) => Enumerable.Empty() .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore(entityFrameworkVersion)) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(entityFrameworkVersion)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/ExpandingArchivesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ExpandingArchivesTest { internal static IEnumerable AdditionalReferences => MetadataReferenceFacade.SystemIoCompression; [TestMethod] public void ExpandingArchives_CS() => new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CS.ExpandingArchives(AnalyzerConfiguration.AlwaysEnabled)) .AddPaths("ExpandingArchives.cs") .AddReferences(AdditionalReferences) .Verify(); [TestMethod] public void ExpandingArchives_CS_Latest() => new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new CS.ExpandingArchives(AnalyzerConfiguration.AlwaysEnabled)) .AddPaths("ExpandingArchives.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(AdditionalReferences) .VerifyNoIssues(); [TestMethod] public void ExpandingArchives_VB() => new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new VB.ExpandingArchives(AnalyzerConfiguration.AlwaysEnabled)) .AddPaths("ExpandingArchives.vb") .AddReferences(AdditionalReferences) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/HardcodedIpAddressTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class HardcodedIpAddressTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.HardcodedIpAddress(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.HardcodedIpAddress(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void HardcodedIpAddress_CS() => builderCS.AddPaths(@"Hotspots\HardcodedIpAddress.cs").Verify(); [TestMethod] public void HardcodedIpAddress_CS_Latest() => builderCS.AddPaths(@"Hotspots\HardcodedIpAddress.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void HardcodedIpAddress_VB() => builderVB.AddPaths(@"Hotspots\HardcodedIpAddress.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/InsecureDeserializationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InsecureDeserializationTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddAnalyzer(() => new InsecureDeserialization(AnalyzerConfiguration.AlwaysEnabled)).WithBasePath("Hotspots"); [TestMethod] public void InsecureDeserialization() => builder.AddPaths("InsecureDeserialization.cs").Verify(); [TestMethod] public void InsecureDeserialization_Latest() => builder.AddPaths("InsecureDeserialization.Latest.cs", "InsecureDeserialization.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/PermissiveCorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PermissiveCorsTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddAnalyzer(() => new PermissiveCors(AnalyzerConfiguration.AlwaysEnabled)) .WithBasePath(@"Hotspots\") .AddReferences(AdditionalReferences); #if NET internal static IEnumerable AdditionalReferences => [ AspNetCoreMetadataReference.MicrosoftAspNetCoreCors, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvc, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftExtensionsDependencyInjectionAbstractions, AspNetCoreMetadataReference.MicrosoftExtensionsPrimitives, AspNetCoreMetadataReference.MicrosoftNetHttpHeadersHeaderNames ]; [TestMethod] public void PermissiveCors_Latest() => builder.AddPaths("PermissiveCors.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #else private static IEnumerable AdditionalReferences => NuGetMetadataReference.MicrosoftNetHttpHeaders("2.1.14") .Concat(NuGetMetadataReference.MicrosoftAspNetMvc(TestConstants.NuGetLatestVersion)) .Concat(NuGetMetadataReference.MicrosoftAspNetWebApiCors(TestConstants.NuGetLatestVersion)) .Concat(NuGetMetadataReference.MicrosoftNetWebApiCore(TestConstants.NuGetLatestVersion)) .Concat(FrameworkMetadataReference.SystemWeb) .Concat(FrameworkMetadataReference.SystemNetHttp); [TestMethod] public void PermissiveCors_AspNet_WebApi() => builder .AddPaths("PermissiveCors.NetFramework.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/PubliclyWritableDirectoriesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PubliclyWritableDirectoriesTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.PubliclyWritableDirectories(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.PubliclyWritableDirectories(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void PubliclyWritableDirectories_CS() => builderCS.AddPaths(@"Hotspots\PubliclyWritableDirectories.cs").Verify(); [TestMethod] public void PubliclyWritableDirectories_CS_Latest() => builderCS.AddPaths(@"Hotspots\PubliclyWritableDirectories.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PubliclyWritableDirectories_VB() => builderVB.AddPaths(@"Hotspots\PubliclyWritableDirectories.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/RequestsWithExcessiveLengthTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RequestsWithExcessiveLengthTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .AddAnalyzer(() => new CS.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled)) .WithBasePath(@"Hotspots") .AddReferences(GetAdditionalReferences()); private readonly VerifierBuilder builderVB = new VerifierBuilder() .AddAnalyzer(() => new VB.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled)) .WithBasePath(@"Hotspots") .AddReferences(GetAdditionalReferences()); public TestContext TestContext { get; set; } [TestMethod] public void RequestsWithExcessiveLength_CS() => builderCS.AddPaths(@"RequestsWithExcessiveLength.cs").Verify(); [TestMethod] public void RequestsWithExcessiveLength_CS_CustomValues() => new VerifierBuilder() .AddAnalyzer(() => new CS.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled) { FileUploadSizeLimit = 42 }) .AddPaths(@"Hotspots\RequestsWithExcessiveLength_CustomValues.cs") .AddReferences(GetAdditionalReferences()) .Verify(); [TestMethod] public void RequestsWithExcessiveLength_CSharp_Latest() => builderCS .AddPaths("RequestsWithExcessiveLength.Latest.cs") .AddReferences(OpenReadStreamReferences()) .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.FromCSharp12) .Verify(); [TestMethod] public void RequestsWithExcessiveLength_CSharp_Latest_Params() => new VerifierBuilder() .AddAnalyzer(() => new CS.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled) { FileUploadSizeLimit = 1024 }) .WithBasePath(@"Hotspots") .AddSnippet(""" using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Forms; public class TestCase { public static void OpenReadStream(IBrowserFile file, InputFileChangeEventArgs inputFileChange) { file.OpenReadStream(); // Noncompliant - Default size is 500 KB Parallel.ForEach(inputFileChange.GetMultipleFiles(9), file => { file.OpenReadStream(1024 * 1024); // Noncompliant }); } } """) .AddReferences(OpenReadStreamReferences()) .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.FromCSharp12) .Verify(); [TestMethod] public void RequestsWithExcessiveLength_VB() => builderVB.AddPaths(@"RequestsWithExcessiveLength.vb").WithOptions(LanguageOptions.FromVisualBasic15).Verify(); [TestMethod] public void RequestsWithExcessiveLength_VB_CustomValues() => new VerifierBuilder() .AddAnalyzer(() => new VB.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled) { FileUploadSizeLimit = 42 }) .AddPaths(@"Hotspots\RequestsWithExcessiveLength_CustomValues.vb") .AddReferences(GetAdditionalReferences()) .Verify(); [TestMethod] [DataRow(true, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\ContentLength")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\DefaultSettings")] [DataRow(true, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\RequestLength")] [DataRow(true, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\RequestAndContentLength")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\CornerCases")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\ValidValues")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\EmptySystemWeb")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\EmptySystemWebServer")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\SmallValues")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\InvalidConfig")] [DataRow(true, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\NoSystemWeb")] [DataRow(true, @"TestCases\WebConfig\RequestsWithExcessiveLength\Values\NoSystemWebServer")] [DataRow(false, @"TestCases\WebConfig\RequestsWithExcessiveLength\UnexpectedContent")] public void RequestsWithExcessiveLength_CS_WebConfig(bool expectIssues, string root) { var webConfigPath = GetWebConfigPath(root); var withAdditionalSourceFiles = builderCS .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, webConfigPath)) .AddAdditionalSourceFiles(webConfigPath); if (expectIssues) { withAdditionalSourceFiles.Verify(); } else { withAdditionalSourceFiles.VerifyNoIssues(); } } [TestMethod] public void RequestsWithExcessiveLength_CS_WebConfig_CustomParameterValue() { // Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/7867 var webConfigPath = GetWebConfigPath(@"TestCases\WebConfig\RequestsWithExcessiveLength\Values\ContentLength_Compliant"); // 83886080 new VerifierBuilder() .AddAnalyzer(() => new CS.RequestsWithExcessiveLength(AnalyzerConfiguration.AlwaysEnabled) { FileUploadSizeLimit = 83_8860_800 }) .AddReferences(GetAdditionalReferences()) .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, webConfigPath)) .AddAdditionalSourceFiles(webConfigPath) .VerifyNoIssues(); } [TestMethod] public void RequestsWithExcessiveLength_CS_CorruptAndNonExistingWebConfigs_ShouldNotFail() { const string root = @"TestCases\WebConfig\RequestsWithExcessiveLength\Corrupt"; const string missingDirectory = @"TestCases\WebConfig\RequestsWithExcessiveLength\NonExistingDirectory"; var corruptFilePath = GetWebConfigPath(root); var nonExistingFilePath = GetWebConfigPath(missingDirectory); builderCS .AddSnippet("// Nothing to see here") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithFilesToAnalyze(TestContext, corruptFilePath, nonExistingFilePath)) .AddAdditionalSourceFiles(corruptFilePath) .VerifyNoIssues(); } internal static IEnumerable GetAdditionalReferences() => NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(TestConstants.NuGetLatestVersion) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcViewFeatures(TestConstants.NuGetLatestVersion)); private static string GetWebConfigPath(string rootFolder) => Path.Combine(rootFolder, "Web.config"); private static IEnumerable OpenReadStreamReferences() => [ ..NuGetMetadataReference.MicrosoftAspNetCoreComponentsWeb(), ..MetadataReferenceFacade.SystemThreadingTasks, ..MetadataReferenceFacade.SystemCollections ]; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/UnsafeCodeBlocksTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnsafeCodeBlocksTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new UnsafeCodeBlocks(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void UnsafeCodeBlocks() => builder.AddPaths("UnsafeCodeBlocks.cs").Verify(); [TestMethod] public void UnsafeRecord() => builder.AddSnippet(""" unsafe record MyRecord(byte* Pointer); // Noncompliant // Error@-1 [CS8908] """) .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void UnsafeRecordStruct() => builder.AddSnippet(""" unsafe record struct MyRecord(byte* Pointer); // Noncompliant // Error@-1 [CS8908] """) .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Hotspots/UsingNonstandardCryptographyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UsingNonstandardCryptographyTest { private readonly VerifierBuilder builderCS = CreateBuilder().AddAnalyzer(() => new CS.UsingNonstandardCryptography(AnalyzerConfiguration.AlwaysEnabled)); private readonly VerifierBuilder builderVB = CreateBuilder().AddAnalyzer(() => new VB.UsingNonstandardCryptography(AnalyzerConfiguration.AlwaysEnabled)); [TestMethod] public void UsingNonstandardCryptography_CS() => builderCS.AddPaths("UsingNonstandardCryptography.cs").Verify(); [TestMethod] public void UsingNonstandardCryptography_CSharp9() => builderCS.AddPaths("UsingNonstandardCryptography.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void UsingNonstandardCryptography_CSharp10() => builderCS.AddPaths("UsingNonstandardCryptography.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void UsingNonstandardCryptography_CSharp12() => builderCS.AddPaths("UsingNonstandardCryptography.CSharp12.cs").WithOptions(LanguageOptions.FromCSharp12).Verify(); [TestMethod] public void UsingNonstandardCryptography_VB() => builderVB.AddPaths("UsingNonstandardCryptography.vb").Verify(); private static VerifierBuilder CreateBuilder() => new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemSecurityCryptography).WithBasePath("Hotspots"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IdentifiersNamedExtensionShouldBeEscapedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class IdentifiersNamedExtensionShouldBeEscapedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void IdentifiersNamedExtensionShouldBeEscaped_BeforeCSharp14() => builder.AddPaths("IdentifiersNamedExtensionShouldBeEscaped.BeforeCSharp14.cs") .WithOptions(LanguageOptions.BeforeCSharp14) .Verify(); [TestMethod] public void IdentifiersNamedExtensionShouldBeEscaped_Latest() => builder.AddPaths("IdentifiersNamedExtensionShouldBeEscaped.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); // Type declarations are tested via parameterized tests to allow version-specific ranges // (record struct requires C# 10+) and to keep the test case files focused on member-level cases. // '$$' marks the position where '@' must be inserted to make the declaration compliant. public static IEnumerable TypeDeclarations() => [ ["""class $$extension { }"""], ["""struct $$extension { }"""], ["""interface $$extension { }"""], ["""enum $$extension { }"""], ["""delegate void $$extension();"""], ["""class MyClass<$$extension> { }"""], ["""using $$extension = System.Object;"""], ["""record $$extension { }"""], ["""record struct $$extension { }"""], ]; [TestMethod] [DynamicData(nameof(TypeDeclarations))] public void IdentifiersNamedExtensionShouldBeEscaped_TypeDeclarations_CSharp10ToCSharp13(string declaration) => builder .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp10, LanguageVersion.CSharp13)) .AddSnippet($"{declaration.Replace("$$", string.Empty)} // Noncompliant") .Verify(); [TestMethod] [DynamicData(nameof(TypeDeclarations))] public void IdentifiersNamedExtensionShouldBeEscaped_TypeDeclarations_Latest(string declaration) => builder .WithOptions(LanguageOptions.CSharpLatest) .AddSnippet($"{declaration.Replace("$$", string.Empty)} // Error [CS9306]") .Verify(); [TestMethod] [DynamicData(nameof(TypeDeclarations))] public void IdentifiersNamedExtensionShouldBeEscaped_TypeDeclarations_Compliant_CSharp10ToCSharp13(string declaration) => builder .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp10, LanguageVersion.CSharp13)) .AddSnippet(declaration.Replace("$$", "@")) .VerifyNoIssues(); [TestMethod] [DynamicData(nameof(TypeDeclarations))] public void IdentifiersNamedExtensionShouldBeEscaped_TypeDeclarations_Compliant_Latest(string declaration) => builder .WithOptions(LanguageOptions.CSharpLatest) .AddSnippet(declaration.Replace("$$", "@")) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IdentifiersNamedFieldShouldBeEscapedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class IdentifiersNamedFieldShouldBeEscapedTest { private readonly VerifierBuilder builderCSharp9To13 = new VerifierBuilder() .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp9, LanguageVersion.CSharp13)) .WithWarningsAsErrors("CS9258"); // Does not fire in C# 9-13 — keep here so the test fails if it ever gets activated. private readonly VerifierBuilder builderLatest = new VerifierBuilder() .WithOptions(LanguageOptions.CSharpLatest); [TestMethod] public void IdentifiersNamedFieldShouldBeEscaped_CSharp9_13() => builderCSharp9To13.AddPaths("IdentifiersNamedFieldShouldBeEscaped.CSharp9-13.cs").Verify(); [TestMethod] public void IdentifiersNamedFieldShouldBeEscaped_Latest() => builderLatest.AddPaths("IdentifiersNamedFieldShouldBeEscaped.Latest.cs") .WithWarningsAsErrors("CS9258") .Verify(); [TestMethod] public void IdentifiersNamedFieldShouldBeEscaped_BeforeCSharp14() => new VerifierBuilder() .WithOptions(LanguageOptions.BeforeCSharp14) .AddPaths("IdentifiersNamedFieldShouldBeEscaped.BeforeCSharp14.cs") .AddPaths("IdentifiersNamedFieldShouldBeEscaped.BeforeCSharp14.partial.cs") .WithAutogenerateConcurrentFiles(false) .Verify(); // Declaration forms that raise CS9273 in C# 14 — S8367 should flag these in C# 9-13. public static IEnumerable CS9273LocalDeclarationForms() => [ // Variable declarations ["""int x = 0, field = 0;"""], ["""using var field = new System.IO.MemoryStream();"""], ["""using (var field = new System.IO.MemoryStream()) { }"""], // foreach ["""foreach (var field in new int[0]) { }"""], ["""foreach (var (field, _) in new (int, int)[0]) { }"""], // catch ["""try { } catch (Exception field) { }"""], // Pattern and deconstruction variables ["""int.TryParse("", out var field);"""], ["""var (field, _) = (0, 0);"""], ["""(int field, int _) = (0, 0);"""], // LINQ range variables ["""var q = from field in new int[0] select 0;"""], ["""var q = from x in new int[0] let field = 0 select x;"""], ["""var q = from x in new int[0] join field in new int[0] on x equals 0 select x;"""], ["""var q = from x in new int[0] join y in new int[0] on x equals y into field select x;"""], ["""var q = from x in new int[0] group x by x into field select 0;"""], ["""var q = from x in new int[0] select x into field select 0;"""], // Local function ["""int field() => 0;"""], ]; // Declaration forms that do NOT raise CS9273 in C# 14 — S8367 will still flag these. public static IEnumerable NonCS9273LocalDeclarationForms() => [ ["""if (GetHashCode() is int field) { }"""], ["""switch (GetHashCode()) { case int field: break; }"""], ["""if (this is object { } field) { }"""], ["""if ((0, 0) is (int field, _)) { }"""], ]; [TestMethod] [DynamicData(nameof(CS9273LocalDeclarationForms))] public void IdentifiersNamedFieldShouldBeEscaped_CS9273LocalDeclarationForms_CSharp9_13(string statement) { statement += " // Noncompliant"; builderCSharp9To13.AddSnippet(WrapInAccessor(statement)).Verify(); } [TestMethod] [DynamicData(nameof(CS9273LocalDeclarationForms))] public void IdentifiersNamedFieldShouldBeEscaped_CS9273LocalDeclarationForms_Latest(string statement) { statement += " // Error [CS9273]"; builderLatest.AddSnippet(WrapInAccessor(statement)).Verify(); } [TestMethod] [DynamicData(nameof(NonCS9273LocalDeclarationForms))] public void IdentifiersNamedFieldShouldBeEscaped_NonCS9273LocalDeclarationForms_CSharp9_13(string statement) { // Although C# 14 does not raise CS9273 for these forms (likely a Roslyn oversight), // S8367 flags them in C# 9-13 anyway since 'field' is equally ambiguous in these contexts. statement += " // Noncompliant"; builderCSharp9To13.AddSnippet(WrapInAccessor(statement)).Verify(); } [TestMethod] [DynamicData(nameof(NonCS9273LocalDeclarationForms))] public void IdentifiersNamedFieldShouldBeEscaped_NonCS9273LocalDeclarationForms_Latest(string statement) => builderLatest.AddSnippet(WrapInAccessor(statement)).VerifyNoIssues(); private static string WrapInAccessor(string statement) => $$""" using System; using System.Linq; class C { private int field; public int Value { get { {{statement}} return 0; } } } """; } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IfChainWithoutElseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class IfChainWithoutElseTest { [TestMethod] public void IfChainWithoutElse_CS() => new VerifierBuilder().AddPaths("IfChainWithoutElse.cs").Verify(); [TestMethod] public void IfChainWithoutElse_VB() => new VerifierBuilder().AddPaths("IfChainWithoutElse.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IfCollapsibleTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class IfCollapsibleTest { [TestMethod] public void IfCollapsible_CS() => new VerifierBuilder().AddPaths("IfCollapsible.cs").Verify(); [TestMethod] public void IfCollapsible_VB() => new VerifierBuilder().AddPaths("IfCollapsible.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ImplementIDisposableCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ImplementIDisposableCorrectlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ImplementIDisposableCorrectly() => builder.AddPaths("ImplementIDisposableCorrectly.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void ImplementIDisposableCorrectly_CS_Latest() => builder.AddPaths("ImplementIDisposableCorrectly.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ImplementIDisposableCorrectly_AbstractClass() => builder.AddPaths("ImplementIDisposableCorrectly.AbstractClass.cs").Verify(); [TestMethod] public void ImplementIDisposableCorrectly_PartialClassesInDifferentFiles() => builder.AddPaths("ImplementIDisposableCorrectlyPartial1.cs", "ImplementIDisposableCorrectlyPartial2.cs").VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ImplementISerializableCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ImplementISerializableCorrectlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ImplementISerializableCorrectly() => builder.AddPaths("ImplementISerializableCorrectly.cs").Verify(); [TestMethod] public void ImplementISerializableCorrectly_CS_Latest() => builder.AddPaths("ImplementISerializableCorrectly.Latest.cs", "ImplementISerializableCorrectly.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ImplementSerializationMethodsCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ImplementSerializationMethodsCorrectlyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ImplementSerializationMethodsCorrectly_CS() => builderCS.AddPaths("ImplementSerializationMethodsCorrectly.cs").Verify(); [TestMethod] public void ImplementSerializationMethodsCorrectly_CS_Latest() => builderCS.AddPaths("ImplementSerializationMethodsCorrectly.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ImplementSerializationMethodsCorrectly_CS_InvalidCode() => builderCS.AddSnippet(""" [System.Serializable] public class Foo { [System.OnDeserializing] // Error [CS0234, CS0234] Type or namespace name does not exist in the namespace 'System' // Error@+5 [CS0548] Property or indexer must have at least one accessor // Error@+4 [CS1001] Identifier expected // Error@+3 [CS1014] A get or set accessor expected // Error@+2 [CS0106] The modifier 'new' is not valid for this item // Error@+1 [CS1520] Method must have a return type public int { throw new NotImplementedException(); } // Error [CS1513] {{} expected}} } // Error [CS1022] Type or namespace definition, or end-of-file expected """) .Verify(); // This one is difficult to do in the source file due to concurrent file generation [TestMethod] public void ImplementSerializationMethodsCorrectly_VB() => builderVB.AddPaths("ImplementSerializationMethodsCorrectly.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IndentSingleLineFollowingConditionalTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class IndentSingleLineFollowingConditionalTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void IndentSingleLineFollowingConditional() => builder.AddPaths("IndentSingleLineFollowingConditional.cs").Verify(); [TestMethod] public void IndentSingleLineFollowingConditional_CS_Latest() => builder.AddPaths("IndentSingleLineFollowingConditional.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void IndentSingleLineFollowingConditional_RazorFile_CorrectMessage() => builder.AddSnippet(""" @code { public int Method(int j) { var total = 0; for(int i = 0; i < 10; i++) // Noncompliant {{Use curly braces or indentation to denote the code conditionally executed by this 'for'}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ total = total + i; // trivia not included in secondary location for single line statements... // ^^^^^^^^^^^^^^^^^^ Secondary if (j > 400) return 4; else if (j > 500) // Noncompliant {{Use curly braces or indentation to denote the code conditionally executed by this 'else if'}} // ^^^^^^^^^^^^^^^^^ return 5; // ^^^^^^^^^ Secondary return 1623; } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IndexOfCheckAgainstZeroTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class IndexOfCheckAgainstZeroTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void IndexOfCheckAgainstZero_CS() => builderCS.AddPaths("IndexOfCheckAgainstZero.cs").Verify(); [TestMethod] public void IndexOfCheckAgainstZero_CS_Latest() => builderCS.AddPaths("IndexOfCheckAgainstZero.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void IndexOfCheckAgainstZero_VB() => new VerifierBuilder().AddPaths("IndexOfCheckAgainstZero.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IndexedPropertyWithMultipleParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class IndexedPropertyWithMultipleParametersTest { [TestMethod] public void IndexedPropertyWithMultipleParameters() => new VerifierBuilder().AddPaths("IndexedPropertyWithMultipleParameters.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InfiniteRecursionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InfiniteRecursionTest { private readonly VerifierBuilder sonarCfg = new VerifierBuilder() .AddAnalyzer(() => new InfiniteRecursion(AnalyzerConfiguration.AlwaysEnabledWithSonarCfg)) .AddReferences(MetadataReferenceFacade.NetStandard21); private readonly VerifierBuilder roslynCfg = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.NetStandard21); [TestMethod] public void InfiniteRecursion_SonarCfg() => sonarCfg.AddPaths("InfiniteRecursion.SonarCfg.cs") .WithOptions(LanguageOptions.OnlyCSharp7) .Verify(); [TestMethod] public void InfiniteRecursion_RoslynCfg() => roslynCfg.AddPaths("InfiniteRecursion.RoslynCfg.cs") .Verify(); [TestMethod] public void InfiniteRecursion_RoslynCfg_Latest() => roslynCfg.AddPaths("InfiniteRecursion.RoslynCfg.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); // https://github.com/SonarSource/sonar-dotnet/issues/8977 [TestMethod] public void InfiniteRecursion_RoslynCfg_8977() { const int rows = 4_000; var code = new StringBuilder(); code.Append(""" using UInt32Value = System.UInt32; using StringValue = System.String; public class WorksheetPart { public Worksheet Worksheet { get; set; } } public class Worksheet { public MarkupCompatibilityAttributes MCAttributes { get; set; } public void AddNamespaceDeclaration(string alias, string xmlNamespace) { } public void Append(SheetData sheetData1) { } } public class SheetData { public void Append(Row r) { } } public class MarkupCompatibilityAttributes { public string Ignorable { get; set; } } public class Row { public UInt32Value RowIndex { get; set; } public ListValue Spans { get; set; } public double DyDescent { get; set; } public void Append(Cell c) { } } public class ListValue { public string InnerText { get; set; } } public class Cell { public string CellReference { get; set; } public UInt32Value StyleIndex { get; set; } public void Append(CellValue c) { } } public class CellValue { public string Text { get; set; } } class Program { public static void Main() { } void GenerateWorksheetPart1Content(WorksheetPart worksheetPart1) { Worksheet worksheet1 = new Worksheet() { MCAttributes = new MarkupCompatibilityAttributes() { Ignorable = "x14ac xr xr2 xr3" } }; worksheet1.AddNamespaceDeclaration("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships"); worksheet1.AddNamespaceDeclaration("mc", "http://schemas.openxmlformats.org/markup-compatibility/2006"); worksheet1.AddNamespaceDeclaration("x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"); worksheet1.AddNamespaceDeclaration("xr", "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"); worksheet1.AddNamespaceDeclaration("xr2", "http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"); worksheet1.AddNamespaceDeclaration("xr3", "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"); SheetData sheetData1 = new SheetData(); """); for (var i = 1; i <= rows; i++) { code.Append($$""" Row row{{i}} = new Row() { RowIndex = (UInt32Value)1U, Spans = new ListValue() { InnerText = "1:1" }, DyDescent = 0.25D }; Cell cell{{i}} = new Cell() { CellReference = "A{{i}}", StyleIndex = (UInt32Value)1U }; CellValue cellValue{{i}} = new CellValue(); cellValue{{i}}.Text = "{{i}}"; cell{{i}}.Append(cellValue{{i}}); row{{i}}.Append(cell{{i}}); """); } for (var i = 1; i <= rows; i++) { code.AppendLine($""" sheetData1.Append(row{i});"""); } code.Append("""" worksheet1.Append(sheetData1); worksheetPart1.Worksheet = worksheet1; } } """"); roslynCfg.AddSnippet(code.ToString()) .WithOptions(LanguageOptions.FromCSharp8) .VerifyNoIssues(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InheritedCollidingInterfaceMembersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InheritedCollidingInterfaceMembersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void InheritedCollidingInterfaceMembers() => builder.AddPaths("InheritedCollidingInterfaceMembers.cs").Verify(); [TestMethod] public void InheritedCollidingInterfaceMembers_DifferentFileSizes() => builder.AddPaths("InheritedCollidingInterfaceMembers.DifferentFile.cs", "InheritedCollidingInterfaceMembers.AnotherFile.cs").Verify(); [TestMethod] public void InheritedCollidingInterfaceMembers_CS_Latest() => builder.AddPaths("InheritedCollidingInterfaceMembers.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InitializeStaticFieldsInlineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InitializeStaticFieldsInlineTest { [TestMethod] public void InitializeStaticFieldsInline() => new VerifierBuilder().AddPaths("InitializeStaticFieldsInline.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InsecureContentSecurityPolicyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InsecureContentSecurityPolicyTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences([ AspNetCoreMetadataReference.MicrosoftAspNetCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftExtensionsPrimitives]); [TestMethod] public void InsecureContentSecurityPolicy_CS() => builder.AddPaths("InsecureContentSecurityPolicy.cs").Verify(); [TestMethod] public void InsecureContentSecurityPolicy_CS_Latest() => builder.AddPaths("InsecureContentSecurityPolicy.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InsecureEncryptionAlgorithmTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InsecureEncryptionAlgorithmTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddReferences(AdditionalReferences()); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddReferences(AdditionalReferences()); [TestMethod] public void InsecureEncryptionAlgorithm_MainProject_CS() => builderCS.AddPaths("InsecureEncryptionAlgorithm.cs").Verify(); [TestMethod] public void InsecureEncryptionAlgorithm_DoesNotRaiseIssuesForTestProject_CS() => builderCS.AddPaths("InsecureEncryptionAlgorithm.cs").AddTestReference().VerifyNoIssuesIgnoreErrors(); [TestMethod] public void InsecureEncryptionAlgorithm_CS_Latest() => builderCS.AddPaths("InsecureEncryptionAlgorithm.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void InsecureEncryptionAlgorithm_VB() => builderVB.AddPaths("InsecureEncryptionAlgorithm.vb").Verify(); [TestMethod] public void InsecureEncryptionAlgorithm_VB_Latest() => builderVB.AddPaths("InsecureEncryptionAlgorithm.Latest.vb").WithOptions(LanguageOptions.VisualBasicLatest).VerifyNoIssues(); private static IEnumerable AdditionalReferences() => MetadataReferenceFacade.SystemSecurityCryptography.Concat(NuGetMetadataReference.BouncyCastle()); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InsecureTemporaryFilesCreationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InsecureTemporaryFilesCreationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void InsecureTemporaryFilesCreation_CS() => builderCS.AddPaths("InsecureTemporaryFilesCreation.cs").Verify(); [TestMethod] public void InsecureTemporaryFilesCreation_CS_Latest() => builderCS.AddPaths("InsecureTemporaryFilesCreation.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void InsecureTemporaryFilesCreation_VB() => new VerifierBuilder().AddPaths("InsecureTemporaryFilesCreation.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InsteadOfAnyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InsteadOfAnyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void InsteadOfAny_CS() => builderCS.AddPaths("InsteadOfAny.cs").Verify(); [TestMethod] public void InsteadOfAny_CS_Latest() => builderCS.AddPaths("InsteadOfAny.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] public void InsteadOfAny_CS_EntityFramework() => builderCS.AddPaths("InsteadOfAny.EntityFramework.Core.cs") .WithOptions(LanguageOptions.FromCSharp8) .AddReferences(ReferencesEntityFrameworkNetCore()) .Verify(); #if NETFRAMEWORK [TestMethod] public void InsteadOfAny_CS_EntityFramework_NetFx() => builderCS.AddPaths("InsteadOfAny.EntityFramework.Framework.cs") .AddReferences(NuGetMetadataReference.EntityFramework()) .VerifyNoIssues(); #endif [TestMethod] public void ExistsInsteadOfAny_VB() => builderVB.AddPaths("InsteadOfAny.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void InsteadOfAny_VB_EntityFramework() => builderVB.AddPaths("InsteadOfAny.EntityFramework.Core.vb") .AddReferences(ReferencesEntityFrameworkNetCore()) .Verify(); private static IEnumerable ReferencesEntityFrameworkNetCore() => Enumerable.Empty() .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational("2.2.6")) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCore("2.2.6")) .Concat(NuGetMetadataReference.SystemComponentModelTypeConverter()); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InterfaceMethodsShouldBeCallableByChildTypesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class InterfaceMethodsShouldBeCallableByChildTypesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void InterfaceMethodsShouldBeCallableByChildTypes() => builder.AddPaths("InterfaceMethodsShouldBeCallableByChildTypes.cs").Verify(); [TestMethod] public void InterfaceMethodsShouldBeCallableByChildTypes_CSharp9() => builder.AddPaths("InterfaceMethodsShouldBeCallableByChildTypes.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InterfaceNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class InterfaceNameTest { [TestMethod] public void InterfaceName() => new VerifierBuilder().AddPaths("InterfaceName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InterfacesShouldNotBeEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class InterfacesShouldNotBeEmptyTest { [TestMethod] public void InterfacesShouldNotBeEmpty() => new VerifierBuilder().AddPaths("InterfacesShouldNotBeEmpty.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InvalidCastToInterfaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InvalidCastToInterfaceTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); // Syntax-based part of the rule, there also exists Sonar SE part private readonly VerifierBuilder builderVB = new VerifierBuilder() // Syntax-based part only .WithWarningsAsErrors("BC42322"); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void InvalidCastToInterface_CS(ProjectType projectType) => builderCS.AddPaths("InvalidCastToInterface.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType).Union(MetadataReferenceFacade.NetStandard21)) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void InvalidCastToInterface_VB() => builderVB.AddPaths("InvalidCastToInterface.vb").Verify(); [TestMethod] public void InvalidCastToInterface_CS_Latest() => builderCS.AddPaths("InvalidCastToInterface.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/InvocationResolvesToOverrideWithParamsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class InvocationResolvesToOverrideWithParamsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void InvocationResolvesToOverrideWithParams() { var anotherAssembly = TestCompiler.CompileCS(""" public class FromAnotherAssembly { protected int ProtectedOverload(object a, string b) => 42; public int ProtectedOverload(string a, params string[] bs) => 42; private protected int PrivateProtectedOverload(object a, string b) => 42; public int PrivateProtectedOverload(string a, params string[] bs) => 42; protected internal int ProtectedInternalOverload(object a, string b) => 42; public int ProtectedInternalOverload(string a, params string[] bs) => 42; internal int InternalOverload(object a, string b) => 42; public int InternalOverload(string a, params string[] bs) => 42; } """).Model.Compilation.ToMetadataReference(); builder.AddPaths("InvocationResolvesToOverrideWithParams.cs") .AddReferences([anotherAssembly]) .Verify(); } [TestMethod] public void InvocationResolvesToOverrideWithParams_TopLevelStatements() => builder.AddPaths("InvocationResolvesToOverrideWithParams.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void InvocationResolvesToOverrideWithParams_CS_Latest() => builder.AddPaths("InvocationResolvesToOverrideWithParams.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/IssueSuppressionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class IssueSuppressionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void IssueSuppression() => builder.AddPaths("IssueSuppression.cs", "IssueSuppression2.cs").WithAutogenerateConcurrentFiles(false).Verify(); [TestMethod] public void IssueSuppression_CSharp9() => builder.AddPaths("IssueSuppression.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void IssueSuppression_CSharp10() => builder.AddPaths("IssueSuppression.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/JSInvokableMethodsShouldBePublicTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class JSInvokableMethodsShouldBePublicTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(NuGetMetadataReference.MicrosoftJSInterop("7.0.14")); public TestContext TestContext { get; set; } [TestMethod] public void JSInvokableMethodsShouldBePublic_CS() => builder.AddPaths("JSInvokableMethodsShouldBePublic.cs").Verify(); [TestMethod] public void JSInvokableMethodsShouldBePublic_Razor() => builder .AddPaths("JSInvokableMethodsShouldBePublic.razor", "JSInvokableMethodsShouldBePublic.razor.cs") .WithOptions(LanguageOptions.FromCSharp9) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void JSInvokableMethodsShouldBePublic_CS_Latest() => builder.AddPaths("JSInvokableMethodsShouldBePublic.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/JwtSignedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class JwtSignedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void JwtSigned_CS() => builderCS.AddPaths("JwtSigned.cs") .AddReferences(NuGetMetadataReference.JWT("6.1.0")) .Verify(); [TestMethod] public void JwtSigned_JWTDecoderExtensions_CS() => builderCS.AddPaths("JwtSigned.Extensions.cs") .AddReferences(NuGetMetadataReference.JWT("7.3.1")) .Verify(); [TestMethod] public void JwtSigned_CS_Latest() => builderCS.AddPaths("JwtSigned.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.JWT("6.1.0")) .Verify(); [TestMethod] public void JwtSigned_VB() => builderVB.AddPaths("JwtSigned.vb") .AddReferences(NuGetMetadataReference.JWT("6.1.0")) .Verify(); [TestMethod] public void JwtSigned_JWTDecoderExtensions_VB() => builderVB.AddPaths("JwtSigned.Extensions.vb") .AddReferences(NuGetMetadataReference.JWT("7.3.1")) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LdapConnectionShouldBeSecureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LdapConnectionShouldBeSecureTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemDirectoryServices); [TestMethod] public void LdapConnectionsShouldBeSecure() => builder.AddPaths("LdapConnectionShouldBeSecure.cs").Verify(); [TestMethod] public void LdapConnectionsShouldBeSecure_CS_Latest() => builder.AddPaths("LdapConnectionShouldBeSecure.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LineContinuationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class LineContinuationTest { [TestMethod] public void LineContinuation() => new VerifierBuilder().AddPaths("LineContinuation.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LineLengthTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class LineLengthTest { [TestMethod] public void LineLength_CS() => new VerifierBuilder().AddAnalyzer(() => new CS.LineLength { Maximum = 127 }) .AddPaths("LineLength.cs") .Verify(); [TestMethod] public void LineLength_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.LineLength { Maximum = 127 }) .AddPaths("LineLength.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LinkedListPropertiesInsteadOfMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LinkedListPropertiesInsteadOfMethodsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void LinkedListPropertiesInsteadOfMethods_CS() => builderCS.AddPaths("LinkedListPropertiesInsteadOfMethods.cs").Verify(); [TestMethod] public void LinkedListPropertiesInsteadOfMethods_CS_CodeFix() => builderCS.AddPaths("LinkedListPropertiesInsteadOfMethods.cs") .WithCodeFix() .WithCodeFixedPaths("LinkedListPropertiesInsteadOfMethods.Fixed.cs") .VerifyCodeFix(); [TestMethod] [DataRow("First")] [DataRow("Last")] public void LinkedListPropertiesInsteadOfMethods_TopLevelStatements(string name) => builderCS.AddSnippet($$$""" using System.Collections.Generic; using System.Linq; var data = new LinkedList(); data.{{{name}}}(); // Noncompliant {{'{{{name}}}' property of 'LinkedList' should be used instead of the '{{{name}}}()' extension method.}} // {{{new string('^', name.Length)}}} data.{{{name}}}(x => x > 0); // Compliant _ = data.{{{name}}}.Value; // Compliant data.Count(); // Compliant data.Append(1).{{{name}}}().ToString(); // Compliant data?.{{{name}}}().ToString(); // Noncompliant """).WithTopLevelStatements().Verify(); [TestMethod] public void LinkedListPropertiesInsteadOfMethods_VB() => new VerifierBuilder().AddPaths("LinkedListPropertiesInsteadOfMethods.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LiteralSuffixUpperCaseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LiteralSuffixUpperCaseTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithWarningsAsErrors("CS0078"); [TestMethod] public void LiteralSuffixUpperCase() => builder.AddPaths("LiteralSuffixUpperCase.cs").Verify(); [TestMethod] public void LiteralSuffixUpperCase_CodeFix() => builder.AddPaths("LiteralSuffixUpperCase.cs") .WithCodeFix() .WithCodeFixedPaths("LiteralSuffixUpperCase.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void LiteralSuffixUpperCase_CS_Latest() => builder.AddPaths("LiteralSuffixUpperCase.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LiteralsShouldNotBePassedAsLocalizedParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class LiteralsShouldNotBePassedAsLocalizedParametersTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives); [TestMethod] public void LiteralsShouldNotBePassedAsLocalizedParameters() => builder.AddPaths("LiteralsShouldNotBePassedAsLocalizedParameters.cs").Verify(); [TestMethod] public void LiteralsShouldNotBePassedAsLocalizedParameters_CSharp9() => builder.AddPaths("LiteralsShouldNotBePassedAsLocalizedParameters.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void LiteralsShouldNotBePassedAsLocalizedParameters_CSharp10() => builder.AddPaths("LiteralsShouldNotBePassedAsLocalizedParameters.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void LiteralsShouldNotBePassedAsLocalizedParameters_CSharp11() => builder.AddPaths("LiteralsShouldNotBePassedAsLocalizedParameters.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LocalVariableNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class LocalVariableNameTest { [TestMethod] public void LocalVariableName() => new VerifierBuilder().AddPaths("LocalVariableName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LockedFieldShouldBeReadonlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LockedFieldShouldBeReadonlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void LockedFieldShouldBeReadonly_CS() => builder.AddPaths("LockedFieldShouldBeReadonly.cs").Verify(); [TestMethod] public void LockedFieldShouldBeReadonly_Latest() => builder .AddPaths("LockedFieldShouldBeReadonly.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoggerFieldsShouldBePrivateStaticReadonlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoggerFieldsShouldBePrivateStaticReadonlyTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_MicrosoftExtensionsLogging_CS() => Builder .AddPaths("LoggerFieldsShouldBePrivateStaticReadonly.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_CSharp8() => Builder .AddSnippet(""" using Microsoft.Extensions.Logging; public class Program { private protected static readonly ILogger logger; // Noncompliant // ^^^^^^ } public interface Service { static ILogger StaticLogger; protected internal static ILogger Logger; private static ILogger Test; } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_Serilog_CS() => Builder .AddSnippet(""" using Serilog; public class Program { ILogger log1, log2; // ^^^^ {{Make the logger 'log1' private static readonly.}} // ^^^^ @-1 {{Make the logger 'log2' private static readonly.}} } """) .AddReferences(NuGetMetadataReference.Serilog()) .Verify(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_NLog_CS() => Builder .AddSnippet(""" using NLog; public class Program { ILogger log1, log2; // ^^^^ {{Make the logger 'log1' private static readonly.}} // ^^^^ @-1 {{Make the logger 'log2' private static readonly.}} Logger log3; // Noncompliant ILoggerBase log4; // Noncompliant MyLogger log5; // Noncompliant } public class MyLogger : Logger { } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_log4net_CS() => Builder .AddSnippet(""" using log4net; using log4net.Core; using log4net.Repository.Hierarchy; public class Program { ILog log1,log2; // ^^^^ {{Make the logger 'log1' private static readonly.}} // ^^^^ @-1 {{Make the logger 'log2' private static readonly.}} ILogger log3; // Noncompliant Logger log4; // Noncompliant MyLogger log5; // Noncompliant } public class MyLogger : Logger { public MyLogger(string name) : base(name) { } } """) .AddReferences(NuGetMetadataReference.Log4Net(TestConstants.NuGetLatestVersion, "netstandard2.0")) .Verify(); [TestMethod] public void LoggerFieldsShouldBePrivateStaticReadonly_CastleCore_CS() => Builder .AddSnippet(""" using Castle.Core.Logging; public class Program { ILogger log; // Noncompliant {{Make the logger 'log' private static readonly.}} // ^^^ } """) .AddReferences(NuGetMetadataReference.CastleCore()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoggerMembersNamesShouldComplyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoggerMembersNamesShouldComplyTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); [TestMethod] [DataRow("log")] [DataRow("_log")] [DataRow("Log")] [DataRow("_Log")] [DataRow("logger")] [DataRow("_logger")] [DataRow("Logger")] [DataRow("_Logger")] [DataRow("instance")] [DataRow("Instance")] public void LoggerMembersNamesShouldComply_Compliant_CS(string name) => Builder.AddSnippet($$""" using System; using Microsoft.Extensions.Logging; public class One { ILogger {{name}}; // Compliant } public class Two { ILogger {{name}} { get; set; } // Compliant } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .VerifyNoIssues(); [TestMethod] public void LoggerMembersNamesShouldComply_MicrosoftExtensionsLogging_CS() => Builder.AddSnippet(""" using System; using Microsoft.Extensions.Logging; public class Program { string mylogger; // Compliant ILogger _log2; // Noncompliant {{Rename this field '_log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} ILogger mylog { get; set; } // Noncompliant {{Rename this property 'mylog' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ ILogger myLogger, _Log2, _logger; // ^^^^^^^^ {{Rename this field 'myLogger' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ @-1 {{Rename this field '_Log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] public void LoggerMembersNamesShouldComply_Serilog_CS() => Builder.AddSnippet(""" using Serilog; public class Program { string mylogger; // Compliant ILogger _Logger; // Compliant ILogger log; // Compliant ILogger _log2; // Noncompliant {{Rename this field '_log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} ILogger mylog { get; set; } // Noncompliant {{Rename this property 'mylog' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ ILogger myLogger, _Log2, _logger; // ^^^^^^^^ {{Rename this field 'myLogger' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ @-1 {{Rename this field '_Log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} } """) .AddReferences(NuGetMetadataReference.Serilog()) .Verify(); [TestMethod] public void LoggerMembersNamesShouldComply_NLog_CS() => Builder.AddSnippet(""" using NLog; public class Program { string mylogger; // Compliant ILogger _Logger; // Compliant ILoggerBase log; // Compliant MyLogger _log2; // Noncompliant {{Rename this field '_log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} ILoggerBase my_logger; // Noncompliant {{Rename this field 'my_logger' to match the regular expression '^_?[Ll]og(ger)?$'.}} Logger mylog { get; set; } // Noncompliant {{Rename this property 'mylog' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ ILogger myLogger, _Log2, _logger; // ^^^^^^^^ {{Rename this field 'myLogger' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ @-1 {{Rename this field '_Log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} } public class MyLogger : Logger { } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] public void LoggerMembersNamesShouldComply_log4net_CS() => Builder.AddSnippet(""" using log4net; using log4net.Core; using log4net.Repository.Hierarchy; public class Program { string mylogger; // Compliant ILog log; // Compliant ILogger _Logger; // Compliant Logger _log; // Compliant ILogger _log2; // Noncompliant {{Rename this field '_log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} ILog my_logger; // Noncompliant {{Rename this field 'my_logger' to match the regular expression '^_?[Ll]og(ger)?$'.}} Logger mylog { get; set; } // Noncompliant {{Rename this property 'mylog' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ MyLogger myLogger, _Log2, _logger; // ^^^^^^^^ {{Rename this field 'myLogger' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ @-1 {{Rename this field '_Log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} } public class MyLogger : Logger { public MyLogger(string name) : base(name) { } } """) .AddReferences(NuGetMetadataReference.Log4Net(TestConstants.NuGetLatestVersion, "netstandard2.0")) .Verify(); [TestMethod] public void LoggerMembersNamesShouldComply_CastleCore_CS() => Builder.AddSnippet(""" using Castle.Core.Logging; public class Program { string mylogger; // Compliant ILogger log; // Compliant ILogger _log2; // Noncompliant {{Rename this field '_log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} ILogger mylog { get; set; } // Noncompliant {{Rename this property 'mylog' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ ILogger myLogger, _Log2, _logger; // ^^^^^^^^ {{Rename this field 'myLogger' to match the regular expression '^_?[Ll]og(ger)?$'.}} // ^^^^^ @-1 {{Rename this field '_Log2' to match the regular expression '^_?[Ll]og(ger)?$'.}} } """) .AddReferences(NuGetMetadataReference.CastleCore()) .Verify(); [TestMethod] public void LoggerMembersNamesShouldComply_Parameterized_CS() => new VerifierBuilder() .AddAnalyzer(() => new LoggerMembersNamesShouldComply { Format = "^chocolate$" }) .AddSnippet(""" using System; using Microsoft.Extensions.Logging; public class Program { ILogger chocolate; // Compliant ILogger running; // Noncompliant {{Rename this field 'running' to match the regular expression '^chocolate$'.}} } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoggersShouldBeNamedForEnclosingTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoggersShouldBeNamedForEnclosingTypeTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); [TestMethod] public void LoggersShouldBeNamedForEnclosingType_CS() => Builder .AddPaths("LoggersShouldBeNamedForEnclosingType.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void LoggersShouldBeNamedForEnclosingType_TopLevelStatements_CS() => Builder .AddSnippet(""" using Microsoft.Extensions.Logging; ILoggerFactory factory = null; factory.CreateLogger(); // Compliant factory.CreateLogger(typeof(int).Name); // Compliant factory.CreateLogger(nameof(RandomType)); // Compliant class RandomType { } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public void LoggersShouldBeNamedForEnclosingType_NLog_CS() => Builder .AddPaths("LoggersShouldBeNamedForEnclosingType.NLog.cs") .AddReferences(NuGetMetadataReference.NLog("5.5.0")) .Verify(); [TestMethod] public void LoggersShouldBeNamedForEnclosingType_Log4net_CS() => Builder .AddSnippet(""" using System.Reflection; using log4net; public class EnclosingType { void Method(Assembly assembly) { LogManager.GetLogger(nameof(EnclosingType)); // Compliant LogManager.GetLogger(typeof(EnclosingType).Name); // Compliant LogManager.GetLogger(nameof(AnotherType)); // Noncompliant {{Update this logger to use its enclosing type.}} // ^^^^^^^^^^^ LogManager.GetLogger(typeof(AnotherType).Name); // Noncompliant // ^^^^^^^^^^^ // These methods are not tracked LogManager.GetLogger(nameof(AnotherType), nameof(AnotherType)); // Compliant LogManager.GetLogger(nameof(AnotherType), typeof(AnotherType)); // Compliant LogManager.GetLogger(assembly, nameof(AnotherType)); // Compliant LogManager.GetLogger(assembly, typeof(AnotherType)); // Compliant } } class AnotherType : EnclosingType { } """) .AddReferences(NuGetMetadataReference.Log4Net("2.0.8", "net45-full")) .Verify(); #if NET [TestMethod] public void LoggersShouldBeNamedForEnclosingType_NLogLatest_CS() => Builder .AddPaths("LoggersShouldBeNamedForEnclosingType.NLog.cs") .AddReferences(NuGetMetadataReference.NLog()) .Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoggingArgumentsShouldBePassedCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoggingArgumentsShouldBePassedCorrectlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void LoggingArgumentsShouldBePassedCorrectly_CS() => builder.AddPaths("LoggingArgumentsShouldBePassedCorrectly.cs").AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()).Verify(); [TestMethod] [DataRow("LogCritical")] [DataRow("LogDebug")] [DataRow("LogError")] [DataRow("LogInformation")] [DataRow("LogTrace")] [DataRow("LogWarning")] public void LoggingArgumentsShouldBePassedCorrectly_MicrosoftExtensionsLogging_NonCompliant_CS(string methodName) => builder.AddSnippet($$""" using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; public class Program { public void Method(ILogger logger, Exception e) { logger.{{methodName}}("Expected exception."); logger.{{methodName}}(e, "Expected exception."); logger.{{methodName}}(null, "Expected exception.", e); // FN logger.{{methodName}}(new EventId(), "Expected exception."); logger.{{methodName}}(new EventId(), e, "Expected exception."); LoggerExtensions.{{methodName}}(logger, "Expected exception."); LoggerExtensions.{{methodName}}(logger, new EventId(), e, "Expected exception."); logger.{{methodName}}(new EventId(), e, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (log level) // Secondary @-1 logger.{{methodName}}(new EventId(), "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception, log level) // Secondary @-1 // Secondary @-2 logger.{{methodName}}(e, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (event id, log level) // Secondary @-1 // Secondary @-2 logger.{{methodName}}("Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception, event id, log level) // Secondary @-1 // Secondary @-2 // Secondary @-3 LoggerExtensions.{{methodName}}(logger, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception, event id, log level) // Secondary @-1 // Secondary @-2 // Secondary @-3 LoggerExtensions.{{methodName}}(logger, new EventId(), e, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (log level) // Secondary @-1 } } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] public void LoggingArgumentsShouldBePassedCorrectly_MicrosoftExtensionsLogging_Log_CS() => builder.AddSnippet(""" using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; public class Program { public void Method(ILogger logger, Exception e) { logger.Log(LogLevel.Warning, "Expected exception."); logger.Log(LogLevel.Warning, e, "Expected exception."); logger.Log(LogLevel.Warning, new EventId(), "Expected exception."); logger.Log(LogLevel.Warning, new EventId(), e, "Expected exception."); LoggerExtensions.Log(logger, LogLevel.Warning, "Expected exception."); LoggerExtensions.Log(logger, LogLevel.Warning, new EventId(), e, "Expected exception."); logger.Log(LogLevel.Warning, new EventId(), e, "Expected exception.", e, new EventId(), LogLevel.Critical); logger.Log(LogLevel.Warning, new EventId(), "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception) // Secondary @-1 logger.Log(LogLevel.Warning, e, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (event id) // Secondary @-1 logger.Log(LogLevel.Warning, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception, event id) // Secondary @-1 // Secondary @-2 LoggerExtensions.Log(logger, LogLevel.Warning, "Expected exception.", e, new EventId(), LogLevel.Critical); // Noncompliant (exception, event id) // Secondary @-1 // Secondary @-2 LoggerExtensions.Log(logger, LogLevel.Warning, new EventId(), e, "Expected exception.", e, new EventId(), LogLevel.Critical); } } """) .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .Verify(); [TestMethod] [DataRow("DebugFormat")] [DataRow("ErrorFormat")] [DataRow("FatalFormat")] [DataRow("InfoFormat")] [DataRow("TraceFormat")] [DataRow("WarnFormat")] public void LoggingArgumentsShouldBePassedCorrectly_CastleCore_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using Castle.Core.Logging; public class Program { public void Method(ILogger logger, string message, Exception e) { logger.{{methodName}}(message); // Compliant logger.{{methodName}}(message, e); // Noncompliant // Secondary @-1 logger.{{methodName}}(CultureInfo.CurrentCulture, message); // Compliant logger.{{methodName}}(CultureInfo.CurrentCulture, message, e); // Noncompliant // Secondary @-1 logger.{{methodName}}(e, message); // Compliant logger.{{methodName}}(e, message, e); // Compliant logger.{{methodName}}(e, CultureInfo.CurrentCulture, message); // Compliant logger.{{methodName}}(e, CultureInfo.CurrentCulture, message, e); // Compliant } } """) .AddReferences(NuGetMetadataReference.CastleCore()) .Verify(); [TestMethod] [DataRow("Log", "LogLevel.Debug,", "")] [DataRow("Debug")] [DataRow("ConditionalDebug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("ConditionalTrace")] [DataRow("Warn")] public void LoggingArgumentsShouldBePassedCorrectly_NLog_CS(string methodName, string logLevel = "", string logLevelExpectation = "// Secondary @-2") => builder.AddSnippet($$""" using System; using System.Globalization; using NLog; public class Program { public void Method(Logger logger, Exception e) { logger.{{methodName}}({{logLevel}} e); // Compliant logger.{{methodName}}({{logLevel}} CultureInfo.CurrentCulture, e); // Compliant logger.{{methodName}}({{logLevel}} e, "Message!"); // Compliant logger.{{methodName}}({{logLevel}} e, "Message!", e); // Compliant logger.{{methodName}}({{logLevel}} e, CultureInfo.CurrentCulture, "Message!", e); // Compliant logger.{{methodName}}({{logLevel}} CultureInfo.CurrentCulture, "Message!", 1, 2, 3, e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} "Message!"); // Compliant logger.{{methodName}}({{logLevel}} "Message!", 1, 2, 3, e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} "Message!", e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} CultureInfo.CurrentCulture, "Message!", e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} "Message!", 1); // Compliant logger.{{methodName}}({{logLevel}} "Message!", e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}}CultureInfo.CurrentCulture, "Message!", 1, e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} "Message!", 1, e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} CultureInfo.CurrentCulture, "Message!", 1, 2, e); // Noncompliant // Secondary @-1 logger.{{methodName}}({{logLevel}} "Message!", 1, LogLevel.Debug, e); // Noncompliant // Secondary @-1 {{logLevelExpectation}} ILoggerExtensions.{{methodName}}(logger, {{logLevel}} e, null); // Compliant } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] [DataRow("ConditionalDebug")] [DataRow("ConditionalTrace")] public void LoggingArgumentsShouldBePassedCorrectly_NLog_ConditionalExtensions_CS(string methodName) => builder.AddSnippet($$""" using System; using System.Globalization; using NLog; public class Program { public void Method(Logger logger, Exception e) { ILoggerExtensions.{{methodName}}(logger, e); // Compliant ILoggerExtensions.{{methodName}}(logger, CultureInfo.CurrentCulture, e); // Compliant ILoggerExtensions.{{methodName}}(logger, e, "Message!"); // Compliant ILoggerExtensions.{{methodName}}(logger, e, "Message!", e); // Compliant ILoggerExtensions.{{methodName}}(logger, e, CultureInfo.CurrentCulture, "Message!", e); // Compliant ILoggerExtensions.{{methodName}}(logger, CultureInfo.CurrentCulture, "Message!", 1, 2, 3, e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, "Message!"); // Compliant ILoggerExtensions.{{methodName}}(logger, "Message!", 1, 2, 3, e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, "Message!", e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, CultureInfo.CurrentCulture, "Message!", e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, "Message!", 1); // Compliant ILoggerExtensions.{{methodName}}(logger, "Message!", e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, CultureInfo.CurrentCulture, "Message!", 1, e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, "Message!", 1, e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, CultureInfo.CurrentCulture, "Message!", 1, 2, e); // Noncompliant // Secondary @-1 ILoggerExtensions.{{methodName}}(logger, "Message!", 1, 2, e); // Noncompliant // Secondary @-1 } } """) .AddReferences(NuGetMetadataReference.NLog()) .Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Information")] [DataRow("Verbose")] [DataRow("Warning")] public void LoggingArgumentsShouldBePassedCorrectly_Serilog_CS(string methodName) => builder.AddSnippet($$""" using System; using Serilog; public class Program { public void Method(ILogger logger, string message, Exception e) { Log.{{methodName}}("Message!"); Log.{{methodName}}("Message!", e); // Noncompliant // Secondary @-1 Log.{{methodName}}("Message!", 1); Log.{{methodName}}("Message!", e); // Noncompliant // Secondary @-1 Log.{{methodName}}("Message!", 1, e); // Noncompliant // Secondary @-1 Log.{{methodName}}("Message!", 1, e, true); // Noncompliant // Secondary @-1 Log.{{methodName}}("Message!", 1, 2, 3, e, true); // Noncompliant // Secondary @-1 Log.{{methodName}}(e, "Message"); Log.{{methodName}}(e, "Message", e); Log.{{methodName}}(e, "Message", 1, e); Log.{{methodName}}(e, "Message", 1, e, true); Log.{{methodName}}(e, "Message!", 1, 2, 3, e, true); } } """) .AddReferences(NuGetMetadataReference.Serilog()) .Verify(); [TestMethod] public void LoggingArgumentsShouldBePassedCorrectly_Serilog_Write_CS() => builder.AddSnippet(""" using System; using Serilog; using Serilog.Events; public class Program { public void Method(LogEvent logEvent, LogEventLevel level, Exception exception) { Log.Write(logEvent); Log.Write(level, "Message"); Log.Write(level, "Message", level); Log.Write(level, "Message", 1); Log.Write(level, "Message", level, exception); // Noncompliant // Secondary @-1 Log.Write(level, "Message", 1, exception, 2); // Noncompliant // Secondary @-1 Log.Write(level, "Message", level, exception, 1, 2); // Noncompliant // Secondary @-1 Log.Write(level, exception, "Message"); Log.Write(level, exception, "Message", level); Log.Write(level, exception, "Message", level, exception); Log.Write(level, exception, "Message", level, exception, 1); Log.Write(level, exception, "Message", level, exception, 1, 2); } } """) .AddReferences(NuGetMetadataReference.Serilog()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoggingTemplatePlaceHoldersShouldBeInOrderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.CSharp.Rules.MessageTemplates; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoggingTemplatePlaceHoldersShouldBeInOrderTest { private static readonly VerifierBuilder Builder = new VerifierBuilder().WithOnlyDiagnostics(LoggingTemplatePlaceHoldersShouldBeInOrder.S6673); [TestMethod] public void LoggingTemplatePlaceHoldersShouldBeInOrder_CS() => Builder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddPaths("LoggingTemplatePlaceHoldersShouldBeInOrder.cs") .Verify(); [TestMethod] [DataRow("LogCritical")] [DataRow("LogDebug")] [DataRow("LogError")] [DataRow("LogInformation")] [DataRow("LogTrace")] [DataRow("LogWarning")] public void LoggingTemplatePlaceHoldersShouldBeInOrder_MicrosoftExtensionsLogging_CS(string methodName) => Builder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddSnippet($$""" using System; using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, int first, int second) { logger.{{methodName}}("{First} {Second}", first, second); // Compliant logger.{{methodName}}("{First} {Second}", second, first); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Information")] [DataRow("Fatal")] [DataRow("Warning")] [DataRow("Verbose")] public void LoggingTemplatePlaceHoldersShouldBeInOrder_Serilog_CS(string methodName) => Builder.AddReferences(NuGetMetadataReference.Serilog(TestConstants.NuGetLatestVersion)) .AddSnippet($$""" using Serilog; using Serilog.Events; public class Program { public void Method(ILogger logger, int first, int second) { logger.{{methodName}}("{First} {Second}", first, second); // Compliant logger.{{methodName}}("{First} {Second}", second, first); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("ConditionalDebug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("ConditionalTrace")] [DataRow("Warn")] public void LoggingTemplatePlaceHoldersShouldBeInOrder_NLog_CS(string methodName) => Builder.AddReferences(NuGetMetadataReference.NLog(TestConstants.NuGetLatestVersion)) .AddSnippet($$""" using NLog; public class Program { public void Method(ILogger iLogger, Logger logger, MyLogger myLogger, int first, int second) { iLogger.{{methodName}}("{First} {Second}", first, second); // Compliant iLogger.{{methodName}}("{First} {Second}", second, first); // Noncompliant // Secondary @-1 logger.{{methodName}}("{First} {Second}", first, second); // Compliant logger.{{methodName}}("{First} {Second}", second, first); // Noncompliant // Secondary @-1 myLogger.{{methodName}}("{First} {Second}", first, second); // Compliant myLogger.{{methodName}}("{First} {Second}", second, first); // Noncompliant // Secondary @-1 } } public class MyLogger : Logger { } """).Verify(); [TestMethod] public void LoggingTemplatePlaceHoldersShouldBeInOrder_FakeLoggerWithSameName() => Builder.AddSnippet(""" public class Program { public void Method(ILogger logger, int first, int second) { logger.Info("{First} {Second}", first, second); // Compliant logger.Info("{First} {Second}", second, first); // Compliant - the method is not from any of the known logging frameworks } } public interface ILogger { void Info(string message, params object[] args); } """).VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LoopsAndLinqTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LoopsAndLinqTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void LoopsAndLinq_CS() => builder.AddPaths("LoopsAndLinq.cs") .AddReferences(MetadataReferenceFacade.SystemData) .Verify(); [TestMethod] public void LoopsAndLinq_CS_Latest() => builder.AddPaths("LoopsAndLinq.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LooseFilePermissionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LooseFilePermissionsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.LooseFilePermissions()); private readonly VerifierBuilder builderVB = new VerifierBuilder().AddAnalyzer(() => new VB.LooseFilePermissions()); [TestMethod] public void LooseFilePermissions_Windows_CS() => builderCS.AddPaths("LooseFilePermissions.Windows.cs").Verify(); [TestMethod] public void LooseFilePermissions_Windows_VB() => builderVB.AddPaths("LooseFilePermissions.Windows.vb").Verify(); [TestMethod] public void LooseFilePermissions_Windows_CSharp9() => builderCS.AddPaths("LooseFilePermissions.Windows.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void LooseFilePermissions_Windows_CSharp10() => builderCS.AddPaths("LooseFilePermissions.Windows.CSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void LooseFilePermissions_Windows_CSharp11() => builderCS.AddPaths("LooseFilePermissions.Windows.CSharp11.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void LooseFilePermissions_Windows_CSharp12() => builderCS.AddPaths("LooseFilePermissions.Windows.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12) .Verify(); [TestMethod] public void LooseFilePermissions_Unix_CS() => builderCS.AddPaths("LooseFilePermissions.Unix.cs") .AddReferences(NuGetMetadataReference.MonoPosixNetStandard()) .Verify(); [TestMethod] public void LooseFilePermissions_Unix_VB() => builderVB.AddPaths("LooseFilePermissions.Unix.vb") .AddReferences(NuGetMetadataReference.MonoPosixNetStandard()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/LossOfFractionInDivisionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class LossOfFractionInDivisionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void LossOfFractionInDivision() => builder.AddPaths("LossOfFractionInDivision.cs").Verify(); [TestMethod] public void LossOfFractionInDivision_CS_Latest() => builder.AddPaths("LossOfFractionInDivision.Latest.cs").WithTopLevelStatements().WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MagicNumberShouldNotBeUsedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class MagicNumberShouldNotBeUsedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MagicNumberShouldNotBeUsed() => builder.AddPaths("MagicNumberShouldNotBeUsed.cs").Verify(); [TestMethod] public void MagicNumberShouldNotBeUsed_CSharp11() => builder.AddPaths("MagicNumberShouldNotBeUsed.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).WithTopLevelStatements().Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkAssemblyWithAssemblyVersionAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MarkAssemblyWithAssemblyVersionAttributeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttribute_CS() => builderCS.AddPaths("MarkAssemblyWithAssemblyVersionAttribute.cs").WithConcurrentAnalysis(false).VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeRazor_CS() => builderCS .AddPaths("MarkAssemblyWithAssemblyVersionAttributeRazor.cs") .WithConcurrentAnalysis(false) .AddReferences(GetAspNetCoreRazorReferences()) .VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttribute_CS_Concurrent() => builderCS .AddPaths("MarkAssemblyWithAssemblyVersionAttribute.cs", "MarkAssemblyWithAssemblyVersionAttributeRazor.cs") .AddReferences(GetAspNetCoreRazorReferences()) .WithAutogenerateConcurrentFiles(false) .VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeNoncompliant_CS() => builderCS.AddPaths("MarkAssemblyWithAssemblyVersionAttributeNoncompliant.cs") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeNoncompliant_NoTargets_ShouldNotRaise_CS() => // False positive. No assembly gets generated when Microsoft.Build.NoTargets is referenced. builderCS.AddSnippet("// Noncompliant ^1#0 {{Provide an 'AssemblyVersion' attribute for assembly 'project0'.}}") .AddReferences(NuGetMetadataReference.MicrosoftBuildNoTargets()) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttribute_VB() => builderVB.AddPaths("MarkAssemblyWithAssemblyVersionAttribute.vb").WithConcurrentAnalysis(false).VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeRazor_VB() => builderVB .AddPaths("MarkAssemblyWithAssemblyVersionAttributeRazor.vb") .WithConcurrentAnalysis(false) .AddReferences(GetAspNetCoreRazorReferences()) .VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttribute_VB_Concurrent() => builderVB .AddPaths("MarkAssemblyWithAssemblyVersionAttribute.vb", "MarkAssemblyWithAssemblyVersionAttributeRazor.vb") .AddReferences(GetAspNetCoreRazorReferences()) .WithAutogenerateConcurrentFiles(false) .VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeNoncompliant_VB() => builderVB.AddPaths("MarkAssemblyWithAssemblyVersionAttributeNoncompliant.vb") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void MarkAssemblyWithAssemblyVersionAttributeNoncompliant_NoTargets_ShouldNotRaise_VB() => // False positive. No assembly gets generated when Microsoft.Build.NoTargets is referenced. builderVB.AddSnippet("' Noncompliant ^1#0 {{Provide an 'AssemblyVersion' attribute for assembly 'project0'.}}") .AddReferences(NuGetMetadataReference.MicrosoftBuildNoTargets()) .WithConcurrentAnalysis(false) .Verify(); private static IEnumerable GetAspNetCoreRazorReferences() => NuGetMetadataReference.MicrosoftAspNetCoreMvcRazorRuntime(TestConstants.NuGetLatestVersion); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkAssemblyWithAttributeUsageAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MarkAssemblyWithAttributeUsageAttributeTest { [TestMethod] public void RequireAttributeUsageAttribute() => new VerifierBuilder().AddPaths(@"RequireAttributeUsageAttribute.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkAssemblyWithClsCompliantAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MarkAssemblyWithClsCompliantAttributeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithConcurrentAnalysis(false); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithConcurrentAnalysis(false); [TestMethod] public void MarkAssemblyWithClsCompliantAttribute_CS() => builderCS.AddPaths(@"MarkAssemblyWithClsCompliantAttribute.cs").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithClsCompliantAttribute_VB() => builderVB.AddPaths(@"MarkAssemblyWithClsCompliantAttribute.vb").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithClsCompliantAttributeNoncompliant_CS() => builderCS.AddPaths(@"MarkAssemblyWithClsCompliantAttributeNoncompliant.cs").Verify(); [TestMethod] public void MarkAssemblyWithClsCompliantAttributeNoncompliant_VB() => builderVB.AddPaths(@"MarkAssemblyWithClsCompliantAttributeNoncompliant.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkAssemblyWithComVisibleAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MarkAssemblyWithComVisibleAttributeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithConcurrentAnalysis(false); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithConcurrentAnalysis(false); [TestMethod] public void MarkAssemblyWithComVisibleAttribute_CS() => builderCS.AddPaths(@"MarkAssemblyWithComVisibleAttribute.cs").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithComVisibleAttribute_VB() => builderVB.AddPaths(@"MarkAssemblyWithComVisibleAttribute.vb").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithComVisibleAttributeNoncompliant_CS() => builderCS.AddPaths(@"MarkAssemblyWithComVisibleAttributeNoncompliant.cs").Verify(); [TestMethod] public void MarkAssemblyWithComVisibleAttributeNoncompliant_VB() => builderVB.AddPaths(@"MarkAssemblyWithComVisibleAttributeNoncompliant.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkAssemblyWithNeutralResourcesLanguageAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class MarkAssemblyWithNeutralResourcesLanguageAttributeTest { private readonly VerifierBuilder builder = new VerifierBuilder().WithConcurrentAnalysis(false); [TestMethod] public void MarkAssemblyWithNeutralResourcesLanguageAttribute_HasResx_HasAttribute_Compliant() => builder.AddPaths("MarkAssemblyWithNeutralResourcesLanguageAttribute.cs", @"Resources\SomeResources.Designer.cs", @"Resources\AnotherResources.Designer.cs").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithNeutralResourcesLanguageAttribute_NoResx_HasAttribute_Compliant() => builder.AddPaths("MarkAssemblyWithNeutralResourcesLanguageAttribute.cs").VerifyNoIssues(); [TestMethod] public void MarkAssemblyWithNeutralResourcesLanguageAttribute_HasResx_HasInvalidAttribute_Noncompliant() => builder.AddPaths("MarkAssemblyWithNeutralResourcesLanguageAttributeNonCompliant.Invalid.cs", @"Resources\SomeResources.Designer.cs").Verify(); [TestMethod] public void MarkAssemblyWithNeutralResourcesLanguageAttribute_HasResx_NoAttribute_Noncompliant() => builder.AddPaths("MarkAssemblyWithNeutralResourcesLanguageAttributeNonCompliant.cs", @"Resources\SomeResources.Designer.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MarkWindowsFormsMainWithStaThreadTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class MarkWindowsFormsMainWithStaThreadTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithConcurrentAnalysis(false); private readonly VerifierBuilder builderVB = new VerifierBuilder().WithConcurrentAnalysis(false); [TestMethod] public void MarkWindowsFormsMainWithStaThread_CS() => builderCS .AddPaths("MarkWindowsFormsMainWithStaThread.cs") .WithOutputKind(OutputKind.WindowsApplication) .AddReferences(MetadataReferenceFacade.SystemWindowsForms) .Verify(); [TestMethod] public void MarkWindowsFormsMainWithStaThread_VB() => builderVB .AddPaths("MarkWindowsFormsMainWithStaThread.vb") .WithOutputKind(OutputKind.WindowsApplication) .AddReferences(MetadataReferenceFacade.SystemWindowsForms) .Verify(); [TestMethod] public void MarkWindowsFormsMainWithStaThread_ClassLibrary_CS() => builderCS .AddPaths("MarkWindowsFormsMainWithStaThread.cs") .WithErrorBehavior(CompilationErrorBehavior.Ignore) .WithOutputKind(OutputKind.DynamicallyLinkedLibrary) .AddReferences(MetadataReferenceFacade.SystemWindowsForms) .VerifyNoIssues(); [TestMethod] public void MarkWindowsFormsMainWithStaThread_ClassLibrary_VB() => builderVB .AddPaths("MarkWindowsFormsMainWithStaThread.vb") .WithOutputKind(OutputKind.DynamicallyLinkedLibrary) .AddReferences(MetadataReferenceFacade.SystemWindowsForms) .VerifyNoIssuesIgnoreErrors(); [TestMethod] public void MarkWindowsFormsMainWithStaThread_CS_NoWindowsForms() => builderCS .AddPaths("MarkWindowsFormsMainWithStaThread_NoWindowsForms.cs") .WithOutputKind(OutputKind.WindowsApplication) .Verify(); [TestMethod] public void MarkWindowsFormsMainWithStaThread_VB_NoWindowsForms() => builderVB .AddPaths("MarkWindowsFormsMainWithStaThread_NoWindowsForms.vb") .WithOutputKind(OutputKind.WindowsApplication) .VerifyNoIssuesIgnoreErrors(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberInitializedToDefaultTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberInitializedToDefaultTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MemberInitializedToDefault() => builder.AddPaths("MemberInitializedToDefault.cs").Verify(); [TestMethod] public void MemberInitializedToDefault_CodeFix() => builder .WithCodeFix() .AddPaths("MemberInitializedToDefault.cs") .WithCodeFixedPaths("MemberInitializedToDefault.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MemberInitializedToDefault_CSharp8() => builder.AddPaths("MemberInitializedToDefault.CSharp8.cs").WithOptions(LanguageOptions.FromCSharp8).VerifyNoIssues(); [TestMethod] public void MemberInitializedToDefault_CSharp9() => builder.AddPaths("MemberInitializedToDefault.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void MemberInitializedToDefault_CSharp10() => builder.AddPaths("MemberInitializedToDefault.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void MemberInitializedToDefault_CSharp11() => builder.AddPaths("MemberInitializedToDefault.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); [TestMethod] public void MemberInitializedToDefault_CSharp11_CodeFix() => builder .WithCodeFix() .AddPaths("MemberInitializedToDefault.CSharp11.cs") .WithCodeFixedPaths("MemberInitializedToDefault.CSharp11.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp11) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberInitializerRedundantTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberInitializerRedundantTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder builderSonarCfg = new VerifierBuilder().AddAnalyzer(() => new MemberInitializerRedundant(AnalyzerConfiguration.AlwaysEnabledWithSonarCfg)); [TestMethod] public void MemberInitializerRedundant_RoslynCfg() => builder.AddPaths(@"MemberInitializerRedundant.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void MemberInitializerRedundant_RoslynCfg_FlowCaptureOperationNotSupported() => builder.AddPaths(@"MemberInitializerRedundant.RoslynCfg.FlowCaptureBug.cs").WithOptions(LanguageOptions.FromCSharp8).VerifyNoIssues(); [TestMethod] public void MemberInitializerRedundant_SonarCfg() => builderSonarCfg.AddPaths(@"MemberInitializerRedundant.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void MemberInitializerRedundant_CodeFix() => builder .WithCodeFix() .AddPaths("MemberInitializerRedundant.cs") .WithCodeFixedPaths("MemberInitializerRedundant.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MemberInitializerRedundant_CS_Latest() => builder.AddPaths("MemberInitializerRedundant.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MemberInitializerRedundant_CS_Latest_CodeFix() => builder .WithCodeFix() .AddPaths("MemberInitializerRedundant.Latest.cs") .WithCodeFixedPaths("MemberInitializerRedundant.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberOverrideCallsBaseMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberOverrideCallsBaseMemberTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MemberOverrideCallsBaseMember() => builder.AddPaths("MemberOverrideCallsBaseMember.cs").Verify(); [TestMethod] public void MemberOverrideCallsBaseMember_Latest() => builder.AddPaths("MemberOverrideCallsBaseMember.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MemberOverrideCallsBaseMember_Latest_CodeFix() => builder.AddPaths("MemberOverrideCallsBaseMember.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithCodeFix() .WithCodeFixedPaths("MemberOverrideCallsBaseMember.Latest.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MemberOverrideCallsBaseMember_CodeFix() => builder.AddPaths("MemberOverrideCallsBaseMember.cs") .WithCodeFix() .WithCodeFixedPaths("MemberOverrideCallsBaseMember.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MemberOverrideCallsBaseMember_ToString() { var toString = "public override string ToString() => base.ToString();"; toString += #if NET "// Noncompliant {{Remove this method 'ToString' to simply inherit its behavior.}}"; #elif NETFRAMEWORK "// FN. ToString has a [__DynamicallyInvokable] attribute in .Net framework"; #endif builder.AddSnippet($$""" class Test { {{toString}} } """) #if NET .Verify(); #elif NETFRAMEWORK .VerifyNoIssues(); #endif } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberShadowsOuterStaticMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberShadowsOuterStaticMemberTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MemberShadowsOuterStaticMember() => builder.AddPaths("MemberShadowsOuterStaticMember.cs").Verify(); [TestMethod] public void MemberShadowsOuterStaticMember_Latest() => builder.AddPaths("MemberShadowsOuterStaticMember.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberShouldBeStaticTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberShouldBeStaticTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow("1.0.0", "3.0.20105.1")] [DataRow(TestConstants.NuGetLatestVersion, TestConstants.NuGetLatestVersion)] public void MemberShouldBeStatic(string aspnetCoreVersion, string aspnetVersion) => builder.AddPaths("MemberShouldBeStatic.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreMvcWebApiCompatShim(aspnetCoreVersion) .Concat(NuGetMetadataReference.MicrosoftAspNetMvc(aspnetVersion)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspnetCoreVersion)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcViewFeatures(aspnetCoreVersion)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreRoutingAbstractions(aspnetCoreVersion))) .Verify(); [TestMethod] public void MemberShouldBeStatic_WinForms() => builder.AddPaths("MemberShouldBeStatic.WinForms.cs").AddReferences(MetadataReferenceFacade.SystemWindowsForms).Verify(); [TestMethod] public void MemberShouldBeStatic_Xaml() => builder.AddPaths("MemberShouldBeStatic.Xaml.cs").AddReferences(MetadataReferenceFacade.PresentationFramework).Verify(); [TestMethod] public void MemberShouldBeStatic_Latest() => builder.AddPaths("MemberShouldBeStatic.Latest.cs") .AddPaths("MemberShouldBeStatic.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void MemberShouldBeStatic_HttpApplication() => builder.AddSnippet(""" public class HttpApplication1 : System.Web.HttpApplication // Error [CS0234] { public int Foo() => 0; protected int FooFoo() => 0; // Noncompliant } """).Verify(); [TestMethod] public void MemberShouldBeStatic_InvalidCode() => // Handle invalid code causing NullReferenceException: https://github.com/SonarSource/sonar-dotnet/issues/819 builder.AddSnippet(""" public class Class7 { public async Task Function(Func>> f) { Result result; result = await f(); return result; } } """) .VerifyNoAD0001(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MemberShouldNotHaveConflictingTransparencyAttributesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MemberShouldNotHaveConflictingTransparencyAttributesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MemberShouldNotHaveConflictingTransparencyAttributes() => builder.AddPaths("MemberShouldNotHaveConflictingTransparencyAttributes.cs", "MemberShouldNotHaveConflictingTransparencyAttributes.Partial.cs").Verify(); [TestMethod] public void MemberShouldNotHaveConflictingTransparencyAttributes_AssemblyLevel() => builder.AddPaths("MemberShouldNotHaveConflictingTransparencyAttributes.AssemblyLevel.cs").WithConcurrentAnalysis(false).Verify(); [TestMethod] public void MemberShouldNotHaveConflictingTransparencyAttributes_CS_Latest() => builder.AddPaths("MemberShouldNotHaveConflictingTransparencyAttributes.Latest.cs", "MemberShouldNotHaveConflictingTransparencyAttributes.Latest.Partial.cs") .WithConcurrentAnalysis(false) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MessageTemplatesShouldBeCorrectTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MessageTemplatesShouldBeCorrectTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); [TestMethod] public void MessageTemplatesShouldBeCorrect_CS() => Builder .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddPaths("MessageTemplatesShouldBeCorrect.cs") .Verify(); [TestMethod] [DataRow("LogCritical", "")] [DataRow("LogDebug", "")] [DataRow("LogError", "")] [DataRow("LogInformation", "")] [DataRow("LogTrace", "")] [DataRow("LogWarning", "")] [DataRow("Log", "LogLevel.Information,")] public void MessageTemplatesShouldBeCorrect_MicrosoftExtensionsLogging_CS(string methodName, string logLevel) => Builder .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddSnippet($$$""" using System; using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, string user) { Console.WriteLine("Login failed for {User", user); // Compliant logger.{{{methodName}}}({{{logLevel}}} "Login failed for {User}", user); // Compliant logger.{{{methodName}}}({{{logLevel}}} "{", user); // Noncompliant LoggerExtensions.{{{methodName}}}(logger, {{{logLevel}}} "{", user); // Noncompliant } } """) .Verify(); [TestMethod] [DataRow("Debug", "")] [DataRow("Error", "")] [DataRow("Information", "")] [DataRow("Fatal", "")] [DataRow("Warning", "")] [DataRow("Verbose", "")] [DataRow("Write", "LogEventLevel.Verbose,")] public void MessageTemplatesShouldBeCorrect_Serilog_CS(string methodName, string logEventLevel) => Builder .AddReferences(NuGetMetadataReference.Serilog()) .AddSnippet($$$""" using System; using Serilog; using Serilog.Events; public class Program { public void Method(ILogger logger, string user) { Console.WriteLine("Login failed for {User", user); // Compliant logger.{{{methodName}}}({{{logEventLevel}}} "Login failed for {User}", user); // Compliant logger.{{{methodName}}}({{{logEventLevel}}} "{", user); // Noncompliant Log.{{{methodName}}}({{{logEventLevel}}} "{", user); // Noncompliant } } """) .Verify(); [TestMethod] [DataRow("Debug")] [DataRow("ConditionalDebug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("ConditionalTrace")] [DataRow("Warn")] public void MessageTemplatesShouldBeCorrect_NLog_CS(string methodName) => Builder .AddReferences(NuGetMetadataReference.NLog()) .AddSnippet($$$""" using System; using NLog; public class Program { public void Method(ILogger iLogger, Logger logger, MyLogger myLogger, string user) { Console.WriteLine("Login failed for {User", user); // Compliant logger.{{{methodName}}}("Login failed for {User}", user); // Compliant iLogger.{{{methodName}}}("{", user); // Noncompliant logger.{{{methodName}}}("{", user); // Noncompliant myLogger.{{{methodName}}}("{", user); // Noncompliant } } public class MyLogger : Logger { } """) .Verify(); [TestMethod] [DataRow("ConditionalDebug")] [DataRow("ConditionalTrace")] public void MessageTemplatesShouldBeCorrect_NLog_ConditionalExtensions_CS(string methodName) => Builder .AddReferences(NuGetMetadataReference.NLog()) .AddSnippet($$$""" using System; using NLog; public class Program { public void Method(ILogger iLogger, string user) { Console.WriteLine("Login failed for {User", user); // Compliant ILoggerExtensions.{{{methodName}}}(iLogger, "{", user); // Noncompliant } } public class MyLogger : Logger { } """) .Verify(); [TestMethod] public void MessageTemplatesShouldBeCorrect_Latest() => Builder.AddPaths("MessageTemplatesShouldBeCorrect.Latest.cs") .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodOverloadOptionalParameterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodOverloadOptionalParameterTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void MethodOverloadOptionalParameter() => builder.AddPaths("MethodOverloadOptionalParameter.cs").AddReferences(MetadataReferenceFacade.NetStandard21).WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void MethodOverloadOptionalParameter_CS_Latest() => builder.AddPaths("MethodOverloadOptionalParameter.Latest.cs", "MethodOverloadOptionalParameter.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodOverloadOptionalParameter_Razor() => builder.AddSnippet( """ @code { void Print2(string[] messages) { } void Print2(string[] messages, string delimiter = "\n") { } // Noncompliant {{This method signature overlaps the one defined on line 3, the default parameter value can't be used.}}; // ^^^^^^^^^^^^^^^^^^^^^^^ } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodOverloadsShouldBeGroupedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodOverloadsShouldBeGroupedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void MethodOverloadsShouldBeGrouped_CS() => builderCS.AddPaths("MethodOverloadsShouldBeGrouped.cs").Verify(); [TestMethod] public void MethodOverloadsShouldBeGrouped_CS_Latest() => builderCS.AddPaths("MethodOverloadsShouldBeGrouped.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodOverloadsShouldBeGrouped_VB() => builderVB.AddPaths("MethodOverloadsShouldBeGrouped.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodOverrideAddsParamsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodOverrideAddsParamsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodOverrideAddsParams() => builder.AddPaths("MethodOverrideAddsParams.cs").WithOptions(LanguageOptions.FromCSharp8).AddReferences(MetadataReferenceFacade.NetStandard21).Verify(); [TestMethod] public void MethodOverrideAddsParams_CS_Latest() => builder.AddPaths("MethodOverrideAddsParams.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodOverrideAddsParams_CodeFix() => builder .WithCodeFix() .AddPaths("MethodOverrideAddsParams.cs") .WithCodeFixedPaths("MethodOverrideAddsParams.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodOverrideChangedDefaultValueTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodOverrideChangedDefaultValueTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodOverrideChangedDefaultValue() => builder.AddPaths("MethodOverrideChangedDefaultValue.cs") .AddReferences(MetadataReferenceFacade.NetStandard21) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void MethodOverrideChangedDefaultValue_CS_Latest() => builder.AddPaths("MethodOverrideChangedDefaultValue.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void MethodOverrideChangedDefaultValue_CS_Latest_CodeFix() => builder.AddPaths("MethodOverrideChangedDefaultValue.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("MethodOverrideChangedDefaultValue.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); [TestMethod] public void MethodOverrideChangedDefaultValue_CodeFix_Synchronize() => builder.AddPaths("MethodOverrideChangedDefaultValue.cs") .WithCodeFix() .WithCodeFixedPaths("MethodOverrideChangedDefaultValue.Synchronize.Fixed.cs", "MethodOverrideChangedDefaultValue.Synchronize.Fixed.Batch.cs") .WithCodeFixTitle(MethodOverrideChangedDefaultValueCodeFix.TitleGeneral) .VerifyCodeFix(); [TestMethod] public void MethodOverrideChangedDefaultValue_CodeFix_Remove() => builder.AddPaths("MethodOverrideChangedDefaultValue.cs") .WithCodeFix() .WithCodeFixedPaths("MethodOverrideChangedDefaultValue.Remove.Fixed.cs") .WithCodeFixTitle(MethodOverrideChangedDefaultValueCodeFix.TitleExplicitInterface) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodOverrideNoParamsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodOverrideNoParamsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodOverrideNoParams() => builder.AddPaths("MethodOverrideNoParams.cs").Verify(); [TestMethod] public void MethodOverrideNoParams_CS_Latest() => builder.AddPaths("MethodOverrideNoParams.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodOverrideNoParams_CodeFix() => builder.WithCodeFix() .AddPaths("MethodOverrideNoParams.cs") .WithCodeFixedPaths("MethodOverrideNoParams.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodParameterMissingOptionalTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodParameterMissingOptionalTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodParameterMissingOptional() => builder.AddPaths("MethodParameterMissingOptional.cs").Verify(); [TestMethod] public void MethodParameterMissingOptional_TopLevelStatements() => builder.AddPaths("MethodParameterMissingOptional.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void MethodParameterMissingOptional_CS_Latest() => builder.AddPaths("MethodParameterMissingOptional.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodParameterMissingOptional_CodeFix() => builder .WithCodeFix() .AddPaths("MethodParameterMissingOptional.cs") .WithCodeFixedPaths("MethodParameterMissingOptional.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodParameterUnusedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodParameterUnusedTest { private readonly VerifierBuilder sonarCS = new VerifierBuilder().AddAnalyzer(() => new CS.MethodParameterUnused(AnalyzerConfiguration.AlwaysEnabledWithSonarCfg)); private readonly VerifierBuilder roslynCS = new VerifierBuilder(); // Default constructor uses Roslyn CFG [TestMethod] public void MethodParameterUnused_CS_SonarCfg() => sonarCS.AddPaths("MethodParameterUnused.SonarCfg.cs").Verify(); [TestMethod] public void MethodParameterUnused_CS_RoslynCfg() => roslynCS.AddPaths("MethodParameterUnused.RoslynCfg.cs").Verify(); [TestMethod] public void MethodParameterUnused_CodeFix_CS() => roslynCS.AddPaths("MethodParameterUnused.RoslynCfg.cs") .WithCodeFix() .WithCodeFixedPaths("MethodParameterUnused.RoslynCfg.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MethodParameterUnused_DoubleCompilation_CS() { // https://github.com/SonarSource/sonar-dotnet/issues/5491 const string code = """ public class Sample { private void Method(int arg) => arg.ToString(); } """; var compilation1 = roslynCS.AddSnippet(code).WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7).Compile().Single(); var compilation2 = compilation1.WithAssemblyName("Different-Compilation-Reusing-Same-Nodes"); // Modified compilation should not reuse cached CFG, because symbols from method would not be equal to symbols from the other CFG. Analyze(compilation1).Should().BeEmpty(); Analyze(compilation2).Should().BeEmpty(); ImmutableArray Analyze(Compilation compilation) => compilation.WithAnalyzers(roslynCS.Analyzers.Select(x => x()).ToImmutableArray()).GetAllDiagnosticsAsync(default).Result; } [TestMethod] public void MethodParameterUnused_VB() => new VerifierBuilder().AddPaths("MethodParameterUnused.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void MethodParameterUnused_CS_RoslynCfg_Latest() => roslynCS .AddPaths("MethodParameterUnused.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] // https://github.com/SonarSource/sonar-dotnet/issues/8988 public void MethodParameterUnused_GeneratedCode_CS() => roslynCS .AddSnippet(""" using System.CodeDom.Compiler; [GeneratedCode("TestTool", "Version")] public partial class Generated { private partial void M(int a, int unused); } """) .AddSnippet(""" using System; public partial class Generated { private partial void M(int a, int unused) // Compliant { Console.WriteLine(a); } } """) .WithOptions(LanguageOptions.FromCSharp9) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodShouldBeNamedAccordingToSynchronicityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodShouldBeNamedAccordingToSynchronicityTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow("4.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] public void MethodShouldBeNamedAccordingToSynchronicity(string tasksVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.cs") .AddReferences(MetadataReferenceFacade.SystemThreadingTasksExtensions(tasksVersion) .Union(NuGetMetadataReference.MicrosoftAspNetSignalRCore()) .Union(MetadataReferenceFacade.SystemComponentModelPrimitives)) .Verify(); [TestMethod] [DataRow("3.0.20105.1")] [DataRow(TestConstants.NuGetLatestVersion)] public void MethodShouldBeNamedAccordingToSynchronicity_MVC(string mvcVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.MVC.cs").AddReferences(NuGetMetadataReference.MicrosoftAspNetMvc(mvcVersion)).VerifyNoIssues(); [TestMethod] [DataRow("2.0.4", "2.0.3")] [DataRow(TestConstants.NuGetLatestVersion, TestConstants.NuGetLatestVersion)] public void MethodShouldBeNamedAccordingToSynchronicity_MVC_Core(string aspNetCoreMvcVersion, string aspNetCoreRoutingVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.MVC.Core.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetCoreMvcVersion) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcViewFeatures(aspNetCoreMvcVersion)) .Concat(NuGetMetadataReference.MicrosoftAspNetCoreRoutingAbstractions(aspNetCoreRoutingVersion))) .Verify(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void MethodShouldBeNamedAccordingToSynchronicity_MsTest(string testFwkVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.MsTest.cs").AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)).VerifyNoIssues(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(Latest)] public void MethodShouldBeNamedAccordingToSynchronicity_NUnit(string testFwkVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.NUnit.cs").AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)).VerifyNoIssues(); [TestMethod] [DataRow("2.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] public void MethodShouldBeNamedAccordingToSynchronicity_Xunit(string testFwkVersion) => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.Xunit.cs").AddReferences(NuGetMetadataReference.XunitFramework(testFwkVersion)).VerifyNoIssues(); [TestMethod] public void MethodShouldBeNamedAccordingToSynchronicity_CSharp8() => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.CSharp8.cs").WithOptions(LanguageOptions.FromCSharp8).AddReferences(MetadataReferenceFacade.NetStandard21).Verify(); [TestMethod] public void MethodShouldBeNamedAccordingToSynchronicity_CSharp11() => builder.AddPaths("MethodShouldBeNamedAccordingToSynchronicity.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .AddReferences(NuGetMetadataReference.MicrosoftAspNetSignalRCore()) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodShouldNotOnlyReturnConstantTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodShouldNotOnlyReturnConstantTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodShouldNotOnlyReturnConstant() => builder.AddPaths("MethodShouldNotOnlyReturnConstant.cs").Verify(); [TestMethod] public void MethodShouldNotOnlyReturnConstant_CS_Latest() => builder.AddPaths("MethodShouldNotOnlyReturnConstant.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodsShouldNotHaveIdenticalImplementationsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodsShouldNotHaveIdenticalImplementationsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void MethodsShouldNotHaveIdenticalImplementations() => builderCS.AddPaths("MethodsShouldNotHaveIdenticalImplementations.cs").Verify(); [TestMethod] [CombinatorialData] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters( [CombinatorialValues("", "where T: struct", "where T: class", "where T: unmanaged", "where T: new()", "where T: class, new()")] string constraint1, [CombinatorialValues("", "where T: struct", "where T: class", "where T: unmanaged", "where T: new()", "where T: class, new()")] string constraint2) { var nonCompliant = constraint1 == constraint2; var builder = builderCS.AddSnippet($$""" using System; public static class TypeConstraints { public static bool Compare1(T? value1, T value2) {{constraint1}} {{(nonCompliant ? "// Secondary" : string.Empty)}} { Console.WriteLine(value1); Console.WriteLine(value2); return true; } public static bool Compare2(T? value1, T value2) {{constraint2}} {{(nonCompliant ? "// Noncompliant" : string.Empty)}} { Console.WriteLine(value1); Console.WriteLine(value2); return true; } } """).WithOptions(LanguageOptions.FromCSharp9); if (nonCompliant) { builder.Verify(); } else { builder.VerifyNoIssues(); } } [TestMethod] [DataRow("where T: IEquatable, IComparable", "where T: System.IComparable, IEquatable")] [DataRow("where T: List>, IList, IComparable", "where T: List>, IComparable, IList")] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters_NonCompliant(string constraint1, string constraint2) => builderCS.AddSnippet($$""" using System; using System.Collections.Generic; public static class TypeConstraints { public static bool Compare1(T? value1, T value2) {{constraint1}} // Secondary { Console.WriteLine(value1); Console.WriteLine(value2); return true; } public static bool Compare2(T? value1, T value2) {{constraint2}} // Noncompliant { Console.WriteLine(value1); Console.WriteLine(value2); return true; } } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] [DataRow("", "")] [DataRow("where TKey: TValue", "where TKey: TValue")] [DataRow("where TKey: TValue where TValue: IComparable", "where TKey: TValue where TValue: IComparable")] [DataRow("where TKey: IEquatable where TValue: IComparable", "where TKey: IEquatable where TValue: IComparable")] [DataRow("where TKey: struct", "where TKey: struct")] [DataRow("where TKey: struct where TValue: class", "where TKey: struct where TValue: class")] [DataRow("where TValue: class where TKey: struct", "where TKey: struct where TValue: class")] [DataRow("where TKey: class", "where TKey: class")] [DataRow("where TKey: unmanaged", "where TKey: unmanaged")] [DataRow("where TKey: new()", "where TKey: new()")] [DataRow("where TKey: IEquatable, IComparable", "where TKey: System.IComparable, IEquatable")] [DataRow("where TKey: IEquatable, IComparable where TValue: IComparable", " where TValue: System.IComparable where TKey: System.IComparable, IEquatable")] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters_Dictionary_NonCompliant(string constraint1, string constraint2) => builderCS.AddSnippet($$""" using System; using System.Collections.Generic; public static class TypeConstraints { public static bool Test1(IDictionary dict) {{constraint1}} // Secondary { Console.WriteLine(dict); Console.WriteLine(dict); return true; } public static bool Test2(IDictionary dict) {{constraint2}} // Noncompliant { Console.WriteLine(dict); Console.WriteLine(dict); return true; } } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] [DataRow("Of TKey, TValue", "Of TKey, TValue")] [DataRow("Of TKey As Structure, TValue", "Of TKey As Structure, TValue")] [DataRow("Of TKey As Structure, TValue As Class", "Of TKey As Structure, TValue As Class")] [DataRow("Of TValue As Class, TKey As Structure", "Of TKey As Structure, TValue As Class")] [DataRow("Of TKey As {Class}, TValue", "Of TKey As Class, TValue")] [DataRow("Of TKey As {New}, TValue", "Of TKey As New, TValue")] [DataRow("Of TKey As {IEquatable(Of TKey), IComparable}, TValue", "Of TKey As {System.IComparable, IEquatable(Of TKey)}, TValue")] [DataRow("Of TKey As {IEquatable(Of TKey), IComparable}, TValue As IComparable", "Of TValue As IComparable, TKey As {System.IComparable, IEquatable(Of TKey)}")] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters_Dictionary_VB_NonCompliant(string constraint1, string constraint2) => builderVB.AddSnippet($$""" Imports System Imports System.Collections.Generic Class TypeConstraints Function Test1({{constraint1}})(dict As IDictionary(Of TKey, TValue)) As Boolean ' Secondary Console.WriteLine(dict) Console.WriteLine(dict) Return True End Function Function Test2({{constraint2}})(dict As IDictionary(Of TKey, TValue)) As Boolean ' Noncompliant Console.WriteLine(dict) Console.WriteLine(dict) Return True End Function End Class """).Verify(); [TestMethod] [DataRow("", "where TKey: struct")] [DataRow("where TKey: struct", "")] [DataRow("where TKey: struct", "where TKey: class")] [DataRow("where TKey: struct where TValue: class", "where TKey: class where TValue: class")] [DataRow("where TValue: class where TKey: struct", "where TKey: class where TValue: struct")] [DataRow("where TKey: class", "where TKey: class, new()")] [DataRow("where TKey: unmanaged", "where TKey: struct")] [DataRow("where TKey: new()", "where TKey: IComparable, new()")] [DataRow("where TKey: IEquatable, IComparable", "where TKey: System.IComparable")] [DataRow("where TKey: IEquatable, IComparable where TValue: IComparable", " where TKey: System.IComparable where TValue: System.IComparable, IEquatable")] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters_Dictionary_Compliant(string constraint1, string constraint2) => builderCS.AddSnippet($$""" using System; using System.Collections.Generic; public static class TypeConstraints { public static bool Test1(IDictionary dict) {{constraint1}} { Console.WriteLine(dict); Console.WriteLine(dict); return true; } public static bool Test2(IDictionary dict) {{constraint2}} { Console.WriteLine(dict); Console.WriteLine(dict); return true; } } """).WithOptions(LanguageOptions.FromCSharp9).VerifyNoIssues(); [TestMethod] [DataRow("Of TKey, TValue", "Of TKey, TValue As Structure")] [DataRow("Of TKey, TValue As Class", "Of TKey, TValue As Structure")] [DataRow("Of TKey As Structure, TValue", "Of TKey, TValue As Structure")] [DataRow("Of TKey As {New, IComparable}, TValue", "Of TKey As New, TValue")] public void MethodsShouldNotHaveIdenticalImplementations_MethodTypeParameters_Dictionary_VB_Compliant(string constraint1, string constraint2) => builderVB.AddSnippet($$""" Imports System Imports System.Collections.Generic Class TypeConstraints Function Test1({{constraint1}})(dict As IDictionary(Of TKey, TValue)) As Boolean Console.WriteLine(dict) Console.WriteLine(dict) Return True End Function Function Test2({{constraint2}})(dict As IDictionary(Of TKey, TValue)) As Boolean Console.WriteLine(dict) Console.WriteLine(dict) Return True End Function End Class """).VerifyNoIssues(); [TestMethod] [DataRow("")] [DataRow("where TKey: struct")] [DataRow("where TKey: struct where TValue: class")] [DataRow("where TValue: class where TKey: struct")] [DataRow("where TKey: class")] [DataRow("where TKey: unmanaged")] [DataRow("where TKey: new()")] [DataRow("where TKey: IEquatable, IComparable")] [DataRow("where TKey: IEquatable, IComparable where TValue: IComparable")] [DataRow("where TKey: TValue")] [DataRow("where TKey: TValue where TValue: IComparable")] [DataRow("where TKey: IEquatable where TValue: IComparable")] public void MethodsShouldNotHaveIdenticalImplementations_ClassTypeParameters_Dictionary_NonCompliant(string constraint) => builderCS.AddSnippet($$""" using System; using System.Collections.Generic; public class TypeConstraints {{constraint}} { public static bool Test1(IDictionary dict) // Secondary { Console.WriteLine(dict); Console.WriteLine(dict); return true; } public static bool Test2(IDictionary dict) // Noncompliant { Console.WriteLine(dict); Console.WriteLine(dict); return true; } } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] [DataRow("where TSelf: IEqualityOperators")] [DataRow("where TSelf: IEqualityOperators, TResult")] [DataRow("where TSelf: IEqualityOperators where TResult: IEqualityOperators")] [DataRow("where TSelf: IComparisonOperators")] [DataRow("where TSelf: IComparisonOperators, TResult")] public void MethodsShouldNotHaveIdenticalImplementations_SelfTypes_NonCompliant(string constraint) => builderCS.AddSnippet($$""" using System; using System.Numerics; public class TypeConstraints { public static bool Test1(IEqualityOperators x) {{constraint}} // Secondary { Console.WriteLine(x); Console.WriteLine(x); return true; } public static bool Test2(IEqualityOperators x) {{constraint}} // Noncompliant { Console.WriteLine(x); Console.WriteLine(x); return true; } } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void MethodsShouldNotHaveIdenticalImplementations_TopLevelStatements() => builderCS.AddPaths("MethodsShouldNotHaveIdenticalImplementations.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void MethodsShouldNotHaveIdenticalImplementations_CS_Latest() => builderCS.AddPaths("MethodsShouldNotHaveIdenticalImplementations.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void MethodsShouldNotHaveIdenticalImplementations_VB() => builderVB.AddPaths("MethodsShouldNotHaveIdenticalImplementations.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodsShouldNotHaveTooManyLinesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MethodsShouldNotHaveTooManyLinesTest { [TestMethod] public void MethodsShouldNotHaveTooManyLines_DefaultValues_CS() => new VerifierBuilder().AddPaths("MethodsShouldNotHaveTooManyLines_DefaultValues.cs").Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CustomValues_CS() => CreateCSBuilder(2).AddPaths("MethodsShouldNotHaveTooManyLines_CustomValues.cs").Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_LocalFunctions() => CreateCSBuilder(5).AddPaths("MethodsShouldNotHaveTooManyLines.LocalFunctions.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_LocalFunctions_CSharp9() => CreateCSBuilder(5).AddPaths("MethodsShouldNotHaveTooManyLines.LocalFunctions.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CustomValues_CSharp9() => CreateCSBuilder(2).AddPaths("MethodsShouldNotHaveTooManyLines_CustomValues.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CustomValues_CSharp10() => CreateCSBuilder(2).AddPaths("MethodsShouldNotHaveTooManyLines_CustomValues.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CSharp9_NoUsing() => CreateCSBuilder(2).AddSnippet(@" int i = 1; i++; void LocalFunction() // Noncompliant {{This local function has 4 lines, which is greater than the 2 lines authorized.}} { i++; i++; i++; i++; }") .WithOptions(LanguageOptions.FromCSharp9) .WithOutputKind(OutputKind.ConsoleApplication) .Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CSharp9_Valid() => CreateCSBuilder(4) .AddSnippet(""" int i = 1; i++; i++; i++; i++; """) .WithOptions(LanguageOptions.FromCSharp9) .WithOutputKind(OutputKind.ConsoleApplication) .VerifyNoIssues(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_DoesntReportInTest_CS() => new VerifierBuilder().AddPaths("MethodsShouldNotHaveTooManyLines_DefaultValues.cs") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_InvalidSyntax_CS() => CreateCSBuilder(2) .AddSnippet(""" public class Foo { public string () { return "f"; } } """) .VerifyNoIssuesIgnoreErrors(); [TestMethod] [DataRow(1)] [DataRow(0)] [DataRow(-1)] public void MethodsShouldNotHaveTooManyLines_InvalidMaxThreshold_CS(int max) { var compilation = SolutionBuilder.CreateSolutionFromPath(@"TestCases\MethodsShouldNotHaveTooManyLines_CustomValues.cs") .Compile(LanguageOptions.CSharpLatest.ToArray()).Single(); var errors = DiagnosticVerifier.AnalyzerExceptions(compilation, new CS.MethodsShouldNotHaveTooManyLines { Max = max }); errors.Should().OnlyContain(x => x.GetMessage(null).Contains("Invalid rule parameter: maximum number of lines = ")).And.HaveCount(12); } [TestMethod] public void MethodsShouldNotHaveTooManyLines_DefaultValues_VB() => new VerifierBuilder().AddPaths("MethodsShouldNotHaveTooManyLines_DefaultValues.vb").Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_CustomValues_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.MethodsShouldNotHaveTooManyLines { Max = 2 }) .AddPaths("MethodsShouldNotHaveTooManyLines_CustomValues.vb") .Verify(); [TestMethod] public void MethodsShouldNotHaveTooManyLines_DoesntReportInTest_VB() => new VerifierBuilder().AddPaths("MethodsShouldNotHaveTooManyLines_DefaultValues.vb") .AddTestReference() .VerifyNoIssues(); [TestMethod] [DataRow(1)] [DataRow(0)] [DataRow(-1)] public void MethodsShouldNotHaveTooManyLines_InvalidMaxThreshold_VB(int max) { var compilation = SolutionBuilder.CreateSolutionFromPath(@"TestCases\MethodsShouldNotHaveTooManyLines_CustomValues.vb") .Compile(LanguageOptions.VisualBasicLatest.ToArray()).Single(); var errors = DiagnosticVerifier.AnalyzerExceptions(compilation, new VB.MethodsShouldNotHaveTooManyLines { Max = max }); errors.Should().OnlyContain(x => x.GetMessage(null).Contains("Invalid rule parameter: maximum number of lines = ")).And.HaveCount(7); } private static VerifierBuilder CreateCSBuilder(int maxLines) => new VerifierBuilder().AddAnalyzer(() => new CS.MethodsShouldNotHaveTooManyLines { Max = maxLines }); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MethodsShouldUseBaseTypesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class MethodsShouldUseBaseTypesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MethodsShouldUseBaseTypes_Internals() { const string code1 = """ internal interface IFoo { bool IsFoo { get; } } public class Foo : IFoo { public bool IsFoo { get; set; } } """; const string code2 = """ internal class Bar { public void MethodOne(Foo foo) { var x = foo.IsFoo; } } """; var solution = SolutionBuilder.Create() .AddProject(AnalyzerLanguage.CSharp) .AddSnippet(code1) .Solution .AddProject(AnalyzerLanguage.CSharp) .AddProjectReference(sln => sln.ProjectIds[0]) .AddSnippet(code2) .Solution; foreach (var compilation in solution.Compile()) { DiagnosticVerifier.Verify(compilation, [new MethodsShouldUseBaseTypes()], CompilationErrorBehavior.Default, null, [], []); } } [TestMethod] public void MethodsShouldUseBaseTypes() => // There are two files provided (identical) in order to be able to test the rule behavior in concurrent environment. // The rule is executed concurrently if there are at least 2 syntax trees. builder.AddPaths("MethodsShouldUseBaseTypes.cs", "MethodsShouldUseBaseTypes.Concurrent.cs").WithAutogenerateConcurrentFiles(false).Verify(); [TestMethod] public void MethodsShouldUseBaseTypes_CSharp8() => builder.AddPaths("MethodsShouldUseBaseTypes.CSharp8.cs").WithAutogenerateConcurrentFiles(false).WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void MethodsShouldUseBaseTypes_Controllers() => builder.AddPaths("MethodsShouldUseBaseTypes.AspControllers.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void MethodsShouldUseBaseTypes_CSharp9() => builder.AddPaths("MethodsShouldUseBaseTypes.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void MethodsShouldUseBaseTypes_InvalidCode() => builder.AddSnippet(""" using System; using System.Collections; using System.Collections.Generic; using System.Linq; public class Foo { private void FooBar(IList , IList) { a.ToList(); } // New test case - code doesn't compile but was making analyzer crash private void Foo(IList a, IList a) { a.ToList(); } } """).VerifyNoIssuesIgnoreErrors(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MultilineBlocksWithoutBraceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MultilineBlocksWithoutBraceTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); [TestMethod] public void MultilineBlocksWithoutBrace() => Builder.AddPaths("MultilineBlocksWithoutBrace.cs").Verify(); [TestMethod] public void MultilineBlocksWithoutBrace_Latest() => Builder .AddPaths("MultilineBlocksWithoutBrace.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MultipleVariableDeclarationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MultipleVariableDeclarationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void MultipleVariableDeclaration_CS() => builderCS.AddPaths("MultipleVariableDeclaration.cs").Verify(); [TestMethod] public void MultipleVariableDeclaration_VB() => builderVB.AddPaths("MultipleVariableDeclaration.vb").Verify(); [TestMethod] public void MultipleVariableDeclaration_CodeFix_CS_WrongIndentation() => builderCS.WithCodeFix() .AddPaths("MultipleVariableDeclaration.WrongIndentation.cs") .WithCodeFixedPaths("MultipleVariableDeclaration.WrongIndentation.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MultipleVariableDeclaration_CodeFix_CS() => builderCS.WithCodeFix() .AddPaths("MultipleVariableDeclaration.cs") .WithCodeFixedPaths("MultipleVariableDeclaration.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void MultipleVariableDeclaration_CodeFix_VB() => builderVB.WithCodeFix() .AddPaths("MultipleVariableDeclaration.vb") .WithCodeFixedPaths("MultipleVariableDeclaration.Fixed.vb") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MutableFieldsShouldNotBePublicReadonlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MutableFieldsShouldNotBePublicReadonlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PublicMutableFieldsShouldNotBeReadonly() => builder.AddPaths("MutableFieldsShouldNotBePublicReadonly.cs").AddReferences(NuGetMetadataReference.SystemCollectionsImmutable("1.3.0")).Verify(); [TestMethod] public void PublicMutableFieldsShouldNotBeReadonly_CS_Latest() => builder.AddPaths("MutableFieldsShouldNotBePublicReadonly.Latest.cs") .AddReferences(MetadataReferenceFacade.SystemCollections) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/MutableFieldsShouldNotBePublicStaticTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MutableFieldsShouldNotBePublicStaticTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void MutableFieldsShouldNotBePublicStatic() => builder.AddPaths("MutableFieldsShouldNotBePublicStatic.cs").AddReferences(NuGetMetadataReference.SystemCollectionsImmutable("1.3.0")).Verify(); [TestMethod] public void MutableFieldsShouldNotBePublicStatic_CS_Latest() => builder.AddPaths("MutableFieldsShouldNotBePublicStatic.Latest.cs") .AddReferences(MetadataReferenceFacade.SystemCollections) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NameOfShouldBeUsedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using RoslynCS = Microsoft.CodeAnalysis.CSharp; using RoslynVB = Microsoft.CodeAnalysis.VisualBasic; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class NameOfShouldBeUsedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void NameOfShouldBeUsed_CSharp6() => builderCS.AddPaths("NameOfShouldBeUsed.cs").WithOptions(LanguageOptions.FromCSharp6).Verify(); [TestMethod] public void NameOfShouldBeUsed_CSharp5() => builderCS.AddPaths("NameOfShouldBeUsed.cs") .WithLanguageVersion(RoslynCS.LanguageVersion.CSharp5) .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); [TestMethod] public void NameOfShouldBeUsed_CSharp11() => builderCS.AddPaths("NameOfShouldBeUsed.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); [TestMethod] public void NameOfShouldBeUsed_FromVB14() => builderVB.AddPaths("NameOfShouldBeUsed.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void NameOfShouldBeUsed_VB12() => builderVB.AddPaths("NameOfShouldBeUsed.vb") .WithLanguageVersion(RoslynVB.LanguageVersion.VisualBasic12) .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NamedPlaceholdersShouldBeUniqueTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.CSharp.Rules.MessageTemplates; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NamedPlaceholdersShouldBeUniqueTest { private static readonly IEnumerable LoggingReferences = NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions() .Concat(NuGetMetadataReference.NLog()) .Concat(NuGetMetadataReference.Serilog()); private static readonly VerifierBuilder Builder = new VerifierBuilder() .AddReferences(LoggingReferences) .WithOnlyDiagnostics(NamedPlaceholdersShouldBeUnique.S6677); [TestMethod] public void NamedPlaceholdersShouldBeUnique_CS() => Builder.AddPaths("NamedPlaceholdersShouldBeUnique.cs").Verify(); [TestMethod] [DataRow("LogCritical")] [DataRow("LogDebug")] [DataRow("LogError")] [DataRow("LogInformation")] [DataRow("LogTrace")] [DataRow("LogWarning")] public void NamedPlaceholdersShouldBeUnique_MicrosoftExtensionsLogging_CS(string methodName) => Builder.AddSnippet($$""" using System; using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, MyLogger myLogger, int arg) { logger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant logger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 myLogger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant myLogger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 } } public class MyLogger : ILogger { public IDisposable BeginScope(TState state) => null; public bool IsEnabled(LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Information")] [DataRow("Fatal")] [DataRow("Warning")] [DataRow("Verbose")] public void NamedPlaceholdersShouldBeUnique_Serilog_CS(string methodName) => Builder.AddSnippet($$""" using Serilog; using Serilog.Events; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant logger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 Log.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant Log.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 Log.Logger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant Log.Logger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Information")] [DataRow("Fatal")] [DataRow("Warning")] [DataRow("Verbose")] public void NamedPlaceholdersShouldBeUnique_Serilog_Derived_CS(string methodName) => Builder.AddSnippet($$""" using Serilog; using Serilog.Events; using Serilog.Core; public class Program { public void Method(Logger logger, int arg) { logger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant logger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("ConditionalDebug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("ConditionalTrace")] [DataRow("Warn")] public void NamedPlaceholdersShouldBeUnique_NLog_CS(string methodName) => Builder.AddSnippet($$""" using NLog; public class Program { public void Method(ILogger iLogger, Logger logger, MyLogger myLogger, int arg) { iLogger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant iLogger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 logger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant logger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 myLogger.{{methodName}}("Hey {foo} and {bar}", arg, arg); // Compliant myLogger.{{methodName}}("Hey {foo} and {foo}", arg, arg); // Noncompliant // Secondary @-1 } } public class MyLogger : Logger { } """).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NamespaceNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class NamespaceNameTest { [TestMethod] public void NamespaceName() => new VerifierBuilder().AddPaths("NamespaceName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NativeMethodsShouldBeWrappedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NativeMethodsShouldBeWrappedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NativeMethodsShouldBeWrapped() => builder.AddPaths("NativeMethodsShouldBeWrapped.cs").Verify(); [TestMethod] public void NativeMethodsShouldBeWrapped_TopLevelStatements() => builder.AddPaths("NativeMethodsShouldBeWrapped.TopLevelStatements.cs").WithTopLevelStatements().Verify(); // NativeMethodsShouldBeWrapped.Latest.SourceGenerator.cs contains the code as generated by the SourceGenerator. To regenerate it: // * Take the code from NativeMethodsShouldBeWrapped.Latest.cs // * Copy it to a new .Net 7 project // * Press F12 on any of the partial methods // * Copy the result to NativeMethodsShouldBeWrapped.Latest.SourceGenerator.cs [TestMethod] public void NativeMethodsShouldBeWrapped_CS_Latest() => builder .AddPaths("NativeMethodsShouldBeWrapped.Latest.cs") .AddPaths("NativeMethodsShouldBeWrapped.Latest.SourceGenerator.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void NativeMethodsShouldBeWrapped_InvalidCode() => builder.AddSnippet(""" public class InvalidSyntax { extern public void Extern1 // Error [CS0670, CS0106, CS1002] extern public void Extern2; // Error [CS0670, CS0106] extern private void Extern3(int x); public void Wrapper // Error [CS0547, CS0548] { Extern3(x); // Error [CS1014, CS1513, CS8124, CS1519] } public void Wrapper( // Error [CS0106, CS8107, CS8803, CS8805, CS8112, CS1001] { Extern3(x); // Error [CS0246, CS1003, CS0246, CS8124, CS1001, CS1026, CS1001] } // Error [CS1022] public void Wrapper() // Error [CS0106, CS0128] { Extern3(x); // Error [CS0103, CS0103] } } // Error [CS1022] """).WithLanguageVersion(LanguageVersion.CSharp7).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NegatedIsExpressionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class NegatedIsExpressionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NegatedIsExpression() => builder.AddPaths("NegatedIsExpression.vb").Verify(); [TestMethod] public void NegatedIsExpression_CodeFix() => builder.WithCodeFix() .AddPaths("NegatedIsExpression.vb") .WithCodeFixedPaths("NegatedIsExpression.Fixed.vb") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NestedCodeBlockTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NestedCodeBlockTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NestedCodeBlock() => builder.AddPaths("NestedCodeBlock.cs").Verify(); [TestMethod] public void NestedCodeBlock_TopLevelStatements() => builder.AddPaths("NestedCodeBlock.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void NestedCodeBlock_CS_Latest() => builder.AddPaths("NestedCodeBlock.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NoExceptionsInFinallyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NoExceptionsInFinallyTest { [TestMethod] public void NoExceptionsInFinally_CS() => new VerifierBuilder().AddPaths("NoExceptionsInFinally.cs").Verify(); [TestMethod] public void NoExceptionsInFinally_VB() => new VerifierBuilder().AddPaths("NoExceptionsInFinally.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NonAsyncTaskShouldNotReturnNullTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NonAsyncTaskShouldNotReturnNullTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NonAsyncTaskShouldNotReturnNull_CS() => builder.AddPaths("NonAsyncTaskShouldNotReturnNull.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void NonAsyncTaskShouldNotReturnNull__CS_Latest() => builder .AddPaths("NonAsyncTaskShouldNotReturnNull.Latest.cs") .AddPaths("NonAsyncTaskShouldNotReturnNull.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void NonAsyncTaskShouldNotReturnNull_VB() => new VerifierBuilder().AddPaths("NonAsyncTaskShouldNotReturnNull.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NonDerivedPrivateClassesShouldBeSealedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NonDerivedPrivateClassesShouldBeSealedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NonDerivedPrivateClassesShouldBeSealed_CS() => builder.AddPaths("NonDerivedPrivateClassesShouldBeSealed.cs", "NonDerivedPrivateClassesShouldBeSealed_PartialClass.cs").Verify(); [TestMethod] public void NonDerivedPrivateClassesShouldBeSealed_CS_Latest() => builder.AddPaths("NonDerivedPrivateClassesShouldBeSealed.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NonFlagsEnumInBitwiseOperationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NonFlagsEnumInBitwiseOperationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NonFlagsEnumInBitwiseOperation() => builder.AddPaths("NonFlagsEnumInBitwiseOperation.cs").AddReferences(MetadataReferenceFacade.SystemComponentModelPrimitives).Verify(); [TestMethod] public void NonFlagsEnumInBitwiseOperation_CodeFix() => builder.WithCodeFix() .AddPaths("NonFlagsEnumInBitwiseOperation.cs") .WithCodeFixedPaths("NonFlagsEnumInBitwiseOperation.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NormalizeStringsToUppercaseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class NormalizeStringsToUppercaseTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NormalizeStringsToUppercase() => builder.AddPaths("NormalizeStringsToUppercase.cs").Verify(); [TestMethod] public void NormalizeStringsToUppercase_CSharp11() => builder.AddPaths("NormalizeStringsToUppercase.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NotAssignedPrivateMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class NotAssignedPrivateMemberTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NotAssignedPrivateMember() => builder.AddPaths("NotAssignedPrivateMember.cs").Verify(); [TestMethod] public void NotAssignedPrivateMember_Latest() => builder .AddPaths("NotAssignedPrivateMember.Latest.cs") .AddPaths("NotAssignedPrivateMember.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void NotAssignedPrivateMember_Razor() => builder.AddPaths("NotAssignedPrivateMember.razor", "NotAssignedPrivateMember.razor.cs").VerifyNoIssues(); [TestMethod] public void NotAssignedPrivateMember_IndexingMovableFixedBuffer() => builder.AddSnippet(""" unsafe struct FixedArray { private fixed int a[42]; // Compliant, because of the fixed modifier private int[] b; // Noncompliant void M() { a[0] = 42; b[0] = 42; } } """).WithLanguageVersion(LanguageVersion.CSharp7_3).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/NumberPatternShouldBeRegularTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class NumberPatternShouldBeRegularTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void NumberPatternShouldBeRegular_BeforeCSharp7() => builder.AddPaths("NumberPatternShouldBeRegular.cs").WithOptions(LanguageOptions.BeforeCSharp7).WithErrorBehavior(CompilationErrorBehavior.Ignore).VerifyNoIssues(); [TestMethod] public void NumberPatternShouldBeRegular_FromCSharp7() => builder.AddPaths("NumberPatternShouldBeRegular.cs").WithOptions(LanguageOptions.FromCSharp7).Verify(); [TestMethod] public void NumberPatternShouldBeRegular_FromCSharp9() => builder.AddPaths("NumberPatternShouldBeRegular.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] [DataRow("1_1_1", "Group size of 1")] [DataRow("123_12", "First group is bigger then the second")] [DataRow("1234_123_123", "First group is bigger then the others")] [DataRow("123_123_12_123", "Another group has a different size")] [DataRow("1_123.123_1234", "Last decimal group is bigger")] [DataRow(".123_123_1234", "No group before the dot")] [DataRow("0xFF_FF_FFF_FF", "3rd group is bigger")] [DataRow("0xFF_FF_FFF", "Last group bigger than 2")] [DataRow("0xFFFF_FFFF_FFFFF", "Last group bigger than 4")] [DataRow("0xFFFF_FFFF_FFFFF", "Last group bigger than 4")] [DataRow("1.234_5678E2", "Exponential format with bigger last group")] [DataRow("____", "Only underscores")] [DataRow("__.__", "Only underscores and a dot")] [DataRow("0xFF___FF___FF", "Multiple _'s as separator")] [DataRow("0xFF________FF___FF", "Multiple irregular _'s as separator")] public void HasIrregularPattern(string numericToken, string message) => Assert.IsTrue(NumberPatternShouldBeRegular.HasIrregularPattern(numericToken), message); [TestMethod] [DataRow(".123_123_123_1", "No group before the dot")] [DataRow("123", "No group character")] [DataRow("1_123_123LU", "With LU suffix")] [DataRow("2_123_123lu", "With lu suffix")] [DataRow("3_123_123lU", "With lU suffix")] [DataRow("1_123_123UL", "With UL suffix")] [DataRow("2_123_123ul", "With ul suffix")] [DataRow("1_123_123L", "With L suffix")] [DataRow("1.1.1", "Two dots")] [DataRow("0b1010_1010", "With binary prefix")] [DataRow("0xFF_FF_12", "With hexadecimal prefix")] [DataRow("0xFF_FF_E2", "With hexadecimal prefix with E but not exponential")] [DataRow("1_123_123", "first group smaller")] [DataRow("123_123_123", "All blocks equal size")] [DataRow("1_123.123_123", "All decimal groups have the same size")] [DataRow("1_123.123_123_12", "Last decimal group is smaller")] [DataRow("1_123.1234567", "Only one group of decimals")] [DataRow("1.234_567E2", "Scientific format with regular size")] [DataRow("1.234_5E2", "Scientific format with smaller last group")] [DataRow("134.45E-2f", "Scientific format f suffix")] [DataRow("134.45E12M", "Scientific format M suffix")] [DataRow("1.0", "Simple floating point")] [DataRow("3D", "Floating point with D suffix")] [DataRow("4d", "Floating point with d suffix")] [DataRow("5M", "Floating point with M suffix")] [DataRow("6m", "Floating point with m suffix")] [DataRow("3_000.5F", "Floating point with group size")] public void HasRegularPattern(string numericToken, string message) => Assert.IsFalse(NumberPatternShouldBeRegular.HasIrregularPattern(numericToken), message); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ObjectCreatedDroppedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ObjectCreatedDroppedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ObjectCreatedDropped() => builder.AddPaths("ObjectCreatedDropped.cs").Verify(); [TestMethod] public void ObjectCreatedDropped_CS_Latest() => builder.AddPaths("ObjectCreatedDropped.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ObjectCreatedDropped_InTest() => builder.AddPaths("ObjectCreatedDropped.cs").AddTestReference().VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ObsoleteAttributesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ObsoleteAttributesTest { private readonly VerifierBuilder explanationNeededCS; private readonly VerifierBuilder explanationNeededVB; private readonly VerifierBuilder removeCS; private readonly VerifierBuilder removeVB; public ObsoleteAttributesTest() { var analyzerCs = new CS.ObsoleteAttributes(); var builderCs = new VerifierBuilder().AddAnalyzer(() => analyzerCs); explanationNeededCS = builderCs.WithOnlyDiagnostics(analyzerCs.ExplanationNeededRule); removeCS = builderCs.WithOnlyDiagnostics(analyzerCs.RemoveRule); var analyzerVb = new VB.ObsoleteAttributes(); var builderVb = new VerifierBuilder().AddAnalyzer(() => analyzerVb); explanationNeededVB = builderVb.WithOnlyDiagnostics(analyzerVb.ExplanationNeededRule); removeVB = builderVb.WithOnlyDiagnostics(analyzerVb.RemoveRule); } [TestMethod] public void ObsoleteAttributesNeedExplanation_CS() => explanationNeededCS.AddPaths("ObsoleteAttributesNeedExplanation.cs").Verify(); [TestMethod] public void ObsoleteAttributesNeedExplanation_CS_Latest() => explanationNeededCS .AddPaths("ObsoleteAttributesNeedExplanation.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void ObsoleteAttributesNeedExplanation_VB14() => explanationNeededVB.AddPaths("ObsoleteAttributesNeedExplanation.VB14.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void ObsoleteAttributesNeedExplanation_VB() => explanationNeededVB.AddPaths("ObsoleteAttributesNeedExplanation.vb").Verify(); [TestMethod] public void RemoveObsoleteCode_CS() => removeCS.AddPaths("RemoveObsoleteCode.cs").Verify(); [TestMethod] public void RemoveObsoleteCode_Latest() => removeCS.AddPaths("RemoveObsoleteCode.Latest.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RemoveObsoleteCode_VB() => removeVB.AddPaths("RemoveObsoleteCode.vb").Verify(); [TestMethod] // All attribute targets of [Obsolete] [DataRow("bool field;")] // AttributeTargets.Field [DataRow("event EventHandler SomeEvent;")] // AttributeTargets.Event [DataRow("bool Prop { get; set; }")] // AttributeTargets.Property [DataRow("void Method() { }")] // AttributeTargets.Method [DataRow("class C { }")] // AttributeTargets.Class [DataRow("struct S { }")] // AttributeTargets.Struct [DataRow("interface I { }")] // AttributeTargets.Interface [DataRow("enum E { A }")] // AttributeTargets.Enum [DataRow("public Test() { }")] // AttributeTargets.Constructor [DataRow("delegate void Del();")] // AttributeTargets.Delegate [DataRow("int this[int i] => 1;")] // Indexer public void RemoveObsoleteCode_AttributeTargetTest_CS(string attributeTargetDeclaration) { removeCS.AddSnippet(WrapInTestCode(string.Empty)).VerifyNoIssues(); removeCS.AddSnippet(WrapInTestCode("[Obsolete] // Noncompliant")).Verify(); removeCS.AddSnippet(WrapInTestCode("[Custom]")).VerifyNoIssues(); removeCS.AddSnippet(WrapInTestCode(""" [Obsolete] // Noncompliant [Custom] """)).Verify(); string WrapInTestCode(string attribute) => $$""" using System; [AttributeUsage(AttributeTargets.All)] public sealed class CustomAttribute: Attribute { } public class Test { {{attribute}} {{attributeTargetDeclaration}} } """; } [TestMethod] // All attribute targets of [Obsolete] [DataRow("Private field As Boolean")] // AttributeTargets.Field [DataRow("Event SomeEvent As EventHandler")] // AttributeTargets.Event [DataRow("Property Prop As Boolean")] // AttributeTargets.Property [DataRow(""" Private Sub Method() End Sub """)] // AttributeTargets.Method [DataRow(""" Class C End Class """)] // AttributeTargets.Class [DataRow(""" Structure S End Structure """)] // AttributeTargets.Struct [DataRow(""" Interface I End Interface """)] // AttributeTargets.Interface [DataRow(""" Enum E A End Enum """)] // AttributeTargets.Enum [DataRow(""" Public Sub New() End Sub """)] // AttributeTargets.Constructor [DataRow("Delegate Sub Del()")] // AttributeTargets.Delegate [DataRow(""" Default ReadOnly Property Item(ByVal i As Integer) As Integer Get Return 1 End Get End Property """)] // Indexer public void RemoveObsoleteCode_AttributeTargetTest_VB(string attributeTargetDeclaration) { removeVB.AddSnippet(WrapInTestCode(string.Empty)).VerifyNoIssues(); removeVB.AddSnippet(WrapInTestCode(" ' Noncompliant")).Verify(); removeVB.AddSnippet(WrapInTestCode("")).VerifyNoIssues(); removeVB.AddSnippet(WrapInTestCode(""" ' Noncompliant """)).Verify(); string WrapInTestCode(string attribute) => $$""" Imports System Public NotInheritable Class CustomAttribute Inherits Attribute End Class Public Class Test {{attribute}} {{attributeTargetDeclaration}} End Class """; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OnErrorStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class OnErrorStatementTest { [TestMethod] public void OnErrorStatement() => new VerifierBuilder().AddPaths("OnErrorStatement.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OperatorOverloadsShouldHaveNamedAlternativesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class OperatorOverloadsShouldHaveNamedAlternativesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OperatorOverloadsShouldHaveNamedAlternatives() => builder.AddPaths("OperatorOverloadsShouldHaveNamedAlternatives.cs").Verify(); [TestMethod] public void OperatorOverloadsShouldHaveNamedAlternatives_CSharp9() => builder.AddPaths("OperatorOverloadsShouldHaveNamedAlternatives.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void OperatorOverloadsShouldHaveNamedAlternatives_CSharp11() => builder.AddPaths("OperatorOverloadsShouldHaveNamedAlternatives.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OperatorsShouldBeOverloadedConsistentlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OperatorsShouldBeOverloadedConsistentlyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OperatorsShouldBeOverloadedConsistently() => builder.AddPaths("OperatorsShouldBeOverloadedConsistently.cs").Verify(); [TestMethod] public void OperatorsShouldBeOverloadedConsistently_CS_Latest() => builder.AddPaths("OperatorsShouldBeOverloadedConsistently.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionExplicitOnTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OptionExplicitOnTest { [TestMethod] public void OptionExplicitOn_IsOffForProject() => CreateBuilder("' Noncompliant ^1#0 {{Configure 'Option Explicit On' for assembly 'project0'.}}", false).Verify(); [TestMethod] public void OptionExplicitOn_IsOff() => CreateBuilder("Option Explicit Off ' Noncompliant ^1#19 {{Change this to 'Option Explicit On'.}}", true).Verify(); [TestMethod] public void OptionExplicitOn_IsOn() => CreateBuilder("Option Explicit On", true).VerifyNoIssues(); [TestMethod] public void OptionExplicitOn_IsMissing() => CreateBuilder("Option Strict Off", true).VerifyNoIssues(); [TestMethod] public void OptionExplicitOn_Concurrent() => CreateBuilder(false) .AddSnippet("' Noncompliant ^1#0 {{Configure 'Option Explicit On' for assembly 'project0'.}}") .AddSnippet("Option Explicit Off ' Noncompliant ^1#19 {{Change this to 'Option Explicit On'.}}") .WithConcurrentAnalysis(true) .Verify(); private static VerifierBuilder CreateBuilder(string snippet, bool optionExplicit) => CreateBuilder(optionExplicit).AddSnippet(snippet); private static VerifierBuilder CreateBuilder(bool optionExplicit) => new VerifierBuilder().WithCompilationOptionsCustomization(x => ((VisualBasicCompilationOptions)x).WithOptionExplicit(optionExplicit)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionStrictOnTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class OptionStrictOnTest { [TestMethod] public void OptionStrictOn_IsOff_ForProject() => CreateBuilder("' Noncompliant ^1#0 {{Configure 'Option Strict On' for assembly 'project0'.}}", OptionStrict.Off).Verify(); [TestMethod] public void OptionStrictOn_IsCustom_ForProject() => CreateBuilder("' Noncompliant ^1#0 {{Configure 'Option Strict On' for assembly 'project0'.}}", OptionStrict.Custom).Verify(); [TestMethod] public void OptionStrictOn_IsOff() => CreateBuilder("Option Strict Off ' Noncompliant ^1#17 {{Change this to 'Option Strict On'.}}", OptionStrict.On).Verify(); [TestMethod] public void OptionStrictOn_IsOn() => CreateBuilder("Option Strict On ' Compliant", OptionStrict.On).VerifyNoIssues(); [TestMethod] public void OptionStrictOn_Concurrent() => CreateBuilder(OptionStrict.On) .AddSnippet("Option Strict Off ' Noncompliant ^1#17 {{Change this to 'Option Strict On'.}}") .AddSnippet("Option Strict On ' Compliant") .WithConcurrentAnalysis(true) .Verify(); private static VerifierBuilder CreateBuilder(string snippet, OptionStrict optionStrict) => CreateBuilder(optionStrict).AddSnippet(snippet); private static VerifierBuilder CreateBuilder(OptionStrict optionStrict) => new VerifierBuilder().WithCompilationOptionsCustomization(x => ((VisualBasicCompilationOptions)x).WithOptionStrict(optionStrict)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionalParameterNotPassedToBaseCallTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OptionalParameterNotPassedToBaseCallTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void OptionalParameterNotPassedToBaseCall_CS() => builderCS.AddPaths("OptionalParameterNotPassedToBaseCall.cs").Verify(); [TestMethod] public void OptionalParameterNotPassedToBaseCall_CS_Latest() => builderCS.AddPaths("OptionalParameterNotPassedToBaseCall.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void OptionalParameterNotPassedToBaseCall_VB() => builderVB.AddPaths("OptionalParameterNotPassedToBaseCall.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionalParameterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OptionalParameterTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void OptionalParameter_CS() => builderCS.AddPaths("OptionalParameter.cs").Verify(); [TestMethod] public void OptionalParameter_VB() => builderVB.AddPaths("OptionalParameter.vb").Verify(); #if NET [TestMethod] public void OptionalParameter_CS_Web() => builderCS.AddPaths("OptionalParameter.Web.cs") .AddReferences(AspNetCoreMetadataReference.BasicReferences).Verify(); [TestMethod] public void OptionalParameter_CSharp10() => builderCS.AddPaths("OptionalParameter.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).VerifyNoIssues(); [TestMethod] public void OptionalParameter_CSharp11() => builderCS.AddPaths("OptionalParameter.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionalParameterWithDefaultValueTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OptionalParameterWithDefaultValueTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OptionalParameterWithDefaultValue() => builder.AddPaths("OptionalParameterWithDefaultValue.cs").Verify(); [TestMethod] public void OptionalParameterWithDefaultValue_CS_Latest() => builder.AddPaths("OptionalParameterWithDefaultValue.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void OptionalParameterWithDefaultValue_CodeFix() => builder.WithCodeFix() .AddPaths("OptionalParameterWithDefaultValue.cs") .WithCodeFixedPaths("OptionalParameterWithDefaultValue.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OptionalRefOutParameterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OptionalRefOutParameterTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OptionalRefOutParameter() => builder.AddPaths("OptionalRefOutParameter.cs").Verify(); [TestMethod] public void OptionalRefOutParameter_TopLevelStatements() => builder.AddPaths("OptionalRefOutParameter.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void OptionalRefOutParameter_CS_Latest() => builder.AddPaths("OptionalRefOutParameter.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void OptionalRefOutParameter_CodeFix() => builder.WithCodeFix() .AddPaths("OptionalRefOutParameter.cs") .WithCodeFixedPaths("OptionalRefOutParameter.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OrderByRepeatedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OrderByRepeatedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OrderByRepeated() => builder.AddPaths("OrderByRepeated.cs").Verify(); [TestMethod] public void OrderByRepeated_CodeFix() => builder .WithCodeFix() .AddPaths("OrderByRepeated.cs") .WithCodeFixedPaths("OrderByRepeated.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/OverrideGetHashCodeOnOverridingEqualsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class OverrideGetHashCodeOnOverridingEqualsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void OverrideGetHashCodeOnOverridingEquals() => builder.AddPaths("OverrideGetHashCodeOnOverridingEquals.cs").Verify(); [TestMethod] public void OverrideGetHashCodeOnOverridingEquals_CS_Latest() => builder.AddPaths("OverrideGetHashCodeOnOverridingEquals.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PInvokesShouldNotBeVisibleTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PInvokesShouldNotBeVisibleTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PInvokesShouldNotBeVisible() => builder.AddPaths("PInvokesShouldNotBeVisible.cs").Verify(); [TestMethod] public void PInvokesShouldNotBeVisible_CSharp9() => builder.AddPaths("PInvokesShouldNotBeVisible.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void PInvokesShouldNotBeVisible_CSharp11() => builder.AddPaths("PInvokesShouldNotBeVisible.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterAssignedToTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParameterAssignedToTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ParameterAssignedTo_CS() => builderCS.AddPaths("ParameterAssignedTo.cs").Verify(); [TestMethod] public void ParameterAssignedTo_CS_Latest() => builderCS .AddPaths("ParameterAssignedTo.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void ParameterAssignedTo_VB() => builderVB.AddPaths("ParameterAssignedTo.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterNameMatchesOriginalTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParameterNameMatchesOriginalTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ParameterNameMatchesOriginal_CS() => builderCS.AddPaths("ParameterNameMatchesOriginal.cs") .AddReferences(MetadataReferenceFacade.NetStandard21) .Verify(); [TestMethod] public void ParameterNameMatchesOriginal_CS_Latest() => builderCS .AddPaths("ParameterNameMatchesOriginal.Latest.cs") .AddPaths("ParameterNameMatchesOriginal.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ParameterNameMatchesOriginal_VB() => builderVB.AddPaths("ParameterNameMatchesOriginal.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParameterNameTest { [TestMethod] public void ParameterName() => new VerifierBuilder().AddPaths("ParameterName.vb").Verify(); [TestMethod] public void ParameterName_CustomPattern() => new VerifierBuilder().AddAnalyzer(() => new ParameterName { Pattern = "^Prefix[A-Z].*" }).AddPaths("ParameterName.CustomPattern.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterNamesShouldNotDuplicateMethodNamesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ParameterNamesShouldNotDuplicateMethodNamesTest { [TestMethod] public void ParameterNamesShouldNotDuplicateMethodNames_CSharp8() => new VerifierBuilder().AddPaths("ParameterNamesShouldNotDuplicateMethodNames.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterTypeShouldMatchRouteTypeConstraintTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParameterTypeShouldMatchRouteTypeConstraintTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void ParameterTypeShouldMatchRouteTypeConstraint_Blazor() => builder.AddPaths("ParameterTypeShouldMatchRouteTypeConstraint.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void ParameterTypeShouldMatchRouteTypeConstraint_Partial() => builder.AddPaths("ParameterTypeShouldMatchRouteTypeConstraint.Partial.razor", "ParameterTypeShouldMatchRouteTypeConstraint.Partial.razor.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void ParameterTypeShouldMatchRouteTypeConstraint_CS() => builder.AddPaths("ParameterTypeShouldMatchRouteTypeConstraint.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreComponents("7.0.13")) .Verify(); [TestMethod] public void ParameterTypeShouldMatchRouteTypeConstraint_CS_Latest() => builder.AddPaths("ParameterTypeShouldMatchRouteTypeConstraint.cs") .AddReferences(NuGetMetadataReference.MicrosoftAspNetCoreComponents("7.0.13")) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ParameterTypeShouldMatchRouteTypeConstraint_Conversion() => builder.AddPaths("ParameterTypeShouldMatchRouteTypeConstraint.Conversion.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterValidationInAsyncShouldBeWrappedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ParameterValidationInAsyncShouldBeWrappedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ParameterValidationInAsyncShouldBeWrapped() => builder.AddPaths("ParameterValidationInAsyncShouldBeWrapped.cs").Verify(); [TestMethod] public void ParameterValidationInAsyncShouldBeWrapped_CSharp10() => builder.AddPaths("ParameterValidationInAsyncShouldBeWrapped.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void ParameterValidationInAsyncShouldBeWrapped_CSharp11() => builder.AddPaths("ParameterValidationInAsyncShouldBeWrapped.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParameterValidationInYieldShouldBeWrappedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParameterValidationInYieldShouldBeWrappedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ParameterValidationInYieldShouldBeWrapped() => builder.AddPaths("ParameterValidationInYieldShouldBeWrapped.cs").Verify(); [TestMethod] public void ParameterValidationInYieldShouldBeWrapped_CS_Latest() => builder.AddPaths("ParameterValidationInYieldShouldBeWrapped.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ParametersCorrectOrderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ParametersCorrectOrderTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ParametersCorrectOrder() => builderCS.AddPaths("ParametersCorrectOrder.cs").Verify(); [TestMethod] public void ParametersCorrectOrder_CS_Latest() => builderCS.AddPaths("ParametersCorrectOrder.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ParametersCorrectOrder_InvalidCode_CS() => builderCS.AddSnippet(""" public class Foo { public void Bar() { new Foo new () new System. () } } """).VerifyNoIssuesIgnoreErrors(); [TestMethod] public void ParametersCorrectOrder_VB() => builderVB.AddPaths("ParametersCorrectOrder.vb").Verify(); [TestMethod] public void ParametersCorrectOrder_InvalidCode_VB() => builderVB.AddSnippet(""" Public Class Foo Public Sub Bar() Dim x = New () Dim y = New System. () End Sub End Class """).VerifyNoIssuesIgnoreErrors(); [TestMethod] public async Task ParametersCorrectOrder_SecondaryLocationsOutsideCurrentCompilation() { var library = TestCompiler.CompileCS(""" public static class Library { public static void Method(int a, int b) { } } """).Model.Compilation; var usage = TestCompiler.CompileCS(""" public class Usage { public void Method() { int a = 4, b = 2; Library.Method(b, a); } } """, library.ToMetadataReference()).Model.Compilation; var diagnostics = await usage.WithAnalyzers([new CS.ParametersCorrectOrder()]).GetAnalyzerDiagnosticsAsync(); diagnostics.Should().ContainSingle().Which.Id.Should().Be("S2234", "we don't want AD0001 here"); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PartCreationPolicyShouldBeUsedWithExportAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PartCreationPolicyShouldBeUsedWithExportAttributeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition); [TestMethod] public void PartCreationPolicyShouldBeUsedWithExportAttribute_CS() => builderCS.AddPaths("PartCreationPolicyShouldBeUsedWithExportAttribute.cs").WithConcurrentAnalysis(false).Verify(); [TestMethod] public void PartCreationPolicyShouldBeUsedWithExportAttribute_UnresolvedSymbol_CS() => builderCS.AddSnippet(""" [UnresolvedAttribute] // Error [CS0246, CS0246] class Bar { } """) .Verify(); [TestMethod] public void PartCreationPolicyShouldBeUsedWithExportAttribute_CS_Latest() => builderCS.AddPaths("PartCreationPolicyShouldBeUsedWithExportAttribute.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PartCreationPolicyShouldBeUsedWithExportAttribute_VB() => new VerifierBuilder().AddPaths("PartCreationPolicyShouldBeUsedWithExportAttribute.vb") .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PartialMethodNoImplementationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PartialMethodNoImplementationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PartialMethodNoImplementation() => builder.AddPaths("PartialMethodNoImplementation.cs").Verify(); [TestMethod] public void PartialMethodNoImplementation_CS_Latest() => builder.AddPaths("PartialMethodNoImplementation.Latest.cs", "PartialMethodNoImplementation.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PasswordsShouldBeStoredCorrectlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PasswordsShouldBeStoredCorrectlyTest { private static readonly VerifierBuilder Builder = new VerifierBuilder(); #if NET [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_Core() => Builder .WithOptions(LanguageOptions.FromCSharp8) .AddPaths("PasswordsShouldBeStoredCorrectly.Core.cs") .AddReferences([ AspNetCoreMetadataReference.MicrosoftExtensionsIdentityCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreCryptographyKeyDerivation, ..MetadataReferenceFacade.SystemSecurityCryptography, ]) .Verify(); #endif [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_PasswordHasherOptions() => Builder .WithOptions(LanguageOptions.FromCSharp9) .AddReferences(NuGetMetadataReference.MicrosoftAspNetIdentity()) .AddSnippet(""" using Microsoft.AspNet.Identity; class Testcases { void Method() { var _ = new PasswordHasherOptions(); // Noncompliant {{PasswordHasher does not support state-of-the-art parameters. Use Rfc2898DeriveBytes instead.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PasswordHasherOptions x = new(); // Noncompliant // ^^^^^ _ = new PasswordHasherOptions() {}; // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ = new PasswordHasherOptions { IterationCount = 42 }; // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ = new Derived(); // Noncompliant // ^^^^^^^^^^^^^ } } public class Derived: PasswordHasherOptions { } """) .Verify(); [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_Rfc2898DeriveBytes() => Builder .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .AddSnippet(""" using System.Security.Cryptography; class Testcases { const int ITERATIONS = 100_000; void Method(int iterations, byte[] bs, HashAlgorithmName han) { new Rfc2898DeriveBytes("password", bs); // Noncompliant new Rfc2898DeriveBytes("password", 42); // Noncompliant new Rfc2898DeriveBytes(bs, bs, 42); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new Rfc2898DeriveBytes(iterations: 42, salt: bs, password: bs); // Noncompliant {{Use at least 100,000 iterations and a state-of-the-art digest algorithm here.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new Rfc2898DeriveBytes("password", bs, 42); // Noncompliant new Rfc2898DeriveBytes("password", 42, 42); // Noncompliant new Rfc2898DeriveBytes(bs, bs, 42, han); // Noncompliant {{Use at least 100,000 iterations here.}} // ^^ new Rfc2898DeriveBytes("password", bs, 42, han); // Noncompliant new Rfc2898DeriveBytes("password", 42, 42, han); // Noncompliant new Rfc2898DeriveBytes(bs, bs, ITERATIONS); // Noncompliant new Rfc2898DeriveBytes("", bs, ITERATIONS); // Noncompliant new Rfc2898DeriveBytes("", 42, ITERATIONS); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ new Rfc2898DeriveBytes(bs, bs, iterations, han); // Compliant new Rfc2898DeriveBytes(bs, bs, ITERATIONS, han); // Compliant new Rfc2898DeriveBytes("", bs, ITERATIONS, han); // Compliant new Rfc2898DeriveBytes("", 42, ITERATIONS, han); // Compliant var x = new Rfc2898DeriveBytes(bs, bs, ITERATIONS, han); x.IterationCount = 1; // Noncompliant {{Use at least 100,000 iterations here.}} // ^^^^^^^^^^^^^^^^ x.IterationCount = 100_042; // Compliant new Rfc2898DeriveBytes(bs, bs, ITERATIONS, han) { IterationCount = 42 // Noncompliant {{Use at least 100,000 iterations here.}} // ^^^^^^^^^^^^^^ }; } void MakeItUnsafe(Rfc2898DeriveBytes password) { password.IterationCount = 1; // Noncompliant {{Use at least 100,000 iterations here.}} // ^^^^^^^^^^^^^^^^^^^^^^^ } } """) .Verify(); [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_BouncyCastle_Generate() => Builder .AddReferences(NuGetMetadataReference.BouncyCastle()) .AddSnippet(""" using Org.BouncyCastle.Crypto.Generators; class Testcases { const int COST = 12; void Method(char[] cs, byte[] bs) { OpenBsdBCrypt.Generate(cs, bs, 4); // Noncompliant {{Use a cost factor of at least 12 here.}} OpenBsdBCrypt.Generate(cost: 4, password: cs, salt: bs); // Noncompliant OpenBsdBCrypt.Generate("", cs, bs, 4); // Noncompliant // ^ OpenBsdBCrypt.Generate( cost: 4, // Noncompliant // ^^^^^^^ version: "", password: cs, salt: bs); OpenBsdBCrypt.Generate(cs, bs, COST); // Compliant OpenBsdBCrypt.Generate(cs, bs, 42); // Compliant OpenBsdBCrypt.Generate("", cs, bs, COST); // Compliant OpenBsdBCrypt.Generate("", cs, bs, 42); // Compliant OpenBsdBCrypt.Generate( cost: 42, // Compliant version: "", password: cs, salt: bs); BCrypt.Generate(bs, bs, 4); // Noncompliant // ^ BCrypt.Generate(bs, bs, COST); // Compliant } } """) .Verify(); [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_BouncyCastle_Init() => Builder .AddReferences(NuGetMetadataReference.BouncyCastle()) .AddSnippet(""" using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; class Testcases { const int ITERATIONS = 100_000; void Method(byte[] bs, PbeParametersGenerator baseGen, Pkcs5S2ParametersGenerator gen, int iterations) { baseGen.Init(bs, bs, 42); // Noncompliant {{Use at least 100,000 iterations here.}} // ^^ gen.Init(iterationCount: 42, password: bs, salt: bs); // Noncompliant // ^^^^^^^^^^^^^^^^^^ baseGen.Init(bs, bs, ITERATIONS); // Compliant gen.Init(iterationCount: iterations, password: bs, salt: bs); // Compliant } } """) .Verify(); [TestMethod] public void PasswordsShouldBeStoredCorrectly_CS_BouncyCastle_Generate_SCrypt() => Builder .AddReferences(NuGetMetadataReference.BouncyCastle()) .AddSnippet(""" using Org.BouncyCastle.Crypto.Generators; class Testcases { void Method(byte[] bs, int p) { SCrypt.Generate(bs, bs, 1 << 12, 8, p, 32); // Compliant SCrypt.Generate(bs, bs, 1 << 11, 42, p, 42); // Noncompliant {{Use a cost factor of at least 2 ^ 12 for N here.}} // ^^^^^^^ SCrypt.Generate(bs, bs, 1 << 12, 7, p, 42); // Noncompliant {{Use a memory factor of at least 8 for r here.}} // ^ SCrypt.Generate(bs, bs, 1 << 12, 42, p, 31); // Noncompliant {{Use an output length of at least 32 for dkLen here.}} // ^^ SCrypt.Generate(bs, bs, 1 << 11, 7, p, 31); // ^^^^^^^ {{Use a cost factor of at least 2 ^ 12 for N here.}} // ^ @-1 {{Use a memory factor of at least 8 for r here.}} // ^^ @-2 {{Use an output length of at least 32 for dkLen here.}} } } """) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PointersShouldBePrivateTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PointersShouldBePrivateTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PointersShouldBePrivate() => builder.AddPaths("PointersShouldBePrivate.cs").Verify(); [TestMethod] public void PointersShouldBePrivate_CSharp9() => builder.AddPaths("PointersShouldBePrivate.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void PointersShouldBePrivate_CSharp11() => builder.AddPaths("PointersShouldBePrivate.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PreferGuidEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PreferGuidEmptyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().WithOptions(LanguageOptions.FromCSharp8); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void PreferGuidEmpty_CS() => builderCS.AddPaths("PreferGuidEmpty.cs").Verify(); [TestMethod] public void PreferGuidEmpty_CS_Latest() => builderCS.AddPaths("PreferGuidEmpty.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PreferGuidEmpty_VB() => builderVB.AddPaths("PreferGuidEmpty.vb").Verify(); [TestMethod] public void PreferGuidEmpty_CodeFix_CS() => builderCS.AddPaths("PreferGuidEmpty.cs").WithCodeFix().WithCodeFixedPaths("PreferGuidEmpty.Fixed.cs").VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PreferJaggedArraysOverMultidimensionalTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PreferJaggedArraysOverMultidimensionalTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PreferJaggedArraysOverMultidimensional() => builder.AddPaths("PreferJaggedArraysOverMultidimensional.cs").Verify(); [TestMethod] public void PreferJaggedArraysOverMultidimensional_Latest() => builder .AddPaths("PreferJaggedArraysOverMultidimensional.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PrivateConstantFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PrivateConstantFieldNameTest { [TestMethod] public void PrivateConstantFieldName() => new VerifierBuilder().AddPaths("PrivateConstantFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PrivateFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PrivateFieldNameTest { [TestMethod] public void PrivateFieldName() => new VerifierBuilder().AddPaths("PrivateFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PrivateFieldUsedAsLocalVariableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PrivateFieldUsedAsLocalVariableTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PrivateFieldUsedAsLocalVariable() => builder.AddPaths("PrivateFieldUsedAsLocalVariable.cs") .Verify(); [TestMethod] public void PrivateFieldUsedAsLocalVariable_CS_Latest() => builder.AddPaths("PrivateFieldUsedAsLocalVariable.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PrivateSharedReadonlyFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PrivateSharedReadonlyFieldNameTest { [TestMethod] public void PrivateSharedReadonlyFieldName() => new VerifierBuilder().AddPaths("PrivateSharedReadonlyFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PrivateStaticMethodUsedOnlyByNestedClassTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PrivateStaticMethodUsedOnlyByNestedClassTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PrivateStaticMethodUsedOnlyByNestedClass_CS() => builder .AddPaths("PrivateStaticMethodUsedOnlyByNestedClass.cs") .Verify(); [TestMethod] public void PrivateStaticMethodUsedOnlyByNestedClass_CS_Latest() => builder .AddPaths("PrivateStaticMethodUsedOnlyByNestedClass.CSharpLatest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertiesAccessCorrectFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PropertiesAccessCorrectFieldTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); private static IEnumerable AdditionalReferences => NuGetMetadataReference.MvvmLightLibs("5.4.1.1") .Concat(MetadataReferenceFacade.WindowsBase) .Concat(MetadataReferenceFacade.PresentationFramework); [TestMethod] public void PropertiesAccessCorrectField_CS() => builderCS.AddPaths("PropertiesAccessCorrectField.cs").AddReferences(AdditionalReferences).Verify(); [TestMethod] public void PropertiesAccessCorrectField_CS_Latest() => builderCS.AddPaths("PropertiesAccessCorrectField.Latest.cs", "PropertiesAccessCorrectField.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PropertiesAccessCorrectField_CS_NetFramework() => builderCS.AddPaths("PropertiesAccessCorrectField.NetFramework.cs").AddReferences(AdditionalReferences).WithNetFrameworkOnly().VerifyNoIssues(); [TestMethod] public void PropertiesAccessCorrectField_VB_NetFramework() => builderVB.AddPaths("PropertiesAccessCorrectField.NetFramework.vb").AddReferences(AdditionalReferences).WithNetFrameworkOnly().VerifyNoIssues(); [TestMethod] public void PropertiesAccessCorrectField_VB() => builderVB.AddPaths("PropertiesAccessCorrectField.vb").AddReferences(AdditionalReferences).WithOptions(LanguageOptions.FromVisualBasic14).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertiesShouldBePreferredTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PropertiesShouldBePreferredTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PropertiesShouldBePreferred() => builder.AddPaths("PropertiesShouldBePreferred.cs").AddReferences(MetadataReferenceFacade.SystemThreadingTasks).Verify(); [TestMethod] public void PropertiesShouldBePreferred_CSharp9() => builder.AddPaths("PropertiesShouldBePreferred.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void PropertiesShouldBePreferred_CSharp10() => builder.AddPaths("PropertiesShouldBePreferred.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void PropertiesShouldBePreferred_CSharp11() => builder.AddPaths("PropertiesShouldBePreferred.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyGetterWithThrowTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PropertyGetterWithThrowTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void PropertyGetterWithThrow_CS() => builderCS.AddPaths("PropertyGetterWithThrow.cs").Verify(); [TestMethod] public void PropertyGetterWithThrow_VB() => new VerifierBuilder().AddPaths("PropertyGetterWithThrow.vb").Verify(); [TestMethod] public void PropertyGetterWithThrow_CS_Latest() => builderCS.AddPaths("PropertyGetterWithThrow.Latest.cs", "PropertyGetterWithThrow.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PropertyNameTest { [TestMethod] public void PropertyName() => new VerifierBuilder().AddPaths("PropertyName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyNamesShouldNotMatchGetMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PropertyNamesShouldNotMatchGetMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PropertyNamesShouldNotMatchGetMethods() => builder.AddPaths("PropertyNamesShouldNotMatchGetMethods.cs").Verify(); [TestMethod] public void PropertyNamesShouldNotMatchGetMethods_InvalidCode() => builder.AddSnippet(""" public class Sample { // Missing identifier on purpose public int { get; } public int () { return 42; } } """).VerifyNoIssuesIgnoreErrors(); [TestMethod] public void PropertyNamesShouldNotMatchGetMethods_Latest() => builder .AddPaths( "PropertyNamesShouldNotMatchGetMethods.Latest.cs", "PropertyNamesShouldNotMatchGetMethods.Latest.Partial1.g.cs", "PropertyNamesShouldNotMatchGetMethods.Latest.Partial2.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyToAutoPropertyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PropertyToAutoPropertyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void PropertyToAutoProperty() => builder.AddPaths("PropertyToAutoProperty.cs").Verify(); [TestMethod] public void PropertyToAutoProperty_Latest() => builder.AddPaths("PropertyToAutoProperty.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyWithArrayTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PropertyWithArrayTypeTest { [TestMethod] public void PropertyWithArrayType() => new VerifierBuilder().AddPaths("PropertyWithArrayType.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PropertyWriteOnlyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PropertyWriteOnlyTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void PropertyWriteOnly_CS() => builderCS.AddPaths("PropertyWriteOnly.cs").Verify(); [TestMethod] public void PropertyWriteOnly_CS_Latest() => builderCS.AddPaths("PropertyWriteOnly.Latest.cs", "PropertyWriteOnly.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PropertyWriteOnly_VB() => new VerifierBuilder().AddPaths("PropertyWriteOnly.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ProvideDeserializationMethodsForOptionalFieldsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ProvideDeserializationMethodsForOptionalFieldsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ProvideDeserializationMethodsForOptionalFields_CS() => builderCS.AddPaths("ProvideDeserializationMethodsForOptionalFields.cs").Verify(); [TestMethod] public void ProvideDeserializationMethodsForOptionalFields_CS_Latest() => builderCS.AddPaths("ProvideDeserializationMethodsForOptionalFields.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ProvideDeserializationMethodsForOptionalFields_VB() => new VerifierBuilder().AddPaths("ProvideDeserializationMethodsForOptionalFields.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PublicConstantFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PublicConstantFieldNameTest { [TestMethod] public void PublicConstantFieldName() => new VerifierBuilder().AddPaths("PublicConstantFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PublicConstantFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PublicConstantFieldTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void PublicConstantField_CSharp() => builderCS.AddPaths("PublicConstantField.cs").Verify(); [TestMethod] public void PublicConstantField_CSharp9() => builderCS.AddPaths("PublicConstantField.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void PublicConstantField_CSharp10() => builderCS.AddPaths("PublicConstantField.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void PublicConstantField_VB() => new VerifierBuilder().AddPaths("PublicConstantField.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PublicFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PublicFieldNameTest { [TestMethod] public void PublicFieldName() => new VerifierBuilder().AddPaths("PublicFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PublicMethodWithMultidimensionalArrayTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PublicMethodWithMultidimensionalArrayTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void PublicMethodWithMultidimensionalArray_CS() => builderCS.AddPaths("PublicMethodWithMultidimensionalArray.cs").Verify(); [TestMethod] public void PublicMethodWithMultidimensionalArray_CS_Latest() => builderCS.AddPaths("PublicMethodWithMultidimensionalArray.Latest.cs", "PublicMethodWithMultidimensionalArray.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PublicMethodWithMultidimensionalArray_VB() => builderVB.AddPaths("PublicMethodWithMultidimensionalArray.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PublicSharedReadonlyFieldNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class PublicSharedReadonlyFieldNameTest { [TestMethod] public void PublicSharedReadonlyFieldName() => new VerifierBuilder().AddPaths("PublicSharedReadonlyFieldName.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/PureAttributeOnVoidMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class PureAttributeOnVoidMethodTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void PureAttributeOnVoidMethod_CS() => builderCS.AddPaths("PureAttributeOnVoidMethod.cs").Verify(); [TestMethod] public void PureAttributeOnVoidMethod_CSharpLatest() => builderCS.AddPaths("PureAttributeOnVoidMethod.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void PureAttributeOnVoidMethod_TopLevelStatements() => builderCS.AddPaths("PureAttributeOnVoidMethod.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void PureAttributeOnVoidMethod_VB() => new VerifierBuilder().AddPaths("PureAttributeOnVoidMethod.vb").Verify(); [TestMethod] public void PureAttributeOnVoidMethod_CSharp7() => builderCS.AddPaths("PureAttributeOnVoidMethod.CSharp7.cs").WithOptions(LanguageOptions.OnlyCSharp7).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundancyInConstructorDestructorDeclarationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundancyInConstructorDestructorDeclarationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFixBuilderRemoveBaseCall = new VerifierBuilder() .WithCodeFix() .WithCodeFixTitle(RedundancyInConstructorDestructorDeclarationCodeFix.TitleRemoveBaseCall); private readonly VerifierBuilder codeFixBuilderRemoveConstructor = new VerifierBuilder() .WithCodeFix() .WithCodeFixTitle(RedundancyInConstructorDestructorDeclarationCodeFix.TitleRemoveConstructor); [TestMethod] public void RedundancyInConstructorDestructorDeclaration() => builder.AddPaths("RedundancyInConstructorDestructorDeclaration.cs").Verify(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CSharp9() => builder.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CSharp10() => builder.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CSharp11() => builder.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CSharp12() => builder.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12) .Verify(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_CSharp9() => codeFixBuilderRemoveBaseCall.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp9.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.CSharp9.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp9) .VerifyCodeFix(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_CSharp10() => codeFixBuilderRemoveConstructor.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp10.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.CSharp10.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp10) .VerifyCodeFix(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_CSharp11() => codeFixBuilderRemoveConstructor.AddPaths("RedundancyInConstructorDestructorDeclaration.CSharp11.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.CSharp11.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp11) .VerifyCodeFix(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_BaseCall() => codeFixBuilderRemoveBaseCall.AddPaths("RedundancyInConstructorDestructorDeclaration.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.BaseCall.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_Constructor() => codeFixBuilderRemoveConstructor.AddPaths("RedundancyInConstructorDestructorDeclaration.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.Constructor.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void RedundancyInConstructorDestructorDeclaration_CodeFix_Destructor() => new VerifierBuilder() .WithCodeFix() .AddPaths("RedundancyInConstructorDestructorDeclaration.cs") .WithCodeFixedPaths("RedundancyInConstructorDestructorDeclaration.Destructor.Fixed.cs") .WithCodeFixTitle(RedundancyInConstructorDestructorDeclarationCodeFix.TitleRemoveDestructor) .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantArgumentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RedundantArgumentTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFixBuilder = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantArgument() => builder.AddPaths("RedundantArgument.cs").Verify(); [TestMethod] public void RedundantArgument_TopLevelStatements() => builder.AddPaths("RedundantArgument.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void RedundantArgument_Latest() => builder.AddPaths("RedundantArgument.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void RedundantArgument_CodeFix_No_Named_Arguments() => codeFixBuilder.AddPaths("RedundantArgument.cs").WithCodeFixedPaths("RedundantArgument.NoNamed.Fixed.cs").WithCodeFixTitle(RedundantArgumentCodeFix.TitleRemove).VerifyCodeFix(); [TestMethod] public void RedundantArgument_CodeFix_Named_Arguments() => codeFixBuilder.AddPaths("RedundantArgument.cs").WithCodeFixedPaths("RedundantArgument.Named.Fixed.cs").WithCodeFixTitle(RedundantArgumentCodeFix.TitleRemoveWithNameAdditions).VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantCastTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RedundantCastTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private static IEnumerable<(string Snippet, bool CompliantWithFlowState, bool CompliantWithoutFlowState)> NullableTestData => [ ("""_ = (string)"Test";""", false, false), ("""_ = (string?)"Test";""", true, false), ("""_ = (string)null;""", true, true), ("""_ = (string?)null;""", true, true), ("""_ = (string)nullable;""", true, false), ("""_ = (string?)nullable;""", false, false), ("""_ = (string)nonNullable;""", false, false), ("""_ = (string?)nonNullable;""", true, false), ("""_ = nullable as string;""", true, false), ("""_ = nonNullable as string;""", false, false), ("""if (nullable != null) _ = (string)nullable;""", false, false), ("""if (nullable != null) _ = (string?)nullable;""", true, false), ("""if (nullable != null) _ = nullable as string;""", false, false), ("""if (nonNullable == null) _ = (string)nonNullable;""", true, false), ("""if (nonNullable == null) _ = (string?)nonNullable;""", false, false), ("""if (nonNullable == null) _ = nonNullable as string;""", true, false), ("""_ = (string)default!;""", true, true), ("""_ = (string)(default)!;""", true, true), ("""_ = (string)((default)!);""", true, true), ("""_ = (string)(default!);""", true, true), ("""_ = (string)(default(string));""", true, false), ("""_ = (string)(default(string)!);""", false, false), ("""_ = (string?)(default(string));""", false, false), ("""_ = (string?)(default(string)!);""", true, false), ]; private static IEnumerable NullableTestDataWithFlowState => NullableTestData.Select(x => new object[] { x.Snippet, x.CompliantWithFlowState }); private static IEnumerable NullableTestDataWithoutFlowState => NullableTestData.Select(x => new object[] { x.Snippet, x.CompliantWithoutFlowState }); [TestMethod] public void RedundantCast() => builder.AddPaths("RedundantCast.cs").Verify(); [TestMethod] public void RedundantCast_Latest() => builder.AddPaths("RedundantCast.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void RedundantCast_CodeFix() => builder.AddPaths("RedundantCast.cs").WithCodeFix().WithCodeFixedPaths("RedundantCast.Fixed.cs").VerifyCodeFix(); [TestMethod] public void RedundantCast_DefaultLiteral() => builder.AddSnippet(""" using System; public static class MyClass { public static void RunAction(Action action) { bool myBool = (bool)default; // FN - the cast is unneeded RunFunc(() => { action(); return default; }, (bool)default); // should not raise because of the generic the cast is mandatory RunFunc(() => { action(); return default; }, (bool)default); // FN - the cast is unneeded } public static T RunFunc(Func func, T returnValue = default) => returnValue; } """).WithLanguageVersion(LanguageVersion.CSharp7_1).VerifyNoIssues(); [TestMethod] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", true)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable>", "IEnumerable", true)] [DataRow("IEnumerable", "IEnumerable>", true)] [DataRow("IEnumerable>", "IEnumerable>", true)] [DataRow("IDictionary>", "IDictionary>", false)] [DataRow("IDictionary>?", "IDictionary>?", false)] [DataRow("IDictionary>?", "IDictionary>?", true)] [DataRow("int?", "Nullable", true)] [DataRow("Nullable", "int?", true)] [DataRow("IEnumerable?>", "IEnumerable?>", true)] [DataRow("IEnumerable?>", "IEnumerable>", false)] [DataRow("IEnumerable>", "IEnumerable?>", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable>", "IEnumerable", true)] [DataRow("IEnumerable", "IEnumerable>", true)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("IEnumerable", "IEnumerable", false)] [DataRow("List", "IntList", true)] [DataRow("IntList", "List", true)] [DataRow("List", "StringList", true)] [DataRow("StringList", "List", true)] [DataRow("List", "StringList", false)] [DataRow("StringList", "List", false)] [DataRow("List", "NullableStringList", true)] [DataRow("NullableStringList", "List", true)] [DataRow("List", "NullableStringList", false)] [DataRow("NullableStringList", "List", false)] [DataRow("string?", "string?", true)] [DataRow("string", "string", true)] [DataRow("string?", "string", false)] [DataRow("string", "string?", false)] public void RedundantCast_TypeArgumentAnnotations(string expressionType, string targetType, bool expected) { var verifier = builder.AddSnippet($$""" #nullable enable using System; using System.Collections.Generic; using System.Linq; using IntList = System.Collections.Generic.List; using StringList = System.Collections.Generic.List; using NullableStringList = System.Collections.Generic.List; class TypeArguments where TStruct : struct where TClass : class { public void Test({{expressionType}} expression) { _ = ({{targetType}})expression; // {{(expected ? "Noncompliant" : string.Empty)}} } } """).WithLanguageVersion(LanguageVersion.CSharp9); if (expected) { verifier.Verify(); } else { verifier.VerifyNoIssues(); } } [TestMethod] [DynamicData(nameof(NullableTestDataWithFlowState))] public void RedundantCast_NullableEnabled(string snippet, bool compliant) => VerifyNullableTests(snippet, "enable", compliant); [TestMethod] [DynamicData(nameof(NullableTestDataWithFlowState))] public void RedundantCast_NullableWarnings(string snippet, bool compliant) => VerifyNullableTests(snippet, "enable warnings", compliant); [TestMethod] [DynamicData(nameof(NullableTestDataWithoutFlowState))] public void RedundantCast_NullableDisabled(string snippet, bool compliant) => VerifyNullableTests(snippet, "disable", compliant); [TestMethod] [DynamicData(nameof(NullableTestDataWithoutFlowState))] public void RedundantCast_NullableAnnotations(string snippet, bool compliant) => VerifyNullableTests(snippet, "enable annotations", compliant); private void VerifyNullableTests(string snippet, string nullableContext, bool compliant) { var code = $$""" #nullable {{nullableContext}} void Test(string nonNullable, string? nullable) { {{snippet}} // {{compliant switch { true => "Compliant", false => "Noncompliant" }}} } """; var snippetVerifier = builder.AddSnippet(code).WithTopLevelStatements(); if (compliant) { snippetVerifier.VerifyNoIssues(); } else { snippetVerifier.Verify(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantConditionalAroundAssignmentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RedundantConditionalAroundAssignmentTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFixBuilder = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantConditionalAroundAssignment() => builder.AddPaths("RedundantConditionalAroundAssignment.cs").Verify(); [TestMethod] public void RedundantConditionalAroundAssignment_CodeFix() => codeFixBuilder.AddPaths("RedundantConditionalAroundAssignment.cs") .WithCodeFixedPaths("RedundantConditionalAroundAssignment.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void RedundantConditionalAroundAssignment_Latest() => builder.AddPaths("RedundantConditionalAroundAssignment.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RedundantConditionalAroundAssignment_Latest_CodeFix() => codeFixBuilder.AddPaths("RedundantConditionalAroundAssignment.Latest.cs") .WithCodeFixedPaths("RedundantConditionalAroundAssignment.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantDeclarationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RedundantDeclarationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder codeFixBuilder = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantDeclaration() => builder.AddPaths("RedundantDeclaration.cs") .WithOptions(LanguageOptions.BeforeCSharp10) .Verify(); [TestMethod] public void RedundantDeclaration_UnusedLambdaParameters_BeforeCSharp9() => builder.AddSnippet(@"using System; public class C { public void M() { Action a = (p1, p2) => { }; /* Compliant - Lambda discard parameters have been introduced in C# 9 */ } }") .WithOptions(LanguageOptions.BeforeCSharp9) .VerifyNoIssues(); [TestMethod] public void RedundantDeclaration_CSharp9() => builder.AddPaths("RedundantDeclaration.CSharp9.cs") .WithTopLevelStatements() .WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9) .Verify(); [TestMethod] public void RedundantDeclaration_CSharp9_CodeFix_TitleRedundantParameterName() => codeFixBuilder.AddPaths("RedundantDeclaration.CSharp9.cs") .WithCodeFixedPaths("RedundantDeclaration.CSharp9.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantParameterName) .WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CSharp10() => builder.AddPaths("RedundantDeclaration.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void RedundantDeclaration_CSharp10_CodeFix_ExplicitDelegate() => codeFixBuilder.AddPaths("RedundantDeclaration.CSharp10.cs") .WithCodeFixedPaths("RedundantDeclaration.CSharp10.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantExplicitDelegate) .WithOptions(LanguageOptions.FromCSharp10) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CSharp12() => builder.AddPaths("RedundantDeclaration.CSharp12.cs") .WithOptions(LanguageOptions.FromCSharp12) .Verify(); [TestMethod] public void RedundantDeclaration_CSharp12_CodeFix_ArraySize() => codeFixBuilder.AddPaths("RedundantDeclaration.CSharp12.cs") .WithCodeFix() .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantArraySize) .WithCodeFixedPaths("RedundantDeclaration.CSharp12.ArraySize.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp12) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CSharp12_CodeFix_LambdaParameterType() => codeFixBuilder.AddPaths("RedundantDeclaration.CSharp12.cs") .WithCodeFix() .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantLambdaParameterType) .WithCodeFixedPaths("RedundantDeclaration.CSharp12.LambdaParameterType.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp12) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_ArraySize() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.ArraySize.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantArraySize) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_ArrayType() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.ArrayType.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantArrayType) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_DelegateParameterList() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.DelegateParameterList.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantDelegateParameterList) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_ExplicitDelegate() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.ExplicitDelegate.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantExplicitDelegate) .WithOptions(LanguageOptions.BeforeCSharp10) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_ExplicitNullable() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.ExplicitNullable.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantExplicitNullable) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_LambdaParameterType() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.LambdaParameterType.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantLambdaParameterType) .VerifyCodeFix(); [TestMethod] public void RedundantDeclaration_CodeFix_ObjectInitializer() => codeFixBuilder.AddPaths("RedundantDeclaration.cs") .WithCodeFixedPaths("RedundantDeclaration.ObjectInitializer.Fixed.cs") .WithCodeFixTitle(RedundantDeclarationCodeFix.TitleRedundantObjectInitializer) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantExitSelectTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantExitSelectTest { [TestMethod] public void RedundantExitSelect() => new VerifierBuilder().AddPaths("RedundantExitSelect.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantInheritanceListTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantInheritanceListTest { private readonly VerifierBuilder rule = new VerifierBuilder(); private readonly VerifierBuilder codeFix = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantInheritanceList() => rule.AddPaths("RedundantInheritanceList.cs").Verify(); [TestMethod] public void RedundantInheritanceList_CSharp9() => rule.AddPaths("RedundantInheritanceList.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void RedundantInheritanceList_CSharp9_CodeFix() => codeFix.AddPaths("RedundantInheritanceList.CSharp9.cs") .WithCodeFixedPaths("RedundantInheritanceList.CSharp9.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp9) .VerifyCodeFix(); [TestMethod] public void RedundantInheritanceList_CSharp10() => rule.AddPaths("RedundantInheritanceList.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] public void RedundantInheritanceList_CSharp10_CodeFix() => codeFix.AddPaths("RedundantInheritanceList.CSharp10.cs") .WithCodeFixedPaths("RedundantInheritanceList.CSharp10.Fixed.cs") .WithOptions(LanguageOptions.FromCSharp10) .VerifyCodeFix(); [TestMethod] public void RedundantInheritanceList_CodeFix() => codeFix.AddPaths("RedundantInheritanceList.cs") .WithCodeFixedPaths("RedundantInheritanceList.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantJumpStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RedundantJumpStatementTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RedundantJumpStatement() => builder.AddPaths("RedundantJumpStatement.cs") .Verify(); [TestMethod] public void RedundantJumpStatement_Latest() => builder.AddPaths("RedundantJumpStatement.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RedundantJumpStatement_TopLevelStatements() => builder.AddPaths("RedundantJumpStatement.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantModifierTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantModifierTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RedundantModifier() => builder.AddPaths("RedundantModifier.cs").Verify(); [TestMethod] public void RedundantModifier_Unsafe_CodeFix() => builder.AddPaths("RedundantModifier.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Fixed.Unsafe.cs") .WithCodeFixTitle(RedundantModifierCodeFix.TitleUnsafe) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Checked_CodeFix() => builder.AddPaths("RedundantModifier.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Fixed.Checked.cs") .WithCodeFixTitle(RedundantModifierCodeFix.TitleChecked) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Partial_CodeFix() => builder.AddPaths("RedundantModifier.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Fixed.Partial.cs") .WithCodeFixTitle(RedundantModifierCodeFix.TitlePartial) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Sealed_CodeFix() => builder.AddPaths("RedundantModifier.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Fixed.Sealed.cs") .WithCodeFixTitle(RedundantModifierCodeFix.TitleSealed) .VerifyCodeFix(); #if NETFRAMEWORK [TestMethod] public void RedundantModifier_Preprocessor() { var options = new CSharpParseOptions(); builder.AddPaths("RedundantModifier.Preprocessor.NetFx.cs") .WithLanguageVersion(LanguageVersion.CSharp13) .AddReferences(MetadataReferenceFacade.RegularExpressions) .WithOptions([options.WithPreprocessorSymbols("NETFRAMEWORK")]) .Verify(); } #endif #if NET [TestMethod] public void RedundantModifier_Preprocessor() => builder.AddPaths("RedundantModifier.Preprocessor.Net.cs", "RedundantModifier.Preprocessor.Net.Partial.cs") .WithLanguageVersion(LanguageVersion.CSharp13) .AddReferences(MetadataReferenceFacade.RegularExpressions) .VerifyNoIssues(); [TestMethod] public void RedundantModifier_Latest() => builder.AddPaths("RedundantModifier.Latest.cs", "RedundantModifier.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RedundantModifier_Checked_CodeFix_Latest() => builder.AddPaths("RedundantModifier.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Latest.Fixed.Checked.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithCodeFixTitle(RedundantModifierCodeFix.TitleChecked) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Partial_CodeFix_Latest() => builder.AddPaths("RedundantModifier.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Latest.Fixed.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithCodeFixTitle(RedundantModifierCodeFix.TitlePartial) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Sealed_CodeFix_Latest() => builder.AddPaths("RedundantModifier.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Latest.Fixed.Sealed.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithCodeFixTitle(RedundantModifierCodeFix.TitleSealed) .VerifyCodeFix(); [TestMethod] public void RedundantModifier_Unsafe_CodeFix_Latest() => builder.AddPaths("RedundantModifier.Latest.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantModifier.Latest.Fixed.Unsafe.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithCodeFixTitle(RedundantModifierCodeFix.TitleUnsafe) .VerifyCodeFix(); #endif } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantNullCheckTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantNullCheckTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder codeFixbuilderCS = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantNullCheck_CS() => builderCS.AddPaths("RedundantNullCheck.cs").Verify(); [TestMethod] public void RedundantNullCheck_CS_CodeFix() => codeFixbuilderCS.AddPaths("RedundantNullCheck.cs") .WithCodeFixedPaths("RedundantNullCheck.Fixed.cs", "RedundantNullCheck.Fixed.Batch.cs") .VerifyCodeFix(); [TestMethod] public void RedundantNullCheck_CSharp9() => builderCS.AddPaths("RedundantNullCheck.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void RedundantNullCheck_CSharp10() => builderCS.AddPaths("RedundantNullCheck.CSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void RedundantNullCheck_CSharp11() => builderCS.AddPaths("RedundantNullCheck.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .VerifyNoIssues(); [TestMethod] public void RedundantNullCheck_CSharp9_CodeFix() => codeFixbuilderCS.AddPaths("RedundantNullCheck.CSharp9.cs") .WithCodeFixedPaths("RedundantNullCheck.CSharp9.Fixed.cs") .WithTopLevelStatements() .VerifyCodeFix(); [TestMethod] public void RedundantNullCheck_CSharp10_CodeFix() => codeFixbuilderCS.AddPaths("RedundantNullCheck.CSharp10.cs") .WithCodeFixedPaths("RedundantNullCheck.CSharp10.Fixed.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .VerifyCodeFix(); [TestMethod] public void RedundantNullCheck_VB() => new VerifierBuilder().AddPaths("RedundantNullCheck.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantNullableTypeComparisonTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantNullableTypeComparisonTest { [TestMethod] public void RedundantNullableTypeComparison() => new VerifierBuilder().AddPaths("RedundantNullableTypeComparison.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantParenthesesObjectCreationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantParenthesesObjectCreationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RedundantParenthesesObjectCreation() => builder.AddPaths("RedundantParenthesesObjectCreation.cs") .Verify(); [TestMethod] public void RedundantParenthesesObjectCreation_CSharp9() => builder.AddPaths("RedundantParenthesesObjectCreation.CSharp9.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void RedundantParenthesesObjectCreation_CSharp10() => builder.AddPaths("RedundantParenthesesObjectCreation.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void RedundantParenthesesObjectCreation_CSharp11() => builder.AddPaths("RedundantParenthesesObjectCreation.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void RedundantParenthesesObjectCreation_CodeFix() => builder.AddPaths("RedundantParenthesesObjectCreation.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantParenthesesObjectCreation.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantParenthesesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantParenthesesTest { [TestMethod] public void RedundantParentheses_CS() => new VerifierBuilder().AddPaths("RedundantParenthesesExpression.cs").Verify(); [TestMethod] public void RedundantParentheses_VB() => new VerifierBuilder().AddPaths("RedundantParentheses.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantPropertyNamesInAnonymousClassTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantPropertyNamesInAnonymousClassTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RedundantPropertyNamesInAnonymousClass() => builder.AddPaths("RedundantPropertyNamesInAnonymousClass.cs").Verify(); [TestMethod] public void RedundantPropertyNamesInAnonymousClass_Latest() => builder.AddPaths("RedundantPropertyNamesInAnonymousClass.Latest.cs", "RedundantPropertyNamesInAnonymousClass.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RedundantPropertyNamesInAnonymousClass_CodeFix() => builder.AddPaths("RedundantPropertyNamesInAnonymousClass.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantPropertyNamesInAnonymousClass.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantToArrayCallTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantToArrayCallTest { private readonly VerifierBuilder builder = new VerifierBuilder(); private readonly VerifierBuilder builderWithCodeFix = new VerifierBuilder().WithCodeFix(); [TestMethod] public void RedundantToArrayCall() => builder.AddPaths("RedundantToArrayCall.cs").Verify(); [TestMethod] public void RedundantToArrayCall_CodeFix() => builderWithCodeFix .AddPaths("RedundantToArrayCall.cs") .WithCodeFixedPaths("RedundantToArrayCall.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void RedundantToArrayCall_CSharp11() => builder.AddPaths("RedundantToArrayCall.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); [TestMethod] public void RedundantToArrayCall_CSharp11_CodeFix() => builderWithCodeFix .AddPaths("RedundantToArrayCall.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .WithCodeFixedPaths("RedundantToArrayCall.CSharp11.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RedundantToStringCallTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RedundantToStringCallTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RedundantToStringCall() => builder.AddPaths("RedundantToStringCall.cs").Verify(); [TestMethod] public void RedundantToStringCall_CSharp9() => builder.AddPaths("RedundantToStringCall.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void RedundantToStringCall_CSharp10() => builder.AddPaths("RedundantToStringCall.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void RedundantToStringCall_CSharp11() => builder.AddPaths("RedundantToStringCall.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void RedundantToStringCall_CodeFix() => builder.AddPaths("RedundantToStringCall.cs") .WithCodeFix() .WithCodeFixedPaths("RedundantToStringCall.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReferenceEqualityCheckWhenEqualsExistsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ReferenceEqualityCheckWhenEqualsExistsTest { [TestMethod] public void ReferenceEqualityCheckWhenEqualsExists() => new VerifierBuilder() .AddPaths("ReferenceEqualityCheckWhenEqualsExists.cs", "ReferenceEqualityCheckWhenEqualsExists2.cs") .WithAutogenerateConcurrentFiles(false) .WithWarningsAsErrors("CS0253") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReferenceEqualsOnValueTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ReferenceEqualsOnValueTypeTest { [TestMethod] public void ReferenceEqualsOnValueType() => new VerifierBuilder().AddPaths("ReferenceEqualsOnValueType.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RegularExpressions/RegexMustHaveValidSyntaxTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RegexMustHaveValidSyntaxTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .WithBasePath("RegularExpressions") .AddReferences(MetadataReferenceFacade.RegularExpressions) .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()); private readonly VerifierBuilder builderVB = new VerifierBuilder() .WithBasePath("RegularExpressions") .AddReferences(MetadataReferenceFacade.RegularExpressions) .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()); [TestMethod] public void RegexMustHaveValidSyntax_CS() => builderCS.AddPaths("RegexMustHaveValidSyntax.cs").Verify(); [TestMethod] public void RegexMustHaveValidSyntax_CS_Latest() => builderCS.AddPaths("RegexMustHaveValidSyntax.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void RegexMustHaveValidSyntax_VB() => builderVB.AddPaths("RegexMustHaveValidSyntax.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RequireAttributeUsageAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class RequireAttributeUsageAttributeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void RequireAttributeUsageAttribute() => builder.AddPaths("RequireAttributeUsageAttribute.cs").Verify(); [TestMethod] public void RequireAttributeUsageAttribute_CSharp11() => builder.AddPaths("RequireAttributeUsageAttribute.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReturnEmptyCollectionInsteadOfNullTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ReturnEmptyCollectionInsteadOfNullTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ReturnEmptyCollectionInsteadOfNull() => builder.AddPaths("ReturnEmptyCollectionInsteadOfNull.cs") .AddReferences(MetadataReferenceFacade.SystemXml) .Verify(); [TestMethod] public void ReturnEmptyCollectionInsteadOfNull_Latest() => builder.AddPaths("ReturnEmptyCollectionInsteadOfNull.Latest.cs") .AddReferences(MetadataReferenceFacade.SystemCollections) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ReturnEmptyCollectionInsteadOfNull_TopLevelStatements() => builder.AddPaths("ReturnEmptyCollectionInsteadOfNull.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReturnTypeNamedPartialShouldBeEscapedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ReturnTypeNamedPartialShouldBeEscapedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ReturnTypeNamedPartialShouldBeEscaped_CSharp8_13() => builder.AddPaths("ReturnTypeNamedPartialShouldBeEscaped.CSharp8-13.cs") .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp8, LanguageVersion.CSharp13)) .Verify(); [TestMethod] public void ReturnTypeNamedPartialShouldBeEscaped_TopLevelStatements_CSharp9_13() => builder.AddPaths("ReturnTypeNamedPartialShouldBeEscaped.TopLevelStatements.CSharp9-13.cs") .WithOptions(LanguageOptions.Between(LanguageVersion.CSharp9, LanguageVersion.CSharp13)) .WithTopLevelStatements() .Verify(); [TestMethod] public void ReturnTypeNamedPartialShouldBeEscaped_TopLevelStatements_Latest() => builder.AddPaths("ReturnTypeNamedPartialShouldBeEscaped.TopLevelStatements.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void ReturnTypeNamedPartialShouldBeEscaped_CS_Latest() => builder.AddPaths("ReturnTypeNamedPartialShouldBeEscaped.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReturnValueIgnoredTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ReturnValueIgnoredTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ReturnValueIgnored() => builder.AddPaths("ReturnValueIgnored.cs").Verify(); [TestMethod] public void ReturnValueIgnored_Latest() => builder.AddPaths("ReturnValueIgnored.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] public void ReturnValueIgnored_TopLevelStatements() => builder.AddPaths("ReturnValueIgnored.TopLevelStatements.cs") .AddReferences(MetadataReferenceFacade.SystemCollections) .WithTopLevelStatements() .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ReversedOperatorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ReversedOperatorsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ReversedOperators_CS() => builderCS.AddPaths("ReversedOperators.cs").Verify(); [TestMethod] public void ReversedOperators_CS_Latest() => builderCS.AddPaths("ReversedOperators.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ReversedOperators_TopLevelStatements() => builderCS.AddPaths("ReversedOperators.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void ReversedOperators_VB() => new VerifierBuilder().AddPaths("ReversedOperators.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/RightCurlyBraceStartsLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class RightCurlyBraceStartsLineTest { [TestMethod] public void RightCurlyBraceStartsLine() => new VerifierBuilder().AddPaths("RightCurlyBraceStartsLine.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SecurityPInvokeMethodShouldNotBeCalledTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SecurityPInvokeMethodShouldNotBeCalledTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void SecurityPInvokeMethodShouldNotBeCalled_CS() => builderCS.AddPaths("SecurityPInvokeMethodShouldNotBeCalled.cs").Verify(); [TestMethod] public void SecurityPInvokeMethodShouldNotBeCalled_CSharp11() => builderCS.AddPaths("SecurityPInvokeMethodShouldNotBeCalled.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); [TestMethod] public void SecurityPInvokeMethodShouldNotBeCalled_CSharp12() => builderCS.AddPaths("SecurityPInvokeMethodShouldNotBeCalled.CSharp12.cs").WithOptions(LanguageOptions.FromCSharp12).Verify(); [TestMethod] public void SecurityPInvokeMethodShouldNotBeCalled_VB() => new VerifierBuilder().AddPaths("SecurityPInvokeMethodShouldNotBeCalled.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SelfAssignmentTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SelfAssignmentTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void SelfAssignment_CS() => builderCS.AddPaths("SelfAssignment.cs").Verify(); [TestMethod] public void SelfAssignment_CS_Latest() => builderCS.AddPaths("SelfAssignment.Latest.cs", "SelfAssignment.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void SelfAssignment_CS_TopLevelStatements() => builderCS.AddPaths("SelfAssignment.TopLevelStatements.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void SelfAssignment_VB() => new VerifierBuilder().AddPaths("SelfAssignment.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SerializationConstructorsShouldBeSecuredTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SerializationConstructorsShouldBeSecuredTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemSecurityPermissions); [TestMethod] public void SerializationConstructorsShouldBeSecured() => builder.AddPaths("SerializationConstructorsShouldBeSecured.cs").WithConcurrentAnalysis(false).Verify(); [TestMethod] public void SerializationConstructorsShouldBeSecured_CSharp9() => builder.AddPaths("SerializationConstructorsShouldBeSecured.CSharp9.cs").WithConcurrentAnalysis(false).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void SerializationConstructorsShouldBeSecured_InvalidCode() => builder.AddSnippet(""" [Serializable] public partial class InvalidCode : ISerializable { [FileIOPermissionAttribute(SecurityAction.Demand, Unrestricted = true)] [ZoneIdentityPermission(SecurityAction.Demand, Unrestricted = true)] public InvalidCode() { } protected (SerializationInfo info, StreamingContext context) { } public void GetObjectData(SerializationInfo info, StreamingContext context) { } } """).VerifyNoIssuesIgnoreErrors(); [TestMethod] public void SerializationConstructorsShouldBeSecured_NoAssemblyAttribute() => builder.AddPaths("SerializationConstructorsShouldBeSecured_NoAssemblyAttribute.cs").VerifyNoIssues(); [TestMethod] public void SerializationConstructorsShouldBeSecured_PartialClasses() => builder.AddPaths("SerializationConstructorsShouldBeSecured_Part1.cs", "SerializationConstructorsShouldBeSecured_Part2.cs").WithConcurrentAnalysis(false).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SetLocaleForDataTypesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SetLocaleForDataTypesTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemData); [TestMethod] public void SetLocaleForDataTypes() => builder.AddPaths("SetLocaleForDataTypes.cs") .Verify(); [TestMethod] public void SetLocaleForDataTypes_CSharp9() => builder.AddPaths("SetLocaleForDataTypes.CSharp9.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void SetLocaleForDataTypes_CSharp10() => builder.AddPaths("SetLocaleForDataTypes.CSharp10.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SetPropertiesInsteadOfMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SetPropertiesInsteadOfMethodsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void SetPropertiesInsteadOfMethods_CS() => builderCS.AddPaths("SetPropertiesInsteadOfMethods.cs").Verify(); [TestMethod] public void SetPropertiesInsteadOfMethods_CS_Latest() => builderCS.AddPaths("SetPropertiesInsteadOfMethods.Latest.cs") .WithTopLevelStatements() .AddReferences(MetadataReferenceFacade.SystemCollections) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void SetPropertiesInsteadOfMethods_VB() => new VerifierBuilder().AddPaths("SetPropertiesInsteadOfMethods.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ShiftDynamicNotIntegerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ShiftDynamicNotIntegerTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ShiftDynamicNotInteger_CS() => builderCS.AddPaths("ShiftDynamicNotInteger.cs").Verify(); [TestMethod] public void ShiftDynamicNotInteger_CSharp9() => builderCS.AddPaths("ShiftDynamicNotInteger.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void ShiftDynamicNotInteger_CSharp11() => builderCS.AddPaths("ShiftDynamicNotInteger.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void ShiftDynamicNotInteger_VB() => new VerifierBuilder().AddPaths("ShiftDynamicNotInteger.vb", "ShiftDynamicNotInteger2.vb").WithAutogenerateConcurrentFiles(false).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ShouldImplementExportedInterfacesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ShouldImplementExportedInterfacesTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemComponentModelComposition); [TestMethod] public void ShouldImplementExportedInterfaces_CS() => builderCS.AddPaths("ShouldImplementExportedInterfaces.cs").Verify(); [TestMethod] public void ShouldImplementExportedInterfaces_SystemComposition_CS() => builderCS.AddPaths("ShouldImplementExportedInterfaces.System.Composition.cs").AddReferences(MetadataReferenceFacade.SystemCompositionAttributedModel).Verify(); [TestMethod] public void ShouldImplementExportedInterfaces_Partial() => builderCS.AddPaths("ShouldImplementExportedInterfaces_Part1.cs", "ShouldImplementExportedInterfaces_Part2.cs").Verify(); [TestMethod] public void ShouldImplementExportedInterfaces_CSharp9() => builderCS.AddPaths("ShouldImplementExportedInterfaces.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void ShouldImplementExportedInterfaces_VB() => new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .AddPaths("ShouldImplementExportedInterfaces.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SimpleDoLoopTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SimpleDoLoopTest { [TestMethod] public void SimpleDoLoop() => new VerifierBuilder().AddPaths("SimpleDoLoop.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SingleStatementPerLineTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SingleStatementPerLineTest { public TestContext TestContext { get; set; } private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void SingleStatementPerLine_CS() => builderCS.AddPaths("SingleStatementPerLine.cs").Verify(); [TestMethod] public void SingleStatementPerLine_CSharp9() => builderCS.AddPaths("SingleStatementPerLine.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void SingleStatementPerLine_Razor() => builderCS.AddSnippet( """ @if (true) { @currentCount } @if (true) {

Test

} @code { private int currentCount = 0; void DoSomething(bool flag) { if (flag) Console.WriteLine("Test"); } // Noncompliant } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] public void SingleStatementPerLine_VB() => new VerifierBuilder().AddPaths("SingleStatementPerLine.vb", "SingleStatementPerLine2.vb").WithAutogenerateConcurrentFiles(false).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SpecifyIFormatProviderOrCultureInfoTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SpecifyIFormatProviderOrCultureInfoTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void SpecifyIFormatProviderOrCultureInfo() => builder.AddPaths("SpecifyIFormatProviderOrCultureInfo.cs").Verify(); [TestMethod] public void SpecifyIFormatProviderOrCultureInfo_BeforeCSharp13() => builder.AddSnippet(""" using System; class C { void M() { string.Format("bla"); // Noncompliant string.Format("%s %s", "foo", "bar", "quix", "hi", "bye"); // Noncompliant } } """) .WithOptions(LanguageOptions.BeforeCSharp13) .Verify(); #if NET [TestMethod] public void SpecifyIFormatProviderOrCultureInfo_CS_Latest() => builder .AddPaths("SpecifyIFormatProviderOrCultureInfo.Latest.cs") .AddReferences(MetadataReferenceFacade.SystemNetPrimitives) .Verify(); // Repro https://sonarsource.atlassian.net/browse/NET-230 [TestMethod] public void SpecifyIFormatProviderOrCultureInfo_FromCSharp13() => builder.AddSnippet(""" using System; string.Format("bla"); // FN string.Format("%s %s", "foo", "bar", "quix", "hi", "bye"); // FN """) .WithOptions(LanguageOptions.FromCSharp13) .WithTopLevelStatements() .VerifyNoIssues(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SpecifyStringComparisonTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SpecifyStringComparisonTest { [TestMethod] public void SpecifyStringComparison() => new VerifierBuilder().AddPaths("SpecifyStringComparison.cs").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SpecifyTimeoutOnRegexTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SpecifyTimeoutOnRegexTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .AddAnalyzer(() => new CS.SpecifyTimeoutOnRegex(AnalyzerConfiguration.AlwaysEnabled)) .WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.RegularExpressions) .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()); private readonly VerifierBuilder builderVB = new VerifierBuilder() .AddAnalyzer(() => new VB.SpecifyTimeoutOnRegex(AnalyzerConfiguration.AlwaysEnabled)) .WithBasePath("Hotspots") .AddReferences(MetadataReferenceFacade.RegularExpressions) .AddReferences(NuGetMetadataReference.SystemComponentModelAnnotations()); [TestMethod] public void SpecifyTimeoutOnRegex_CS() => builderCS.AddPaths("SpecifyTimeoutOnRegex.cs").Verify(); [TestMethod] public void SpecifyTimeoutOnRegex_CS_Latest() => builderCS.AddPaths("SpecifyTimeoutOnRegex.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void SpecifyTimeoutOnRegex_DefaultMatchTimeout() => builderCS.AddPaths("SpecifyTimeoutOnRegex.DefaultMatchTimeout.cs").WithTopLevelStatements().Verify(); [TestMethod] public void SpecifyTimeoutOnRegex_VB() => builderVB.AddPaths("SpecifyTimeoutOnRegex.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SqlKeywordsDelimitedBySpaceTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SqlKeywordsDelimitedBySpaceTest { private static readonly VerifierBuilder Builder = new VerifierBuilder() .AddReferences(NuGetMetadataReference.SystemDataSqlClient()); [TestMethod] public void SqlKeywordsDelimitedBySpace_Csharp8() => Builder.AddPaths("SqlKeywordsDelimitedBySpace.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void SqlKeywordsDelimitedBySpace_UsingInsideNamespace() => Builder.AddPaths("SqlKeywordsDelimitedBySpace_InsideNamespace.cs") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void SqlKeywordsDelimitedBySpace_DefaultNamespace() => Builder.AddPaths("SqlKeywordsDelimitedBySpace_DefaultNamespace.cs") .AddTestReference() .VerifyNoIssues(); [TestMethod] public void SqlKeywordsDelimitedBySpace_CSharp10_GlobalUsings() => Builder.AddPaths("SqlKeywordsDelimitedBySpace.CSharp10.GlobalUsing.cs", "SqlKeywordsDelimitedBySpace.CSharp10.GlobalUsingConsumer.cs") .WithOptions(LanguageOptions.FromCSharp10) .WithConcurrentAnalysis(false) .VerifyNoIssues(); [TestMethod] public void SqlKeywordsDelimitedBySpace_CSharp10_FileScopesNamespace() => Builder.AddPaths("SqlKeywordsDelimitedBySpace.CSharp10.FileScopedNamespaceDeclaration.cs") .WithOptions(LanguageOptions.FromCSharp10) .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void SqlKeywordsDelimitedBySpace_Latest() => Builder.AddPaths("SqlKeywordsDelimitedBySpace.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithConcurrentAnalysis(false) .Verify(); [DataRow("System.Data")] [DataRow("System.Data.SqlClient")] [DataRow("System.Data.SQLite")] [DataRow("System.Data.SqlServerCe")] [DataRow("System.Data.Entity")] [DataRow("System.Data.Odbc")] [DataRow("Dapper")] [DataRow("Microsoft.Data.SqlClient")] [DataRow("Microsoft.Data.Sqlite")] [DataRow("NHibernate")] [DataRow("PetaPoco")] [TestMethod] public void SqlKeywordsDelimitedBySpace_DotnetFramework(string sqlNamespace) => Builder .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(NuGetMetadataReference.Dapper()) .AddReferences(NuGetMetadataReference.EntityFramework()) .AddReferences(NuGetMetadataReference.MicrosoftDataSqlClient()) .AddReferences(NuGetMetadataReference.MicrosoftDataSqliteCore()) .AddReferences(NuGetMetadataReference.MicrosoftSqlServerCompact()) .AddReferences(NuGetMetadataReference.NHibernate()) .AddReferences(NuGetMetadataReference.PetaPocoCompiled()) .AddReferences(NuGetMetadataReference.SystemDataOdbc()) .AddReferences(NuGetMetadataReference.SystemDataSQLiteCore()) .AddSnippet($@" using {sqlNamespace}; namespace TestNamespace {{ public class Test {{ private string field = ""SELECT * FROM table"" + ""WHERE col ="" + // Noncompliant ""val""; }} }}").Verify(); [DataRow("System.Data.SqlClient")] [DataRow("System.Data.OracleClient")] [DataRow("Microsoft.EntityFrameworkCore")] [DataRow("ServiceStack.OrmLite")] [TestMethod] public void SqlKeywordsDelimitedBySpace_DotnetCore(string sqlNamespace) => Builder .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore("2.2.0")) .AddReferences(NuGetMetadataReference.ServiceStackOrmLite()) .AddReferences(NuGetMetadataReference.SystemDataOracleClient()) .AddSnippet($@" using {sqlNamespace}; namespace TestNamespace {{ public class Test {{ private string field = ""SELECT * FROM table"" + ""WHERE col ="" + // Noncompliant ""val""; }} }}").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticFieldInGenericClassTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StaticFieldInGenericClassTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StaticFieldInGenericClass() => builder.AddPaths("StaticFieldInGenericClass.cs").Verify(); [TestMethod] public void StaticFieldInGenericClass_Latest() => builder.AddPaths("StaticFieldInGenericClass.Latest.cs", "StaticFieldInGenericClass.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticFieldInitializerOrderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StaticFieldInitializerOrderTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StaticFieldInitializerOrder() => builder.AddPaths("StaticFieldInitializerOrder.cs", "StaticFieldInitializerOrder.Partial.cs") .Verify(); [TestMethod] public void StaticFieldInitializerOrder_Latest() => builder.AddPaths("StaticFieldInitializerOrder.Latest.cs", "StaticFieldInitializerOrder.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticFieldVisibleTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StaticFieldVisibleTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StaticFieldVisible() => builder.AddPaths("StaticFieldVisible.cs").Verify(); [TestMethod] public void StaticFieldVisible_Latest() => builder.AddPaths("StaticFieldVisible.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticFieldWrittenFromInstanceConstructorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StaticFieldWrittenFromInstanceConstructorTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StaticFieldWrittenFromInstanceConstructor() => builder.AddPaths("StaticFieldWrittenFromInstanceConstructor.cs").Verify(); [TestMethod] public void StaticFieldWrittenFromInstanceConstructor_Latest() => builder.AddPaths("StaticFieldWrittenFromInstanceConstructor.Latest.cs", "StaticFieldWrittenFromInstanceConstructor.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticFieldWrittenFromInstanceMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StaticFieldWrittenFromInstanceMemberTest { private readonly VerifierBuilder builder = new(); [TestMethod] public void StaticFieldWrittenFromInstanceMember() => builder.AddPaths("StaticFieldWrittenFromInstanceMember.cs").Verify(); [TestMethod] public void StaticFieldWrittenFromInstanceMember_Latest() => builder.AddPaths("StaticFieldWrittenFromInstanceMember.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void StaticFieldWrittenFromInstanceMember_Latest_TopLevelStatements() => builder.AddPaths("StaticFieldWrittenFromInstanceMember.TopLevelStatements.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .VerifyNoIssues(); [TestMethod] public async Task SecondaryIssueInReferencedCompilation() { var firstClass = """ public class Foo { public static int Count = 0; // Secondary } """; var secondClass = """ public class Bar { public int Increment() => Foo.Count++; } """; var analyzers = ImmutableArray.Empty.Add(new StaticFieldWrittenFromInstanceMember()); var firstCompilation = CreateCompilation(CSharpSyntaxTree.ParseText(firstClass), "First").WithAnalyzers(analyzers).Compilation; var secondCompilation = CreateCompilation(CSharpSyntaxTree.ParseText(secondClass), "Second") .AddReferences(firstCompilation.ToMetadataReference()) .WithAnalyzers(analyzers); var result = await secondCompilation.GetAnalyzerDiagnosticsAsync(); firstCompilation.GetDiagnostics().Should().BeEmpty(); result.Should().BeEquivalentTo(new[] { new { Id = "S2696", AdditionalLocations = Array.Empty() } }); result.Single().GetMessage().Should().StartWith("Make the enclosing instance method 'static' or remove this set on the 'static' field."); } private static CSharpCompilation CreateCompilation(SyntaxTree tree, string name) => CSharpCompilation .Create(name, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location)) .AddSyntaxTrees(tree); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StaticSealedClassProtectedMembersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class StaticSealedClassProtectedMembersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StaticSealedClassProtectedMembers() => builder.AddPaths("StaticSealedClassProtectedMembers.cs").Verify(); [TestMethod] public void StaticSealedClassProtectedMembers_Latest() => builder.AddPaths("StaticSealedClassProtectedMembers.Latest.cs", "StaticSealedClassProtectedMembers.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StreamReadStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StreamReadStatementTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StreamReadStatement() => builder.AddPaths("StreamReadStatement.cs").Verify(); [TestMethod] public void StreamReadStatement_Latest() => builder.AddPaths("StreamReadStatement.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringConcatenationInLoopTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StringConcatenationInLoopTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void StringConcatenationInLoop_CS() => builderCS.AddPaths("StringConcatenationInLoop.cs").Verify(); [TestMethod] public void StringConcatenationInLoop_CS_Latest() => builderCS.AddPaths("StringConcatenationInLoop.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void StringConcatenationInLoop_VB() => new VerifierBuilder().AddPaths("StringConcatenationInLoop.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringConcatenationWithPlusTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class StringConcatenationWithPlusTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemXml.Concat(MetadataReferenceFacade.SystemXmlLinq)); [TestMethod] public void StringConcatenationWithPlus() => builder.AddPaths("StringConcatenationWithPlus.vb").Verify(); [TestMethod] public void StringConcatenationWithPlus_CodeFix() => builder.AddPaths("StringConcatenationWithPlus.vb") .WithCodeFix() .WithCodeFixedPaths("StringConcatenationWithPlus.Fixed.vb") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringFormatValidatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class StringFormatValidatorTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void StringFormatValidator_RuntimeExceptionFree(ProjectType projectType) => builder.AddPaths("StringFormatValidator.RuntimeExceptionFree.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void StringFormatValidator_TypoFree(ProjectType projectType) => builder.AddPaths("StringFormatValidator.TypoFree.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .Verify(); [TestMethod] public void StringFormatValidator_EdgeCases() => builder.AddPaths("StringFormatValidator.EdgeCases.cs").Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void StringFormatValidator_RuntimeExceptionFree_CSharp11(ProjectType projectType) => builder.AddPaths("StringFormatValidator.RuntimeExceptionFree.CSharp11.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void StringFormatValidator_TypoFree_CSharp11(ProjectType projectType) => builder.AddPaths("StringFormatValidator.TypoFree.CSharp11.cs") .AddReferences(TestCompiler.ProjectTypeReference(projectType)) .WithOptions(LanguageOptions.FromCSharp11) .Verify(); [TestMethod] public void StringFormatValidator_Latest() => builder.AddPaths("StringFormatValidator.Latest.cs") .AddReferences(TestCompiler.ProjectTypeReference(ProjectType.Product)) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringLiteralShouldNotBeDuplicatedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StringLiteralShouldNotBeDuplicatedTest { #if NET private static readonly ImmutableArray DapperReferences = [ CoreMetadataReference.SystemDataCommon, CoreMetadataReference.SystemComponentModelPrimitives, ..NuGetMetadataReference.Dapper(), ..NuGetMetadataReference.SystemDataSqlClient() ]; #endif private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_CS() => builderCS .AddPaths("StringLiteralShouldNotBeDuplicated.cs", "StringLiteralShouldNotBeDuplicated.Partial.cs") .AddReferences(NuGetMetadataReference.EntityFramework()) .Verify(); #if NET [TestMethod] public void StringLiteralShouldNotBeDuplicated_CS_Latest() => builderCS.AddPaths("StringLiteralShouldNotBeDuplicated.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational("2.2.6")) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore("2.2.6")) .Verify(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_CS_WithTopLevelStatements() => builderCS.AddPaths("StringLiteralShouldNotBeDuplicated.WithTopLevelStatements.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_CS_Dapper() => builderCS.AddPaths("StringLiteralShouldNotBeDuplicated.Dapper.cs") .AddReferences(DapperReferences) .VerifyNoIssues(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_VB_Dapper() => new VerifierBuilder() .AddPaths("StringLiteralShouldNotBeDuplicated.Dapper.vb") .AddReferences(DapperReferences) .VerifyNoIssues(); #endif [TestMethod] public void StringLiteralShouldNotBeDuplicated_Attributes_CS() => new VerifierBuilder().AddAnalyzer(() => new CS.StringLiteralShouldNotBeDuplicated { Threshold = 2 }) .AddPaths("StringLiteralShouldNotBeDuplicated_Attributes.cs") .WithConcurrentAnalysis(false) .Verify(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_VB() => new VerifierBuilder() .AddPaths("StringLiteralShouldNotBeDuplicated.vb") .Verify(); [TestMethod] public void StringLiteralShouldNotBeDuplicated_Attributes_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.StringLiteralShouldNotBeDuplicated() { Threshold = 2 }) .AddPaths("StringLiteralShouldNotBeDuplicated_Attributes.vb") .WithConcurrentAnalysis(false) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringOffsetMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StringOffsetMethodsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StringOffsetMethods() => builder.AddPaths("StringOffsetMethods.cs").Verify(); [TestMethod] public void StringOffsetMethods_Latest() => builder.AddPaths("StringOffsetMethods.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringOperationWithoutCultureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class StringOperationWithoutCultureTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StringOperationWithoutCulture() => builder.AddPaths("StringOperationWithoutCulture.cs").Verify(); [TestMethod] public void StringOperationWithoutCulture_CSharp10() => builder.AddPaths("StringOperationWithoutCulture.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .VerifyNoIssues(); [TestMethod] public void StringOperationWithoutCulture_CSharp11() => builder.AddPaths("StringOperationWithoutCulture.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/StringOrIntegralTypesForIndexersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class StringOrIntegralTypesForIndexersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void StringOrIntegralTypesForIndexers_CSharp8() => builder.AddPaths("StringOrIntegralTypesForIndexers.cs") .AddReferences(MetadataReferenceFacade.NetStandard21) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void StringOrIntegralTypesForIndexers_Latest() => builder.AddPaths("StringOrIntegralTypesForIndexers.Latest.cs", "StringOrIntegralTypesForIndexers.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SuppressFinalizeUselessTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SuppressFinalizeUselessTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void SuppressFinalizeUseless() => builder.AddPaths("SuppressFinalizeUseless.cs").Verify(); [TestMethod] public void SuppressFinalizeUseless_CSharp9() => builder.AddPaths("SuppressFinalizeUseless.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void SuppressFinalizeUseless_CodeFix() => builder.AddPaths("SuppressFinalizeUseless.cs") .WithCodeFix() .WithCodeFixedPaths("SuppressFinalizeUseless.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwaggerActionReturnTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ #if NET using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SwaggerActionReturnTypeTest { private readonly VerifierBuilder builder = new VerifierBuilder() .WithOptions(LanguageOptions.FromCSharp11) .AddReferences([ ..NuGetMetadataReference.SwashbuckleAspNetCoreAnnotations(), ..NuGetMetadataReference.SwashbuckleAspNetCoreSwagger(), AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpAbstractions, AspNetCoreMetadataReference.MicrosoftAspNetCoreHttpResults, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, ]); [TestMethod] public void SwaggerActionReturnType_CS() => builder.AddPaths("SwaggerActionReturnType.cs").Verify(); [TestMethod] [DataRow("""Ok(bar)""")] [DataRow("""Created("uri", bar)""")] [DataRow("""Created(new Uri("uri"), bar)""")] [DataRow("""CreatedAtAction("actionName", bar)""")] [DataRow("""CreatedAtAction("actionName", null, bar)""")] [DataRow("""CreatedAtAction("actionName", "controllerName", null, bar)""")] [DataRow("""CreatedAtRoute("routeName", bar)""")] [DataRow("""CreatedAtRoute("default(object)", bar)""")] [DataRow("""CreatedAtRoute("routeName", null, bar)""")] [DataRow("""Accepted(bar)""")] [DataRow("""Accepted("uri", bar)""")] [DataRow("""Accepted(new Uri("uri"), bar)""")] [DataRow("""AcceptedAtAction("actionName", bar)""")] [DataRow("""AcceptedAtAction("actionName", "controllerName", bar)""")] [DataRow("""AcceptedAtAction("actionName", "controllerName", null, bar)""")] [DataRow("""AcceptedAtRoute("routeName", null, bar)""")] [DataRow("""AcceptedAtRoute(default(object), bar)""")] public void SwaggerActionReturnType_IActionResult(string invocation) => builder .AddSnippet($$""" using System; using Microsoft.AspNetCore.Mvc; [ApiController] public class Foo : Controller { [HttpGet("a")] public IActionResult Method() => // Noncompliant {{invocation}}; // Secondary private Bar bar = new(); } public class Bar {} """) .Verify(); [TestMethod] [DataRow("""Accepted("uri")""")] [DataRow("""Accepted(uri)""")] public void SwaggerActionReturnType_IActionResult_Compliant(string invocation) => builder .AddSnippet($$""" using System; using Microsoft.AspNetCore.Mvc; [ApiController] public class Foo : Controller { [HttpGet("a")] public IActionResult Method() => {{invocation}}; private Bar bar = new(); private Uri uri; } public class Bar {} """) .VerifyNoIssues(); [TestMethod] [DataRow("""Results.Ok(bar)""")] [DataRow("""Results.Ok((object) bar)""")] [DataRow("""Results.Ok(bar)""")] [DataRow("""Results.Created("uri", bar)""")] [DataRow("""Results.Created("uri", (object) bar)""")] [DataRow("""Results.Created(new Uri("uri"), bar)""")] [DataRow("""Results.Created(new Uri("uri"), (object) bar)""")] [DataRow("""Results.CreatedAtRoute(value: (object) bar)""")] [DataRow("""Results.CreatedAtRoute("", null, (object) bar)""")] [DataRow("""Results.CreatedAtRoute(value: bar)""")] [DataRow("""Results.CreatedAtRoute("", null, bar)""")] [DataRow("""Results.Accepted("uri", bar)""")] [DataRow("""Results.Accepted("uri", (object) bar)""")] [DataRow("""Results.AcceptedAtRoute(value: (object) bar)""")] [DataRow("""Results.AcceptedAtRoute("", null, (object) bar)""")] [DataRow("""Results.AcceptedAtRoute(value: bar)""")] [DataRow("""Results.AcceptedAtRoute("", null, bar)""")] public void SwaggerActionReturnType_IResult(string invocation) => builder .AddSnippet($$""" using System; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; [ApiController] public class Foo : Controller { [HttpGet("a")] public IResult Method() => // Noncompliant {{invocation}}; // Secondary private Bar bar = new(); } public class Bar {} """) .Verify(); [TestMethod] public void ApiConventionType_AssemblyLevel() => builder .AddSnippet(""" using Microsoft.AspNetCore.Mvc; [assembly: ApiConventionType(typeof(DefaultApiConventions))] namespace MyNameSpace; [ApiController] public class Foo : Controller { [HttpGet("a")] public IActionResult Method() => Ok(bar); // Compliant private Bar bar = new(); } public class Bar {} """) .VerifyNoIssues(); } #endif ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchCaseFallsThroughToDefaultTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SwitchCaseFallsThroughToDefaultTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void SwitchCaseFallsThroughToDefault() => builder.AddPaths("SwitchCaseFallsThroughToDefault.cs").Verify(); [TestMethod] public void SwitchCaseFallsThroughToDefault_TopLevelStatements() => builder.AddPaths("SwitchCaseFallsThroughToDefault.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void SwitchCaseFallsThroughToDefault_CodeFix() => builder.AddPaths("SwitchCaseFallsThroughToDefault.cs") .WithCodeFix() .WithCodeFixedPaths("SwitchCaseFallsThroughToDefault.Fixed.cs") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchCasesMinimumThreeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SwitchCasesMinimumThreeTest { [TestMethod] public void SwitchCasesMinimumThree_CSharp8() => new VerifierBuilder().AddPaths("SwitchCasesMinimumThree.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void SwitchCasesMinimumThree_VB() => new VerifierBuilder().AddPaths("SwitchCasesMinimumThree.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchDefaultClauseEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SwitchDefaultClauseEmptyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void SwitchDefaultClauseEmpty() => builder.AddPaths("SwitchDefaultClauseEmpty.cs").Verify(); [TestMethod] public void SwitchDefaultClauseEmpty_CodeFix() => builder.AddPaths("SwitchDefaultClauseEmpty.cs") .WithCodeFix() .WithCodeFixedPaths("SwitchDefaultClauseEmpty.Fixed.cs") .VerifyCodeFix(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchSectionShouldNotHaveTooManyStatementsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SwitchSectionShouldNotHaveTooManyStatementsTest { [TestMethod] public void SwitchSectionShouldNotHaveTooManyStatements_DefaultValue_CS() => new VerifierBuilder().AddPaths("SwitchSectionShouldNotHaveTooManyStatements_DefaultValue.cs").Verify(); [TestMethod] public void SwitchSectionShouldNotHaveTooManyStatements_CustomValue_CSharp8() => new VerifierBuilder().AddAnalyzer(() => new CS.SwitchSectionShouldNotHaveTooManyStatements { Threshold = 1 }) .AddPaths("SwitchSectionShouldNotHaveTooManyStatements_CustomValue.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void SwitchSectionShouldNotHaveTooManyStatements_DefaultValue_VB() => new VerifierBuilder().AddPaths("SwitchSectionShouldNotHaveTooManyStatements_DefaultValue.vb").Verify(); [TestMethod] public void SwitchSectionShouldNotHaveTooManyStatements_CustomValue_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.SwitchSectionShouldNotHaveTooManyStatements { Threshold = 1 }) .AddPaths("SwitchSectionShouldNotHaveTooManyStatements_CustomValue.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchShouldNotBeNestedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class SwitchShouldNotBeNestedTest { [TestMethod] public void SwitchShouldNotBeNested_CSharp8() => new VerifierBuilder().AddPaths("SwitchShouldNotBeNested.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void SwitchShouldNotBeNested_VB() => new VerifierBuilder().AddPaths("SwitchShouldNotBeNested.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/SwitchWithoutDefaultTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class StringConcatenationInLoopSwitchWithoutDefaultTest { [TestMethod] public void SwitchWithoutDefault_CS() => new VerifierBuilder().AddPaths("SwitchWithoutDefault.cs").Verify(); [TestMethod] public void SwitchWithoutDefault_VB() => new VerifierBuilder().AddPaths("SwitchWithoutDefault.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TabCharacterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class TabCharacterTest { [TestMethod] public void TabCharacter_CS() => new VerifierBuilder().AddPaths("TabCharacter.cs").Verify(); [TestMethod] public void TabCharacter_VB() => new VerifierBuilder().AddPaths("TabCharacter.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TaskConfigureAwaitTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TaskConfigureAwaitTest { private readonly VerifierBuilder builder = new VerifierBuilder(); #if NETFRAMEWORK [TestMethod] public void TaskConfigureAwait_NetFx() => builder.AddPaths("TaskConfigureAwait.NetFx.cs").Verify(); #else [TestMethod] public void TaskConfigureAwait_NetCore() => builder.AddPaths("TaskConfigureAwait.NetCore.cs").VerifyNoIssues(); #endif [TestMethod] public void TaskConfigureAwait_ConsoleApp() => builder.AddSnippet(""" using System.Threading.Tasks; public static class EntryPoint { public async static Task Main() => await Task.Delay(1000); // Compliant } """) .WithOutputKind(OutputKind.ConsoleApplication) .WithOptions(LanguageOptions.FromCSharp8) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TestClassShouldHaveTestMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TestClassShouldHaveTestMethodTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(NUnit.Ver3Latest)] // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 public void TestClassShouldHaveTestMethod_NUnit(string testFwkVersion) => builder .AddPaths("TestClassShouldHaveTestMethod.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] public void TestClassShouldHaveTestMethod_NUnit4() => builder .AddPaths("TestClassShouldHaveTestMethod.NUnit4.cs") .AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)) .Verify(); [TestMethod] [DataRow("3.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] public void TestClassShouldHaveTestMethod_NUnit3(string testFwkVersion) => builder .AddPaths("TestClassShouldHaveTestMethod.NUnit3.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void TestClassShouldHaveTestMethod_MSTest(string testFwkVersion) => builder .AddPaths("TestClassShouldHaveTestMethod.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] public void TestClassShouldHaveTestMethod_Latest() => builder .AddPaths("TestClassShouldHaveTestMethod.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)) .Verify(); [TestMethod] public void TestClassShouldHaveTestMethod_NUnit4_AliasedNamespace() => builder.AddReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)).AddSnippet(""" using NUnit.Framework; namespace Aliased { using Assert = NUnit.Framework.Legacy.ClassicAssert; [TestFixture] class ClassTest1 // Noncompliant { } [TestFixture] public class ClassTest2 { [TestCaseSource("DivideCases")] public void DivideTest(int n, int d, int q) { Assert.AreEqual(q, n / d); } static object[] DivideCases = { new object[] { 12, 3, 4 } }; } } """).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TestMethodShouldContainAssertionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Test.Common; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TestMethodShouldContainAssertionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void TestMethodShouldContainAssertion_MSTest_Common(string testFwkVersion) => WithTestReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .AddPaths("TestMethodShouldContainAssertion.MsTest.Common.cs", "TestMethodShouldContainAssertion.MsTest.AnotherFile.cs") .Verify(); [TestMethod] public void TestMethodShouldContainAssertion_MSTest_V3() => WithTestReferences(NuGetMetadataReference.MSTestTestFrameworkV3) .AddPaths("TestMethodShouldContainAssertion.MsTest.V3.cs", "TestMethodShouldContainAssertion.MsTest.AnotherFile.cs") .VerifyNoIssues(); [TestMethod] public void TestMethodShouldContainAssertion_MSTest_Latest() => WithTestReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)) .AddPaths("TestMethodShouldContainAssertion.MsTest.Latest.cs", "TestMethodShouldContainAssertion.MsTest.AnotherFile.cs") .Verify(); [TestMethod] [DataRow(NUnit.Ver3, FluentAssertionsVersions.Ver7, Latest)] [DataRow(NUnit.Ver3Latest, FluentAssertionsVersions.Ver5, Latest)] // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 [DataRow(NUnit.Ver3Latest, FluentAssertionsVersions.Ver7, Latest)] // Breaking changes in NUnit 4.0 would fail the test https://github.com/SonarSource/sonar-dotnet/issues/8409 public void TestMethodShouldContainAssertion_NUnit(string testFwkVersion, string fluentVersion, string nSubstituteVersion) => WithTestReferences(NuGetMetadataReference.NUnit(testFwkVersion), fluentVersion, nSubstituteVersion).AddPaths("TestMethodShouldContainAssertion.NUnit.cs").Verify(); [TestMethod] public void TestMethodShouldContainAssertion_NUnit4() => WithTestReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)).AddPaths("TestMethodShouldContainAssertion.NUnit4.cs").Verify(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(NUnit.Ver27)] public void TestMethodShouldContainAssertion_NUnit_V2Specific(string testFwkVersion) => WithTestReferences(NuGetMetadataReference.NUnit(testFwkVersion)).AddSnippet(""" using System; using NUnit.Framework; [TestFixture] public class Foo { [TestCase] [ExpectedException(typeof(Exception))] public void TestCase4() { var x = System.IO.File.Open("", System.IO.FileMode.Open); } [Theory] [ExpectedException(typeof(Exception))] public void Theory4() { var x = System.IO.File.Open("", System.IO.FileMode.Open); } [TestCaseSource("Foo")] [ExpectedException(typeof(Exception))] public void TestCaseSource4() { var x = System.IO.File.Open("", System.IO.FileMode.Open); } [Test] [ExpectedException(typeof(Exception))] public void Test4() { var x = System.IO.File.Open("", System.IO.FileMode.Open); } } """).VerifyNoIssues(); [TestMethod] [DataRow(XUnitVersions.Ver2, FluentAssertionsVersions.Ver7, Latest)] [DataRow(XUnitVersions.Ver253, FluentAssertionsVersions.Ver7, Latest)] public void TestMethodShouldContainAssertion_Xunit(string testFwkVersion, string fluentVersion, string nSubstituteVersion) => WithTestReferences(NuGetMetadataReference.XunitFramework(testFwkVersion), fluentVersion, nSubstituteVersion).AddPaths("TestMethodShouldContainAssertion.Xunit.cs").Verify(); [TestMethod] public void TestMethodShouldContainAssertion_Xunit_Legacy() => WithTestReferences(NuGetMetadataReference.XunitFrameworkV1).AddPaths("TestMethodShouldContainAssertion.Xunit.Legacy.cs").Verify(); [TestMethod] public void TestMethodShouldContainAssertion_XunitV3() => WithTestReferences(NuGetMetadataReference.XunitFrameworkV3(TestConstants.NuGetLatestVersion)) .AddPaths("TestMethodShouldContainAssertion.Xunit.cs") .AddPaths("TestMethodShouldContainAssertion.XunitV3.cs") .AddReferences(NuGetMetadataReference.SystemMemory(TestConstants.NuGetLatestVersion)) .AddReferences(MetadataReferenceFacade.NetStandard) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] [DataRow(NUnit.Ver25, FluentAssertionsVersions.Ver1)] [DataRow(NUnit.Ver25, FluentAssertionsVersions.Ver4)] public void TestMethodShouldContainAssertion_NUnit_FluentAssertionsLegacy(string testFwkVersion, string fluentVersion) => WithTestReferences(NuGetMetadataReference.NUnit(testFwkVersion), fluentVersion).AddSnippet(""" using System; using FluentAssertions; using NUnit.Framework; [TestFixture] public class Foo { [Test] public void Test1() // Noncompliant { var x = 42; } [Test] public void ShouldThrowTest() { Action act = () => { throw new Exception(); }; act.ShouldThrow(); } [Test] public void ShouldNotThrowTest() { Action act = () => { throw new Exception(); }; act.ShouldNotThrow(); } } """).Verify(); [TestMethod] public void TestMethodShouldContainAssertion_NUnit_NFluentLegacy() => WithTestReferences(NuGetMetadataReference.NUnit(NUnit.Ver25), nFluentVersion: "1.3.1").AddSnippet(""" using System; using NFluent; using NUnit.Framework; [TestFixture] public class Foo { [Test] public void Test1() { throw new NFluent.FluentCheckException("You failed me!"); } } """).VerifyNoIssues(); [TestMethod] public void TestMethodShouldContainAssertion_Moq() => WithTestReferences(NuGetMetadataReference.MSTestTestFramework(Latest)).AddPaths("TestMethodShouldContainAssertion.Moq.cs").Verify(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void TestMethodShouldContainAssertion_CustomAssertionMethod_Common(string version) => builder.AddPaths("TestMethodShouldContainAssertion.Custom.Common.cs").AddReferences(NuGetMetadataReference.MSTestTestFramework(version)).Verify(); [TestMethod] public void TestMethodShouldContainAssertion_CustomAssertionMethod_V3() => builder.AddPaths("TestMethodShouldContainAssertion.Custom.V3.cs").AddReferences(NuGetMetadataReference.MSTestTestFrameworkV3).VerifyNoIssues(); [TestMethod] public void TestMethodShouldContainAssertion_FsCheck_XUnit() => WithTestReferences(NuGetMetadataReference.FsCheckXunit(Latest)) .AddPaths("TestMethodShouldContainAssertion.XUnit.FsCheck.cs") .VerifyNoIssues(); [TestMethod] public void TestMethodShouldContainAssertion_FsCheck_NUnit() => WithTestReferences(NuGetMetadataReference.FsCheckNunit(Latest)) .AddPaths("TestMethodShouldContainAssertion.NUnit.FsCheck.cs") .VerifyNoIssues(); [TestMethod] public void TestMethodShouldContainAssertion_CodeGenerator() => builder .AddPaths("TestMethodShouldContainAssertion.SourceGenerators.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.MicrosoftCodeAnalysisCSharp()) .AddReferences(NuGetMetadataReference.MicrosoftCodeAnalysisCSharpSourceGeneratorsTesting()) .AddReferences(NuGetMetadataReference.MicrosoftCodeAnalysisAnalyzerTesting()) .WithOptions(LanguageOptions.FromCSharp13) .Verify(); [TestMethod] public void TestMethodShouldContainAssertion_Latest() => builder.AddPaths("TestMethodShouldContainAssertion.Latest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFrameworkV1) .AddReferences(NuGetMetadataReference.XunitFramework(Latest)) .AddReferences(NuGetMetadataReference.NUnit(Latest)) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void TestMethodShouldContainAssertion_NUnit4_AliasedNamespace() => WithTestReferences(NuGetMetadataReference.NUnit(NUnit.Ver4)).AddSnippet(""" using NUnit.Framework; namespace Aliased { using Assert = NUnit.Framework.Legacy.ClassicAssert; [TestFixture] class TestAliased { [Test] public void Test1() // Noncompliant { var x = 42; } [Test] public void Test2() { var x = 42; Assert.AreEqual(x, 42); } } } """).Verify(); internal static VerifierBuilder WithTestReferences(IEnumerable testFrameworkReference, string fluentVersion = FluentAssertionsVersions.Ver7, string nSubstituteVersion = Latest, string nFluentVersion = Latest, string shouldlyVersion = Latest, string moqVersion = Latest) => new VerifierBuilder() .AddReferences(testFrameworkReference) .AddReferences(NuGetMetadataReference.FluentAssertions(fluentVersion)) .AddReferences(NuGetMetadataReference.NSubstitute(nSubstituteVersion)) .AddReferences(NuGetMetadataReference.NFluent(nFluentVersion)) .AddReferences(NuGetMetadataReference.Shouldly(shouldlyVersion)) .AddReferences(NuGetMetadataReference.Moq(moqVersion)) .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemNetHttp) .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemXmlLinq) .AddReferences(MetadataReferenceFacade.SystemThreadingTasks); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TestMethodShouldHaveCorrectSignatureTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TestMethodShouldHaveCorrectSignatureTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] [DataRow(MsTest.Ver11)] [DataRow(MsTest.Ver37)] public void TestMethodShouldHaveCorrectSignature_MsTest_Legacy(string testFwkVersion) => builder.AddPaths("TestMethodShouldHaveCorrectSignature.MsTest.Legacy.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] [DataRow(MsTest.Ver38)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void TestMethodShouldHaveCorrectSignature_MsTest(string testFwkVersion) => builder.AddPaths("TestMethodShouldHaveCorrectSignature.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(Latest)] public void TestMethodShouldHaveCorrectSignature_NUnit(string testFwkVersion) => builder.AddPaths("TestMethodShouldHaveCorrectSignature.NUnit.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] [DataRow("2.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] public void TestMethodShouldHaveCorrectSignature_Xunit(string testFwkVersion) => builder.AddPaths("TestMethodShouldHaveCorrectSignature.Xunit.cs") .AddReferences(NuGetMetadataReference.XunitFramework(testFwkVersion)) .Verify(); [TestMethod] public void TestMethodShouldHaveCorrectSignature_Xunit_Legacy() => builder.AddPaths("TestMethodShouldHaveCorrectSignature.Xunit.Legacy.cs") .AddReferences(NuGetMetadataReference.XunitFrameworkV1) .Verify(); [TestMethod] public void TestMethodShouldHaveCorrectSignature_XunitV3() => builder.AddPaths("TestMethodShouldHaveCorrectSignature.Xunit.cs") .AddReferences(NuGetMetadataReference.XunitFrameworkV3(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.SystemMemory(TestConstants.NuGetLatestVersion)) .AddReferences(MetadataReferenceFacade.NetStandard) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); [TestMethod] public void TestMethodShouldHaveCorrectSignature_MSTest_Miscellaneous() => // Additional test cases e.g. partial classes, and methods with multiple faults. // We have to specify a test framework for the tests, but it doesn't really matter which // one, so we're using MSTest and only testing a single version. builder.AddPaths("TestMethodShouldHaveCorrectSignature.Misc.cs") .AddReferences(NuGetMetadataReference.MSTestTestFrameworkV1) .Verify(); [TestMethod] public void TestMethodShouldHaveCorrectSignature_Latest() => builder.AddPaths("TestMethodShouldHaveCorrectSignature.Latest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFrameworkV1) .AddReferences(NuGetMetadataReference.XunitFramework(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TestMethodShouldNotBeIgnoredTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using static SonarAnalyzer.TestFramework.MetadataReferences.NugetPackageVersions; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TestMethodShouldNotBeIgnoredTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)); [TestMethod] public void TestMethodShouldNotBeIgnored_MsTest_Legacy() => new VerifierBuilder() .AddReferences(NuGetMetadataReference.MSTestTestFrameworkV1) .AddPaths("TestMethodShouldNotBeIgnored.MsTest.cs") .WithErrorBehavior(CompilationErrorBehavior.Ignore) // IgnoreAttribute doesn't contain any reason param .Verify(); [TestMethod] [DataRow(MsTest.Ver12)] [DataRow(MsTest.Ver311)] [DataRow(Latest)] public void TestMethodShouldNotBeIgnored_MsTest(string testFwkVersion) => new VerifierBuilder() .AddPaths("TestMethodShouldNotBeIgnored.MsTest.cs") .AddReferences(NuGetMetadataReference.MSTestTestFramework(testFwkVersion)) .Verify(); [TestMethod] public void TestMethodShouldNotBeIgnored_MsTest_InvalidCases() => builder.AddSnippet(""" using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.Diagnostics.TestMethods { [ThisDoesNotExist] public class MsTestClass3 { } [Ignore] } """) .VerifyNoIssuesIgnoreErrors(); [TestMethod] [DataRow(NUnit.Ver25)] [DataRow(NUnit.Ver27)] public void TestMethodShouldNotBeIgnored_NUnit_V2(string testFwkVersion) => builder.AddPaths("TestMethodShouldNotBeIgnored.NUnit.V2.cs") .AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)) .Verify(); [TestMethod] [DataRow("3.0.0")] // Ignore without reason no longer exist [DataRow(TestConstants.NuGetLatestVersion)] public void TestMethodShouldNotBeIgnored_NUnit(string testFwkVersion) => builder.AddPaths("TestMethodShouldNotBeIgnored.NUnit.cs").AddReferences(NuGetMetadataReference.NUnit(testFwkVersion)).Verify(); [TestMethod] [DataRow("2.0.0")] [DataRow(TestConstants.NuGetLatestVersion)] public void TestMethodShouldNotBeIgnored_Xunit(string testFwkVersion) => builder.AddPaths("TestMethodShouldNotBeIgnored.Xunit.cs").AddReferences(NuGetMetadataReference.XunitFramework(testFwkVersion)).VerifyNoIssues(); [TestMethod] public void TestMethodShouldNotBeIgnored_Xunit_v1() => builder.AddPaths("TestMethodShouldNotBeIgnored.Xunit.v1.cs").AddReferences(NuGetMetadataReference.XunitFrameworkV1).VerifyNoIssues(); [TestMethod] public void TestMethodShouldNotBeIgnored_Latest() => builder.AddPaths("TestMethodShouldNotBeIgnored.Latest.cs") .AddReferences(NuGetMetadataReference.XunitFrameworkV1) .AddReferences(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TestsShouldNotUseThreadSleepTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Test.Common; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TestsShouldNotUseThreadSleepTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .AddReferences(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.XunitFramework(XUnitVersions.Ver253)); private readonly VerifierBuilder builderVB = new VerifierBuilder() .AddReferences(NuGetMetadataReference.NUnit(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.MSTestTestFramework(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.XunitFramework(XUnitVersions.Ver253)); [TestMethod] public void TestsShouldNotUseThreadSleep_CS() => builderCS.AddPaths("TestsShouldNotUseThreadSleep.cs").Verify(); [TestMethod] public void TestsShouldNotUseThreadSleep_VB() => builderVB.AddPaths("TestsShouldNotUseThreadSleep.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ThisShouldNotBeExposedFromConstructorsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class ThisShouldNotBeExposedFromConstructorsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ThisShouldNotBeExposedFromConstructors() => builder.AddPaths("ThisShouldNotBeExposedFromConstructors.cs").Verify(); [TestMethod] public void ThisShouldNotBeExposedFromConstructors_CSharp9() => builder.AddPaths("ThisShouldNotBeExposedFromConstructors.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void ThisShouldNotBeExposedFromConstructors_CSharp10() => builder.AddPaths("ThisShouldNotBeExposedFromConstructors.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ThreadResumeOrSuspendShouldNotBeCalledTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ThreadResumeOrSuspendShouldNotBeCalledTest { [TestMethod] public void ThreadResumeOrSuspendShouldNotBeCalled_CS() => new VerifierBuilder().AddPaths("ThreadResumeOrSuspendShouldNotBeCalled.cs").Verify(); [TestMethod] public void ThreadResumeOrSuspendShouldNotBeCalled_VB() => new VerifierBuilder().AddPaths("ThreadResumeOrSuspendShouldNotBeCalled.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ThreadStaticNonStaticFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ThreadStaticNonStaticFieldTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ThreadStaticNonStaticField() => builder.AddPaths("ThreadStaticNonStaticField.cs").Verify(); [TestMethod] public void ThreadStaticNonStaticField_Latest() => builder.AddPaths("ThreadStaticNonStaticField.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ThreadStaticNonStaticField_CodeFix() => builder.WithCodeFix() .AddPaths("ThreadStaticNonStaticField.cs") .WithCodeFixedPaths("ThreadStaticNonStaticField.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void ThreadStaticNonStaticField_Latest_CodeFix() => builder.WithCodeFix() .AddPaths("ThreadStaticNonStaticField.Latest.cs") .WithCodeFixedPaths("ThreadStaticNonStaticField.Latest.Fixed.cs") .WithOptions(LanguageOptions.CSharpLatest) .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ThreadStaticWithInitializerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ThreadStaticWithInitializerTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ThreadStaticWithInitializer() => builder.AddPaths("ThreadStaticWithInitializer.cs").Verify(); [TestMethod] public void ThreadStaticWithInitializer_Latest() => builder.AddPaths("ThreadStaticWithInitializer.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ThrowReservedExceptionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ThrowReservedExceptionsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void ThrowReservedExceptions_CS() => builderCS.AddPaths("ThrowReservedExceptions.cs").Verify(); [TestMethod] public void ThrowReservedExceptions_CS_Latest() => builderCS.AddPaths("ThrowReservedExceptions.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void ThrowReservedExceptions_VB() => new VerifierBuilder().AddPaths("ThrowReservedExceptions.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ToStringShouldNotReturnNullTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ToStringShouldNotReturnNullTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ToStringShouldNotReturnNull_CS() => builderCS.AddPaths("ToStringShouldNotReturnNull.cs").Verify(); [TestMethod] public void ToStringShouldNotReturnNull_TopLevelStatements() => builderCS .WithTopLevelStatements() .AddPaths("ToStringShouldNotReturnNull.TopLevelStatements.cs") .VerifyNoIssues(); [TestMethod] public void ToStringShouldNotReturnNull_CS_Latest() => builderCS .AddPaths("ToStringShouldNotReturnNull.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void ToStringShouldNotReturnNull_VB() => builderVB.AddPaths("ToStringShouldNotReturnNull.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TooManyGenericParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TooManyGenericParametersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TooManyGenericParameters_DefaultValues() => builder.AddPaths("TooManyGenericParameters.DefaultValues.cs").Verify(); [TestMethod] public void TooManyGenericParameters_Latest() => builder.AddPaths("TooManyGenericParameters.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void TooManyGenericParameters_TopLevelStatements() => builder.AddPaths("TooManyGenericParameters.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void TooManyGenericParameters_CustomValues() => new VerifierBuilder() .AddAnalyzer(() => new TooManyGenericParameters { MaxNumberOfGenericParametersInClass = 4, MaxNumberOfGenericParametersInMethod = 4 }) .AddPaths("TooManyGenericParameters.CustomValues.cs") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TooManyLabelsInSwitchTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TooManyLabelsInSwitchTest { private readonly VerifierBuilder builderCS = new VerifierBuilder().AddAnalyzer(() => new CS.TooManyLabelsInSwitch() { Maximum = 2 }); [TestMethod] public void TooManyLabelsInSwitch_CS() => builderCS.AddPaths("TooManyLabelsInSwitch.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void TooManyLabelsInSwitch_CS_Latest() => builderCS.AddPaths("TooManyLabelsInSwitch.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void TooManyLabelsInSwitch_VB() => new VerifierBuilder().AddAnalyzer(() => new VB.TooManyLabelsInSwitch() { Maximum = 2 }) .AddPaths("TooManyLabelsInSwitch.vb") .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TooManyLoggingCallsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TooManyLoggingCallsTest { private readonly VerifierBuilder defaultBuilder = new VerifierBuilder(); private readonly VerifierBuilder configuredBuilder = new VerifierBuilder().AddAnalyzer(() => new TooManyLoggingCalls { DebugThreshold = 1, }); private readonly VerifierBuilder misconfiguredBuilder = new VerifierBuilder().AddAnalyzer(() => new TooManyLoggingCalls { DebugThreshold = 0, ErrorThreshold = -1, }); [TestMethod] public void TooManyLoggingCalls_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddPaths("TooManyLoggingCalls.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_TopLevelStatements_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .WithTopLevelStatements() .AddSnippet(""" using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; var logger = NullLogger.Instance; logger.LogError("Error 1"); // Noncompliant logger.LogError("Error 2"); // Secondary logger.LogWarning("Warn 1"); // Compliant """).Verify(); [TestMethod] public void TooManyLoggingCalls_CastleCoreLogging_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.CastleCore()) .AddPaths("TooManyLoggingCalls.Castle.Core.Logging.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_Log4Net_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.Log4Net("2.0.8", "net45-full")) .AddPaths("TooManyLoggingCalls.Log4Net.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_MicrosoftExtensionsLogging_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddPaths("TooManyLoggingCalls.Microsoft.Extensions.Logging.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_NLog_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.NLog()) .AddPaths("TooManyLoggingCalls.NLog.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_Serilog_CS() => defaultBuilder.AddReferences(NuGetMetadataReference.Serilog()) .AddPaths("TooManyLoggingCalls.Serilog.cs") .Verify(); [TestMethod] public void TooManyLoggingCalls_ConfiguredThresholds_CS() => configuredBuilder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddSnippet(""" using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger) { // The threshold was set to 1 logger.LogDebug("Debug 1"); // Noncompliant {{Reduce the number of Debug logging calls within this code block from 2 to the 1 allowed.}} logger.LogDebug("Debug 2"); // Secondary // The threshold was not configured, so it remains the default 1 logger.LogError("Error 1"); // Noncompliant {{Reduce the number of Error logging calls within this code block from 2 to the 1 allowed.}} logger.LogError("Error 2"); // Secondary } } """).Verify(); [TestMethod] public void TooManyLoggingCalls_MisconfiguredThresholds_CS() => misconfiguredBuilder.AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions()) .AddSnippet(""" using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger) { // The threshold was set to 0 logger.LogDebug("Debug 1"); // Noncompliant {{Reduce the number of Debug logging calls within this code block from 1 to the 0 allowed.}} // The threshold was misconfigured to -1, so it's using 0 as threshold logger.LogError("Error 1"); // Noncompliant {{Reduce the number of Error logging calls within this code block from 1 to the 0 allowed.}} } } """).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TooManyParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TooManyParametersTest { private readonly VerifierBuilder builderCSMax3 = new VerifierBuilder().AddAnalyzer(() => new CS.TooManyParameters { Maximum = 3 }); private readonly VerifierBuilder builderVBMax3 = new VerifierBuilder().AddAnalyzer(() => new VB.TooManyParameters { Maximum = 3 }); [TestMethod] public void TooManyParameters_CS_CustomValues() => builderCSMax3.AddPaths("TooManyParameters_CustomValues.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void TooManyParameters_CS_CustomValues_TopLevelStatements() => builderCSMax3.AddPaths("TooManyParameters_CustomValues.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void TooManyParameters_CS_CustomValues_Latest() => builderCSMax3.AddPaths("TooManyParameters_CustomValues.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void TooManyParameters_VB_CustomValues() => builderVBMax3.AddPaths("TooManyParameters_CustomValues.vb").Verify(); [TestMethod] public void TooManyParameters_CS_DefaultValues() => new VerifierBuilder().AddPaths("TooManyParameters_DefaultValues.cs") .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void TooManyParameters_VB_DefaultValues() => new VerifierBuilder().AddPaths("TooManyParameters_DefaultValues.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TrackNotImplementedExceptionTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class TrackNotImplementedExceptionTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TrackNotImplementedException_CSharp8() => builder.AddPaths("TrackNotImplementedException.cs") .AddReferences(MetadataReferenceFacade.NetStandard21) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void TrackNotImplementedException_CSharp11() => builder.AddPaths("TrackNotImplementedException.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TryStatementsWithIdenticalCatchShouldBeMergedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class TryStatementsWithIdenticalCatchShouldBeMergedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void TryStatementsWithIdenticalCatchShouldBeMerged() => builder.AddPaths("TryStatementsWithIdenticalCatchShouldBeMerged.cs").Verify(); [TestMethod] public void TryStatementsWithIdenticalCatchShouldBeMerged_RazorFile_CorrectMessage() => builder.AddSnippet( """ @using System; @code { public void Method() { try { } catch (Exception) { } finally { } try { } // Noncompliant {{Combine this 'try' with the one starting on line 6.}} catch (Exception) { } finally { } } } """, "SomeRazorFile.razor") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TypeExaminationOnSystemTypeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TypeExaminationOnSystemTypeTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TypeExaminationOnSystemType() => builder.AddPaths("TypeExaminationOnSystemType.cs").Verify(); [TestMethod] public void TypeExaminationOnSystemType_TopLevelStatements() => builder.AddPaths("TypeExaminationOnSystemType.TopLevelStatements.cs") .WithTopLevelStatements() .WithOptions(LanguageOptions.FromCSharp12) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TypeMemberVisibilityTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TypeMemberVisibilityTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TypeMemberVisibility_CS() => builder.AddPaths("TypeMemberVisibility.cs").Verify(); [TestMethod] public void TypeMemberVisibility_Latest() => builder.AddPaths("TypeMemberVisibility.Latest.cs", "TypeMemberVisibility.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TypeNamesShouldNotMatchNamespacesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class TypeNamesShouldNotMatchNamespacesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TypeNamesShouldNotMatchNamespaces() => builder.AddPaths("TypeNamesShouldNotMatchNamespaces.cs").Verify(); [TestMethod] public void TypeNamesShouldNotMatchNamespaces_CSharp9() => builder.AddPaths("TypeNamesShouldNotMatchNamespaces.CSharp9.cs").WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void TypeNamesShouldNotMatchNamespaces_CSharp10() => builder.AddPaths("TypeNamesShouldNotMatchNamespaces.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TypeParameterNameTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class TypeParameterNameTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void TypeParameterName_S119() => builder.WithOnlyDiagnostics(TypeParameterName.S119) .AddPaths("TypeParameterName.vb") .Verify(); [TestMethod] public void TypeParameterName_S2373() => builder.WithOnlyDiagnostics(TypeParameterName.S2373) .AddPaths("TypeParameterName.vb") .Verify(); [TestMethod] public void TypeParameterName_S119_CustomRegex() => new VerifierBuilder().AddAnalyzer(() => new TypeParameterName { Pattern = "^[A-Z]{2}\\d{2}$" }) .WithOnlyDiagnostics(TypeParameterName.S119) .AddPaths("TypeParameterName_CustomRegex.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/TypesShouldNotExtendOutdatedBaseTypesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TypesShouldNotExtendOutdatedBaseTypesTest { [TestMethod] public void TypesShouldNotExtendOutdatedBaseTypes() => new VerifierBuilder() .AddPaths("TypesShouldNotExtendOutdatedBaseTypes.cs") .AddReferences(MetadataReferenceFacade.SystemXml.Concat(MetadataReferenceFacade.SystemCollections)) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnaryPrefixOperatorRepeatedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnaryPrefixOperatorRepeatedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UnaryPrefixOperatorRepeated() => builderCS.AddPaths("UnaryPrefixOperatorRepeated.cs").Verify(); [TestMethod] public void UnaryPrefixOperatorRepeated_CS_TopLevelStatements() => builderCS.AddPaths("UnaryPrefixOperatorRepeated.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void UnaryPrefixOperatorRepeated_CS_Latest() => builderCS.AddPaths("UnaryPrefixOperatorRepeated.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void UnaryPrefixOperatorRepeated_CodeFix() => builderCS.WithCodeFix() .AddPaths("UnaryPrefixOperatorRepeated.cs") .WithCodeFixedPaths("UnaryPrefixOperatorRepeated.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void UnaryPrefixOperatorRepeated_VB() => builderVB.AddPaths("UnaryPrefixOperatorRepeated.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnchangedLocalVariablesShouldBeConstTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnchangedLocalVariablesShouldBeConstTest { private readonly VerifierBuilder verifier = new VerifierBuilder(); public TestContext TestContext { get; set; } [TestMethod] public void UnchangedLocalVariablesShouldBeConst() => verifier.AddPaths("UnchangedLocalVariablesShouldBeConst.cs").Verify(); [TestMethod] public void UnchangedLocalVariablesShouldBeConst_CSharp7() => verifier.AddSnippet(""" public class Test{ public void Message() { var s1 = "Test"; // Noncompliant {{Add the 'const' modifier to 's1', and replace 'var' with 'string'.}} string s2 = $"This is a {nameof(Message)}"; // Compliant - constant string interpolation is only valid in C# 10 and above var s3 = $"This is a {nameof(Message)}"; // Compliant - constant string interpolation is only valid in C# 10 and above var s4 = "This is a" + $" {nameof(Message)}"; // Compliant - constant string interpolation is only valid in C# 10 and above var s5 = $@"This is a {nameof(Message)}"; // Compliant - constant string interpolation is only valid in C# 10 and above } } """) .WithOptions(LanguageOptions.OnlyCSharp7).Verify(); #if NET [TestMethod] public void UnchangedLocalVariablesShouldBeConst_TopLevelStatements() => verifier.AddPaths("UnchangedLocalVariablesShouldBeConst.TopLevelStatements.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithTopLevelStatements() .Verify(); [TestMethod] public void UnchangedLocalVariablesShouldBeConst_Latest() => verifier.AddPaths("UnchangedLocalVariablesShouldBeConst.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .WithWarningsAsErrors("CS9193") .Verify(); [TestMethod] public void UnchangedLocalVariablesShouldBeConst_CshtmlIdeGenerated() => verifier.AddPaths("UnchangedLocalVariablesShouldBeConst.cshtml.ide.g.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .WithConcurrentAnalysis(false) // With concurrent analysis the issues are not raised .AddReferences( [ AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcCore, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcViewFeatures, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcRazor, AspNetCoreMetadataReference.MicrosoftAspNetCoreMvcAbstractions, ..NuGetMetadataReference.MicrosoftExtensionsConfigurationAbstractions("9.0.1"), ..NuGetMetadataReference.MicrosoftAspNetCoreMvcRazorRuntime("2.3.0"), ]) .WithOptions(LanguageOptions.CSharpLatest) .Verify(); #endif [TestMethod] public void UnchangedLocalVariablesShouldBeConst_InvalidCode() => verifier.AddSnippet(""" // invalid code public void Test_TypeThatCannotBeConst(int arg) // Error [CS0106, CS8805] { System.Random random = 1; // Error [CS0029] } // invalid code public void (int arg) // Error [CS0116, CS0119, CS1525, CS1073, CS1002] { int intVar = 1; // Noncompliant } """).WithOptions(LanguageOptions.FromCSharp9).Verify(); [TestMethod] public void UnchangedLocalVariablesShouldBeConst_Fix() => verifier .AddPaths("UnchangedLocalVariablesShouldBeConst.ToFix.cs") .WithCodeFixedPaths("UnchangedLocalVariablesShouldBeConst.Fixed.cs") .WithCodeFix() .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnconditionalJumpStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnconditionalJumpStatementTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void UnconditionalJumpStatement() => builderCS.AddPaths("UnconditionalJumpStatement.cs").Verify(); [TestMethod] public void UnconditionalJumpStatement_CSharpLatest() => builderCS.AddPaths("UnconditionalJumpStatement.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void UnconditionalJumpStatement_VB() => new VerifierBuilder().AddPaths("UnconditionalJumpStatement.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UninvokedEventDeclarationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UninvokedEventDeclarationTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UninvokedEventDeclaration() => builder.AddPaths("UninvokedEventDeclaration.cs") .Verify(); [TestMethod] public void UninvokedEventDeclaration_CSharpLatest() => builder.AddPaths("UninvokedEventDeclaration.Latest.cs", "UninvokedEventDeclaration.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnnecessaryBitwiseOperationTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnnecessaryBitwiseOperationTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UnnecessaryBitwiseOperation_CS() => builderCS.AddPaths("UnnecessaryBitwiseOperation.cs").Verify(); [TestMethod] public void UnnecessaryBitwiseOperation_CS_TopLevelStatements() => builderCS.AddPaths("UnnecessaryBitwiseOperation.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void UnnecessaryBitwiseOperation_CS_Latest() => builderCS.AddPaths("UnnecessaryBitwiseOperation.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void UnnecessaryBitwiseOperation_CS_CodeFix() => builderCS.AddPaths("UnnecessaryBitwiseOperation.cs") .WithCodeFix() .WithCodeFixedPaths("UnnecessaryBitwiseOperation.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void UnnecessaryBitwiseOperation_VB() => builderVB.AddPaths("UnnecessaryBitwiseOperation.vb").Verify(); [TestMethod] public void UnnecessaryBitwiseOperation_VB_CodeFix() => builderVB.AddPaths("UnnecessaryBitwiseOperation.vb") .WithCodeFix() .WithCodeFixedPaths("UnnecessaryBitwiseOperation.Fixed.vb") .VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnnecessaryMathematicalComparisonTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnnecessaryMathematicalComparisonTest { private readonly VerifierBuilder builderCS = new VerifierBuilder() .WithWarningsAsErrors("CS0652"); [TestMethod] public void UnnecessaryMathematicalComparison_CS() => builderCS.AddPaths("UnnecessaryMathematicalComparison.cs").Verify(); [TestMethod] public void UnnecessaryMathematicalComparison_CSharpLatest() => builderCS.AddPaths("UnnecessaryMathematicalComparison.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnnecessaryUsingsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnnecessaryUsingsTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.MicrosoftWin32Primitives) .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .WithWarningsAsErrors("CS0105"); public TestContext TestContext { get; set; } [TestMethod] public void UnnecessaryUsings() => builder.AddPaths("UnnecessaryUsings.cs", "UnnecessaryUsings2.cs", "UnnecessaryUsingsFNRepro.cs").WithAutogenerateConcurrentFiles(false).Verify(); [TestMethod] public void UnnecessaryUsings_InheritedProperty() => builder.AddPaths("UnnecessaryUsings.InheritedPropertyBase.cs", "UnnecessaryUsings.InheritedPropertyChild.cs", "UnnecessaryUsings.InheritedPropertyUsage.cs") .WithAutogenerateConcurrentFiles(false) .VerifyNoIssues(); #if NET [TestMethod] public void UnnecessaryUsings_CSharp10_GlobalUsings() => builder.AddPaths("UnnecessaryUsings.CSharp10.Global.cs", "UnnecessaryUsings.CSharp10.Consumer.cs").WithTopLevelStatements().WithOptions(LanguageOptions.FromCSharp10).Verify(); [TestMethod] [DataRow("_ViewImports.cshtml")] [DataRow("_viewimports.cshtml")] [DataRow("_viEwiMpoRts.cshtml")] public void UnnecessaryUsings_RazorViewImportsCshtmlFile_NoIssueReported(string fileName) => builder .AddSnippet(@"@using System.Text.Json;", fileName) .AddReferences(NuGetMetadataReference.SystemTextJson("7.0.4")) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .VerifyNoIssues(); [TestMethod] [DataRow("_Imports.razor")] [DataRow("_imports.razor")] [DataRow("_iMpoRts.razor")] public void UnnecessaryUsings_RazorImportsRazorFile_NoIssueReported(string fileName) => builder .AddSnippet(@"@using System.Text.Json;", fileName) .AddReferences(NuGetMetadataReference.SystemTextJson("7.0.4")) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .VerifyNoIssues(); [TestMethod] [DataRow("RandomFile_ViewImports.cshtml")] [DataRow("RandomFile_Imports.cshtml")] [DataRow("_Imports.cshtml")] public void UnnecessaryUsings_RazorViewImportsSimilarCshtmlFile_IssuesReported(string fileName) => builder .AddSnippet("@using System.Linq;", "_ViewImports.cshtml") .AddSnippet(@"@using System.Text.Json; @* Noncompliant *@", fileName) .AddReferences(NuGetMetadataReference.SystemTextJson("7.0.4")) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] [DataRow("RandomFile_ViewImports.razor")] [DataRow("RandomFile_Imports.razor")] [DataRow("_ViewImports.razor")] public void UnnecessaryUsings_RazorViewImportsSimilarRazorFile_IssuesReported(string fileName) => builder .AddSnippet("@using System.Linq;", "_Imports.razor") .AddSnippet(@"@using System.Text.Json; @* Noncompliant *@", fileName) .AddReferences(NuGetMetadataReference.SystemTextJson("7.0.4")) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, ProjectType.Product)) .Verify(); [TestMethod] [DataRow("_ViewImports.cs")] [DataRow("_Imports.cs")] public void UnnecessaryUsings_RazorViewImportsSimilarCSFile_IssuesReported(string fileName) => builder.AddSnippet(@"using System.Text; // Noncompliant", fileName).Verify(); [TestMethod] public void UnnecessaryUsings_CSharp10_FileScopedNamespace() => builder.AddPaths("UnnecessaryUsings.CSharp10.FileScopedNamespace.cs").WithOptions(LanguageOptions.FromCSharp10).WithConcurrentAnalysis(false).Verify(); [TestMethod] public void UnnecessaryUsings_CodeFix_CSharp10_FileScopedNamespace() => builder.AddPaths("UnnecessaryUsings.CSharp10.FileScopedNamespace.cs") .WithOptions(LanguageOptions.FromCSharp10) .WithCodeFix() .WithCodeFixedPaths("UnnecessaryUsings.CSharp10.FileScopedNamespace.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void UnnecessaryUsings_CSharp9() => builder.AddPaths("UnnecessaryUsings.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void UnnecessaryUsings_TupleDeconstruct_NetCore() => builder.AddPaths("UnnecessaryUsings.TupleDeconstruct.NetCore.cs").Verify(); [TestMethod] public void UnnecessaryUsings_CSharp12() => builder.AddPaths("UnnecessaryUsings.CSharp12.cs").WithOptions(LanguageOptions.FromCSharp12).VerifyNoIssues(); #elif NETFRAMEWORK [TestMethod] public void UnnecessaryUsings_TupleDeconstruct_NetFx() => builder.AddPaths("UnnecessaryUsings.TupleDeconstruct.NetFx.cs").Verify(); #endif [TestMethod] public void UnnecessaryUsings_CodeFix() => builder.AddPaths("UnnecessaryUsings.cs") .WithCodeFix() .WithCodeFixedPaths("UnnecessaryUsings.Fixed.cs", "UnnecessaryUsings.Fixed.Batch.cs") .VerifyCodeFix(); [TestMethod] public void UnnecessaryUsings_DocumentationModeNone() => builder .AddSnippet(""" using System; // Noncompliant FP, used by cref https://sonarsource.atlassian.net/browse/NET-1950 namespace SonarAnalyzer.Experiments.CSharp { public enum S1128 { /// DateTimeValue, } } """) .WithOptions(ImmutableArray.Create(new CSharpParseOptions(documentationMode: DocumentationMode.None))) .Verify(); [TestMethod] public void EquivalentNameSyntax_Equals_Object() { var main = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Lorem")); object same = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Lorem")); object different = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Ipsum")); main.Equals(same).Should().BeTrue(); main.Equals(null).Should().BeFalse(); main.Equals("different type").Should().BeFalse(); main.Equals(different).Should().BeFalse(); } [TestMethod] public void EquivalentNameSyntax_Equals_EquivalentNameSyntax() { var main = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Lorem")); var same = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Lorem")); var different = new EquivalentNameSyntax(SyntaxFactory.IdentifierName("Ipsum")); main.Equals(same).Should().BeTrue(); main.Equals(null).Should().BeFalse(); main.Equals(different).Should().BeFalse(); } [TestMethod] public void NamespaceComparer_MatchesAncestors_WhenDefaultEqualityDoesNot() { var compiler = new SnippetCompiler(""" namespace System.IO { class C { Exception e; } } """); var fromDeclaration = compiler.NamespaceSymbol("IO").ContainingNamespace; var fromType = compiler.Symbol(compiler.Nodes().Single(x => x.Identifier.Text == "Exception")).ContainingNamespace; // Both represent "System" but Roslyn returns a merged namespace from the declaration chain vs a per-assembly namespace from ContainingNamespace fromDeclaration.GetHashCode().Should().NotBe(fromType.GetHashCode()); fromDeclaration.Equals(fromType).Should().BeFalse(); NamespaceComparer.Instance.GetHashCode(fromDeclaration).Should().Be(NamespaceComparer.Instance.GetHashCode(fromType)); NamespaceComparer.Instance.Equals(fromDeclaration, fromType).Should().BeTrue(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnsignedTypesUsageTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UnsignedTypesUsageTest { [TestMethod] public void UnsignedTypesUsage() => new VerifierBuilder().AddPaths("UnsignedTypesUsage.vb").Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.Constructors.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class UnusedPrivateMemberTest { [TestMethod] public void UnusedPrivateMember_Constructor_Accessibility() => builder.AddSnippet(@" public class PrivateConstructors { private PrivateConstructors(int i) { var x = 5; } // Noncompliant {{Remove the unused private constructor 'PrivateConstructors'.}} // ^^^^^^^^^^^^^^^^^^^ static PrivateConstructors() { var x = 5; } private class InnerPrivateClass // Noncompliant { internal InnerPrivateClass(int i) { var x = 5; } // Noncompliant protected InnerPrivateClass(string s) { var x = 5; } // Noncompliant protected internal InnerPrivateClass(double d) { var x = 5; } // Noncompliant public InnerPrivateClass(char c) { var x = 5; } // Noncompliant } private class OtherPrivateClass // Noncompliant { private OtherPrivateClass() { var x = 5; } // Noncompliant } } public class NonPrivateMembers { internal NonPrivateMembers(int i) { var x = 5; } protected NonPrivateMembers(string s) { var x = 5; } protected internal NonPrivateMembers(double d) { var x = 5; } public NonPrivateMembers(char c) { var x = 5; } public class InnerPublicClass { internal InnerPublicClass(int i) { var x = 5; } protected InnerPublicClass(string s) { var x = 5; } protected internal InnerPublicClass(double d) { var x = 5; } public InnerPublicClass(char c) { var x = 5; } } } ").Verify(); [TestMethod] public void UnusedPrivateMember_Constructor_DirectReferences() => builder.AddSnippet(""" public abstract class PrivateConstructors { public class Constructor1 { public static readonly Constructor1 Instance = new Constructor1(); private Constructor1() { var x = 5; } } public class Constructor2 { public Constructor2(int a) { } private Constructor2() { var x = 5; } // Compliant - FN } public class Constructor3 { public Constructor3(int a) : this() { } private Constructor3() { var x = 5; } } public class Constructor4 { static Constructor4() { var x = 5; } } } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Constructor_Inheritance() => builder.AddSnippet(@" public class Inheritance { private abstract class BaseClass1 { protected BaseClass1() { var x = 5; } } private class DerivedClass1 : BaseClass1 // Noncompliant {{Remove the unused private class 'DerivedClass1'.}} { public DerivedClass1() : base() { } } // https://github.com/SonarSource/sonar-dotnet/issues/1398 private abstract class BaseClass2 { protected BaseClass2() { var x = 5; } } private class DerivedClass2 : BaseClass2 // Noncompliant {{Remove the unused private class 'DerivedClass2'.}} { public DerivedClass2() { } } } ").Verify(); [TestMethod] public void UnusedPrivateMember_Empty_Constructors() => builder.AddSnippet(""" public class PrivateConstructors { private PrivateConstructors(int i) { } // Compliant, empty ctors are reported from another rule } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Illegal_Interface_Constructor() => // While typing code in IDE, we can end up in a state where an interface has a constructor defined. // Even though this results in a compiler error (CS0526), IDE will still trigger rules on the interface. builder.AddSnippet(@" public interface IInterface { // UnusedPrivateMember rule does not trigger AD0001 error from NullReferenceException IInterface() {} // Error [CS0526] } ").Verify(); [TestMethod] [DataRow("private", "Remove the unused private constructor 'Foo'.")] [DataRow("protected", "Remove unused constructor of private type 'Foo'.")] [DataRow("internal", "Remove unused constructor of private type 'Foo'.")] [DataRow("public", "Remove unused constructor of private type 'Foo'.")] public void UnusedPrivateMember_NonPrivateConstructorInPrivateClass(string accessModifier, string expectedMessage) => builder.AddSnippet($$$""" public class Some { private class Foo // Noncompliant { {{{accessModifier}}} Foo() // Noncompliant {{{{{expectedMessage}}}}} { var a = 1; } } } """).Verify(); [TestMethod] public void UnusedPrivateMember_RecordPositionalConstructor() => builder.AddSnippet(""" // https://github.com/SonarSource/sonar-dotnet/issues/5381 public abstract record Foo { Foo(string value) { Value = value; } public string Value { get; } public sealed record Bar(string Value) : Foo(Value); } """).WithOptions(LanguageOptions.FromCSharp9).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_NonExistentRecordPositionalConstructor() => builder.AddSnippet(@" public abstract record Foo { public sealed record Bar(string Value) : RandomRecord(Value); // Error [CS0246, CS1729] no suitable method found to override }").WithOptions(LanguageOptions.FromCSharp10).Verify(); // Tests private nested sealed class with [Export] attribute - a valid MEF pattern used in VS extensions // Production example: https://github.com/NoahRic/EditorItemTemplates/blob/master/ClassifierTemplate.cs [TestMethod] public void UnusedPrivateMember_Constructor_MefExportOnType() => builder.AddSnippet(""" using System.ComponentModel.Composition; public interface IFormatter { string Format(string input); } public class FormatterContainer { [Export(typeof(IFormatter))] private sealed class MefExportedFormatter : IFormatter // Compliant - MEF exported type { private readonly string _prefix; MefExportedFormatter() => _prefix = "Formatted: "; // Compliant - MEF instantiates via reflection public string Format(string input) => _prefix + input; } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests InheritedExport on interface - implementing classes automatically inherit the export // Per MS docs: "an interface can be decorated with an InheritedExport attribute at the interface level, // and that export along with any associated metadata will be inherited by any implementing classes" // Source: https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef [TestMethod] public void UnusedPrivateMember_Constructor_MefInheritedExportOnInterface() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(IClassifier))] public interface IClassifier { string Classify(string input); } public class ClassifierContainer { private sealed class SimpleClassifier : IClassifier // Compliant - implements InheritedExport interface { private readonly string _prefix; SimpleClassifier() => _prefix = "Classified: "; // Compliant - MEF instantiates via reflection public string Classify(string input) => _prefix + input; } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests InheritedExport on base class - subclasses automatically inherit and provide the same export // Per MS docs: "a part can export itself by using the InheritedExport attribute. // Subclasses of the part will inherit and provide the same export, including contract name and contract type" // Source: https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef [TestMethod] public void UnusedPrivateMember_Constructor_MefInheritedExportOnBaseClass() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(HandlerBase))] public abstract class HandlerBase { public abstract string Handle(string input); } public class HandlerContainer { private sealed class SimpleHandler : HandlerBase // Compliant - derives from InheritedExport base { private readonly string _tag; SimpleHandler() => _tag = "[handled] "; // Compliant - MEF instantiates via reflection public override string Handle(string input) => _tag + input; } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests MEF2 (System.Composition) Export attribute - same pattern as MEF1 but from the newer lightweight composition API [TestMethod] public void UnusedPrivateMember_Constructor_Mef2ExportOnType() => builder.AddSnippet(""" using System.Composition; public interface IProcessor { string Process(string input); } public class ProcessorContainer { [Export(typeof(IProcessor))] private sealed class SimpleProcessor : IProcessor // Compliant - MEF2 exported type { private readonly string _suffix; SimpleProcessor() => _suffix = " [processed]"; // Compliant - MEF2 instantiates via reflection public string Process(string input) => input + _suffix; } } """) .AddReferences(MetadataReferenceFacade.SystemCompositionAttributedModel) .VerifyNoIssues(); // Tests custom attribute derived from ExportAttribute - MEF recognizes derived export attributes // Per MS docs: custom attributes inheriting from ExportAttribute can include metadata as properties // Source: https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef [TestMethod] public void UnusedPrivateMember_Constructor_MefCustomExportAttribute() => builder.AddSnippet(""" using System.ComponentModel.Composition; public class MyCustomExportAttribute : ExportAttribute { } public class PluginContainer { [MyCustomExport] private sealed class CustomPlugin // Compliant - custom Export-derived attribute { CustomPlugin() { var x = 1; } // Compliant - MEF instantiates via reflection } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests InheritedExport on generic interface - implementing classes inherit the export // MEF supports generic types: https://www.codeproject.com/Articles/323919/MEF-Generics [TestMethod] public void UnusedPrivateMember_Constructor_MefInheritedExportOnGenericInterface() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(IHandler<>))] public interface IHandler { void Handle(T item); } public class Container { private sealed class StringHandler : IHandler // Compliant - implements InheritedExport generic interface { StringHandler() { var x = 1; } // Compliant - MEF instantiates via reflection public void Handle(string item) { } } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests InheritedExport on generic base class - derived classes inherit the export [TestMethod] public void UnusedPrivateMember_Constructor_MefInheritedExportOnGenericBaseClass() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(ProcessorBase<>))] public abstract class ProcessorBase { public abstract void Process(T item); } public class Container { private sealed class IntProcessor : ProcessorBase // Compliant - derives from InheritedExport generic base { IntProcessor() { var x = 1; } // Compliant - MEF instantiates via reflection public override void Process(int item) { } } } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.Fields.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class UnusedPrivateMemberTest { [TestMethod] public void UnusedPrivateMember_Field_Accessibility() => builder.AddSnippet(""" public class PrivateMembers { private int privateField; // Noncompliant {{Remove the unused private field 'privateField'.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^ private static int privateStaticField; // Noncompliant private class InnerPrivateClass // Noncompliant { internal int internalField; // Noncompliant protected int protectedField; // Noncompliant protected internal int protectedInternalField; // Noncompliant public int publicField; // Noncompliant internal static int internalStaticField; // Noncompliant protected static int protectedStaticField; // Noncompliant protected internal static int protectedInternalStaticField; // Noncompliant public static int publicStaticField; // Noncompliant } } public class NonPrivateMembers { internal int internalField; protected int protectedField; protected internal int protectedInternalField; public int publicField; internal static int internalStaticField; protected static int protectedStaticField; protected internal static int protectedInternalStaticField; public static int publicStaticField; public class InnerPublicClass { internal int internalField; protected int protectedField; protected internal int protectedInternalField; public int publicField; internal static int internalStaticField; protected static int protectedStaticField; protected internal static int protectedInternalStaticField; public static int publicStaticField; } } """).Verify(); [TestMethod] public void UnusedPrivateMember_Field_MultipleDeclarations() => builder.AddSnippet(""" public class PrivateMembers { private int x, y, z; // Noncompliant {{Remove the unused private field 'x'.}} // ^^^^^^^^^^^^^^^^^^^^ private int a, b, c; // ^ {{Remove the unused private field 'a'.}} // ^ @-1 {{Remove the unused private field 'c'.}} public int Method1() => b; } """).Verify(); [TestMethod] public void UnusedPrivateMember_Fields_DirectReferences() => builder.AddSnippet(""" using System; public class FieldUsages { private int field1; // Noncompliant {{Remove this unread private field 'field1' or refactor the code to use its value.}} private int field2; // Noncompliant {{Remove this unread private field 'field2' or refactor the code to use its value.}} private int field3; // Noncompliant {{Remove this unread private field 'field3' or refactor the code to use its value.}} private int field4; private int field5; private int field6; public int Method1() { field1 = 0; this.field2 = 0; int.TryParse("1", out field3); Console.Write(field4); Func x = () => field5; return field6; } private int field7; public int ExpressionBodyMethod() => field7; private static int field8; public int Property { get; set; } = field8; public FieldUsages(int number) { } private static int field9; public FieldUsages() : this(field9) { } private int field10; private int field11; // Compliant nameof(field11) public object Method2() { var x = new[] { field10 }; var name = nameof(field11); return null; } } """).Verify(); [TestMethod] public void UnusedPrivateMember_Fields_StructLayout() => builder.AddSnippet(""" // https://github.com/SonarSource/sonar-dotnet/issues/6912 using System.Runtime.InteropServices; public class Foo { [StructLayout(LayoutKind.Sequential)] private struct NetResource { public string LocalName; // Compliant: Unused members in a struct with StructLayout attribute are compliant public string RemoteName; } [DllImport("mpr.dll")] private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags); public void Bar() { var netResource = new NetResource { RemoteName = "foo" }; WNetAddConnection2(netResource, "password", "username", 0); } } """).VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.Methods.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class UnusedPrivateMemberTest { [TestMethod] public void UnusedPrivateMember_Method_Accessibility() => builder.AddSnippet(@" public class PrivateMembers { private int PrivateMethod() { return 0; } // Noncompliant {{Remove the unused private method 'PrivateMethod'.}} // ^^^^^^^^^^^^^ private static int PrivateStaticMethod() { return 0; } // Noncompliant private class InnerPrivateClass // Noncompliant { internal int InternalMethod() { return 0; } // Noncompliant protected int ProtectedMethod() { return 0; } // Noncompliant protected internal int ProtectedInternalMethod() { return 0; } // Noncompliant public int PublicMethod() { return 0; } // Noncompliant internal static int InternalStaticMethod() { return 0; } // Noncompliant protected static int ProtectedStaticMethod() { return 0; } // Noncompliant protected internal static int ProtectedInternalStaticMethod() { return 0; } // Noncompliant public static int PublicStaticMethod() { return 0; } // Noncompliant } } public class NonPrivateMembers { internal int InternalMethod() { return 0; } protected int ProtectedMethod() { return 0; } protected internal int ProtectedInternalMethod() { return 0; } public int PublicMethod() { return 0; } internal static int InternalStaticMethod() { return 0; } protected static int ProtectedStaticMethod() { return 0; } protected internal static int ProtectedInternalStaticMethod() { return 0; } public static int PublicStaticMethod() { return 0; } public class InnerPublicClass { internal int InternalMethod() { return 0; } protected int ProtectedMethod() { return 0; } protected internal int ProtectedInternalMethod() { return 0; } public int PublicMethod() { return 0; } internal static int InternalStaticMethod() { return 0; } protected static int ProtectedStaticMethod() { return 0; } protected internal static int ProtectedInternalStaticMethod() { return 0; } public static int PublicStaticMethod() { return 0; } } } public interface IInterface { int InterfaceMethod(); } public class InterfaceImpl : IInterface { int IInterface.InterfaceMethod() => 0; } ").Verify(); [TestMethod] public void UnusedPrivateMember_Methods_DirectReferences() => builder.AddSnippet(""" using System; using System.Linq; public class MethodUsages { private int Method1() { return 0; } private int Method2() { return 0; } private int Method3() { return 0; } private int Method4() { return 0; } private int Method5() { return 0; } private int Method6() { return 0; } private int Method7() { return 0; } public int Test1(MethodUsages other) { int i; i = Method1(); i = this.Method2(); Console.Write(Method3()); new MethodUsages().Method4(); Func x = () => Method5(); other.Method6(); return Method7(); } private int Method8() { return 0; } public int ExpressionBodyMethod() => Method8(); private static int Method9() { return 0; } public MethodUsages(int number) { } public MethodUsages() : this(Method9()) { } private int Method10() { return 0; } private int Method11() { return 0; } public object Test2() { var x = new[] { Method10() }; var name = nameof(Method11); return null; } private int Method12(int i) { return 0; } public void Test3() { new[] { 1, 2, 3 }.Select(Method12); } } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Methods_Main() => builder.AddSnippet(@" using System.Threading.Tasks; public class NewClass1 { // See https://github.com/SonarSource/sonar-dotnet/issues/888 static async Task Main() { } // Compliant - valid main method since C# 7.1 } public class NewClass2 { static async Task Main() { return 1; } // Compliant - valid main method since C# 7.1 } public class NewClass3 { static async Task Main(string[] args) { } // Compliant - valid main method since C# 7.1 } public class NewClass4 { static async Task Main(string[] args) { return 1; } // Compliant - valid main method since C# 7.1 } public class NewClass5 { static async Task Main(string[] args) { return ""a""; } // Noncompliant } ").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.Properties.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class UnusedPrivateMemberTest { [TestMethod] public void UnusedPrivateMember_Property_Accessibility() => builder.AddSnippet(@" public class PrivateMembers { private int PrivateProperty { get; set; } // Noncompliant {{Remove the unused private property 'PrivateProperty'.}} // ^^^^^^^^^^^^^^^ private static int PrivateStaticProperty { get; set; } // Noncompliant private int this[string i] { get { return 5; } set { } } // Noncompliant private class InnerPrivateClass // Noncompliant { internal int InternalProperty { get; set; } // Noncompliant protected int ProtectedProperty { get; set; } // Noncompliant protected internal int ProtectedInternalProperty { get; set; } // Noncompliant public int PublicProperty { get; set; } // Noncompliant internal static int InternalStaticProperty { get; set; } // Noncompliant protected static int ProtectedStaticProperty { get; set; } // Noncompliant protected internal static int ProtectedInternalStaticProperty { get; set; } // Noncompliant public static int PublicStaticProperty { get; set; } // Noncompliant } } public class NonPrivateMembers { internal int InternalProperty { get; set; } protected int ProtectedProperty { get; set; } protected internal int ProtectedInternalProperty { get; set; } public int PublicProperty { get; set; } internal static int InternalStaticProperty { get; set; } protected static int ProtectedStaticProperty { get; set; } protected internal static int ProtectedInternalStaticProperty { get; set; } public static int PublicStaticProperty { get; set; } public class InnerPublicClass { internal int InternalProperty { get; set; } protected int ProtectedProperty { get; set; } protected internal int ProtectedInternalProperty { get; set; } public int PublicProperty { get; set; } internal static int InternalStaticProperty { get; set; } protected static int ProtectedStaticProperty { get; set; } protected internal static int ProtectedInternalStaticProperty { get; set; } public static int PublicStaticProperty { get; set; } } } public interface IInterface { int InterfaceProperty { get; set; } } public class InterfaceImpl : IInterface { int IInterface.InterfaceProperty { get { return 0; } set { } } } ").Verify(); [TestMethod] public void UnusedPrivateMember_Properties_DirectReferences() => builder.AddSnippet(""" using System; public class PropertyUsages { private int Property1 { get; set; } private int Property2 { get; set; } private int Property4 { get; set; } private int Property5 { get; set; } private int Property6 { get; set; } public int Method1(PropertyUsages other) { Property1 = 0; this.Property2 = 0; ((Property4)) = 0; Console.Write(Property4); new PropertyUsages().Property5 = 0; Func x = () => Property5; other.Property6 = 0; return Property6; } private int Property7 { get; set; } = 0; public int ExpressionBodyMethod() => Property7; private static int Property8 { get; set; } = 0; public int SomeProperty { get; set; } = Property8; private static int Property9 { get; set; } static PropertyUsages() { Property9 = 0; } public PropertyUsages(int number) { } public PropertyUsages() : this(Property9) { } private int Property10 { get; set; } private int Property11 { get; set; } public object Method2() { if ((Property10 = 0) == 0) { } var x = new[] { Property10 }; var name = nameof(Property11); return null; } private int this[string i] { get { return 5; } set { } } public void Method3() { var x = this["5"]; this["5"] = 10; } private int Property12 { get; set; } = 42; // FN } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Properties_Accessors() => builder.AddSnippet(@" using System; public class PropertyUsages { public int AProperty { private get; set; } // Noncompliant {{Remove the unused private getter 'get_AProperty'.}} public int BProperty { get; private set; } // Noncompliant {{Remove the unused private setter 'set_BProperty'.}} public int CProperty { internal get; set; } // Compliant public int DProperty { get; internal set; } // Compliant public int EProperty { protected get; set; } // Compliant public int E2Property { get; protected set; } // Compliant public int FProperty { get; private set; } // Compliant public int GProperty { private get; set; } // Noncompliant {{Remove the unused private getter 'get_GProperty'.}} public int HProperty { get; private set; } // Noncompliant {{Remove the unused private setter 'set_HProperty'.}} public int IProperty { private get; set; } // Compliant public int JProperty { get; private set; } // Compliant: both read and write public int KProperty { private get; set; } // Compliant: both read and write public int LProperty { get; private set; } // FN: private set is used in the constructor, not necessary protected int MProperty { private get; set; } // Noncompliant {{Remove the unused private getter 'get_MProperty'.}} public PropertyUsages() { LProperty = 42; } public void Method() { FProperty = HProperty; GProperty = IProperty; JProperty = KProperty; KProperty = JProperty; } public interface ISomeInterface { string Something { get; } string SomethingElse { get; } } public class SomeClass : ISomeInterface { public string Something { get; private set; } // Compliant public string SomethingElse { get; private set; } // Noncompliant public void Method(string str) { Something = str; } } } ").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.Types.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class UnusedPrivateMemberTest { [TestMethod] public void UnusedPrivateMember_Types_Accessibility() => builder.AddSnippet(@" public class PrivateTypes { private class InnerPrivateClass // Noncompliant {{Remove the unused private class 'InnerPrivateClass'.}} { protected class ProtectedClass { } // Noncompliant protected internal class ProtectedInternalClass { } // Noncompliant public class PublicClass { } // Noncompliant } private class PrivateClass { } // Noncompliant // ^^^^^^^^^^^^ internal class InternalClass { } // Noncompliant private struct PrivateStruct { } // Noncompliant internal struct InternalStruct { } // Noncompliant } public class NonPrivateTypes { protected class ProtectedClass { } protected internal class ProtectedInternalClass { } public class PublicClass { } protected struct ProtectedStruct { } protected internal struct ProtectedInternalStruct { } public struct PublicStruct { } } ").Verify(); [TestMethod] public void UnusedPrivateMember_Types_InternalsVisibleTo() => builder.AddSnippet(@" [assembly:System.Runtime.CompilerServices.InternalsVisibleTo("""")] public class PrivateTypes { private class PrivateClass { } // Noncompliant internal class InternalClass { } // Compliant, internal types are not reported when InternalsVisibleTo is present } ").Verify(); [TestMethod] public void UnusedPrivateMember_Types_Internals() => builder.AddSnippet(""" // https://github.com/SonarSource/sonar-dotnet/issues/1225 // https://github.com/SonarSource/sonar-dotnet/issues/904 using System; public class Class1 { public void Method1() { var x = Sample.Constants.X; } } public class Sample { internal class Constants { public const int X = 5; } } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Types_DirectReferences() => builder.AddSnippet(@" using System.Linq; public class PrivateTypes { private class PrivateClass1 { } private class PrivateClass2 { } private class PrivateClass3 { } private class PrivateClass4 { } private class PrivateClass5 // When Method() is removed, this class will raise issue { public void Method() // Noncompliant { var x = new PrivateClass5(); } } public void Test1() { var x = new PrivateClass1(); var t = typeof(PrivateClass2); var n = nameof(PrivateClass3); var o = new object[0]; o.OfType(); } } ").Verify(); [TestMethod] public void UnusedPrivateMember_SupportTypeKinds() => builder.AddSnippet(@" public class PrivateTypes { private class MyPrivateClass { } // Noncompliant private struct MyPrivateStruct { } // Noncompliant private enum MyPrivateEnum { } // Noncompliant private interface MyPrivateInterface { } // Noncompliant private delegate int MyPrivateDelegate(int x, int y); // Noncompliant public class MyPublicClass { } public struct MyPublicStruct { } public enum MyPublicEnum { } public interface MyPublicInterface { } public delegate int MyPublicDelegate(int x, int y); private class Something : MyPublicInterface {} public void Foo() { new MyPublicClass(); new MyPublicStruct(); new MyPublicEnum(); new Something(); MyPublicDelegate handler = PerformCalculation; } public static int PerformCalculation(int x, int y) => x + y; } ").Verify(); // Tests that private types implementing InheritedExport interfaces are not flagged as unused // Per MS docs: "an interface can be decorated with an InheritedExport attribute at the interface level, // and that export along with any associated metadata will be inherited by any implementing classes" // Source: https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef [TestMethod] public void UnusedPrivateMember_Types_MefInheritedExportOnInterfaceTypeNotRemovable() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(IPlugin))] public interface IPlugin { } public class Outer { private class MyPlugin : IPlugin { } // Compliant - implements InheritedExport interface } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); // Tests that private types inheriting from InheritedExport base classes are not flagged as unused // Per MS docs: "a part can export itself by using the InheritedExport attribute. // Subclasses of the part will inherit and provide the same export, including contract name and contract type" // Source: https://learn.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef [TestMethod] public void UnusedPrivateMember_Types_MefInheritedExportOnBaseClassTypeNotRemovable() => builder.AddSnippet(""" using System.ComponentModel.Composition; [InheritedExport(typeof(HandlerBase))] public abstract class HandlerBase { } public class Outer { private class MyHandler : HandlerBase { } // Compliant - derives from InheritedExport base class } """) .AddReferences(MetadataReferenceFacade.SystemComponentModelComposition) .VerifyNoIssues(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedPrivateMemberTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using FluentAssertions.Extensions; using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public partial class UnusedPrivateMemberTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UnusedPrivateMember_DebuggerDisplay_Attribute() => builder.AddSnippet(""" // https://github.com/SonarSource/sonar-dotnet/issues/1195 [System.Diagnostics.DebuggerDisplay("{field1}", Name = "{Property1} {Property3}", Type = "{Method1()}")] public class MethodUsages { private int field1; private int field2; // Noncompliant private int Property1 { get; set; } private int Property2 { get; set; } // Noncompliant private int Property3 { get; set; } private int Method1() { return 0; } private int Method2() { return 0; } // Noncompliant public void Method() { var x = Property3; } } """).Verify(); [TestMethod] public void UnusedPrivateMember_Members_With_Attributes_Are_Not_Removable() => builder.AddSnippet(""" using System; public class FieldUsages { [Obsolete] private int field1; [Obsolete] private int Property1 { get; set; } [Obsolete] private int Method1() { return 0; } [Obsolete] private class Class1 { } } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Assembly_Level_Attributes() => builder.AddSnippet(""" [assembly: System.Reflection.AssemblyCompany(Foo.Constants.AppCompany)] public static class Foo { internal static class Constants // Compliant, detect usages from assembly level attributes. { public const string AppCompany = "foo"; } } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMemberWithPartialClasses() => builder.AddPaths("UnusedPrivateMember.part1.cs", "UnusedPrivateMember.part2.cs").Verify(); [TestMethod] public void UnusedPrivateMember_Methods_EventHandler() => // Event handler methods are not reported because in WPF an event handler // could be added through XAML and no warning will be generated if the // method is removed, which could lead to serious problems that are hard // to diagnose. builder.AddSnippet(""" using System; public class NewClass { private void Handler(object sender, EventArgs e) { } // Compliant } public partial class PartialClass { private void Handler(object sender, EventArgs e) { } // intentional False Negative } """).VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Unity3D_Ignored() => builder.AddSnippet(""" // https://github.com/SonarSource/sonar-dotnet/issues/159 public class UnityMessages1 : UnityEngine.MonoBehaviour { private void SomeMethod(bool hasFocus) { } // Compliant } public class UnityMessages2 : UnityEngine.ScriptableObject { private void SomeMethod(bool hasFocus) { } // Compliant } public class UnityMessages3 : UnityEditor.AssetPostprocessor { private void SomeMethod(bool hasFocus) { } // Compliant } public class UnityMessages4 : UnityEditor.AssetModificationProcessor { private void SomeMethod(bool hasFocus) { } // Compliant } // Unity3D does not seem to be available as a nuget package and we cannot use the original classes namespace UnityEngine { public class MonoBehaviour { } public class ScriptableObject { } } namespace UnityEditor { public class AssetPostprocessor { } public class AssetModificationProcessor { } } """).VerifyNoIssues(); [TestMethod] public void EntityFrameworkMigration_Ignored() => builder.AddSnippet(""" namespace EntityFrameworkMigrations { using Microsoft.EntityFrameworkCore.Migrations; public class SkipMigration : Migration { private void SomeMethod(bool condition) { } // Compliant protected override void Up(MigrationBuilder migrationBuilder) { } } } """) .AddReferences(EntityFrameworkCoreReferences("7.0.14")) .VerifyNoIssues(); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void UnusedPrivateMember(ProjectType projectType) => builder.AddPaths("UnusedPrivateMember.cs").AddReferences(TestCompiler.ProjectTypeReference(projectType)).Verify(); [TestMethod] public void UnusedPrivateMember_CS_Latest() => builder.AddPaths("UnusedPrivateMember.Latest.cs", "UnusedPrivateMember.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .AddReferences(MetadataReferenceFacade.NetStandard21) .AddReferences(MetadataReferenceFacade.MicrosoftExtensionsDependencyInjectionAbstractions) .Verify(); [TestMethod] public void UnusedPrivateMember_TopLevelStatements() => builder.AddPaths("UnusedPrivateMember.TopLevelStatements.cs").WithTopLevelStatements().Verify(); #if NET [TestMethod] public void UnusedPrivateMemeber_EntityFramework_DontRaiseOnUnusedEntityPropertiesPrivateSetters() => builder.AddSnippet(""" // Repro https://github.com/SonarSource/sonar-dotnet/issues/9416 using Microsoft.EntityFrameworkCore; internal class MyContext : DbContext { public DbSet Blogs { get; set; } } public class Blog { public Blog(int id, string name) { Name = name; } public int Id { get; private set; } // Noncompliant FP public string Name { get; private set; } } """) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore("8.0.6")) .Verify(); #endif [TestMethod] public void UnusedPrivateMember_CodeFix() => builder.AddPaths("UnusedPrivateMember.cs") .WithCodeFix() .WithCodeFixedPaths("UnusedPrivateMember.Fixed.cs", "UnusedPrivateMember.Fixed.Batch.cs") .VerifyCodeFix(); [TestMethod] public void UnusedPrivateMember_UsedInGeneratedFile() => builder.AddPaths("UnusedPrivateMember.CalledFromGenerated.cs", "UnusedPrivateMember.Generated.cs").VerifyNoIssues(); [TestMethod] public void UnusedPrivateMember_Performance() => // Once the NuGet packages are downloaded, the time to execute the analyzer on the given file is // about ~1 sec. It was reduced from ~11 min by skipping Guids when processing ObjectCreationExpression. // The threshold is set here to 30 seconds to avoid flaky builds due to slow build agents or network connections. builder.AddPaths("UnusedPrivateMember.Performance.cs") .AddReferences(EntityFrameworkCoreReferences("5.0.12")) // The latest before 6.0.0 for .NET 6 that has Linq versioning collision issue .Invoking(x => x.VerifyNoIssues()) .ExecutionTime().Should().BeLessOrEqualTo(30.Seconds()); private static ImmutableArray EntityFrameworkCoreReferences(string entityFrameworkVersion) => MetadataReferenceFacade.NetStandard .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqlServer(entityFrameworkVersion)) .Concat(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(entityFrameworkVersion)) .ToImmutableArray(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedReturnValueTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnusedReturnValueTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UnusedReturnValue() => builder.AddPaths("UnusedReturnValue.cs", "UnusedReturnValue.Partial.cs").Verify(); [TestMethod] public void UnusedReturnValue_CS_Latest() => builder.AddPaths("UnusedReturnValue.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void UnusedReturnValue_CS_TopLevelStatements() => builder.AddPaths("UnusedReturnValue.TopLevelStatements.cs").WithTopLevelStatements().WithOptions(LanguageOptions.FromCSharp10).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UnusedStringBuilderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UnusedStringBuilderTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UnusedStringBuilder_CS() => builderCS.AddPaths("UnusedStringBuilder.cs").Verify(); [TestMethod] public void UnusedStringBuilder_VB() => builderVB.AddPaths("UnusedStringBuilder.vb").WithOptions(LanguageOptions.FromVisualBasic14).Verify(); [TestMethod] public void UnusedStringBuilder_CS_Latest() => builderCS.AddPaths("UnusedStringBuilder.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] [DataRow("", false)] [DataRow("sb.ToString();", true)] [DataRow("""var a = sb.Append("").Append("").Append("").Append("").ToString().ToLower();""", true)] [DataRow("sb.CopyTo(0, new char[1], 0, 1);", true)] [DataRow("sb.GetChunks();", true)] [DataRow("var a = sb[0];", true)] [DataRow("""sb?.Append("").ToString().ToLower();""", false)] // FP see https://github.com/SonarSource/sonar-dotnet/issues/6743 [DataRow("sb?.ToString().ToLower();", false)] // FP [DataRow("""@sb.Append("").ToString();""", true)] [DataRow("sb.Remove(sb.Length - 1, 1);", true)] [DataRow("var a = sb.Length;", true)] [DataRow("var a = sb.Capacity;", false)] [DataRow("var a = sb.MaxCapacity;", false)] [DataRow("""var a = $"{sb} is ToStringed here";""", true)] [DataRow("var a = sb;", true)] public void UnusedStringBuilder_TopLevelStatements(string expression, bool compliant) { var code = $$""" using System.Text; var sb = new StringBuilder(); // {{(compliant ? "Compliant" : "Noncompliant")}} {{expression}} """; var builder = builderCS.AddSnippet(code).WithTopLevelStatements(); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] [DataRow("", false)] [DataRow("sb.ToString();", true)] [DataRow("""var a = sb.Append("").Append("").Append("").Append("").ToString().ToLower();""", true)] [DataRow("sb.CopyTo(0, new char[1], 0, 1);", true)] [DataRow("var a = sb[0];", true)] [DataRow("""sb?.Append("").ToString().ToLower();""", false)] // FP see https://github.com/SonarSource/sonar-dotnet/issues/6743 [DataRow("sb?.ToString();", false)] // FP [DataRow("""@sb.Append("").ToString();""", true)] [DataRow("sb.Remove(sb.Length - 1, 1);", true)] [DataRow("var a = sb.Length;", true)] [DataRow("var a = sb.Capacity;", false)] [DataRow("var a = sb.MaxCapacity;", false)] [DataRow("""var a = $"{sb} is ToStringed here";""", true)] [DataRow("var a = sb;", true)] #if NET [DataRow("sb.GetChunks();", true)] #endif public void UnusedStringBuilder_CSExpressionsTest(string expression, bool compliant) { var code = $$""" using System.Text; public class MyClass { public void MyMethod() { var sb = new StringBuilder(); // {{(compliant ? "Compliant" : "Noncompliant")}} {{expression}} } } """; var builder = builderCS.AddSnippet(code); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } [TestMethod] [DataRow("", false)] [DataRow("sb.ToString()", true)] [DataRow("""Dim a = sb.Append("").Append("").Append("").Append("").ToString().ToLower()""", true)] [DataRow("sb.CopyTo(0, New Char(0) {}, 0, 1)", true)] [DataRow("Dim a = sb(0)", true)] [DataRow("""sb?.Append("").ToString().ToLower()""", false)] // FP see https://github.com/SonarSource/sonar-dotnet/issues/6743 [DataRow("sb?.ToString().ToLower()", false)] // FP [DataRow("""sb.Append("").ToString()""", true)] [DataRow("sb.Remove(sb.Length - 1, 1)", true)] [DataRow("""Dim a = $"{sb} is ToStringed here" """, true)] [DataRow("Dim a = sb.Length", true)] [DataRow("Dim a = sb.Capacity", false)] [DataRow("Dim a = sb.MaxCapacity", false)] [DataRow("Dim a = SB.ToString()", true)] [DataRow("Dim a = sb.TOSTRING()", true)] [DataRow("Dim a = sb.LENGTH", true)] [DataRow("Dim a = sb", true)] #if NET [DataRow("sb.GetChunks()", true)] #endif public void UnusedStringBuilder_VBExpressionsTest(string expression, bool compliant) { var code = $$""" Imports System.Text Public Class [MyClass] Public Sub MyMethod() Dim sb = New StringBuilder() ' {{(compliant ? "Compliant" : "Noncompliant")}} {{expression}} End Sub End Class """; var builder = builderVB.AddSnippet(code); if (compliant) { builder.VerifyNoIssues(); } else { builder.Verify(); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UriShouldNotBeHardcodedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UriShouldNotBeHardcodedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UriShouldNotBeHardcoded_CS() => builderCS.AddPaths("UriShouldNotBeHardcoded.cs").Verify(); [TestMethod] public void UriShouldNotBeHardcoded_CS_Exceptions() => builderCS .AddPaths("UriShouldNotBeHardcoded.Exceptions.cs") .AddReferences(MetadataReferenceFacade.SystemXml) .Verify(); [TestMethod] public void UriShouldNotBeHardcoded_CS_Latest() => builderCS.AddPaths("UriShouldNotBeHardcoded.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] [DataRow("3.0.20105.1")] [DataRow(TestConstants.NuGetLatestVersion)] public void UriShouldNotBeHardcoded_CS_VirtualPath_AspNet(string aspNetMvcVersion) => builderCS .AddPaths("UriShouldNotBeHardcoded.AspNet.cs") .AddReferences(MetadataReferenceFacade.SystemWeb.Concat(NuGetMetadataReference.MicrosoftAspNetMvc(aspNetMvcVersion))) .WithNetFrameworkOnly() .Verify(); [TestMethod] [DataRow("2.0.4", "2.0.3", "2.1.1")] [DataRow("2.2.0", "2.2.0", "2.2.0")] public void UriShouldNotBeHardcoded_CS_VirtualPath_AspNetCore(string aspNetCoreMvcVersion, string aspNetCoreRoutingVersion, string netHttpHeadersVersion) => builderCS .AddPaths("UriShouldNotBeHardcoded.AspNetCore.cs") .AddReferences(AdditionalReferences(aspNetCoreMvcVersion, aspNetCoreRoutingVersion, netHttpHeadersVersion)) .Verify(); [TestMethod] public void UriShouldNotBeHardcoded_VB() => builderVB.AddPaths("UriShouldNotBeHardcoded.vb").Verify(); private static IEnumerable AdditionalReferences(string aspNetCoreMvcVersion, string aspNetCoreRoutingVersion, string netHttpHeadersVersion) => NuGetMetadataReference.MicrosoftAspNetCoreMvcCore(aspNetCoreMvcVersion) // for Controller .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcViewFeatures(aspNetCoreMvcVersion)) // for IActionResult .Concat(NuGetMetadataReference.MicrosoftAspNetCoreMvcAbstractions(aspNetCoreMvcVersion)) // for IRouter and VirtualPathData .Concat(NuGetMetadataReference.MicrosoftAspNetCoreRoutingAbstractions(aspNetCoreRoutingVersion)) // for IRouteBuilder .Concat(NuGetMetadataReference.MicrosoftAspNetCoreRouting(aspNetCoreRoutingVersion)) .Concat(NuGetMetadataReference.MicrosoftNetHttpHeaders(netHttpHeadersVersion)); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseAwaitableMethodTest { private const string EntityFrameworkVersion = "7.0.18"; private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseAwaitableMethod_CS() => builder.AddPaths("UseAwaitableMethod.cs").AddReferences(MetadataReferenceFacade.SystemXml).Verify(); [TestMethod] public void UseAwaitableMethod_Moq() => builder.AddPaths("UseAwaitableMethod.Moq.cs").AddReferences(NuGetMetadataReference.Moq(TestConstants.NuGetLatestVersion)).Verify(); [TestMethod] public void UseAwaitableMethod_Sockets() => builder.AddPaths("UseAwaitableMethod_Sockets.cs").AddReferences(MetadataReferenceFacade.SystemNetPrimitives).AddReferences(MetadataReferenceFacade.SystemNetSockets).Verify(); [TestMethod] public void UseAwaitableMethod_CS_TopLevelStatements() => builder.AddPaths("UseAwaitableMethod.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void UseAwaitableMethod_CS_Latest() => builder.AddPaths("UseAwaitableMethod.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); #if NET [TestMethod] public void UseAwaitableMethod_EF() => builder .WithOptions(LanguageOptions.CSharpLatest) .AddReferences([CoreMetadataReference.SystemComponentModelTypeConverter, CoreMetadataReference.SystemDataCommon]) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore(EntityFrameworkVersion)) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(EntityFrameworkVersion)) .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqlServer(EntityFrameworkVersion)) .AddPaths("UseAwaitableMethod_EF.cs") .Verify(); #endif [TestMethod] public void UseAwaitableMethod_MongoDb() => builder.AddPaths("UseAwaitableMethod_MongoDBDriver.cs").AddReferences(NuGetMetadataReference.MongoDBDriver()).WithOptions(LanguageOptions.CSharpLatest).VerifyNoIssues(); // Starting from FluentValidation 12, the library support only net8 and newer [TestMethod] public void UseAwaitableMethod_FluentValidation11() => builder.AddPaths("UseAwaitableMethod_FluentValidation.cs").AddReferences(NuGetMetadataReference.FluentValidation("11.11.0")).Verify(); [TestMethod] public void UseAwaitableMethod_FluentValidationLatest() => builder.AddPaths("UseAwaitableMethod_FluentValidation.cs").AddReferences(NuGetMetadataReference.FluentValidation()).WithNetOnly().Verify(); [TestMethod] public void UseAwaitableMethod_DbDataReader() => builder.AddPaths("UseAwaitableMethod_DbDataReader.cs").AddReferences(MetadataReferenceFacade.SystemData).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseCharOverloadOfStringMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseCharOverloadOfStringMethodsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); #if NETFRAMEWORK [TestMethod] public void UseCharOverloadOfStringMethods_CS() => builderCS.AddPaths("UseCharOverloadOfStringMethods.Framework.cs").Verify(); [TestMethod] public void UseCharOverloadOfStringMethods_VB() => builderVB.AddPaths("UseCharOverloadOfStringMethods.Framework.vb").VerifyNoIssues(); #else [TestMethod] public void UseCharOverloadOfStringMethods_CS() => builderCS.AddPaths("UseCharOverloadOfStringMethods.cs").Verify(); [TestMethod] public void UseCharOverloadOfStringMethods_CS_Latest() => builderCS.AddPaths("UseCharOverloadOfStringMethods.Latest.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void UseCharOverloadOfStringMethods_VB() => builderVB.AddPaths("UseCharOverloadOfStringMethods.vb").Verify(); [TestMethod] public void UseCharOverloadOfStringMethods_CS_Fix() => builderCS .WithCodeFix() .AddPaths("UseCharOverloadOfStringMethods.ToFix.cs") .WithCodeFixedPaths("UseCharOverloadOfStringMethods.Fixed.cs") .VerifyCodeFix(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseConstantLoggingTemplateTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseConstantLoggingTemplateTest { private readonly VerifierBuilder builder = CreateVerifier(); [TestMethod] public void UseConstantLoggingTemplate_CS() => builder.AddPaths("UseConstantLoggingTemplate.cs").Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("Warn")] public void UseConstantLoggingTemplate_CastleCoreLogging_CS(string methodName) => builder.AddSnippet($$""" using Castle.Core.Logging; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}("Message"); // Compliant logger.{{methodName}}($"{arg}"); // Noncompliant logger.{{methodName}}Format("{Arg}", arg); // Compliant logger.{{methodName}}Format($"{arg}"); // Noncompliant } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Warn")] public void UseConstantLoggingTemplate_Log4Net_CS(string methodName) => builder.AddSnippet($$""" using System; using log4net; public class Program { public void Method(ILog logger, int arg) { logger.{{methodName}}("Message"); // Compliant logger.{{methodName}}($"{arg}"); // Noncompliant logger.{{methodName}}Format("Arg: {0}", arg); // Compliant logger.{{methodName}}Format($"{arg}"); // Noncompliant } //https://github.com/SonarSource/sonar-dotnet/issues/9547 void Repro_9547(ILog logger, string filePath, Exception ex) { logger.{{methodName}}($"Error while loading file '{filePath}'!", ex); // Compliant } } """).Verify(); [TestMethod] [DataRow("Log", "LogLevel.Warning,")] [DataRow("LogCritical")] [DataRow("LogDebug")] [DataRow("LogError")] [DataRow("LogInformation")] [DataRow("LogTrace")] [DataRow("LogWarning")] public void UseConstantLoggingTemplate_MicrosoftExtensionsLogging_CS(string methodName, string logLevel = "") => builder.AddSnippet($$""" using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}({{logLevel}} "Message"); // Compliant logger.{{methodName}}({{logLevel}} $"{arg}"); // Noncompliant } } """).Verify(); [TestMethod] [DataRow("ConditionalDebug")] [DataRow("ConditionalTrace")] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Log", "LogLevel.Warn,")] [DataRow("Trace")] [DataRow("Warn")] public void UseConstantLoggingTemplate_NLog_CS(string methodName, string logLevel = "") => builder.AddSnippet($$""" using NLog; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}({{logLevel}} "Message"); // Compliant logger.{{methodName}}({{logLevel}} $"{arg}"); // Noncompliant } } """).Verify(); public void UseConstantLoggingTemplate_NLog_AdditionalLoggers_CS() => builder.AddSnippet(""" using NLog; public class Program { public void Method(ILoggerBase logger, NullLogger nullLogger, int arg) { logger.Log(LogLevel.Warn, "Message"); // Compliant logger.Log(LogLevel.Warn, $"{arg}"); // Noncompliant nullLogger.Log(LogLevel.Warn, "Message"); // Compliant nullLogger.Log(LogLevel.Warn, $"{arg}"); // Noncompliant } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Information")] [DataRow("Verbose")] [DataRow("Warning")] public void UseConstantLoggingTemplate_Serilog_CS(string methodName) => builder.AddSnippet($$""" using Serilog; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}("Message without argument"); // Compliant logger.{{methodName}}("The argument is {@Argument}", arg); // Compliant logger.{{methodName}}($"The argument is {arg}"); // Noncompliant Log.{{methodName}}("Message without argument"); // Compliant Log.{{methodName}}("The argument is {@Argument}", arg); // Compliant Log.{{methodName}}($"The argument is {arg}"); // Noncompliant } } """).Verify(); private static VerifierBuilder CreateVerifier() where TAnalyzer : DiagnosticAnalyzer, new() => new VerifierBuilder() .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.CastleCore(TestConstants.NuGetLatestVersion)) .AddReferences(NuGetMetadataReference.Serilog()) .AddReferences(NuGetMetadataReference.Log4Net("2.0.8", "net45-full")) .AddReferences(NuGetMetadataReference.NLog()); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseConstantsWhereAppropriateTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseConstantsWhereAppropriateTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseConstantsWhereAppropriate() => builder.AddPaths("UseConstantsWhereAppropriate.cs").Verify(); [TestMethod] public void UseConstantsWhereAppropriate_CSharp11() => builder.AddPaths("UseConstantsWhereAppropriate.CSharp11.cs").WithOptions(LanguageOptions.FromCSharp11).Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseCurlyBracesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseCurlyBracesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseCurlyBraces() => builder.AddPaths("UseCurlyBraces.cs").Verify(); [TestMethod] public void UseCurlyBraces_FromCSharp7() => builder.AddPaths("UseCurlyBraces.CSharp7.cs") .WithOptions(LanguageOptions.FromCSharp7) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseDateTimeOffsetInsteadOfDateTimeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseDateTimeOffsetInsteadOfDateTimeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UseDateTimeInsteadOfDateTimeOffset_CS() => builderCS.AddPaths("UseDateTimeOffsetInsteadOfDateTime.cs").Verify(); #if NET [TestMethod] public void UseDateTimeInsteadOfDateTimeOffset_CSharp9() => builderCS.AddPaths("UseDateTimeOffsetInsteadOfDateTime.CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void UseDateTimeInsteadOfDateTimeOffset_VB_Net() => builderVB.AddPaths("UseDateTimeOffsetInsteadOfDateTime.Net.vb").Verify(); #endif [TestMethod] public void UseDateTimeInsteadOfDateTimeOffset_VB() => builderVB.AddPaths("UseDateTimeOffsetInsteadOfDateTime.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseFindSystemTimeZoneByIdTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseFindSystemTimeZoneByIdTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); #if NET [TestMethod] public void UseFindSystemTimeZoneById_Net_CS() => builderCS.AddReferences(NuGetMetadataReference.TimeZoneConverter()) .AddPaths("UseFindSystemTimeZoneById.Net.cs") .Verify(); [TestMethod] public void UseFindSystemTimeZoneById_Net_VB() => builderVB.AddReferences(NuGetMetadataReference.TimeZoneConverter()) .AddPaths("UseFindSystemTimeZoneById.Net.vb") .Verify(); [TestMethod] public void UseFindSystemTimeZoneById_Net_WithoutReference_DoesNotRaise_CS() => builderCS.AddPaths("UseFindSystemTimeZoneById.Net.cs") .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); [TestMethod] public void UseFindSystemTimeZoneById_Net_WithoutReference_DoesNotRaise_VB() => builderVB.AddPaths("UseFindSystemTimeZoneById.Net.vb") .WithErrorBehavior(CompilationErrorBehavior.Ignore) .VerifyNoIssues(); #else [TestMethod] public void UseFindSystemTimeZoneById_CS() => builderCS.AddReferences(NuGetMetadataReference.TimeZoneConverter()) .AddPaths("UseFindSystemTimeZoneById.cs").VerifyNoIssues(); [TestMethod] public void UseFindSystemTimeZoneById_VB() => builderVB.AddReferences(NuGetMetadataReference.TimeZoneConverter()) .AddPaths("UseFindSystemTimeZoneById.vb").VerifyNoIssues(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseGenericEventHandlerInstancesTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseGenericEventHandlerInstancesTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseGenericEventHandlerInstances() => builder.AddPaths("UseGenericEventHandlerInstances.cs") .Verify(); [TestMethod] public void UseGenericEventHandlerInstances_CSharp9() => builder.AddPaths("UseGenericEventHandlerInstances.CSharp9.cs") .WithOptions(LanguageOptions.FromCSharp9) .Verify(); [TestMethod] public void UseGenericEventHandlerInstances_CSharp11() => builder.AddPaths("UseGenericEventHandlerInstances.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseGenericWithRefParametersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseGenericWithRefParametersTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseGenericWithRefParameters() => builder.AddPaths("UseGenericWithRefParameters.cs").Verify(); [TestMethod] public void UseGenericWithRefParameters_InvalidCode() => builder.AddSnippet(""" public void (ref object o1) { } """) .VerifyNoIssuesIgnoreErrors(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseIFormatProviderForParsingDateAndTimeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Globalization; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseIFormatProviderForParsingDateAndTimeTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UseIFormatProviderForParsingDateAndTime_CS() => builderCS.AddPaths("UseIFormatProviderForParsingDateAndTime.cs").Verify(); [TestMethod] public void UseIFormatProviderForParsingDateAndTime_VB() => builderVB.AddPaths("UseIFormatProviderForParsingDateAndTime.vb").Verify(); #if NET [TestMethod] [DataRow(nameof(DateTime), nameof(DateTimeStyles))] [DataRow(nameof(DateTimeOffset), nameof(DateTimeStyles))] [DataRow(nameof(TimeSpan), nameof(TimeSpanStyles))] [DataRow(nameof(DateOnly), nameof(DateTimeStyles))] [DataRow(nameof(TimeOnly), nameof(DateTimeStyles))] public void UseIFormatProviderForParsingDateAndTime__MethodOverloads_CS(string temporalTypeName, string styleTypeName) => builderCS.AddSnippet($$$""" using System; using System.Globalization; class Test { void ParseOverloads() { {{{temporalTypeName}}}.Parse("01/02/2000"); // Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", null); // Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", CultureInfo.InvariantCulture); // Compliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), null); // Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture); // Compliant {{{temporalTypeName}}}.Parse("01/02/2000", null); // Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", CultureInfo.InvariantCulture); // Compliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), null); // Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture); // Compliant } void ParseExactOverloads() { {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", null); // Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture); // Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy".AsSpan(), null); // Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy".AsSpan(), CultureInfo.InvariantCulture); // Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", null, {{{styleTypeName}}}.None); // Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None); // Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000", new[] { "dd/MM/yyyy", "dd MM yyyy" }, null, {{{styleTypeName}}}.None); // Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", new[] { "dd/MM/yyyy", "dd MM yyyy" }, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None); // Compliant } void TryParseOverloads() { {{{temporalTypeName}}} parsedDate; {{{temporalTypeName}}}.TryParse("01/02/2000", out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000", null, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000", CultureInfo.InvariantCulture, out parsedDate); // Compliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), null, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture, out parsedDate); // Compliant } void TryParseExactOverloads() { {{{temporalTypeName}}} parsedDate; {{{temporalTypeName}}}.TryParseExact("01/02/2000", "dd/MM/yyyy", null, {{{styleTypeName}}}.None, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, out parsedDate); // Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy", null, {{{styleTypeName}}}.None, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, out parsedDate); // Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", new[] { "dd/MM/yyyy", "dd MM yyyy" }, null, {{{styleTypeName}}}.None, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", new[] { "dd/MM/yyyy", "dd MM yyyy" }, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, out parsedDate); // Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), new[] { "dd/MM/yyyy", "dd MM yyyy" }, null, {{{styleTypeName}}}.None, out parsedDate); // Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), new[] { "dd/MM/yyyy", "dd MM yyyy" }, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, out parsedDate); // Compliant } } """).Verify(); [TestMethod] [DataRow(nameof(DateTime), nameof(DateTimeStyles))] [DataRow(nameof(DateTimeOffset), nameof(DateTimeStyles))] [DataRow(nameof(TimeSpan), nameof(TimeSpanStyles))] [DataRow(nameof(DateOnly), nameof(DateTimeStyles))] [DataRow(nameof(TimeOnly), nameof(DateTimeStyles))] public void UseIFormatProviderForParsingDateAndTime__MethodOverloads_VB(string temporalTypeName, string styleTypeName) => builderVB.AddSnippet($$$""" Imports System.Globalization Class Test Sub ParseOverloads() {{{temporalTypeName}}}.Parse("01/02/2000") ' Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", Nothing) ' Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", CultureInfo.InvariantCulture) ' Compliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), Nothing) ' Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture) ' Compliant {{{temporalTypeName}}}.Parse("01/02/2000", Nothing) ' Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000", CultureInfo.InvariantCulture) ' Compliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), Nothing) ' Noncompliant {{{temporalTypeName}}}.Parse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture) ' Compliant End Sub Sub ParseExactOverloads() {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", Nothing) ' Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture) ' Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy".AsSpan(), Nothing) ' Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy".AsSpan(), CultureInfo.InvariantCulture) ' Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", Nothing, {{{styleTypeName}}}.None) ' Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None) ' Compliant {{{temporalTypeName}}}.ParseExact("01/02/2000", {"dd/MM/yyyy", "dd MM yyyy"}, Nothing, {{{styleTypeName}}}.None) ' Noncompliant {{{temporalTypeName}}}.ParseExact("01/02/2000", {"dd/MM/yyyy", "dd MM yyyy"}, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None) ' Compliant End Sub Sub TryParseOverloads() Dim parsedDate As {{{temporalTypeName}}} {{{temporalTypeName}}}.TryParse("01/02/2000", parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000", Nothing, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000", CultureInfo.InvariantCulture, parsedDate) ' Compliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), Nothing, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParse("01/02/2000".AsSpan(), CultureInfo.InvariantCulture, parsedDate) ' Compliant End Sub Sub TryParseExactOverloads() Dim parsedDate As {{{temporalTypeName}}} {{{temporalTypeName}}}.TryParseExact("01/02/2000", "dd/MM/yyyy", Nothing, {{{styleTypeName}}}.None, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, parsedDate) ' Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy", Nothing, {{{styleTypeName}}}.None, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), "dd/MM/yyyy", CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, parsedDate) ' Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", {"dd/MM/yyyy", "dd MM yyyy"}, Nothing, {{{styleTypeName}}}.None, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000", {"dd/MM/yyyy", "dd MM yyyy"}, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, parsedDate) ' Compliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), {"dd/MM/yyyy", "dd MM yyyy"}, Nothing, {{{styleTypeName}}}.None, parsedDate) ' Noncompliant {{{temporalTypeName}}}.TryParseExact("01/02/2000".AsSpan(), {"dd/MM/yyyy", "dd MM yyyy"}, CultureInfo.InvariantCulture, {{{styleTypeName}}}.None, parsedDate) ' Compliant End Sub End Class """).Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseIndexingInsteadOfLinqMethodsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseIndexingInsteadOfLinqMethodsTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UseIndexingInsteadOfLinqMethods_CS() => builderCS.AddPaths("UseIndexingInsteadOfLinqMethods.cs").Verify(); [TestMethod] public void UseIndexingInsteadOfLinqMethods_VB() => builderVB.AddPaths("UseIndexingInsteadOfLinqMethods.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseLambdaParameterInConcurrentDictionaryTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseLambdaParameterInConcurrentDictionaryTest { [TestMethod] public void UseLambdaParameterInConcurrentDictionary_CSharp8() => new VerifierBuilder().AddPaths("UseLambdaParameterInConcurrentDictionary.CSharp8.cs") .AddReferences(MetadataReferenceFacade.SystemCollections) .WithOptions(LanguageOptions.FromCSharp8) .Verify(); [TestMethod] public void UseLambdaParameterInConcurrentDictionary_VB() => new VerifierBuilder().AddPaths("UseLambdaParameterInConcurrentDictionary.vb") .WithOptions(LanguageOptions.FromVisualBasic14) .AddReferences(MetadataReferenceFacade.SystemCollections) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseNumericLiteralSeparatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseNumericLiteralSeparatorTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseNumericLiteralSeparator_BeforeCSharp7() => builder.AddPaths("UseNumericLiteralSeparator.cs") .WithErrorBehavior(CompilationErrorBehavior.Ignore) .WithOptions(LanguageOptions.BeforeCSharp7) .VerifyNoIssues(); [TestMethod] public void UseNumericLiteralSeparator_FromCSharp7() => builder.AddPaths("UseNumericLiteralSeparator.cs") .WithOptions(LanguageOptions.FromCSharp7) .Verify(); [TestMethod] public void UseNumericLiteralSeparator_CSharp9() => builder.AddPaths("UseNumericLiteralSeparator.CSharp9.cs") .WithTopLevelStatements() .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseParamsForVariableArgumentsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseParamsForVariableArgumentsTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseParamsForVariableArguments() => builder.AddPaths("UseParamsForVariableArguments.cs") .Verify(); [TestMethod] public void UseParamsForVariableArguments_CSharpLatest() => builder.AddPaths("UseParamsForVariableArguments.Latest.cs", "UseParamsForVariableArguments.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UsePascalCaseForNamedPlaceHoldersTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.CSharp.Rules.MessageTemplates; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UsePascalCaseForNamedPlaceHoldersTest { private static readonly IEnumerable LoggingReferences = NuGetMetadataReference.MicrosoftExtensionsLoggingAbstractions() .Concat(NuGetMetadataReference.NLog()) .Concat(NuGetMetadataReference.Serilog()); private static readonly VerifierBuilder Builder = new VerifierBuilder() .AddReferences(LoggingReferences) .WithOnlyDiagnostics(UsePascalCaseForNamedPlaceHolders.S6678); [TestMethod] public void UsePascalCaseForNamedPlaceHolders_CS() => Builder.AddPaths("UsePascalCaseForNamedPlaceHolders.cs").Verify(); [TestMethod] public void UsePascalCaseForNamedPlaceHolders_Latest_CS() => Builder.AddPaths("UsePascalCaseForNamedPlaceHolders.Latest.cs").WithLanguageVersion(LanguageVersion.Latest).Verify(); [TestMethod] [DataRow("LogCritical")] [DataRow("LogDebug")] [DataRow("LogError")] [DataRow("LogInformation")] [DataRow("LogTrace")] [DataRow("LogWarning")] public void UsePascalCaseForNamedPlaceHolders_MicrosoftExtensionsLogging_CS(string methodName) => Builder.AddSnippet($$""" using System; using Microsoft.Extensions.Logging; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}("Arg: {Arg}", arg); // Compliant logger.{{methodName}}("Arg: {arg}", arg); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("Error")] [DataRow("Information")] [DataRow("Fatal")] [DataRow("Warning")] [DataRow("Verbose")] public void UsePascalCaseForNamedPlaceHolders_Serilog_CS(string methodName) => Builder.AddSnippet($$""" using Serilog; using Serilog.Events; public class Program { public void Method(ILogger logger, int arg) { logger.{{methodName}}("Arg: {Arg}", arg); // Compliant logger.{{methodName}}("Arg: {arg}", arg); // Noncompliant // Secondary @-1 } } """).Verify(); [TestMethod] [DataRow("Debug")] [DataRow("ConditionalDebug")] [DataRow("Error")] [DataRow("Fatal")] [DataRow("Info")] [DataRow("Trace")] [DataRow("ConditionalTrace")] [DataRow("Warn")] public void UsePascalCaseForNamedPlaceHolders_NLog_CS(string methodName) => Builder.AddSnippet($$""" using NLog; public class Program { public void Method(ILogger iLogger, Logger logger, MyLogger myLogger, int arg) { logger.{{methodName}}("Arg: {Arg}", arg); // Compliant logger.{{methodName}}("Arg: {arg}", arg); // Noncompliant // Secondary @-1 } } public class MyLogger : Logger { } """).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseReturnStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseReturnStatementTest { [TestMethod] public void UseReturnStatement() => new VerifierBuilder().AddPaths("UseReturnStatement.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseShortCircuitingOperatorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseShortCircuitingOperatorTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UseShortCircuitingOperators_VB() => builderVB.AddPaths("UseShortCircuitingOperator.vb").Verify(); [TestMethod] public void UseShortCircuitingOperators_VB_CodeFix() => builderVB.WithCodeFix().AddPaths("UseShortCircuitingOperator.vb").WithCodeFixedPaths("UseShortCircuitingOperator.Fixed.vb").VerifyCodeFix(); [TestMethod] public void UseShortCircuitingOperators_CS() => builderCS.AddPaths("UseShortCircuitingOperator.cs").Verify(); [TestMethod] public void UseShortCircuitingOperators_CS_Latest() => builderCS.AddPaths("UseShortCircuitingOperator.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void UseShortCircuitingOperators_CS_TopLevelStatements() => builderCS.AddPaths("UseShortCircuitingOperator.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void UseShortCircuitingOperators_CS_TopLevelStatements_CodeFix() => builderCS.WithCodeFix().AddPaths("UseShortCircuitingOperator.TopLevelStatements.cs").WithCodeFixedPaths("UseShortCircuitingOperator.TopLevelStatements.Fixed.cs").WithTopLevelStatements().VerifyCodeFix(); [TestMethod] public void UseShortCircuitingOperators_CS_CodeFix() => builderCS.WithCodeFix().AddPaths("UseShortCircuitingOperator.cs").WithCodeFixedPaths("UseShortCircuitingOperator.Fixed.cs").VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseStringCreateTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseStringCreateTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); #if NET6_0_OR_GREATER [TestMethod] public void UseStringCreate_CSharp10() => builderCS.AddPaths("UseStringCreate.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); #else [TestMethod] public void UseStringCreate() => builderCS.AddPaths("UseStringCreate.cs").Verify(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseStringIsNullOrEmptyTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseStringIsNullOrEmptyTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseStringNullOrEmpty() => builder.AddPaths("UseStringIsNullOrEmpty.cs").Verify(); [TestMethod] public void UseStringNullOrEmpty_CSharp10() => builder.AddPaths("UseStringIsNullOrEmpty.CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .Verify(); [TestMethod] public void UseStringNullOrEmpty_CSharp11() => builder.AddPaths("UseStringIsNullOrEmpty.CSharp11.cs") .WithOptions(LanguageOptions.FromCSharp11) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseTestableTimeProviderTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseTestableTimeProviderTest { [TestMethod] public void UseTestableTimeProvider_CS() => new VerifierBuilder() .AddAnalyzer(() => new CS.UseTestableTimeProvider()) .AddPaths("UseTestableTimeProvider.cs") .Verify(); [TestMethod] public void UseTestableTimeProvider_VB() => new VerifierBuilder() .AddAnalyzer(() => new VB.UseTestableTimeProvider()) .AddPaths("UseTestableTimeProvider.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseTrueForAllTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseTrueForAllTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void UseTrueForAll_CS() => builderCS.AddPaths("UseTrueForAll.cs").Verify(); [TestMethod] public void UseTrueForAll_CS_Immutable() => builderCS.AddPaths("UseTrueForAll.Immutable.cs").AddReferences(MetadataReferenceFacade.SystemCollections).Verify(); [TestMethod] public void UseTrueForAll_VB() => builderVB.AddPaths("UseTrueForAll.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseUnixEpochTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseUnixEpochTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); #if NETFRAMEWORK [TestMethod] public void UseUnixEpoch_Framework_CS() => builderCS.AddPaths("UseUnixEpoch.Framework.cs").VerifyNoIssues(); [TestMethod] public void UseUnixEpoch_Framework_VB() => builderVB.AddPaths("UseUnixEpoch.Framework.vb").VerifyNoIssues(); #else [TestMethod] public void UseUnixEpoch_CS() => builderCS.AddPaths("UseUnixEpoch.cs").Verify(); [TestMethod] public void UseUnixEpoch_CSharp9() => builderCS .AddPaths("UseUnixEpoch.CSharp9.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void UseUnixEpoch_VB() => builderVB.AddPaths("UseUnixEpoch.vb").Verify(); [TestMethod] public void UseUnixEpoch_CodeFix_CS() => builderCS .AddPaths("UseUnixEpoch.cs") .WithCodeFix() .WithCodeFixedPaths("UseUnixEpoch.Fixed.cs") .VerifyCodeFix(); [TestMethod] public void UseUnixEpoch_CodeFix_CSharp9() => builderCS .AddPaths("UseUnixEpoch.CSharp9.cs") .WithCodeFix() .WithCodeFixedPaths("UseUnixEpoch.CSharp9.Fixed.cs") .WithTopLevelStatements() .VerifyCodeFix(); [TestMethod] public void UseUnixEpoch_CodeFix_VB() => builderVB .AddPaths("UseUnixEpoch.vb") .WithCodeFix() .WithCodeFixedPaths("UseUnixEpoch.Fixed.vb") .VerifyCodeFix(); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseUriInsteadOfStringTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseUriInsteadOfStringTest { private readonly VerifierBuilder builder = new VerifierBuilder().AddReferences(MetadataReferenceFacade.SystemDrawing); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void UseUriInsteadOfString(ProjectType projectType) => builder.AddPaths("UseUriInsteadOfString.cs").AddReferences(TestCompiler.ProjectTypeReference(projectType)).Verify(); [TestMethod] public void UseUriInsteadOfString_TopLevelStatements() => builder.AddPaths("UseUriInsteadOfString.TopLevelStatements.cs") .WithTopLevelStatements() .Verify(); [TestMethod] public void UseUriInsteadOfString_Latest() => builder.AddPaths("UseUriInsteadOfString.Latest.cs", "UseUriInsteadOfString.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void UseUriInsteadOfString_InvalidCode() => builder.AddSnippet(@" public class NoMembers { } public class InvalidCode : NoMembers { public override string UriProperty { get; set; } // Error [CS0115] 'Bar.UriProperty': no suitable method found to override public override string UriMethod() => """"; // Error [CS0115] 'Bar.UriMethod()': no suitable method found to override public void Main() { Uri.TryCreate(new object(), UriKind.Absolute, out result); // Compliant - invalid code // Error@-1 [CS0103 ]The name 'UriKind' does not exist in the current context // Error@-2 [CS0103] The name 'Uri' does not exist in the current context // Error@-3 [CS0103] The name 'result' does not exist in the current context } }").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseValueParameterTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseValueParameterTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void UseValueParameter() => builder.AddPaths("UseValueParameter.cs").Verify(); [TestMethod] public void UseValueParameter_CS_Latest() => builder.AddPaths("UseValueParameter.Latest.cs", "UseValueParameter.Latest.Partial.cs") .WithOptions(LanguageOptions.CSharpLatest) .Verify(); [TestMethod] public void UseValueParameter_InvalidCode() => builder.AddSnippet(""" public int Foo { get => someField; // Error [CS0103] set => // Noncompliant // Error@-1 [CS1002] // Error@-2 [CS1525] } """) .Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseWhereBeforeOrderByTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseWhereBeforeOrderByTest { [TestMethod] public void UseWhereBeforeOrderBy_CS() => new VerifierBuilder().AddPaths("UseWhereBeforeOrderBy.cs").Verify(); [TestMethod] public void UseWhereBeforeOrderBy_VB() => new VerifierBuilder().AddPaths("UseWhereBeforeOrderBy.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseWhileLoopInsteadTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class UseWhileLoopInsteadTest { [TestMethod] public void UseWhileLoopInstead() => new VerifierBuilder().AddPaths("UseWhileLoopInstead.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/UseWithStatementTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class UseWithStatementTest { [TestMethod] public void UseWithStatement() => new VerifierBuilder().AddAnalyzer(() => new UseWithStatement() { MinimumSeriesLength = 2 }) .AddPaths("UseWithStatement.vb") .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/AnalysisWarningAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.CFG.Common; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class AnalysisWarningAnalyzerTest { public TestContext TestContext { get; set; } [TestMethod] [DataRow(LanguageNames.CSharp, true)] [DataRow(LanguageNames.CSharp, false)] [DataRow(LanguageNames.VisualBasic, true)] [DataRow(LanguageNames.VisualBasic, false)] public void AnalysisWarning_MSBuildSupportedScenario_NoWarning(string languageName, bool isAnalyzerEnabled) { var expectedPath = ExecuteAnalyzer(languageName, isAnalyzerEnabled, RoslynVersion.VS2017MajorVersion, RoslynVersion.MinimalSupportedMajorVersion); // Using production value that is lower than our UT Roslyn version File.Exists(expectedPath).Should().BeFalse("Analysis warning file should not be generated."); } [TestMethod] [DataRow(LanguageNames.CSharp)] [DataRow(LanguageNames.VisualBasic)] public void AnalysisWarning_MSBuild14UnsupportedScenario_GenerateWarning(string languageName) { var expectedPath = ExecuteAnalyzer(languageName, true, 1000, 1001); // Requiring too high Roslyn version => we're under unsupported scenario File.Exists(expectedPath).Should().BeTrue(); File.ReadAllText(expectedPath).Should().Be("""[{"text": "The analysis using MsBuild 14 is no longer supported and the analysis with MsBuild 15 is deprecated. Please update your pipeline to MsBuild 16 or higher."}]"""); } [TestMethod] [DataRow(LanguageNames.CSharp)] [DataRow(LanguageNames.VisualBasic)] public void AnalysisWarning_MSBuild15DeprecatedScenario_GenerateWarning(string languageName) { var expectedPath = ExecuteAnalyzer(languageName, true, RoslynVersion.VS2017MajorVersion, 1000); // Requiring too high Roslyn version => we're under unsupported scenario File.Exists(expectedPath).Should().BeTrue(); File.ReadAllText(expectedPath).Should().Be("""[{"text": "The analysis using MsBuild 15 is deprecated. Please update your pipeline to MsBuild 16 or higher."}]"""); } [TestMethod] [DataRow(LanguageNames.CSharp)] [DataRow(LanguageNames.VisualBasic)] public void AnalysisWarning_LockFile_PathShouldBeReused(string languageName) { var expectedPath = ExecuteAnalyzer(languageName, true, RoslynVersion.VS2017MajorVersion, 1000); // Lock file and run it for 2nd time using var lockedFile = new FileStream(expectedPath, FileMode.Open, FileAccess.Write, FileShare.None); ExecuteAnalyzer(languageName, true, RoslynVersion.VS2017MajorVersion, 1000).Should().Be(expectedPath, "path should be reused and analyzer should not fail"); } [TestMethod] [DataRow(LanguageNames.CSharp)] [DataRow(LanguageNames.VisualBasic)] public void AnalysisWarning_FileExceptions_AreIgnored(string languageName) { // This will not create the output directory, causing an exception in the File.WriteAllText(...) var expectedPath = ExecuteAnalyzer(languageName, true, 500, 1000, false); // Requiring too high Roslyn version => we're under unsupported scenario File.Exists(expectedPath).Should().BeFalse(); } [TestMethod] public void VirtualProperties() { var sut = new TestAnalysisWarningAnalyzer_NoOverrides(); sut.PublicVS2017MajorVersion.Should().Be(2); sut.PublicMinimalSupportedRoslynVersion.Should().Be(3); } private string ExecuteAnalyzer(string languageName, bool isAnalyzerEnabled, int vs2017MajorVersion, int minimalSupportedRoslynVersion, bool createDirectory = true) { var language = AnalyzerLanguage.FromName(languageName); var analysisOutPath = TestFiles.TestPath(TestContext, @$"{languageName}\.sonarqube\out"); var projectOutPath = Path.GetFullPath(Path.Combine(analysisOutPath, "0", "output-language")); if (createDirectory) { Directory.CreateDirectory(analysisOutPath); } UtilityAnalyzerBase analyzer = language.LanguageName switch { LanguageNames.CSharp => new TestAnalysisWarningAnalyzer_CS(isAnalyzerEnabled, vs2017MajorVersion, minimalSupportedRoslynVersion, projectOutPath), LanguageNames.VisualBasic => new TestAnalysisWarningAnalyzer_VB(isAnalyzerEnabled, vs2017MajorVersion, minimalSupportedRoslynVersion, projectOutPath), _ => throw new UnexpectedLanguageException(language) }; new VerifierBuilder().AddAnalyzer(() => analyzer).AddSnippet(string.Empty).VerifyNoIssues(); // Nothing to analyze, just make it run return Path.Combine(analysisOutPath, "AnalysisWarnings.MsBuild.json"); } private sealed class TestAnalysisWarningAnalyzer_CS : CS.AnalysisWarningAnalyzer { private readonly bool isAnalyzerEnabled; private readonly string outPath; protected override int VS2017MajorVersion { get; } protected override int MinimalSupportedRoslynVersion { get; } public TestAnalysisWarningAnalyzer_CS(bool isAnalyzerEnabled, int vs2017MajorVersion, int minimalSupportedRoslynVersion, string outPath) { this.isAnalyzerEnabled = isAnalyzerEnabled; VS2017MajorVersion = vs2017MajorVersion; MinimalSupportedRoslynVersion = minimalSupportedRoslynVersion; this.outPath = outPath; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = isAnalyzerEnabled, OutPath = outPath }; } private sealed class TestAnalysisWarningAnalyzer_VB : VB.AnalysisWarningAnalyzer { private readonly bool isAnalyzerEnabled; private readonly string outPath; protected override int VS2017MajorVersion { get; } protected override int MinimalSupportedRoslynVersion { get; } public TestAnalysisWarningAnalyzer_VB(bool isAnalyzerEnabled, int vs2017MajorVersion, int minimalSupportedRoslynVersion, string outPath) { this.isAnalyzerEnabled = isAnalyzerEnabled; VS2017MajorVersion = vs2017MajorVersion; MinimalSupportedRoslynVersion = minimalSupportedRoslynVersion; this.outPath = outPath; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = isAnalyzerEnabled, OutPath = outPath }; } private sealed class TestAnalysisWarningAnalyzer_NoOverrides : AnalysisWarningAnalyzerBase { public int PublicVS2017MajorVersion => VS2017MajorVersion; public int PublicMinimalSupportedRoslynVersion => MinimalSupportedRoslynVersion; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/CopyPasteTokenAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class CopyPasteTokenAnalyzerTest { private const string BasePath = @"Utilities\CopyPasteTokenAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] public void Verify_Unique_CS() => Verify("Unique.cs", x => { x.Should().HaveCount(102); x.Count(token => token.TokenValue == "$str").Should().Be(9); x.Count(token => token.TokenValue == "$num").Should().Be(1); x.Count(token => token.TokenValue == "$char").Should().Be(2); }); [TestMethod] public void Verify_Unique_CSharp11() => Verify("Unique.Csharp11.cs", x => { x.Should().HaveCount(155); x.Count(token => token.TokenValue == "$str").Should().Be(16); x.Count(token => token.TokenValue == "$num").Should().Be(1); x.Count(token => token.TokenValue == "$char").Should().Be(2); }); [TestMethod] public void Verify_Unique_CSharp12() => Verify("Unique.Csharp12.cs", x => { x.Should().HaveCount(81); x.Count(token => token.TokenValue == "$str").Should().Be(4); x.Count(token => token.TokenValue == "$num").Should().Be(4); x.Count(token => token.TokenValue == "$char").Should().Be(4); }); [TestMethod] public void Verify_Unique_VB() => Verify("Unique.vb", x => { x.Should().HaveCount(88); x.Where(token => token.TokenValue == "$str").Should().HaveCount(3); x.Where(token => token.TokenValue == "$num").Should().HaveCount(7); x.Should().ContainSingle(token => token.TokenValue == "$char"); }); [TestMethod] public void Verify_Duplicated_CS() => Verify("Duplicated.cs", x => { x.Should().HaveCount(39); x.Where(token => token.TokenValue == "$num").Should().HaveCount(2); }); [TestMethod] public void Verify_Duplicated_CS_GlobalUsings() => CreateBuilder(ProjectType.Product, "Duplicated.CSharp10.cs") .VerifyUtilityAnalyzer(x => { x.Should().ContainSingle(); var info = x.Single(); info.FilePath.Should().Be(Path.Combine(BasePath, "Duplicated.CSharp10.cs")); info.TokenInfo.Should().HaveCount(39); info.TokenInfo.Where(token => token.TokenValue == "$num").Should().HaveCount(2); }); [TestMethod] public void Verify_DuplicatedDifferentLiterals_CS() => Verify("DuplicatedDifferentLiterals.cs", x => { x.Should().HaveCount(39); x.Where(token => token.TokenValue == "$num").Should().HaveCount(2); }); [TestMethod] public void Verify_NotRunForTestProject_CS() => CreateBuilder(ProjectType.Test, "DuplicatedDifferentLiterals.cs").VerifyUtilityAnalyzerProducesEmptyProtobuf(); [TestMethod] [DataRow("Unique.cs")] [DataRow("SomethingElse.cs")] public void Verify_UnchangedFiles(string unchangedFileName) => CreateBuilder(ProjectType.Product, "Unique.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)) .VerifyUtilityAnalyzer(x => x.Should().NotBeEmpty()); private void Verify(string fileName, Action> verifyTokenInfo) => CreateBuilder(ProjectType.Product, fileName) .VerifyUtilityAnalyzer(x => { x.Should().ContainSingle(); var info = x.Single(); info.FilePath.Should().Be(Path.Combine(BasePath, fileName)); verifyTokenInfo(info.TokenInfo); }); [TestMethod] [DataRow("Razor.razor")] [DataRow("Razor.cshtml")] public void Verify_NoMetricsAreComputedForRazorFiles(string fileName) => CreateBuilder(ProjectType.Product, fileName) .VerifyUtilityAnalyzer(x => x.Select(token => Path.GetFileName(token.FilePath)).Should().BeEmpty()); private VerifierBuilder CreateBuilder(ProjectType projectType, string fileName) { var testRoot = BasePath + TestContext.TestName; var language = AnalyzerLanguage.FromPath(fileName); UtilityAnalyzerBase analyzer = language.LanguageName switch { LanguageNames.CSharp => new TestCopyPasteTokenAnalyzer_CS(testRoot, projectType == ProjectType.Test), LanguageNames.VisualBasic => new TestCopyPasteTokenAnalyzer_VB(testRoot, projectType == ProjectType.Test), _ => throw new UnexpectedLanguageException(language) }; return new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddPaths(fileName) .WithBasePath(BasePath) .WithOptions(LanguageOptions.Latest(language)) .WithProtobufPath(@$"{testRoot}\token-cpd.pb"); } // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestCopyPasteTokenAnalyzer_CS : CS.CopyPasteTokenAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestCopyPasteTokenAnalyzer_CS(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } private sealed class TestCopyPasteTokenAnalyzer_VB : VB.CopyPasteTokenAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestCopyPasteTokenAnalyzer_VB(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/FileMetadataAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Test.Rules; [TestClass] public class FileMetadataAnalyzerTest { private const string BasePath = @"Utilities\FileMetadataAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Autogenerated(ProjectType projectType) { var autogeneratedProjectFiles = new[] { "autogenerated_comment.cs", "autogenerated_comment2.cs", "class.designer.cs", "class.g.cs", "class.g.something.cs", "class.generated.cs", "class_generated.cs", "compiler_generated.cs", "compiler_generated_attr.cs", "debugger_non_user_code.cs", "debugger_non_user_code_attr.cs", "generated_code_attr.cs", "generated_code_attr_local_function.cs", "generated_code_attr2.cs", "TEMPORARYGENERATEDFILE_class.cs" }; VerifyAllFilesAreGenerated(projectType, autogeneratedProjectFiles, autogeneratedProjectFiles); } [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void NotAutogenerated(ProjectType projectType) { var notAutogeneratedFiles = new[] { "normal_file.cs", "generated_region.cs", "generated_region_2.cs" }; CreateBuilder(projectType, notAutogeneratedFiles) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(x => x.Should().BeEquivalentTo(notAutogeneratedFiles.Select(expected => new FileMetadataInfo { IsGenerated = false, FilePath = BasePath + expected, }))); } [TestMethod] [DataRow(true)] [DataRow(false)] public void CreateMessage_NoEncoding_SetsEmptyString(bool isTestProject) { var tree = Substitute.For(); tree.FilePath.Returns("File.Generated.cs"); // Generated to simplify mocking for GeneratedCodeRecognizer tree.Encoding.Returns(x => null); var model = TestCompiler.CompileCS(string.Empty).Model; var sut = new TestFileMetadataAnalyzer(null, isTestProject); sut.TestCreateMessage(UtilityAnalyzerParameters.Default, tree, model).Encoding.Should().BeEmpty(); } [TestMethod] [DataRow("class.generated.cs", 0)] [DataRow("SomethingElse.cs", 1)] public void Verify_UnchangedFiles(string unchangedFileName, int expectedFileCount) => CreateBuilder(ProjectType.Product, "class.generated.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)) .VerifyUtilityAnalyzer(x => x.Should().HaveCount(expectedFileCount)); [TestMethod] [DataRow("Razor.razor")] [DataRow("Razor.cshtml")] public void Verify_RazorFilesAreIgnored(string fileName) => CreateBuilder(ProjectType.Product, fileName) .VerifyUtilityAnalyzer(x => x.Select(fileInfo => Path.GetFileName(fileInfo.FilePath)).Should().BeEmpty()); // There are more files on some PCs: JSExports.g.cs, LibraryImports.g.cs, JSImports.g.cs private void VerifyAllFilesAreGenerated(ProjectType projectType, string[] projectFiles, string[] autogeneratedFiles) => CreateBuilder(projectType, projectFiles) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(x => { x.Should().AllBeEquivalentTo(new { IsGenerated = true }); x.Should().SatisfyRespectively(autogeneratedFiles.Select>(expected => actual => actual.FilePath.EndsWith(expected))); }); private VerifierBuilder CreateBuilder(ProjectType projectType, params string[] projectFiles) { var testRoot = BasePath + TestContext.TestName; return new VerifierBuilder() .AddAnalyzer(() => new TestFileMetadataAnalyzer(testRoot, projectType == ProjectType.Test)) .AddPaths(projectFiles) .WithBasePath(BasePath) .WithOptions(LanguageOptions.CSharpLatest) .WithProtobufPath(@$"{testRoot}\file-metadata.pb"); } // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestFileMetadataAnalyzer : FileMetadataAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestFileMetadataAnalyzer(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; public FileMetadataInfo TestCreateMessage(UtilityAnalyzerParameters parameters, SyntaxTree tree, SemanticModel model) => CreateMessage(parameters, tree, model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/LogAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class LogAnalyzerTest { private const string BasePath = @"Utilities\LogAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] public void LogCompilationMessages_CS() => Verify(new[] { "Normal.cs", "Second.cs" }, VerifyCompilationMessagesConcurrentRuleExecution); [TestMethod] public void LogCompilationMessages_CS_NonConcurrent() { using var scope = new EnvironmentVariableScope() { EnableConcurrentAnalysis = false }; Verify(new[] { "Normal.cs", "Second.cs" }, VerifyCompilationMessagesNonConcurrentRuleExecution); } [TestMethod] public void LogCompilationMessages_VB() => Verify(new[] { "Normal.vb", "Second.vb" }, VerifyCompilationMessagesConcurrentRuleExecution); [TestMethod] public void LogAutogenerated_CS() => Verify(new[] { "Normal.cs", "GeneratedByName.generated.cs", "GeneratedByContent.cs" }, VerifyGenerated); [TestMethod] public void LogAutogenerated_VB() => Verify(new[] { "Normal.vb", "GeneratedByName.generated.vb", "GeneratedByContent.vb" }, VerifyGenerated); [TestMethod] public void LogAutogenerated_CsHtml() { const string fileName = "Generated_cshtml.g.cs"; CreateBuilder(fileName) .VerifyUtilityAnalyzer(x => x.Should().NotContain(x => x.Text.IndexOf(fileName, StringComparison.OrdinalIgnoreCase) != -1)); } [TestMethod] public void LogAutogenerated_Razor() => CreateBuilder("Generated_razor.g.cs") .VerifyUtilityAnalyzer(x => x.Select(x => x.Text).Should().Contain($"File 'Utilities\\LogAnalyzer\\Generated_razor.g.cs' was recognized as razor generated")); [TestMethod] [DataRow("GeneratedByName.generated.cs", 0)] [DataRow("SomethingElse.cs", 1)] public void Verify_UnchangedFiles(string unchangedFileName, int expectedGeneratedFiles) => CreateBuilder("GeneratedByName.generated.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)) .VerifyUtilityAnalyzer(x => x.Where(info => info.Text.Contains("generated")).Should().HaveCount(expectedGeneratedFiles)); private void Verify(string[] paths, Action> verifyProtobuf) => CreateBuilder(paths).VerifyUtilityAnalyzer(verifyProtobuf); private VerifierBuilder CreateBuilder(params string[] paths) { var testRoot = BasePath + TestContext.TestName; var language = AnalyzerLanguage.FromPath(paths.First()); UtilityAnalyzerBase analyzer = language.LanguageName switch { LanguageNames.CSharp => new TestLogAnalyzer_CS(testRoot), LanguageNames.VisualBasic => new TestLogAnalyzer_VB(testRoot), _ => throw new UnexpectedLanguageException(language) }; return new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddPaths(paths) .WithBasePath(BasePath) .WithProtobufPath(@$"{testRoot}\log.pb"); } private static void VerifyCompilationMessagesNonConcurrentRuleExecution(IReadOnlyList messages) => VerifyCompilationMessagesBase(messages, "disabled"); private static void VerifyCompilationMessagesConcurrentRuleExecution(IReadOnlyList messages) => VerifyCompilationMessagesBase(messages, "enabled"); private static void VerifyCompilationMessagesBase(IReadOnlyList messages, string expectedConcurrencyMessage) { VerifyRoslynVersion(messages); VerifyLanguageVersion(messages); VerifyConcurrentExecution(messages, expectedConcurrencyMessage); } private static void VerifyRoslynVersion(IReadOnlyList messages) { messages.Should().NotBeEmpty(); var versionMessage = messages.SingleOrDefault(x => x.Text.Contains("Roslyn version")); versionMessage.Should().NotBeNull(); versionMessage.Severity.Should().Be(LogSeverity.Info); versionMessage.Text.Should().MatchRegex(@"^Roslyn version: \d+(\.\d+){3}"); var version = new Version(versionMessage.Text.Substring(16)); version.Should().BeGreaterThan(new Version(3, 0)); // Avoid 1.0.0.0 } private static void VerifyLanguageVersion(IReadOnlyList messages) { messages.Should().NotBeEmpty(); var versionMessage = messages.SingleOrDefault(x => x.Text.Contains("Language version")); versionMessage.Should().NotBeNull(); versionMessage.Severity.Should().Be(LogSeverity.Info); versionMessage.Text.Should().MatchRegex(@"^Language version: (Preview|(CSharp|VisualBasic)\d+)"); } private static void VerifyConcurrentExecution(IReadOnlyList messages, string expectedConcurrencyMessage) { messages.Should().NotBeEmpty(); var executionState = messages.SingleOrDefault(x => x.Text.Contains("Concurrent execution: ")); executionState.Should().NotBeNull(); executionState.Severity.Should().Be(LogSeverity.Info); executionState.Text.Should().Be($"Concurrent execution: {expectedConcurrencyMessage}"); } private static void VerifyGenerated(IReadOnlyList messages) { messages.Should().NotBeEmpty(); messages.FirstOrDefault(x => x.Text.Contains("Normal.")).Should().BeNull(); var generatedByName = messages.SingleOrDefault(x => x.Text.Contains("GeneratedByName.generated.")); generatedByName.Should().NotBeNull(); generatedByName.Severity.Should().Be(LogSeverity.Debug); generatedByName.Text.Should().Match(@"File 'Utilities\LogAnalyzer\GeneratedByName.generated.*' was recognized as generated"); var generatedByContent = messages.SingleOrDefault(x => x.Text.Contains("GeneratedByContent.")); generatedByContent.Should().NotBeNull(); generatedByContent.Severity.Should().Be(LogSeverity.Debug); generatedByContent.Text.Should().Match(@"File 'Utilities\LogAnalyzer\GeneratedByContent.*' was recognized as generated"); } // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestLogAnalyzer_CS : CS.LogAnalyzer { private readonly string outPath; public TestLogAnalyzer_CS(string outPath) { this.outPath = outPath; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = false }; } private sealed class TestLogAnalyzer_VB : VB.LogAnalyzer { private readonly string outPath; public TestLogAnalyzer_VB(string outPath) { this.outPath = outPath; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = false }; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/MethodDeclarationInfoComparerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Test.Rules.Utilities; [TestClass] public class MethodDeclarationInfoComparerTest { [TestMethod] [DataRow("type", "method", "type", "method", true)] [DataRow("Type", "method", "type", "method", false)] // Case-sensitive [DataRow("type", "Method", "type", "method", false)] // Case-sensitive [DataRow("type", "method", "type", "method2", false)] [DataRow("type", "method", "type2", "method", false)] public void Equals(string firstType, string firstMethod, string secondType, string secondMethod, bool expected) { var first = new MethodDeclarationInfo { TypeName = firstType, MethodName = firstMethod }; var second = new MethodDeclarationInfo { TypeName = secondType, MethodName = secondMethod }; var sut = new MethodDeclarationInfoComparer(); sut.Equals(first, second).Should().Be(expected); } [TestMethod] public void Equals_Null() { var sut = new MethodDeclarationInfoComparer(); sut.Equals(null, null).Should().BeTrue(); sut.Equals(new MethodDeclarationInfo(), null).Should().BeFalse(); sut.Equals(null, new MethodDeclarationInfo()).Should().BeFalse(); } [TestMethod] public void GetHashCode_EqualForObjectsWithTheSameProperties() { var first = new MethodDeclarationInfo { TypeName = "type", MethodName = "method" }; var second = new MethodDeclarationInfo { TypeName = "type", MethodName = "method" }; var sut = new MethodDeclarationInfoComparer(); sut.GetHashCode(first).Should().Be(sut.GetHashCode(second)); } [TestMethod] public void GetHashCode_DifferentForObjectsWithDifferentProperties() { var first = new MethodDeclarationInfo { TypeName = "type", MethodName = "method" }; var second = new MethodDeclarationInfo { TypeName = "different type", MethodName = "method" }; var third = new MethodDeclarationInfo { TypeName = "different type", MethodName = "different method" }; var sut = new MethodDeclarationInfoComparer(); sut.GetHashCode(first).Should().NotBe(sut.GetHashCode(second)); sut.GetHashCode(first).Should().NotBe(sut.GetHashCode(third)); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/MetricsAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.CSharp.Rules; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Test.Rules; [TestClass] public class MetricsAnalyzerTest { private const string BasePath = @"Utilities\MetricsAnalyzer\"; private const string AllMetricsFileName = "AllMetrics.cs"; private const string RazorFileName = "Razor.razor"; private const string CsHtmlFileName = "Razor.cshtml"; private const string CSharpLatestFileName = "Metrics.Latest.cs"; private const string ImportsRazorFileName = "_Imports.razor"; public TestContext TestContext { get; set; } [TestMethod] public void VerifyMetrics() => CreateBuilder(false, AllMetricsFileName) .VerifyUtilityAnalyzer(messages => { messages.Should().ContainSingle(); var metrics = messages.Single(); metrics.FilePath.Should().Be(Path.Combine(BasePath, AllMetricsFileName)); metrics.ClassCount.Should().Be(4); metrics.CodeLine.Should().HaveCount(24); metrics.CognitiveComplexity.Should().Be(1); metrics.Complexity.Should().Be(2); metrics.ExecutableLines.Should().HaveCount(5); metrics.FunctionCount.Should().Be(1); metrics.NoSonarComment.Should().ContainSingle(); metrics.NonBlankComment.Should().ContainSingle(); metrics.StatementCount.Should().Be(5); }); [TestMethod] public void VerifyMetrics_Razor() => CreateBuilder(false, RazorFileName, "Component.razor") .VerifyUtilityAnalyzer(messages => { var orderedMessages = messages.OrderBy(x => x.FilePath, StringComparer.InvariantCulture).ToArray(); orderedMessages.Select(x => Path.GetFileName(x.FilePath)).Should().BeEquivalentTo(RazorFileName, "Component.razor"); var metrics = messages.Single(x => x.FilePath.EndsWith(RazorFileName)); metrics.ClassCount.Should().Be(1); metrics.CodeLine.Should().BeEquivalentTo([1, 2, 3, 5, 8, 10, 13, 15, 16, 17, 19, 22, 23, 24, 26, 28, 29, 32, 33, 34, 36, 37, 39, 40, 43]); metrics.CognitiveComplexity.Should().Be(3); metrics.Complexity.Should().Be(4); metrics.ExecutableLines.Should().BeEquivalentTo([3, 5, 13, 15, 17, 24, 28, 29, 32, 36, 39, 43]); // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 metrics.FunctionCount.Should().Be(1); metrics.NoSonarComment.Should().BeEmpty(); metrics.NonBlankComment.Should().BeEquivalentTo([7, 8, 10, 15, 21, 22, 23, 28, 29, 32, 33, 36, 37, 38]); metrics.StatementCount.Should().Be(13); // This is incorrect, see https://sonarsource.atlassian.net/browse/NET-2052 }); [TestMethod] // This is incorrect, see see https://sonarsource.atlassian.net/browse/NET-2052 public void VerifyMetrics_Razor_Usings() => CreateBuilder(false, ImportsRazorFileName) .VerifyUtilityAnalyzer(messages => { var orderedMessages = messages.OrderBy(x => x.FilePath, StringComparer.InvariantCulture).ToArray(); orderedMessages.Select(x => Path.GetFileName(x.FilePath)).Should().BeEquivalentTo(ImportsRazorFileName); var metrics = messages.Single(x => x.FilePath.EndsWith(ImportsRazorFileName)); metrics.ClassCount.Should().Be(0); metrics.CodeLine.Should().BeEquivalentTo([1, 2, 4, 5]); // FN: this file has only 3 lines. See: https://github.com/SonarSource/sonar-dotnet/issues/9288 and https://sonarsource.atlassian.net/browse/NET-2052 metrics.CognitiveComplexity.Should().Be(0); metrics.Complexity.Should().Be(1); metrics.ExecutableLines.Should().BeEquivalentTo(Array.Empty()); metrics.FunctionCount.Should().Be(0); metrics.NoSonarComment.Should().BeEmpty(); metrics.NonBlankComment.Should().BeEquivalentTo(Array.Empty()); metrics.StatementCount.Should().Be(0); }); [TestMethod] public void VerifyMetrics_CsHtml() => CreateBuilder(false, CsHtmlFileName) .VerifyUtilityAnalyzer(x => // There should be no metrics messages for the cshtml files. x.Select(x => Path.GetFileName(x.FilePath)).Should().BeEmpty()); [TestMethod] public void VerifyMetrics_Latest() => CreateBuilder(false, CSharpLatestFileName) .VerifyUtilityAnalyzer(messages => { messages.Should().ContainSingle(); var metrics = messages.Single(); metrics.ClassCount.Should().Be(4); metrics.CodeLine.Should().HaveCount(41); metrics.CognitiveComplexity.Should().Be(2); metrics.Complexity.Should().Be(9); metrics.ExecutableLines.Should().HaveCount(8); // 7, 9, 11, 23, 25, 35, 36, 44 metrics.FunctionCount.Should().Be(6); metrics.NoSonarComment.Should().BeEmpty(); metrics.NonBlankComment.Should().ContainSingle(); metrics.StatementCount.Should().Be(8); }); [TestMethod] public void Verify_NotRunForTestProject() => CreateBuilder(true, AllMetricsFileName).VerifyUtilityAnalyzerProducesEmptyProtobuf(); [TestMethod] [DataRow(AllMetricsFileName, true)] [DataRow("SomethingElse.cs", false)] public void Verify_UnchangedFiles(string unchangedFileName, bool expectedProtobufIsEmpty) { var builder = CreateBuilder(false, AllMetricsFileName) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)); if (expectedProtobufIsEmpty) { builder.VerifyUtilityAnalyzerProducesEmptyProtobuf(); } else { builder.VerifyUtilityAnalyzer(x => x.Should().NotBeEmpty()); } } private VerifierBuilder CreateBuilder(bool isTestProject, params string[] fileNames) { var testRoot = BasePath + TestContext.TestName; return new VerifierBuilder() .AddAnalyzer(() => new TestMetricsAnalyzer(testRoot, isTestProject)) .AddPaths(fileNames) .WithBasePath(BasePath) .WithOptions(LanguageOptions.CSharpLatest) .WithProtobufPath(@$"{testRoot}\metrics.pb"); } // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestMetricsAnalyzer : MetricsAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestMetricsAnalyzer(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/SymbolReferenceAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class SymbolReferenceAnalyzerTest { private const string BasePath = @"Utilities\SymbolReferenceAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Method_PreciseLocation_CS(ProjectType projectType) => Verify("Method.cs", projectType, x => { x.Select(x => x.Declaration.StartLine).Should().BeEquivalentTo([1, 3, 5, 7]); // class 'Sample' on line 1, method 'Method' on line 3, method 'method' on line 5 and method 'Go' on line 7 var methodDeclaration = x.Single(x => x.Declaration.StartLine == 3); methodDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 3, EndLine = 3, StartOffset = 16, EndOffset = 22 }); methodDeclaration.Reference.Should().Equal(new TextRange { StartLine = 9, EndLine = 9, StartOffset = 8, EndOffset = 14 }); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Method_PreciseLocation_VB(ProjectType projectType) => Verify("Method.vb", projectType, x => { x.Select(x => x.Declaration.StartLine).Should().BeEquivalentTo([1, 3, 6, 10]); var procedureDeclaration = x.Single(x => x.Declaration.StartLine == 3); procedureDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 3, EndLine = 3, StartOffset = 15, EndOffset = 21 }); procedureDeclaration.Reference.Should().BeEquivalentTo( [ new TextRange { StartLine = 11, EndLine = 11, StartOffset = 8, EndOffset = 14 }, new TextRange { StartLine = 13, EndLine = 13, StartOffset = 8, EndOffset = 14 } ]); var functionDeclaration = x.Single(x => x.Declaration.StartLine == 6); functionDeclaration.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 6, EndLine = 6, StartOffset = 13, EndOffset = 23 }); functionDeclaration.Reference.Should().Equal(new TextRange { StartLine = 12, EndLine = 12, StartOffset = 8, EndOffset = 18 }); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Event_CS(ProjectType projectType) => Verify("Event.cs", projectType, 6, 5, 9, 10); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Event_VB(ProjectType projectType) => Verify("Event.vb", projectType, 4, 3, 6, 8, 11); [TestMethod] [DataRow("Field.cs", ProjectType.Product)] [DataRow("Field.cs", ProjectType.Test)] [DataRow("Field.ReservedKeyword.cs", ProjectType.Product)] [DataRow("Field.ReservedKeyword.cs", ProjectType.Test)] [DataRow("Field.EscapedNonKeyword.cs", ProjectType.Product)] [DataRow("Field.EscapedNonKeyword.cs", ProjectType.Test)] public void Verify_Field_CS(string fileName, ProjectType projectType) => Verify(fileName, projectType, 4, 3, 7, 8); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Field_EscapedSequences_CS(ProjectType projectType) => Verify("Field.EscapedSequences.cs", projectType, 3, 3, 7, 8, 9, 10, 11, 12); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_MissingDeclaration_CS(ProjectType projectType) => Verify("MissingDeclaration.cs", projectType, 1, 3); [TestMethod] [DataRow("Field.vb", ProjectType.Product)] [DataRow("Field.vb", ProjectType.Test)] [DataRow("Field.ReservedKeyword.vb", ProjectType.Product)] [DataRow("Field.ReservedKeyword.vb", ProjectType.Test)] [DataRow("Field.EscapedNonKeyword.vb", ProjectType.Product)] [DataRow("Field.EscapedNonKeyword.vb", ProjectType.Test)] public void Verify_Field_VB(string fileName, ProjectType projectType) => Verify(fileName, projectType, 4, 3, 6, 7); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Tuples_CS(ProjectType projectType) => Verify("Tuples.cs", projectType, 4, 7, 8); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Tuples_VB(ProjectType projectType) => Verify("Tuples.vb", projectType, 4, 4, 8); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_LocalFunction_CS(ProjectType projectType) => Verify("LocalFunction.cs", projectType, 4, 7, 5); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Method_CS(ProjectType projectType) => Verify("Method.cs", projectType, 4, 3, 9); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Method_Partial_CS(ProjectType projectType) { var builder = CreateBuilder(projectType, "Method_Partial1.cs", "Method_Partial2.cs"); builder.VerifyUtilityAnalyzer(x => x.OrderBy(x => x.FilePath).Should().SatisfyRespectively( p1 => { p1.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\Method_Partial1.cs"); p1.Reference.Should().HaveCount(7, because: "a class, 5 partial methods, and a referencing method are declared"); var unimplementedReference = p1.Reference[1]; unimplementedReference.Declaration.StartLine.Should().Be(5, because: "the Unimplemented() method is declared here"); unimplementedReference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(14, because: "The reference to Unimplemented() happens here"); var implemented1Reference = p1.Reference[2]; implemented1Reference.Declaration.StartLine.Should().Be(6, because: "the Implemented1() method is declared here"); implemented1Reference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(15, because: "The reference to Implemented1() happens here"); var implemented2Reference = p1.Reference[3]; implemented2Reference.Declaration.StartLine.Should().Be(7, because: "the Implemented2() method is declared here"); implemented2Reference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(16, because: "The reference to Implemented2() happens here"); var declaredAndImplementedReference = p1.Reference[4]; declaredAndImplementedReference.Declaration.StartLine.Should().Be(9, because: "the DeclaredAndImplemented() method is declared here"); declaredAndImplementedReference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(18, because: "The reference to DeclaredAndImplemented() happens here"); var declaredAndImplementedReference2 = p1.Reference[5]; declaredAndImplementedReference2.Declaration.StartLine.Should().Be(10, because: "the DeclaredAndImplemented() method is implemented here"); declaredAndImplementedReference2.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(18, because: "The reference to DeclaredAndImplemented() happens here"); }, p2 => { p2.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\Method_Partial2.cs"); p2.Reference.Should().HaveCount(4, because: "a class, 2 partial methods, and a referencing method are declared"); var implemented1Reference = p2.Reference[1]; implemented1Reference.Declaration.StartLine.Should().Be(5, because: "the Implemented1() method is declared here"); implemented1Reference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference2() once").Subject.StartLine.Should().Be(11, because: "The reference to Implemented1() happens here"); var implemented2Reference = p2.Reference[2]; implemented2Reference.Declaration.StartLine.Should().Be(6, because: "the Implemented2() method is declared here"); implemented2Reference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference2() once").Subject.StartLine.Should().Be(12, because: "The reference to Implemented2() happens here"); })); } [TestMethod] [DataRow("NamedType.cs", ProjectType.Product)] [DataRow("NamedType.cs", ProjectType.Test)] [DataRow("NamedType.ReservedKeyword.cs", ProjectType.Product)] [DataRow("NamedType.ReservedKeyword.cs", ProjectType.Test)] public void Verify_NamedType_CS(string fileName, ProjectType projectType) => Verify(fileName, projectType, 4, 3, 7); [TestMethod] [DataRow("NamedType.vb", ProjectType.Product)] [DataRow("NamedType.vb", ProjectType.Test)] [DataRow("NamedType.ReservedKeyword.vb", ProjectType.Product)] [DataRow("NamedType.ReservedKeyword.vb", ProjectType.Test)] public void Verify_NamedType_VB(string fileName, ProjectType projectType) => Verify(fileName, projectType, 5, 1, 4, 4, 5); [TestMethod] [DataRow("Parameter.cs", ProjectType.Product)] [DataRow("Parameter.cs", ProjectType.Test)] [DataRow("Parameter.ReservedKeyword.cs", ProjectType.Product)] [DataRow("Parameter.ReservedKeyword.cs", ProjectType.Test)] public void Verify_Parameter_CS(string fileName, ProjectType projectType) => Verify(fileName, projectType, 4, 4, 6, 7); [TestMethod] [DataRow("Parameter.vb", ProjectType.Product)] [DataRow("Parameter.vb", ProjectType.Test)] [DataRow("Parameter.ReservedKeyword.vb", ProjectType.Product)] [DataRow("Parameter.ReservedKeyword.vb", ProjectType.Test)] public void Verify_Parameter_VB(string fileName, ProjectType projectType) => Verify(fileName, projectType, 4, 4, 5, 6); [TestMethod] [DataRow("Property.cs", ProjectType.Product)] [DataRow("Property.cs", ProjectType.Test)] [DataRow("Property.FieldKeyword.cs", ProjectType.Product)] [DataRow("Property.FieldKeyword.cs", ProjectType.Test)] public void Verify_Property_CS(string fileName, ProjectType projectType) => Verify(fileName, projectType, 5, 3, 9, 10); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_ExtensionKeyword_CS(ProjectType projectType) => Verify("ExtensionKeyword.cs", projectType, 5, 3, 5, 10); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Property_Partial_CS(ProjectType projectType) { var builder = CreateBuilder(projectType, "Property_Partial1.cs", "Property_Partial2.cs"); builder.VerifyUtilityAnalyzer(x => x.OrderBy(x => x.FilePath).Should().SatisfyRespectively( p1 => { p1.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\Property_Partial1.cs"); p1.Reference.Should().HaveCount(5, because: "a class, three properties, and a method are declared"); var secondReference = p1.Reference[1]; secondReference.Declaration.StartLine.Should().Be(5, because: "the property is declared here"); secondReference.Reference.Should().ContainSingle(because: "the property is referenced inside Reference1() once").Subject.StartLine.Should().Be(12, because: "The reference to Property happens here"); var declaredAndImplementedReference = p1.Reference[2]; declaredAndImplementedReference.Declaration.StartLine.Should().Be(7, because: "the DeclaredAndImplemented property is declared here"); declaredAndImplementedReference.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(13, because: "The reference to DeclaredAndImplemented happens here"); var declaredAndImplementedReference2 = p1.Reference[3]; declaredAndImplementedReference2.Declaration.StartLine.Should().Be(8, because: "the DeclaredAndImplemented property is implemented here"); declaredAndImplementedReference2.Reference.Should().ContainSingle(because: "the method is referenced inside Reference1() once").Subject.StartLine.Should().Be(13, because: "The reference to DeclaredAndImplemented happens here"); }, p2 => { p2.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\Property_Partial2.cs"); p2.Reference.Should().HaveCount(3, because: "a class, a property, and a method are declared"); var secondReference = p2.Reference[1]; secondReference.Declaration.StartLine.Should().Be(5, because: "the property is declared here"); secondReference.Reference.Should().ContainSingle(because: "the property is referenced inside Reference2() once").Subject.StartLine.Should().Be(9, because: "The reference to Property happens here"); })); } [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Constructor_Partial_CS(ProjectType projectType) => CreateBuilder(projectType, "PartialConstructor.cs", "PartialConstructor.Partial.cs") .VerifyUtilityAnalyzer(x => x.OrderBy(x => x.FilePath) .Should().SatisfyRespectively( x => { x.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\PartialConstructor.cs"); VerifyReferences(x.Reference, 2, 1); VerifyReferences(x.Reference, 2, 3); }, x => { x.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\PartialConstructor.Partial.cs"); VerifyReferences(x.Reference, 3, 1); VerifyReferences(x.Reference, 3, 8); VerifyReferences(x.Reference, 3, 10, 11); })); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Event_Partial_CS(ProjectType projectType) => CreateBuilder(projectType, "PartialEvent.cs", "PartialEvent.Partial.cs") .VerifyUtilityAnalyzer(x => x.OrderBy(x => x.FilePath) .Should().SatisfyRespectively( x => { x.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\PartialEvent.cs"); VerifyReferences(x.Reference, 6, 3); VerifyReferences(x.Reference, 6, 5); VerifyReferences(x.Reference, 6, 6); }, x => { x.FilePath.Should().Be(@"Utilities\SymbolReferenceAnalyzer\PartialEvent.Partial.cs"); VerifyReferences(x.Reference, 2, 3); VerifyReferences(x.Reference, 2, 7); })); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Property_VB(ProjectType projectType) => Verify("Property.vb", projectType, 5, 3, 6, 7, 8); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_TypeParameter_CS(ProjectType projectType) => Verify("TypeParameter.cs", projectType, 5, 2, 4, 6); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_TypeParameter_VB(ProjectType projectType) => Verify("TypeParameter.vb", projectType, 5, 2, 4, 5); [TestMethod] public void Verify_TokenThreshold() => // In TokenThreshold.cs there are 40009 tokens which is more than the current limit of 40000 Verify("TokenThreshold.cs", ProjectType.Product, _ => { }, false); [TestMethod] [DataRow("Method.cs", true)] [DataRow("SomethingElse.cs", false)] public void Verify_UnchangedFiles(string unchangedFileName, bool expectedProtobufIsEmpty) { var builder = CreateBuilder(ProjectType.Product, "Method.cs").WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)); if (expectedProtobufIsEmpty) { builder.VerifyUtilityAnalyzerProducesEmptyProtobuf(); } else { builder.VerifyUtilityAnalyzer(x => x.Should().NotBeEmpty()); } } [TestMethod] public void Verify_Razor() => CreateBuilder(ProjectType.Product, "Razor.razor", "Razor.razor.cs", "RazorComponent.razor", "ToDo.cs", "Razor.cshtml") .WithConcurrentAnalysis(false) .VerifyUtilityAnalyzer(x => { var orderedSymbols = x.OrderBy(x => x.FilePath, StringComparer.InvariantCulture).ToArray(); orderedSymbols.Select(x => Path.GetFileName(x.FilePath)).Should().BeEquivalentTo("Razor.razor", "Razor.razor.cs", "RazorComponent.razor", "ToDo.cs"); orderedSymbols[0].FilePath.Should().EndWith("Razor.razor"); VerifyReferences(orderedSymbols[0].Reference, 10, 14, 5, 7, 21, 49); // currentCount VerifyReferences(orderedSymbols[0].Reference, 10, 17, 11, 21, 22); // IncrementAmount VerifyReferences(orderedSymbols[0].Reference, 10, 19, 9, 53); // IncrementCount VerifyReferences(orderedSymbols[0].Reference, 10, 35, 35); // x VerifyReferences(orderedSymbols[0].Reference, 10, 38, 29, 35); // todos VerifyReferences(orderedSymbols[0].Reference, 10, 40, 26); // AddTodo VerifyReferences(orderedSymbols[0].Reference, 10, 42); // x VerifyReferences(orderedSymbols[0].Reference, 10, 43); // y VerifyReferences(orderedSymbols[0].Reference, 10, 45, 42); // LocalMethod VerifyReferences(orderedSymbols[0].Reference, 10, 58, 52); // AdditionalAttributes VerifyReferencesColumns(orderedSymbols[0].Reference, 14, 5, 5, 19, 31); // currentCount: line 5 VerifyReferencesColumns(orderedSymbols[0].Reference, 14, 7, 7, 1, 13); // currentCount: line 7 VerifyReferencesColumns(orderedSymbols[0].Reference, 14, 21, 21, 8, 20); // currentCount: line 21 VerifyReferencesColumns(orderedSymbols[0].Reference, 14, 49, 49, 26, 38); // currentCount: line 49 orderedSymbols[1].FilePath.Should().EndWith("RazorComponent.razor"); // RazorComponent.razor // https://github.com/SonarSource/sonar-dotnet/issues/8417 // before dotnet 8.0.5 SDK: Declaration (1,0) - (1,17) Reference (1,6) - (1,23) <- Overlapping // Declaration of TSomeVeryLongName is placed starting at index 0 (ignoring "@typeparam ") // Reference "where TSomeVeryLongName" is placed starting at index 6 (ignoring "@typeparam TSomeVeryLongName ") VerifyReferences(orderedSymbols[1].Reference, 1, 1, 1); orderedSymbols[1].Reference.Single().Reference.Single().Should().BeEquivalentTo(new TextRange { StartLine = 1, EndLine = 1, StartOffset = 35, EndOffset = 52 }); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_PrimaryConstructor_PreciseLocation_CSharp12(ProjectType projectType) => Verify("PrimaryConstructor.cs", projectType, x => { x.Select(x => x.Declaration.StartLine).Should().BeEquivalentTo([1, 3, 6, 6, 8, 8, 10, 11, 12, 14, 17, 17, 19, 20, 21, 21, 23, 23, 25]); var primaryCtorParameter = x.Single(x => x.Declaration.StartLine == 8 && x.Declaration.StartOffset == 19); // b1, primary ctor primaryCtorParameter.Declaration.Should().BeEquivalentTo(new TextRange { StartLine = 8, EndLine = 8, StartOffset = 19, EndOffset = 21 }); primaryCtorParameter.Reference.Should().BeEquivalentTo( [ new TextRange { StartLine = 10, EndLine = 10, StartOffset = 24, EndOffset = 26 }, // Field new TextRange { StartLine = 11, EndLine = 11, StartOffset = 41, EndOffset = 43 }, // Property new TextRange { StartLine = 12, EndLine = 12, StartOffset = 21, EndOffset = 23 } // b1 ]); var ctorDeclaration = x.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 6); // B ctorDeclaration.Reference.Should().BeEmpty(); // FN, not reporting constructor 'B' and 'this' (line 21) var fieldNameEqualToParameter = x.Single(x => x.Declaration.StartLine == 12 && x.Declaration.StartOffset == 16); // b1, field fieldNameEqualToParameter.Reference.Should().Equal( new TextRange { StartLine = 14, EndLine = 14, StartOffset = 20, EndOffset = 22 }); // b1, returned by Method var ctorParameterDeclaration = x.Single(x => x.Declaration.StartLine == 21 && x.Declaration.StartOffset == 17); // b1, internal ctor ctorParameterDeclaration.Reference.Should().Equal( new TextRange { StartLine = 21, EndLine = 21, StartOffset = 36, EndOffset = 38 }); // b1, this parameter var primaryCtorParameterB = x.Single(x => x.Declaration.StartLine == 17 && x.Declaration.StartOffset == 12); // b1, primary ctor B primaryCtorParameterB.Reference.Should().BeEquivalentTo( [ new TextRange { StartLine = 19, EndLine = 19, StartOffset = 24, EndOffset = 26 }, // Field new TextRange { StartLine = 20, EndLine = 20, StartOffset = 41, EndOffset = 43 }, // Property new TextRange { StartLine = 25, EndLine = 25, StartOffset = 20, EndOffset = 22 } // returned by Method ]); var classADeclaration = x.Single(x => x.Declaration.StartLine == 1 && x.Declaration.StartOffset == 13); // A classADeclaration.Reference.Should().BeEquivalentTo( [ new TextRange { StartLine = 6, EndLine = 6, StartOffset = 34, EndOffset = 35 }, // primary ctor default parameter new TextRange { StartLine = 23, EndLine = 23, StartOffset = 25, EndOffset = 26 } // lambda default parameter ]); var constFieldDeclaration = x.Single(x => x.Declaration.StartLine == 3 && x.Declaration.StartOffset == 21); // I constFieldDeclaration.Reference.Should().BeEquivalentTo( [ new TextRange { StartLine = 6, EndLine = 6, StartOffset = 36, EndOffset = 37 }, // primary ctor default parameter new TextRange { StartLine = 23, EndLine = 23, StartOffset = 27, EndOffset = 28 } // lambda default parameter ]); }); private void Verify(string fileName, ProjectType projectType, int expectedDeclarationCount, int assertedDeclarationLine, params int[] assertedDeclarationLineReferences) => Verify(fileName, projectType, x => VerifyReferences(x, expectedDeclarationCount, assertedDeclarationLine, assertedDeclarationLineReferences)); private void Verify(string fileName, ProjectType projectType, Action> verifyReference, bool isMessageExpected = true) => CreateBuilder(projectType, fileName) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(x => { x.Should().HaveCount(isMessageExpected ? 1 : 0); if (isMessageExpected) { var info = x.Single(); info.FilePath.Should().Be(Path.Combine(BasePath, fileName)); verifyReference(info.Reference); } }); private VerifierBuilder CreateBuilder(ProjectType projectType, params string[] fileNames) { var testRoot = BasePath + TestContext.TestName; var language = AnalyzerLanguage.FromPath(fileNames[0]); UtilityAnalyzerBase analyzer = language.LanguageName switch { LanguageNames.CSharp => new TestSymbolReferenceAnalyzer_CS(testRoot, projectType == ProjectType.Test), LanguageNames.VisualBasic => new TestSymbolReferenceAnalyzer_VB(testRoot, projectType == ProjectType.Test), _ => throw new UnexpectedLanguageException(language) }; return new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddPaths(fileNames) .WithBasePath(BasePath) .WithOptions(LanguageOptions.Latest(language)) .WithProtobufPath(@$"{testRoot}\symrefs.pb"); } private static void VerifyReferences(IReadOnlyList references, int expectedDeclarationCount, int assertedDeclarationLine, params int[] assertedDeclarationLineReferences) { references.Where(x => x.Declaration is not null).Should().HaveCount(expectedDeclarationCount); references.Should().ContainSingle(x => x.Declaration.StartLine == assertedDeclarationLine).Subject.Reference.Select(x => x.StartLine) .Should().BeEquivalentTo(assertedDeclarationLineReferences); } private static void VerifyReferencesColumns(IReadOnlyList symbolReference, int declarationLine, int startLine, int endLine, int startOffset, int endOffset) => symbolReference.Should().ContainSingle(x => x.Declaration.StartLine == declarationLine).Subject.Reference .Should().ContainSingle(x => x.StartLine == startLine && x.EndLine == endLine && x.StartOffset == startOffset && x.EndOffset == endOffset); // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestSymbolReferenceAnalyzer_CS : CS.SymbolReferenceAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestSymbolReferenceAnalyzer_CS(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } private sealed class TestSymbolReferenceAnalyzer_VB : VB.SymbolReferenceAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestSymbolReferenceAnalyzer_VB(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/TelemetryAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using Microsoft.CodeAnalysis.Text; using CodeAnalysisCS = Microsoft.CodeAnalysis.CSharp; using CodeAnalysisVB = Microsoft.CodeAnalysis.VisualBasic; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class TelemetryAnalyzerTest { private const string BasePath = @"Utilities\TelemetryAnalyzer"; private static int testNumber = 0; public TestContext TestContext { get; set; } [TestMethod] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp7)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp8)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp9)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp10)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp11)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp12)] [DataRow(CodeAnalysisCS.LanguageVersion.CSharp13)] public async Task TelemetryAnalyzer_CreateAnalysisMessages_ValidData_CS(CodeAnalysisCS.LanguageVersion langVersion) { var telemetry = await Telemetry_CS(langVersion); telemetry.Should().Be(new Protobuf.Telemetry { LanguageVersion = langVersion.ToString(), ProjectFullPath = "A.csproj", }); telemetry.LanguageVersion.Should().StartWith("CSharp"); } [TestMethod] public async Task TelemetryAnalyzer_CreateAnalysisMessages_InvalidLanguageVersion_CS() { var telemetry = await Telemetry_CS((CodeAnalysisCS.LanguageVersion)99999); telemetry.Should().Be(new Protobuf.Telemetry { LanguageVersion = "99999", ProjectFullPath = "A.csproj", }); } [TestMethod] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic15_3)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic15_5)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic11)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic12)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic14)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic15)] [DataRow(CodeAnalysisVB.LanguageVersion.VisualBasic16)] public async Task TelemetryAnalyzer_CreateAnalysisMessages_ValidData_VB(CodeAnalysisVB.LanguageVersion langVersion) { var telemetry = await Telemetry_VB(langVersion); telemetry.Should().Be(new Protobuf.Telemetry { LanguageVersion = langVersion.ToString(), ProjectFullPath = "A.vbproj", }); telemetry.LanguageVersion.Should().StartWith("VisualBasic"); } [TestMethod] public async Task TelemetryAnalyzer_CreateAnalysisMessages_InvalidLanguageVersion_VB() { var telemetry = await Telemetry_VB((CodeAnalysisVB.LanguageVersion)99999); telemetry.Should().Be(new Protobuf.Telemetry { LanguageVersion = "99999", ProjectFullPath = "A.vbproj", }); } [TestMethod] public async Task TelemetryAnalyzer_CreateAnalysisMessages_MissingProjectName() { var telemetry = await Telemetry_CS(CodeAnalysisCS.LanguageVersion.CSharp12, null); telemetry.Should().Be(new Protobuf.Telemetry { LanguageVersion = "CSharp12", ProjectFullPath = string.Empty, }); } private async Task Telemetry_CS(CodeAnalysisCS.LanguageVersion languageVersion, string projectName = "A.csproj") { var syntaxTrees = new[] { CodeAnalysisCS.SyntaxFactory.ParseSyntaxTree(string.Empty, CodeAnalysisCS.CSharpParseOptions.Default.WithLanguageVersion(languageVersion)) }; var compilation = CodeAnalysisCS.CSharpCompilation.Create(null, syntaxTrees); var outPath = Path.Combine(BasePath, TestContext.TestName, Interlocked.Increment(ref testNumber).ToString()); var compilationWithAnalyzer = compilation.WithAnalyzers( [new CS.TelemetryAnalyzer()], new AnalyzerOptions([SonarProjectConfigXmlMock(projectName, outPath), SonarLintXmlMock()])); var result = await compilationWithAnalyzer.GetAnalysisResultAsync(CancellationToken.None); result.GetAllDiagnostics().Should().BeEmpty(); return ParseTelemetryProtobuf(Path.Combine(outPath, "output-cs", "telemetry.pb")); } private async Task Telemetry_VB(CodeAnalysisVB.LanguageVersion languageVersion, string projectName = "A.vbproj") { var syntaxTrees = new[] { CodeAnalysisVB.SyntaxFactory.ParseSyntaxTree(string.Empty, CodeAnalysisVB.VisualBasicParseOptions.Default.WithLanguageVersion(languageVersion)) }; var compilation = CodeAnalysisVB.VisualBasicCompilation.Create(null, syntaxTrees); var outPath = Path.Combine(BasePath, TestContext.TestName, Interlocked.Increment(ref testNumber).ToString()); var compilationWithAnalyzer = compilation.WithAnalyzers( [new VB.TelemetryAnalyzer()], new AnalyzerOptions([SonarProjectConfigXmlMock(projectName, outPath), SonarLintXmlMock()])); var result = await compilationWithAnalyzer.GetAnalysisResultAsync(CancellationToken.None); result.GetAllDiagnostics().Should().BeEmpty(); return ParseTelemetryProtobuf(Path.Combine(outPath, "output-vbnet", "telemetry.pb")); } private Protobuf.Telemetry ParseTelemetryProtobuf(string protobufFilePath) { File.Exists(protobufFilePath).Should().BeTrue(); TestContext.AddResultFile(protobufFilePath); // Including the file in the test results using var protoFile = File.Open(protobufFilePath, FileMode.Open); return Protobuf.Telemetry.Parser.ParseDelimitedFrom(protoFile); } private static AdditionalText SonarLintXmlMock() { var sonarLint = Substitute.For(); sonarLint.Path.Returns("SonarLint.xml"); sonarLint.GetText().Returns(SourceText.From(""" """)); return sonarLint; } private static AdditionalText SonarProjectConfigXmlMock(string projectName, string outPath) { var config = Substitute.For(); config.Path.Returns("SonarProjectConfig.xml"); config.GetText().Returns(SourceText.From($""" SonarQubeAnalysisConfig.xml {(projectName is not null ? $"{projectName}" : string.Empty)} {(outPath is not null ? $"{outPath}" : string.Empty)} """)); return config; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/TestMethodDeclarationsAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; namespace SonarAnalyzer.Test.Rules.Utilities; [TestClass] public class TestMethodDeclarationsAnalyzerTest { private const string BasePath = @"Utilities\MethodDeclarationsAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] public void VerifyMethodDeclarations_ShouldGenerateMetrics_AvoidGeneratedFiles() => CreateCSharpBuilder(isTestProject: true, "TestMethodDeclarations.Generated.g.cs") .VerifyUtilityAnalyzer(x => x.Should().BeEmpty()); [TestMethod] public void VerifyMethodDeclarations_TestCode_CSharp() => CreateCSharpBuilder(isTestProject: true, "TestMethodDeclarations.cs", "TestMethodDeclarations.Partial.cs") .VerifyUtilityAnalyzer(x => { x.Should().HaveCount(2); var firstFileDeclarations = x.OrderBy(declaration => declaration.FilePath).First(); firstFileDeclarations.AssemblyName.Should().Be("project0"); firstFileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.cs")); firstFileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "Samples.CSharp.Address", MethodName = "GetZipCode" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.BaseClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.BaseClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Company", MethodName = "GetCompanyName" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.DerivedClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.DerivedClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Employee", MethodName = "GetEmployeeName" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.FileClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.GenericClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.LocalFunctions", MethodName = "Main" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.MultipleLevelInheritance", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.MultipleLevelInheritance", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.MultipleLevelInheritance", MethodName = "MultipleLevelInheritanceMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.IInterfaceWithTestDeclarations", MethodName = "GetZipCode" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.MultipleMethods", MethodName = "Method1" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.MultipleMethods", MethodName = "Method2" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.NoModifiers", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Overloads", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "InFirstFile" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "PartialMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Person", MethodName = "GetFullName" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "InternalMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "NoAccessModifierMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "PrivateMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "PrivateProtectedMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "ProtectedInternalMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "ProtectedMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility", MethodName = "PublicMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility.InternalClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.Visibility.PrivateClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.WithGenericMethod", MethodName = "Method" }, ]); var secondFileDeclarations = x.OrderBy(declaration => declaration.FilePath).Skip(1).First(); secondFileDeclarations.AssemblyName.Should().Be("project0"); secondFileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.Partial.cs")); secondFileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "PartialMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "InSecondFile" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.CSharp.PartialClass", MethodName = "Method" } ]); }); [TestMethod] public void VerifyMethodDeclarations_TestCode_GlobalNamespace_CSharp() => CreateCSharpBuilder(isTestProject: true, "TestMethodDeclarations.GlobalNamespace.cs") .VerifyUtilityAnalyzer(x => { x.Should().HaveCount(1); var fileDeclarations = x.OrderBy(declaration => declaration.FilePath).First(); fileDeclarations.AssemblyName.Should().Be("project0"); fileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.GlobalNamespace.cs")); fileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "TestClass", MethodName = "TestMethod" } ]); }); [TestMethod] public void VerifyMethodDeclarations_TestCode_GlobalNamespace_VB() => CreateVisualBasicBuilder(isTestProject: true, "TestMethodDeclarations.GlobalNamespace.vb") .VerifyUtilityAnalyzer(x => { x.Should().HaveCount(1); var fileDeclarations = x.OrderBy(declaration => declaration.FilePath).First(); fileDeclarations.AssemblyName.Should().Be("project0"); fileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.GlobalNamespace.vb")); fileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "TestClass", MethodName = "TestMethod" } ]); }); [TestMethod] public void VerifyMethodDeclarations_MainCode_CSharp() => CreateCSharpBuilder(isTestProject: false, "TestMethodDeclarations.cs", "TestMethodDeclarations.Partial.cs").VerifyUtilityAnalyzer(x => { x.Should().BeEmpty(); }); [TestMethod] public void VerifyMethodDeclarations_NoDeclarations_CSharp() => CreateCSharpBuilder(isTestProject: true, "TestMethodDeclarations.NoMethods.cs").VerifyUtilityAnalyzer(x => { x.Should().BeEmpty(); }); [TestMethod] public void VerifyMethodDeclarations_TestCode_VB() => CreateVisualBasicBuilder(isTestProject: true, "TestMethodDeclarations.vb", "TestMethodDeclarations.Partial.vb") .VerifyUtilityAnalyzer(x => { x.Should().HaveCount(2); var firstFileDeclarations = x.OrderBy(declaration => declaration.FilePath).First(); firstFileDeclarations.AssemblyName.Should().Be("project0"); firstFileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.Partial.vb")); firstFileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "InSecondFile" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "PartialMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "Method" } ]); var secondFileDeclarations = x.OrderBy(declaration => declaration.FilePath).Skip(1).First(); secondFileDeclarations.AssemblyName.Should().Be("project0"); secondFileDeclarations.FilePath.Should().Be(Path.Combine(BasePath, "TestMethodDeclarations.vb")); secondFileDeclarations.MethodDeclarations.Should().BeEquivalentTo([ new MethodDeclarationInfo { TypeName = "Samples.VB.Address", MethodName = "GetZipCode" }, new MethodDeclarationInfo { TypeName = "Samples.VB.BaseClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.BaseClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.DerivedClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.DerivedClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.GenericClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.MultipleLevelInheritance", MethodName = "MultipleLevelInheritanceMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.MultipleLevelInheritance", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.MultipleLevelInheritance", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.IInterfaceWithTestDeclarations", MethodName = "GetZipCode" }, new MethodDeclarationInfo { TypeName = "Samples.VB.MultipleMethods", MethodName = "Method1" }, new MethodDeclarationInfo { TypeName = "Samples.VB.MultipleMethods", MethodName = "Method2" }, new MethodDeclarationInfo { TypeName = "Samples.VB.NoModifiers", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.OverloadedMethods", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "InFirstFile" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "PartialMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "BaseClassMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.PartialClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Person", MethodName = "GetFullName" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "FriendMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "NoAccessModifierMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "PrivateMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "PrivateProtectedMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "ProtectedFriendMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "ProtectedMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility", MethodName = "PublicMethod" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility.FriendClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.Visibility.PrivateClass", MethodName = "Method" }, new MethodDeclarationInfo { TypeName = "Samples.VB.WithGenericMethod", MethodName = "Method" }, ]); }); [TestMethod] public void VerifyMethodDeclarations_MainCode_VB() => CreateVisualBasicBuilder(isTestProject: false, "TestMethodDeclarations.vb", "TestMethodDeclarations.Partial.vb").VerifyUtilityAnalyzer(x => { x.Should().BeEmpty(); }); [TestMethod] public void VerifyMethodDeclarations_NoDeclarations_VB() => CreateVisualBasicBuilder(isTestProject: true, "TestMethodDeclarations.NoMethods.vb").VerifyUtilityAnalyzer(x => { x.Should().BeEmpty(); }); private VerifierBuilder CreateCSharpBuilder(bool isTestProject, params string[] fileNames) => CreateBuilder(LanguageOptions.CSharpLatest, new TestMetricsAnalyzerCSharp(GetFilePath(), isTestProject), fileNames); private VerifierBuilder CreateVisualBasicBuilder(bool isTestProject, params string[] fileNames) => CreateBuilder(LanguageOptions.VisualBasicLatest, new TestMetricsAnalyzerVisualBasic(GetFilePath(), isTestProject), fileNames); private VerifierBuilder CreateBuilder(ImmutableArray parseOptions, DiagnosticAnalyzer analyzer, string[] fileNames) => new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddPaths(fileNames) .WithBasePath(BasePath) .WithOptions(parseOptions) .AddTestReference() .WithProtobufPath(@$"{GetFilePath()}\test-method-declarations.pb"); private string GetFilePath() => Path.Combine(BasePath, TestContext.TestName!); private sealed class TestMetricsAnalyzerCSharp(string outPath, bool isTestProject) : SonarAnalyzer.CSharp.Rules.TestMethodDeclarationsAnalyzer { protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } private sealed class TestMetricsAnalyzerVisualBasic(string outPath, bool isTestProject) : SonarAnalyzer.VisualBasic.Rules.TestMethodDeclarationsAnalyzer { protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/TokenTypeAnalyzerTest.Classifier.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ namespace SonarAnalyzer.Test.Rules; public partial class TokenTypeAnalyzerTest { [TestMethod] public void ClassClassifications() => ClassifierTestHarness.AssertTokenTypes(""" [k:using] [u:System]; [k:public] [k:class] [t:Test] { [c:// SomeComment] [k:public] [t:Test]() { } [c:/// /// A Prop /// ] [k:int] [u:Prop] { [k:get]; } [k:void] [u:Method]<[t:T]>([t:T] [u:t]) [k:where] [t:T]: [k:class], [t:IComparable], [u:System].[u:Collections].[t:IComparer] { [k:var] [u:i] = [n:1]; var s = [s:"Hello"]; [t:T] [u:local] = [k:default]; } [u:}] """); [TestMethod] [DataRow(""" [s:"Text"] """)] [DataRow(""" [s:""] """)] [DataRow(""" [s:" "] """)] [DataRow(""" [s:@""] """)] [DataRow(""" [s:$"]{true}[s:"] """)] [DataRow(""" [s:$"][s: ]{true}[s: ][s:"] """)] [DataRow(""" [s:$@"]{true}[s:"] """)] [DataRow("""" [s:""" """] """")] [DataRow("""" [s:$"""]{true}[s:"""] """")] [DataRow("""" [s:$"""][s: ]{true}[s: ][s:"""] """")] public void StringClassClassification(string stringExpression) => ClassifierTestHarness.AssertTokenTypes($$""" public class Test { void Method() { string s = {{stringExpression}}; } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_QueryComprehensions() => ClassifierTestHarness.AssertTokenTypes(""" using System.Linq; public class Test { public void M() { _ = from [u:i] in new int[0] let [u:j] = i join [u:k] in new int[0] on i equals k into [u:l] select l into [u:m] select m; } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_VariableDeclarator() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { int [u:i] = 0, [u:j] = 0; public void M() { int [u:k], [u:l]; } } """, false); [TestMethod] public void IdentifierToken_LabeledStatement() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { public void M() { goto Label; [u:Label]: ; } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_Catch() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { public void M() { try { } catch(System.Exception [u:ex]) { } } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_ForEach() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { public void M() { foreach(var [u:i] in new int[0]) { } } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_MethodParameterConstructorDestructorLocalFunctionPropertyEvent() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { public int [u:Prop] { get; set; } public event System.EventHandler [u:TestEventField]; public event System.EventHandler [u:TestEventDeclaration] { add { } remove { } } public [t:Test]() { } public void [u:Method]<[t:T]>(int [u:parameter]) { } ~[t:Test]() { void [u:LocalFunction]() { } } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_BaseTypeDelegateEnumMember() => ClassifierTestHarness.AssertTokenTypes(""" public class [t:TestClass] { } public struct [t:TestStruct] { } public record [t:TestRecord] { } public record struct [t:TestRecordStruct] { } public delegate void [t:TestDelegate](); public enum [t:TestEnum] { [u:EnumMember] } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_TupleDesignation() => ClassifierTestHarness.AssertTokenTypes(""" class Test { void M() { [k:var] ([u:i], [u:j]) = (1, 2); (int [u:i], int [u:j]) [u:t]; } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_FunctionPointerUnmanagedCallingConvention() => ClassifierTestHarness.AssertTokenTypes(""" unsafe class Test { void M(delegate* unmanaged[[u:Cdecl]] m) { } } """, allowSemanticModel: false); [TestMethod] public void IdentifierToken_ExternAlias() => ClassifierTestHarness.AssertTokenTypes(""" extern alias [u:ThisIsAnAlias]; public class Test { } """, allowSemanticModel: false, ignoreCompilationErrors: true); [TestMethod] public void IdentifierToken_AccessorDeclaration() => ClassifierTestHarness.AssertTokenTypes(""" public class Test { public string Property { [u:unknown]; } } """, allowSemanticModel: false, ignoreCompilationErrors: true); [TestMethod] [DataRow("assembly")] [DataRow("module")] [DataRow("event")] [DataRow("field")] [DataRow("method")] [DataRow("param")] [DataRow("property")] [DataRow("return")] [DataRow("type")] [DataRow("typevar")] public void IdentifierToken_AttributeTargetSpecifier_Keyword(string specifier) => ClassifierTestHarness.AssertTokenTypes($$""" [[k:{{specifier}}]:System.Obsolete] public class Test { } """, allowSemanticModel: false, ignoreCompilationErrors: true); [TestMethod] public void IdentifierToken_AttributeTargetSpecifier_UnknownSpecifier() => ClassifierTestHarness.AssertTokenTypes(""" [[k:unknown]:System.Obsolete] public class Test { } """, allowSemanticModel: false, ignoreCompilationErrors: true); [TestMethod] [DataRow("using [u:System];", false)] [DataRow("using [u:System].[u:Collections].[u:Generic];", false)] [DataRow("using [k:global]::[u:System].[u:Collections].[u:Generic];", false)] [DataRow("using [t:X] = System.Math;", true)] [DataRow("using X = [u:System].Math;", true)] [DataRow("using X = System.[t:Math];", true)] [DataRow("using X = [t:InGlobalNamespace];", true)] [DataRow("using X = [k:global]::[t:InGlobalNamespace];", true)] [DataRow("using [u:X] = System.Collections;", true)] [DataRow("using X = [u:System].Collections;", true)] [DataRow("using X = System.[u:Collections];", true)] [DataRow("using [k:static] [u:System].[t:Math];", true)] [DataRow("using [k:static] [u:System].[u:Collections].[u:Generic].[t:List]<[k:int]>;", true)] [DataRow("using [k:static] [u:System].[u:Collections].[u:Generic].[t:List]<[u:System].[u:Collections].[u:Generic].[t:List]<[k:int]>>;", true)] [DataRow("using [k:static] [u:System].[u:Collections].[u:Generic].[t:HashSet]<[k:int]>.[t:Enumerator];", true)] #if NET [DataRow("using [u:System].[u:Buffers];", false)] #endif public void IdentifierToken_Usings(string usings, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" {{usings}} class InGlobalNamespace { } """, allowSemanticModel); [TestMethod] [DataRow("namespace [u:A] { }", false)] [DataRow("namespace [u:A].[u:B] { }", false)] [DataRow("namespace [u:A];", false)] [DataRow("namespace [u:A].[u:B];", false)] public void IdentifierToken_Namespaces(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes(syntax, allowSemanticModel); [TestMethod] [DataRow("class TypeParameter: [t:List]<[t:Exception]> { }", false)] // We cannot be sure without calling the model but we assume this will rarely be a type [DataRow("class TypeParameter: System.Collections.Generic.[t:List] { }", false)] [DataRow("class TypeParameter: [u:System].[u:Collections].[u:Generic].List<[u:System].Exception> { }", true)] [DataRow("class TypeParameter: [t:List]<[t:List]<[t:Exception]>> { }", false)] [DataRow("class TypeParameter: [t:Outer].Inner { }", true)] [DataRow("class TypeParameter: Outer.[t:Inner] { }", false)] [DataRow("class TypeParameter: [t:HashSet]<[t:List]<[k:int]>> { }", false)] [DataRow("class TypeParameter : [t:List] where T: [t:List]<[t:Exception]> { }", false)] [DataRow("class TypeParameter<[t:T]> : List<[t:T]> where [t:T]: List { }", false)] [DataRow("class TypeParameter<[t:T1], [t:T2]> where [t:T1] : class where [t:T2] : [t:T1], new() { }", false)] public void IdentifierToken_TypeParameters(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; class Outer { public class Inner { } } {{syntax}} """, allowSemanticModel); [TestMethod] [DataRow("class [t:BaseTypeList]: System.[t:Exception] { }", false)] [DataRow("class BaseTypeList: [u:System].Exception { }", true)] [DataRow("class BaseTypeList: Outer.[t:Inner] { }", false)] [DataRow("class BaseTypeList: [t:Outer].Inner { }", true)] [DataRow(""" class BaseTypeList: [t:IFormattable], [t:IFormatProvider] { public object? GetFormat(Type? formatType) => default; public string ToString(string? format, IFormatProvider? formatProvider) => default; } """, false)] public void IdentifierToken_BaseTypeList(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; class Outer { public class Inner { } } {{syntax}} """, allowSemanticModel); [TestMethod] [DataRow("class", false)] [DataRow("struct", false)] [DataRow("record", false)] [DataRow("record struct", false)] #if NET [DataRow("interface", false)] #endif public void IdentifierToken_BaseTypeList_DifferentTypeKind(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" {{syntax}} X : System.[t:IFormattable] { public string ToString(string? format, System.IFormatProvider? formatProvider) => null; } """, allowSemanticModel); [TestMethod] [DataRow("_ = typeof(System.[t:Exception]);", false)] [DataRow("_ = typeof([u:System].Exception);", true)] [DataRow("_ = typeof([u:System].[u:Collections].[u:Generic].[t:Dictionary]<,>);", true)] [DataRow("_ = typeof([t:Inner]);", false)] [DataRow("_ = typeof([t:C].[t:Inner]);", true)] [DataRow("_ = typeof([t:Int32][]);", false)] [DataRow("_ = typeof([t:Int32]*);", false)] [DataRow("_ = typeof([k:delegate]*<[t:Int32], void>);", false)] public void IdentifierToken_TypeOf(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { void TypeOf() { {{syntax}} } public class Inner { } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Exception]", false)] [DataRow("[u:System].Exception", true)] [DataRow("System.[t:Exception]", false)] [DataRow("[t:List]<[t:Int32]>", false)] [DataRow("[t:List]<[t:Int32]>", false)] [DataRow("[t:HashSet]<[t:Int32]>.[t:Enumerator]", false)] [DataRow("System.Collections.Generic.[t:HashSet]<[t:Int32]>.[t:Enumerator]", false)] [DataRow("[u:System].[u:Collections].[u:Generic].HashSet.Enumerator", true)] public void IdentifierToken_TypeInDeclaration(string type, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { void Parameter({{type}} parameter) { } } """, allowSemanticModel); [TestMethod] [DataRow("_ = nameof([t:Exception]);", true)] [DataRow("_ = nameof([u:System].[t:Exception]);", true)] [DataRow("_ = nameof([t:Dictionary]<[t:Int32], [t:Exception]>);", true)] [DataRow("_ = nameof([t:Dictionary]<[t:Int32], [u:System].[t:Exception]>);", true)] [DataRow("_ = nameof([u:System].[u:Collections].[u:Generic]);", true)] [DataRow("_ = nameof([u:NameOf]);", true)] [DataRow("_ = nameof([t:DateTimeKind].[u:Utc]);", true)] [DataRow("_ = nameof([t:Inner]);", true)] [DataRow("_ = nameof([t:C].[t:Inner]);", true)] public void IdentifierToken_NameOf(string syntax, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { void NameOf() { {{syntax}} } public class Inner { } } """, allowSemanticModel); [TestMethod] [DataRow("_ = [k:value];", true)] [DataRow("_ = this.[u:value];", false)] [DataRow("int [u:Value] = 0; _ = [u:Value]++;", false)] [DataRow("_ = nameof([k:value]);", true)] [DataRow("_ = nameof([k:value].ToString);", true)] [DataRow("_ = [k:value].ToString();", true)] [DataRow("_ = [k:value].InnerException.InnerException;", true)] [DataRow("_ = [k:value]?.InnerException.InnerException;", true)] [DataRow("_ = [k:value].InnerException?.InnerException;", true)] [DataRow("_ = [k:value]?.InnerException?.InnerException;", true)] [DataRow("_ = [k:value] ?? new Exception();", true)] public void IdentifierToken_ValueInPropertySetter(string valueAccess, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { private int value; Exception Property { set { {{valueAccess}} } } } """, allowSemanticModel); [TestMethod] [DataRow("_ = [k:value];", true)] [DataRow("_ = this.[u:value];", false)] [DataRow("int [u:Value] = 0; _ = [u:Value]++;", false)] public void IdentifierToken_ValueInIndexerSetter(string valueAccess, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { private int value; int this[int i] { set { {{valueAccess}} } } } """, allowSemanticModel); [TestMethod] [DataRow("_ = [k:value];", true)] [DataRow("_ = this.[u:value];", false)] public void IdentifierToken_ValueInEventAddRemove(string valueAccess, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { private int value; event EventHandler SomeEvent { add { {{valueAccess}} } remove { {{valueAccess}} } } } """, allowSemanticModel); [TestMethod] [DataRow("_ = [u:value];", true)] [DataRow("_ = this.[u:value];", false)] [DataRow("ValueMethod([u:value]: 1);", true)] // could be false, but it is an edge case not worth investing. [DataRow("ValueMethod(value: [u:value]);", true)] public void IdentifierToken_ValueInOtherPlaces(string valueAccess, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { private int value; public void M() { int value = 0; {{valueAccess}} } public void ValueMethod(int value) { } } """, allowSemanticModel); [TestMethod] [DataRow("[n:42] is [t:Int32].[u:MinValue]", true)] // IsPattern [DataRow("ex is [t:ArgumentException]", true)] [DataRow("ex is [u:System].[t:ArgumentException]", true)] [DataRow("ex is [t:ArgumentException] [u:argEx]", false)] [DataRow("ex is ArgumentException { InnerException: [t:InvalidOperationException] }", true)] // ConstantPattern: could also be a constant [DataRow("ex is ArgumentException { HResult: [t:Int32].[u:MinValue] }", true)] // ConstantPattern: could also be a type [DataRow("ex is ArgumentException { HResult: [n:2] }", true)] [DataRow("ex is ArgumentException { [u:InnerException]: [t:InvalidOperationException] { } }", false)] // RecursivePattern.Type [DataRow("ex is ArgumentException { [u:InnerException].[u:InnerException]: [t:InvalidOperationException] { } [u:inner] }", true)] // TODO false, InnerException.InnerException can be classified without semModel [DataRow("ex as [t:ArgumentException]", false)] [DataRow("ex as [u:System].ArgumentException", true)] [DataRow("([t:ArgumentException])ex", false)] [DataRow("([u:System].ArgumentException)ex", true)] #if NET [DataRow("new object[0] is [[t:Exception]];", true)] [DataRow("new object[0] is [[t:Int32].[u:MinValue]];", true)] #endif public void IdentifierToken_Expressions(string expression, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; using System.Collections.Generic; public class Test { public void M(Exception ex) { var x = {{expression}}; } } """, allowSemanticModel); [TestMethod] [DataRow("([k:string], [t:Exception]) => true,", true)] [DataRow("""([s:""], [k:null]) => true,""", true)] [DataRow("""([s:""], [k:var] b) => true,""", true)] [DataRow("([t:String] a, [t:Exception] b) => 1,", true)] [DataRow("([u:System].[t:String] a, [u:System].[t:Exception] b) => 1,", true)] [DataRow("([t:HashSet]<[t:Int32]> a, null) => 1,", true)] [DataRow("([t:List]<[t:List]<[t:Int32]>> a, null) => 1,", true)] [DataRow("([t:Test].[t:Inner], null) => 1,", true)] [DataRow("([t:Inner], null) => 1,", true)] [DataRow("([u:first]: [t:Inner], [u:second]: null) => 1,", true)] [DataRow("""("", [t:ArgumentException] { HResult: > 2 }) => true,""", false)] [DataRow("(([t:Int32], System.[t:String], Int32.[u:MaxValue]), second: null) => 1,", true)] public void IdentifierToken_Tuples(string switchBranch, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; using System.Collections.Generic; public class Test { public void M(Object o, Exception exception) { var x = (first: o, second: exception) switch { {{switchBranch}} _ => default, }; } public class Inner { } } """, allowSemanticModel); [TestMethod] [DataRow("([t:String] s1, [t:String] s2)", false)] [DataRow("([u:System].String s1, int i)", true)] [DataRow("(([t:String] s, [k:int] i), [t:Boolean] b)", false)] public void IdentifierToken_TupleDeclaration(string tupleDeclaration, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public {{tupleDeclaration}} M() => default; } """, allowSemanticModel); [TestMethod] [DataRow("[t:Exception] ex;", false)] [DataRow("[u:System].Exception ex;", true)] [DataRow("[t:List]<[t:Exception]> ex;", false)] [DataRow("List<[u:System].Exception> ex;", true)] [DataRow("[k:var] i = 1;", false)] [DataRow("[k:dynamic] i = 1;", false)] public void IdentifierToken_LocalDeclaration(string declaration, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; using System.Collections.Generic; public class Test { public void M() { {{declaration}} } } """, allowSemanticModel); [TestMethod] [DataRow("__refvalue([u:tf], Exception)", false)] [DataRow("__refvalue(tf, [t:Exception])", false)] [DataRow("__refvalue(tf, [u:System].Exception)", true)] [DataRow("__refvalue(tf, System.[t:Exception])", false)] public void IdentifierToken_Type_RefValue(string refValueExpression, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; public class Test { public void M(TypedReference tf) { Exception tfValue = {{refValueExpression}}; } } """, allowSemanticModel); [TestMethod] [DataRow("default([t:Exception])", false)] [DataRow("default([u:System].Exception)", true)] public void IdentifierToken_Type_DefaultValue(string defaultValueExpression, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; public class Test { public void M() { var x = {{defaultValueExpression}}; } } """, allowSemanticModel); [TestMethod] [DataRow("sizeof([t:Int32])", false)] [DataRow("sizeof([u:System].Int32)", true)] public void IdentifierToken_Type_SizeOfValue(string sizeOfExpression, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; public class Test { public void M() { var x = {{sizeOfExpression}}; } } """, allowSemanticModel); #if NET [TestMethod] [DataRow("stackalloc [t:Int32][2]", false)] [DataRow("stackalloc [u:System].Int32[2]", true)] [DataRow("stackalloc [t:Int32]", false, true)] // compilation error. Type can not be resolved (must be an array type) public void IdentifierToken_Type_StackAlloc(string stackAllocExpression, bool allowSemanticModel = true, bool ignoreCompilationErrors = false) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; public class Test { public void M() { Span x = {{stackAllocExpression}}; } } """, allowSemanticModel, ignoreCompilationErrors); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[t:Int32]?", false)] [DataRow("System.[t:Int32]", false)] [DataRow("System.Nullable<[t:Int32]>", false)] [DataRow("[t:T]", false)] public void IdentifierToken_Type_Ref(string refTypeName, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { {{refTypeName}} item; public ref {{refTypeName}} M() { return ref item; } } """, allowSemanticModel); #endif [TestMethod] [DataRow(""" from [t:Int32] x in c select x """, false)] [DataRow(""" from [u:System].Int32 x in c select x """, true)] [DataRow(""" from x in c join [t:Int32] y in c on x equals y into g select g """, false)] public void IdentifierToken_Type_QueryComprehensions(string query, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; using System.Linq; public class Test { public void M() { var c = new long[0]; _ = {{query}}; } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_ForEach(string forEachVariableType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes( $$""" using System; public class Test { public void M() { foreach ({{forEachVariableType}} x in new int[0]) { } } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Exception]", false)] [DataRow("[u:System].Exception", true)] public void IdentifierToken_Type_Catch(string catchType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public void M() { try { } catch ({{catchType}} ex) { } } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_DelegateDeclaration(string returnType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; delegate {{returnType}} M(); """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_MethodDeclaration(string returnType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public {{returnType}} M() => default; } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_OperatorDeclaration(string returnType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public static {{returnType}} operator + (Test x) => default; } """, allowSemanticModel); [TestMethod] [DataRow("public static explicit operator [t:Int32](Test x)", false)] [DataRow("public static explicit operator [u:System].Int32(Test x)", true)] [DataRow("public static implicit operator [t:Int32](Test x)", false)] [DataRow("public static implicit operator [u:System].Int32(Test x)", true)] public void IdentifierToken_Type_ConversionOperatorDeclaration(string conversion, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { {{conversion}} => default; } """, allowSemanticModel); [TestMethod] [DataRow("public [t:Int32] Prop { get; set; }", false)] [DataRow("public [t:Int32] Prop { get => 1; set { } }", false)] [DataRow("public [t:Int32] this[int index] { get => 1; set { } }", false)] [DataRow("public event [t:EventHandler] E;", false)] [DataRow("public event [t:EventHandler] E { add { } remove { } }", false)] public void IdentifierToken_Type_PropertyDeclaration(string property, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { {{property}} } """, allowSemanticModel); [TestMethod] [DataRow("public [k:partial] [t:Int32] Prop { get; set; }", "public [k:partial] Int32 Prop { get => 1; set { } }")] [DataRow("public partial [t:Int32] this[int index] { get; set; }", "public partial Int32 this[int index] { get => 1; set { } }")] public void IdentifierToken_Type_PartialPropertyDeclaration(string propertyDeclaration, string propertyImplementation) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public partial class Test { {{propertyDeclaration}} } public partial class Test { {{propertyImplementation}} } """); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_LocalFunction(string returnType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public void M() { {{returnType}} LocalFunction() => default; } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", false)] [DataRow("[u:System].Int32", true)] public void IdentifierToken_Type_ParenthesizedLambda(string returnType, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { public void M() { var _ = {{returnType}}() => default; } } """, allowSemanticModel); [TestMethod] [DataRow("[[t:Obsolete]]", false)] [DataRow("[[u:System].Obsolete]", true)] public void IdentifierToken_Type_Attribute(string attributeDeclaration, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; {{attributeDeclaration}} public class Test { } """, allowSemanticModel); [TestMethod] [DataRow("[t:IFormattable]", false)] [DataRow("[u:System].IFormattable", true)] public void IdentifierToken_Type_ExplicitInterfaceSpecifier(string interfaceName, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test: IFormattable { string {{interfaceName}}.ToString(string? format, IFormatProvider? formatProvider) => default; } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]?", false)] [DataRow("[u:System].Int32?", true)] [DataRow("[k:int]?", false)] [DataRow("[t:IDisposable]?", false)] [DataRow("[t:IDisposable]?[]", false)] [DataRow("[t:IDisposable]?[]?", false)] public void IdentifierToken_Type_Nullable(string typeName, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" #nullable enable using System; public class Test { {{typeName}} someField; } """, allowSemanticModel); [TestMethod] [DataRow("[k:global]::[t:Test]", false)] [DataRow("[k:global]::[u:System].Int32", true)] [DataRow("[k:global]::System.[t:Int32]", false)] public void IdentifierToken_Type_Global(string typeName, bool allowSemanticModel = true) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class Test { {{typeName}} M() => default; } """, allowSemanticModel); [TestMethod] [DataRow("[u:aInstance]", false)] // Some simple identifier syntax in an ordinary expression context must be boud to a field/property/local or something else that produces a value, but it can not be a type [DataRow("[u:ToString]()", false)] // Some simple invocation syntax in an ordinary expression. A type can not be invoked. [DataRow("aInstance.InstanceProp.[u:InstanceProp]", false)] // Most right can not be a type in an ordinary expression context [DataRow("[u:aInstance].[u:InstanceProp].InstanceProp", true)] // Could be types [DataRow("[t:A].StaticProp", true)] // Here it starts with a type [DataRow("A.[u:StaticProp]", false)] // Most right hand side can not be a type [DataRow("[t:A].[t:B].[u:StaticProp].InstanceProp", true)] // Mixture: some nested types and then properties [DataRow("A.B.StaticProp.[u:InstanceProp]", false)] // The most right hand side [DataRow("A.B.[u:StaticProp]?.[u:InstanceProp]", false)] // Can not be a type on the left side of a ?. [DataRow("[u:Prop]?.[u:InstanceProp]?.[u:InstanceProp]", false)] // Can not be a type on the left side of a ?. or on the right [DataRow("global::[t:A].StaticProp", true)] // Can be a namespace or type [DataRow("this.[u:Prop]", false)] // Right of this: must be a property/field [DataRow("int.[u:MaxValue]", false)] // Right of pre-defined type: must be a property/field because pre-defined types do not have nested types [DataRow("this.[u:Prop].[u:InstanceProp].[u:InstanceProp]", false)] // must be properties or fields [DataRow("(true ? Prop : Prop).[u:InstanceProp].[u:InstanceProp]", false)] // Right of some expression: must be properties or fields [DataRow("[t:A].StaticProp", true)] // TODO: false, Generic name. Must be a type because not in an invocation context, like A() [DataRow("A.[u:StaticProp]", false)] // Most right hand side [DataRow("A.[u:StaticProp].InstanceProp", true)] // Not the right hand side, could be a nested type [DataRow("A.[t:B].StaticProp", true)] // Not the right hand side, is a nested type [DataRow("[t:A].[u:StaticProp]?.[u:InstanceProp]", true)] // TODO: false, Can all be infered from the positions [DataRow("[t:A].[t:B].[u:StaticProp]", true)] // TODO: false, Generic names must be types and StaticProp is most right hand side [DataRow("[t:A].[u:StaticM]().[u:InstanceProp]", true)] // TODO: false, A must be a type StaticM is invoked and InstanceProp is after the invocation [DataRow("A.StaticM().[u:InstanceProp].InstanceProp", false)] // Is right from invocation public void IdentifierToken_SimpleMemberAccess_InOrdinaryExpression(string memberAccessExpression, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" public class A { public static A StaticProp { get; } public A InstanceProp { get; } public A this[int i] => new A(); public class B { public static A StaticProp { get; } } } public class A : A { public A InstanceProp { get; } public static A StaticProp { get; } public class B : B { public static B StaticProp { get; } public A M() => new A(); public static A StaticM() => new A(); } public A M() => new A(); public static A StaticM() => new A(); } public class Test { public A Prop { get; } public void M() { var aInstance = new A(); // Ordinary expression context: no typeof(), no nameof(), no ExpressionColon (in pattern) or the like _ = {{memberAccessExpression}}; } } """, allowSemanticModel); [TestMethod] [DataRow("M([u:MethodGroup]);", false)] [DataRow("M([u:MethodGroup]);", false)] [DataRow("M(C.[u:StaticMethodGroup]);", false)] [DataRow("global::[u:System].Diagnostics.Debug.WriteLine(\"Message\");", true)] [DataRow("global::System.Diagnostics.[t:Debug].WriteLine(\"Message\");", true)] [DataRow("M([t:C].StaticMethodGroup);", true)] // TODO false, must be a type public void IdentifierToken_SimpleMemberAccess_GenericMethodGroup(string invocation, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class C { public void M(Action a) { {{invocation}} } public void MethodGroup() { } public static void StaticMethodGroup() { } } """, allowSemanticModel); [TestMethod] [DataRow("_ = [u:i];")] [DataRow("[u:i] = [u:i] + [u:i];")] [DataRow("_ = [u:i].[u:ToString]().[u:ToString]();")] // Heuristic: "i" is lower case and identified as "not a type". [DataRow("_ = [u:ex].[u:InnerException].[u:InnerException];")] // Heuristic: "ex" is lower case and identified as "not a type". [DataRow("_ = [u:ex]?.[u:ToString]();")] [DataRow("_ = ([u:ex] ?? new Exception()).[u:ToString]();")] [DataRow("[u:ex] ??= new Exception();")] [DataRow("_ = String.Format([k:string].[u:Empty], [u:i]);")] [DataRow("_ = [u:ex] is ArgumentException { };")] [DataRow("_ = [u:i] == [u:i] ? [u:b] : ![u:b];")] [DataRow("if ([u:i] == [u:i]) { }")] [DataRow("if ([u:b]) { }")] [DataRow("switch ([u:i]) { }")] [DataRow("_ = [u:i] switch { _ => true };")] [DataRow("foreach (var [u:e] in [u:l]) { }")] [DataRow("for(int [u:x] = 0; [u:b]; [u:x]++) { }")] [DataRow("while([u:b]) { }")] [DataRow("do i++; while([u:b]);")] [DataRow("_ = l.[u:Where]([u:x] => [u:x] == null);")] [DataRow("_ = ([u:i], [u:i]);")] [DataRow("_ = int.TryParse(string.Empty, out [u:i]);")] [DataRow("_ = new int[[u:i]];")] [DataRow("await [u:t];")] [DataRow("_ = unchecked([u:i] + [u:i]);")] [DataRow("_ = [u:l][[u:i]];")] [DataRow("_ = (byte)([u:i]);")] [DataRow("_ = new { [u:A] = [u:i] };")] [DataRow(""" _ = from [u:x] in [u:l] select x; """)] [DataRow(""" _ = from [u:x] in [u:l] let [u:y] = [u:x] join [u:z] in [u:l] on [u:x] equals [u:z] where [u:x] == [u:z] orderby [u:x], [u:z] select new { [u:x], Y = [u:y] }; """)] [DataRow("_ = ([u:i]);")] [DataRow("_ = +[u:i];")] [DataRow("_ = ++[u:i];")] [DataRow("_ = -[u:i];")] [DataRow("_ = --[u:i];")] [DataRow("_ = ~[u:i];")] [DataRow("_ = ![u:b];")] [DataRow("_ = [u:i]++;")] [DataRow("_ = [u:i]--;")] [DataRow("_ = [u:l]!;")] [DataRow("_ = [u:i] + [u:i];")] [DataRow("_ = [u:i] - [u:i];")] [DataRow("_ = [u:i] / [u:i];")] [DataRow("_ = [u:i] * [u:i];")] [DataRow("_ = [u:i] % [u:i];")] [DataRow("_ = [u:i] >> [u:i];")] [DataRow("_ = [u:i] << [u:i];")] [DataRow("_ = [u:b] && [u:b];")] [DataRow("_ = [u:b] || [u:b];")] [DataRow("_ = [u:i] & [u:i];")] [DataRow("_ = [u:i] | [u:i];")] [DataRow("_ = [u:i] ^ [u:i];")] [DataRow("_ = [u:i] == [u:i];")] [DataRow("_ = [u:i] != [u:i];")] [DataRow("_ = [u:i] < [u:i];")] [DataRow("_ = [u:i] <= [u:i];")] [DataRow("_ = [u:i] > [u:i];")] [DataRow("_ = [u:i] >= [u:i];")] [DataRow("_ = [u:i] is iConst;")] // iConst could be a type [DataRow("_ = [u:ex] as [t:ArgumentException];")] [DataRow("_ = [u:ex] ?? [u:ex];")] [DataRow("i += [u:i];")] [DataRow("i -= [u:i];")] [DataRow("i *= [u:i];")] [DataRow("i /= [u:i];")] [DataRow("i %= [u:i];")] [DataRow("i &= [u:i];")] [DataRow("i ^= [u:i];")] [DataRow("i |= [u:i];")] [DataRow("i >>= [u:i];")] [DataRow("i <<= [u:i];")] [DataRow("ex ??= [u:ex];")] [DataRow("Func _ = delegate() { return [u:i]; };")] // Illustration: There is an AnonymousMethodExpressionSyntax.ExpressionBody property, but it is never set. Only block syntax is allowed. [DataRow("Func _ = () => [u:i];")] [DataRow("Func _ = [u:i] => [u:i];")] [DataRow("_ = [u:b] ? 1 : throw [u:ex];")] [DataRow("_ = ex switch { _ when [u:b] => true };")] [DataRow("_ = i switch { [u:iConst] => true };", true)] // could be a type [DataRow("_ = i switch { >[u:iConst] => [u:b] };")] [DataRow("""_ = $"{[u:i]}";""")] [DataRow("""_ = $"{[u:i]:000}";""")] [DataRow("""_ = $"{[u:i],[u:iConst]}";""")] [DataRow(""" switch(i) { case 42: goto case [u:iConst]; case iConst: break; } """)] [DataRow(""" switch(AttributeTargets.Assembly) { case AttributeTargets.Class: goto case AttributeTargets.[u:Enum]; case AttributeTargets.Enum: break; } """)] [DataRow(""" goto [u:label]; label: ; """)] [DataRow("return [u:i];")] [DataRow("throw [u:ex];")] [DataRow("IEnumerable YieldReturn() { yield return [u:i]; }")] [DataRow("using(var x = [u:d]);")] [DataRow("lock([u:d]);")] [DataRow("try { } catch when ([u:b]) { }")] public void IdentifierToken_SingleExpressionIdentifier(string statement, bool allowSemanticModel = false) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; public class Test { public async Task M() { const int iConst = 0; var b = true; var i = 0; var ex = new Exception(); var l = new List(); Task t = null; IDisposable d = null; {{statement}} return 0; } } """, allowSemanticModel); [TestMethod] [WorkItem(8388)] // https://github.com/SonarSource/sonar-dotnet/pull/8388 [DataRow("""_ = [u:iPhone].Latest;""")] // [u:iPhone] -> False classification because of heuristic. Should be t: [DataRow("""_ = [u:iPhone].[u:Latest];""")] // [u:iPhone] -> False classification because of heuristic. Should be t: [DataRow("""[u:iPhone].[u:iPhone15Pro].[u:M]();""")] // [u:iPhone] -> False classification because of heuristic. Should be t: [DataRow("""[u:iPhone15Pro].[u:M]();""")] // Correct public void IdentifierToken_MemberAccess_FalseClassification(string statement) => ClassifierTestHarness.AssertTokenTypes($$""" public class iPhone { public static iPhone iPhone15Pro; public static iPhone Latest; public void M() { {{statement}} } } """, allowSemanticModel: false); #if NET [TestMethod] [DataRow("_ = [u:r] with { [u:A] = [u:iConst] };", false)] [DataRow("_ = [u:r] is ([u:iConst], [t:Int32]);", true)] // semantic model must be called for iConst and Int32 [DataRow("_ = [u:l][^[u:iConst]];", false)] [DataRow("_ = [u:a][[u:iConst]..^([u:iConst]-1)];", false)] [DataRow("foreach((var [u:x], int [u:y]) in ts);", false)] [DataRow("foreach(var ([u:x], [u:y]) in ts);", false)] public void IdentifierToken_SingleExpressionIdentifier_NetCore(string statement, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; public record R(int A, int B); public class Test { public async Task M() { const int iConst = 0; var l = new List(); var ts = new (int, int)[0]; var a = new int[0]; var r = new R(1, 1); {{statement}} } } """, allowSemanticModel); #endif [TestMethod] [DataRow("[Obsolete([u:sConst])]")] [DataRow("[AttributeUsage(AttributeTargets.All, AllowMultiple = [u:bConst])]")] [DataRow("[AttributeUsage([t:AttributeTargets].All, AllowMultiple = [u:bConst])]", true)] public void IdentifierToken_SingleExpressionIdentifier_Attribute(string attribute, bool allowSemanticModel = false) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; {{attribute}} public class TestAttribute: Attribute { const string sConst ="Test"; const bool bConst = true; } """, allowSemanticModel); [TestMethod] [DataRow("[u:System]", true)] [DataRow("[t:Exception]", true)] [DataRow("[t:List]<[t:Int32]>", true)] // TODO false, generic names are always types in nameof [DataRow("[t:HashSet]<[t:Int32]>.Enumerator", true)] [DataRow("HashSet.[t:Enumerator]", true)] [DataRow("[u:System].[u:Linq].[t:Enumerable].[u:Where]", true)] public void IdentifierToken_SimpleMemberAccess_NameOf(string memberaccess, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { public void M() { _ = nameof({{memberaccess}}); } } """, allowSemanticModel); [TestMethod] [DataRow("{ [u:InnerException].[u:InnerException]: {} }", true)] // TODO false, in SubpatternSyntax.ExpressionColon context. Must be properties [DataRow("{ [u:InnerException].[u:InnerException].[u:Data]: {} }", true)] // TODO false, in SubpatternSyntax.ExpressionColon context. Must be properties public void IdentifierToken_SimpleMemberAccess_ExpressionColon(string pattern, bool allowSemanticModel) => // found in SubpatternSyntax.ExpressionColon ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { public void M(Exception ex) { _ = ex is {{pattern}}; } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", true)] [DataRow("[u:i]", true)] [DataRow("[t:Int32].[u:MaxValue]", true)] [DataRow("[u:System].[t:Int32]", true)] [DataRow("[t:T]", true)] [DataRow("[t:HashSet].[t:Enumerator]", true)] [DataRow("not [t:Int32]", true)] [DataRow("not [u:i]", true)] [DataRow("not [t:Int32].[u:MaxValue]", true)] [DataRow("not [u:System].[t:Int32]", true)] public void IdentifierToken_SimpleMemberAccess_Is(string pattern, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { public void M(object o) { const int i = 42; _ = o is {{pattern}}; } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", true)] [DataRow("[u:i]", true)] [DataRow("[t:Int32].[u:MaxValue]", true)] [DataRow("[u:System].[t:Int32].[u:MaxValue]", true)] [DataRow("not [t:Int32]", true)] [DataRow("not [u:i]", true)] [DataRow("not [t:Int32].[u:MaxValue]", true)] [DataRow("not [u:System].[t:Int32]", true)] public void IdentifierToken_SimpleMemberAccess_SwitchArm(string pattern, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { public void M(object o) { const int i = 42; switch(o) { case {{pattern}}: break; } } } """, allowSemanticModel); [TestMethod] [DataRow("[t:Int32]", true)] [DataRow("[u:i]", true)] [DataRow("[t:Int32].[u:MaxValue]", true)] [DataRow("[u:System].[t:Int32].[u:MaxValue]", true)] [DataRow("not [t:Int32]", true)] [DataRow("not [u:i]", true)] [DataRow("not [t:Int32].[u:MaxValue]", true)] [DataRow("not [u:System].[t:Int32]", true)] public void IdentifierToken_SimpleMemberAccess_SwitchExpression(string pattern, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; using System.Collections.Generic; public class C { public void M(object o) { const int i = 42; _ = o switch { {{pattern}} => true, }; } } """, allowSemanticModel); [TestMethod] [DataRow("[k:scoped] ref S s2 = ref s1;", false)] [DataRow("scoped ref [t:S] s2 = ref s1;", false)] [DataRow("ref [t:S] s2 = ref s1;", false)] [DataRow("scoped [t:S] s2 = s1;", false)] [DataRow("[k:scoped] [k:ref] [k:readonly] [t:S] [u:s2] = [k:ref] [u:s1];", false)] [DataRow("[k:int] [u:scoped] = 1;", false)] public void IdentifierToken_Scoped_Local(string localDeclaration, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public ref struct S { } public class C { public void M(ref S s1) { {{localDeclaration}} } } """, allowSemanticModel); [TestMethod] [DataRow("[k:scoped] ref S s", false)] [DataRow("scoped ref [t:S] s", false)] [DataRow("ref [t:S] s", false)] [DataRow("scoped [t:S] s", false)] [DataRow("[k:scoped] [k:ref] [t:S] [u:s]", false)] [DataRow("[k:int] [u:scoped]", false)] public void IdentifierToken_Scoped_Parameter(string parameterDeclaration, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public ref struct S { } public class C { public void M({{parameterDeclaration}}) { } } """, allowSemanticModel); [TestMethod] [DataRow("var d = [u:dateTimePointer]->Date;", false)] [DataRow("var d = dateTimePointer->[u:Date];", false)] [DataRow("var d = (*[u:dateTimePointer]).Date;", false)] [DataRow("var d = (*dateTimePointer).[u:Date];", false)] [DataRow("var d = [u:dateTimePointer][0];", false)] [DataRow("[u:dateTimePointer][0] = *(&[u:dateTimePointer][0]);", false)] [DataRow("[t:Int32]* iPointer;", false)] [DataRow("[t:Int32]?* iPointer;", false)] [DataRow("[t:Int32]?** iPointerPointer;", false)] [DataRow("[t:Nullable]<[t:Int32]>** iPointerPointer;", false)] [DataRow("[u:System].Int32* iPointer;", true)] [DataRow("System.[t:Int32]* iPointer;", false)] [DataRow("[k:void]* voidPointer;", false)] [DataRow("DateTime d = default; M(&[u:d]);", false)] [DataRow("[t:DateTime]** dt = &[u:dateTimePointer];", false)] [DataRow("_ = (*(&[u:dateTimePointer]))->Date;", false)] [DataRow("_ = (**(&[u:dateTimePointer])).Date;", false)] public void IdentifierToken_Unsafe_Pointers(string statement, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class C { public unsafe void M(DateTime* dateTimePointer) { {{statement}} } } """, allowSemanticModel); [TestMethod] [DataRow("[k:int] @int;", false)] [DataRow("[k:volatile] [t:@volatile] [u:@volatile];", false)] [DataRow("[t:Int32] [u:@someName];", false)] public void IdentifierToken_KeywordEscaping(string fieldDeclaration, bool allowSemanticModel) => ClassifierTestHarness.AssertTokenTypes($$""" using System; public class @volatile { } public class C { {{fieldDeclaration}} } """, allowSemanticModel); [TestMethod] public void CSharp12Syntax_Classification() => ClassifierTestHarness.AssertTokenTypes(""" using System; class PrimaryConstructor([t:Int32] [u:i] = [n:1]) { public PrimaryConstructor(int a1, int a2) : [k:this]([u:a1]) { var f = ([t:Int32] [u:i] = [n:1]) => i; } } """); #if NET [TestMethod] public void KeywordToken_AllowsAntiConstraintAndParameterModifiers() => ClassifierTestHarness.AssertTokenTypes(""" class Allows where T: [k:allows] [k:ref] [k:struct] { public void M1([k:scoped] [t:T] [u:t]) { } public void M1([k:ref] [k:readonly] T t) { } public void M2([k:in] T t) { } } """); #endif } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/TokenTypeAnalyzerTest.ClassifierTestHarness.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Text; using System.Text.RegularExpressions; using Microsoft.CodeAnalysis.Text; using SonarAnalyzer.Protobuf; using static SonarAnalyzer.CSharp.Rules.TokenTypeAnalyzer; using Match = System.Text.RegularExpressions.Match; namespace SonarAnalyzer.Test.Rules; public partial class TokenTypeAnalyzerTest { private static class ClassifierTestHarness { private const int TokenAnnotationChars = 4; // [u:] private const int PrefixTokenAnnotationChars = 3; // [u: private static readonly Regex TokenTypeRegEx = new(TokenGroups( TokenGroup(TokenType.Keyword, "k"), TokenGroup(TokenType.NumericLiteral, "n"), TokenGroup(TokenType.StringLiteral, "s"), TokenGroup(TokenType.TypeName, "t"), TokenGroup(TokenType.Comment, "c"), TokenGroup(TokenType.UnknownTokentype, "u"))); public static void AssertTokenTypes(string code, bool allowSemanticModel = true, bool ignoreCompilationErrors = false) { var (tree, model, expectedTokens) = ParseTokens(code, ignoreCompilationErrors); model = allowSemanticModel ? model : null; // The TokenClassifier will throw if the semantic model is used. var tokenClassifier = new TokenClassifier(model, false); var triviaClassifier = new TriviaClassifier(); expectedTokens.Should().SatisfyRespectively(expectedTokens.Select( (Func>)(_ => token => CheckClassifiedToken(tokenClassifier, triviaClassifier, tree, token)))); } private static void CheckClassifiedToken(TokenClassifier tokenClassifier, TriviaClassifier triviaClassifier, SyntaxTree tree, ExpectedToken expected) { var expectedLineSpan = tree.GetLocation(expected.Position).GetLineSpan(); var because = $$"""token with text "{{expected.TokenText}}" at position {{expectedLineSpan}} was marked as {{expected.TokenType}}"""; var (actualLocation, classification) = FindActual(tokenClassifier, triviaClassifier, tree, expected, because); if (classification == null) { because = $$"""classification for token with text "{{expected.TokenText}}" at position {{expectedLineSpan}} is null"""; expected.TokenType.Should().Be(TokenType.UnknownTokentype, because); actualLocation.SourceSpan.Should().Be(expected.Position, because); } else { classification.Should().Be(new TokenTypeInfo.Types.TokenInfo { TokenType = expected.TokenType, TextRange = new TextRange { StartLine = expectedLineSpan.StartLinePosition.Line + 1, StartOffset = expectedLineSpan.StartLinePosition.Character, EndLine = expectedLineSpan.EndLinePosition.Line + 1, EndOffset = expectedLineSpan.EndLinePosition.Character, }, }, because); } } private static (Location Location, TokenTypeInfo.Types.TokenInfo TokenInfo) FindActual(TokenClassifier tokenClassifier, TriviaClassifier triviaClassifier, SyntaxTree tree, ExpectedToken expected, string because) { if (expected.TokenType == TokenType.Comment) { var trivia = tree.GetRoot().FindTrivia(expected.Position.Start); return (tree.GetLocation(trivia.FullSpan), triviaClassifier.ClassifyTrivia(trivia)); } else { var token = tree.GetRoot().FindToken(expected.Position.Start); var f = () => tokenClassifier.ClassifyToken(token); var tokenInfo = f.Should().NotThrow($"semanticModel should not be queried for {because}").Which; return (token.GetLocation(), tokenInfo); } } private static (SyntaxTree Tree, SemanticModel Model, IReadOnlyCollection ExpectedTokens) ParseTokens(string code, bool ignoreCompilationErrors = false) { var matches = TokenTypeRegEx.Matches(code); var sb = new StringBuilder(code.Length); var expectedTokens = new List(matches.Count); var lastMatchEnd = 0; var match = 0; foreach (var group in matches.Cast().Select(m => m.Groups.Cast().First(g => g.Success && g.Name != "0"))) { var expectedTokenType = (TokenType)Enum.Parse(typeof(TokenType), group.Name); var position = group.Index - (match * TokenAnnotationChars); var length = group.Length - TokenAnnotationChars; var tokenText = group.Value.Substring(PrefixTokenAnnotationChars, group.Value.Length - TokenAnnotationChars); expectedTokens.Add(new ExpectedToken(expectedTokenType, tokenText, new TextSpan(position, length))); sb.Append(code.Substring(lastMatchEnd, group.Index - lastMatchEnd)); sb.Append(tokenText); lastMatchEnd = group.Index + group.Length; match++; } sb.Append(code.Substring(lastMatchEnd)); var (tree, model) = ignoreCompilationErrors ? TestCompiler.CompileIgnoreErrorsCS(sb.ToString()) : TestCompiler.CompileCS(sb.ToString()); return (tree, model, expectedTokens); } private static string TokenGroups(params string[] groups) => string.Join("|", groups); private static string TokenGroup(TokenType tokenType, string shortName) => $$"""(?'{{tokenType}}'\[{{shortName}}\:[^\]]+\])"""; private readonly record struct ExpectedToken(TokenType TokenType, string TokenText, TextSpan Position); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/TokenTypeAnalyzerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.IO; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; using SonarAnalyzer.Protobuf; using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public partial class TokenTypeAnalyzerTest { private const string BasePath = @"Utilities\TokenTypeAnalyzer\"; public TestContext TestContext { get; set; } [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_MainTokens_CS(ProjectType projectType) => Verify("Tokens.cs", projectType, x => { x.Should().HaveCount(16); x.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(10); x.Where(x => x.TokenType == TokenType.StringLiteral).Should().HaveCount(4); x.Should().ContainSingle(x => x.TokenType == TokenType.TypeName); x.Should().ContainSingle(x => x.TokenType == TokenType.NumericLiteral); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_MainTokens_CS_Latest(ProjectType projectType) => Verify("Tokens.Latest.cs", projectType, x => { x.Should().HaveCount(94); x.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(58); x.Where(x => x.TokenType == TokenType.StringLiteral).Should().HaveCount(20); x.Where(x => x.TokenType == TokenType.NumericLiteral).Should().HaveCount(5); x.Where(x => x.TokenType == TokenType.TypeName).Should().HaveCount(10); x.Should().ContainSingle(x => x.TokenType == TokenType.Comment); }); [TestMethod] [DataRow("Razor.razor")] [DataRow("Razor.cshtml")] public void Verify_NoMetricsAreComputedForRazorFiles(string fileName) => CreateBuilder(ProjectType.Product, fileName) .VerifyUtilityAnalyzer(x => x.Select(x => Path.GetFileName(x.FilePath)).Should().BeEmpty()); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_MainTokens_VB(ProjectType projectType) => Verify("Tokens.vb", projectType, x => { x.Should().HaveCount(19); x.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(15); x.Where(x => x.TokenType == TokenType.StringLiteral).Should().HaveCount(2); x.Should().ContainSingle(x => x.TokenType == TokenType.TypeName); x.Should().ContainSingle(x => x.TokenType == TokenType.NumericLiteral); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Identifiers_CS(ProjectType projectType) => Verify("Identifiers.cs", projectType, x => { x.Should().HaveCount(40); x.Count(x => x.TokenType == TokenType.Keyword).Should().Be(29); x.Count(x => x.TokenType == TokenType.TypeName).Should().Be(8); x.Count(x => x.TokenType == TokenType.NumericLiteral).Should().Be(1); x.Count(x => x.TokenType == TokenType.StringLiteral).Should().Be(2); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Identifiers_VB(ProjectType projectType) => Verify("Identifiers.vb", projectType, x => { x.Should().HaveCount(63); x.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(56); x.Where(x => x.TokenType == TokenType.TypeName).Should().HaveCount(6); x.Should().ContainSingle(x => x.TokenType == TokenType.NumericLiteral); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Trivia_CS(ProjectType projectType) => Verify("Trivia.cs", projectType, x => { x.Should().HaveCount(5); x.Where(x => x.TokenType == TokenType.Comment).Should().HaveCount(4); x.Should().ContainSingle(x => x.TokenType == TokenType.Keyword); }); [TestMethod] [DataRow(ProjectType.Product)] [DataRow(ProjectType.Test)] public void Verify_Trivia_VB(ProjectType projectType) => Verify("Trivia.vb", projectType, x => { x.Should().HaveCount(6); x.Should().ContainSingle(x => x.TokenType == TokenType.Comment); x.Where(x => x.TokenType == TokenType.Keyword).Should().HaveCount(4); }); [TestMethod] public void Verify_IdentifierTokenThreshold() => Verify("IdentifierTokenThreshold.cs", ProjectType.Product, x => // IdentifierTokenThreshold.cs has 4001 identifiers which exceeds current threshold of 4000. Due to this, the identifiers are not classified x.Should().NotContain(token => token.TokenType == TokenType.TypeName)); [TestMethod] [DataRow("Tokens.cs", true)] [DataRow("SomethingElse.cs", false)] public void Verify_UnchangedFiles(string unchangedFileName, bool expectedProtobufIsEmpty) { var builder = CreateBuilder(ProjectType.Product, "Tokens.cs") .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfigWithUnchangedFiles(TestContext, BasePath + unchangedFileName)); if (expectedProtobufIsEmpty) { builder.VerifyUtilityAnalyzerProducesEmptyProtobuf(); } else { builder.VerifyUtilityAnalyzer(x => x.Should().NotBeEmpty()); } } private void Verify(string fileName, ProjectType projectType, Action> verifyTokenInfo) => CreateBuilder(projectType, fileName) .WithAdditionalFilePath(AnalysisScaffolding.CreateSonarProjectConfig(TestContext, projectType)) .VerifyUtilityAnalyzer(x => { x.Should().ContainSingle(); var info = x.Single(); info.FilePath.Should().Be(Path.Combine(BasePath, fileName)); verifyTokenInfo(info.TokenInfo); }); private VerifierBuilder CreateBuilder(ProjectType projectType, string fileName) { var testRoot = BasePath + TestContext.TestName; var language = AnalyzerLanguage.FromPath(fileName); UtilityAnalyzerBase analyzer = language.LanguageName switch { LanguageNames.CSharp => new TestTokenTypeAnalyzer_CS(testRoot, projectType == ProjectType.Test), LanguageNames.VisualBasic => new TestTokenTypeAnalyzer_VB(testRoot, projectType == ProjectType.Test), _ => throw new UnexpectedLanguageException(language) }; return new VerifierBuilder() .AddAnalyzer(() => analyzer) .AddPaths(fileName) .WithBasePath(BasePath) .WithOptions(LanguageOptions.Latest(language)) .WithProtobufPath(@$"{testRoot}\token-type.pb"); } // We need to set protected properties and this class exists just to enable the analyzer without bothering with additional files with parameters private sealed class TestTokenTypeAnalyzer_CS : CS.TokenTypeAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestTokenTypeAnalyzer_CS(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } private sealed class TestTokenTypeAnalyzer_VB : VB.TokenTypeAnalyzer { private readonly string outPath; private readonly bool isTestProject; public TestTokenTypeAnalyzer_VB(string outPath, bool isTestProject) { this.outPath = outPath; this.isTestProject = isTestProject; } protected override UtilityAnalyzerParameters ReadParameters(IAnalysisContext context) => base.ReadParameters(context) with { IsAnalyzerEnabled = true, OutPath = outPath, IsTestProject = isTestProject }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/Utilities/UtilityAnalyzerBaseTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.VisualBasic; using SonarAnalyzer.Core.AnalysisContext; using SonarAnalyzer.Core.Rules; namespace SonarAnalyzer.Test.Rules.Utilities; [TestClass] public class UtilityAnalyzerBaseTest { private const string DefaultSonarProjectConfig = @"TestResources\SonarProjectConfig\Path_Windows\SonarProjectConfig.xml"; private const string DefaultProjectOutFolderPath = @"TestResources\ProjectOutFolderPath.txt"; public TestContext TestContext { get; set; } [TestMethod] [DataRow(LanguageNames.CSharp, DefaultProjectOutFolderPath, @"path\output-cs")] [DataRow(LanguageNames.VisualBasic, DefaultProjectOutFolderPath, @"path\output-vbnet")] [DataRow(LanguageNames.CSharp, DefaultSonarProjectConfig, @"C:\foo\bar\.sonarqube\out\0\output-cs")] [DataRow(LanguageNames.VisualBasic, DefaultSonarProjectConfig, @"C:\foo\bar\.sonarqube\out\0\output-vbnet")] public void ReadConfig_OutPath(string language, string additionalPath, string expectedOutPath) { // We do not test what is read from the SonarLint file, but we need it var utilityAnalyzer = new TestUtilityAnalyzer(language, @"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml", additionalPath); utilityAnalyzer.Parameters.OutPath.Should().Be(expectedOutPath); utilityAnalyzer.Parameters.IsAnalyzerEnabled.Should().BeTrue(); } [TestMethod] [DataRow(DefaultProjectOutFolderPath, DefaultSonarProjectConfig)] [DataRow(DefaultSonarProjectConfig, DefaultProjectOutFolderPath)] public void ReadConfig_OutPath_FromSonarProjectConfig_HasPriority(string firstFile, string secondFile) { // We do not test what is read from the SonarLint file, but we need it var utilityAnalyzer = new TestUtilityAnalyzer(LanguageNames.CSharp, @"TestResources\SonarLintXml\All_properties_cs\SonarLint.xml", firstFile, secondFile); utilityAnalyzer.Parameters.OutPath.Should().Be(@"C:\foo\bar\.sonarqube\out\0\output-cs"); utilityAnalyzer.Parameters.IsAnalyzerEnabled.Should().BeTrue(); } [TestMethod] [DataRow(LanguageNames.CSharp, true)] [DataRow(LanguageNames.CSharp, false)] [DataRow(LanguageNames.VisualBasic, true)] [DataRow(LanguageNames.VisualBasic, false)] public void ReadsSettings_AnalyzeGenerated(string language, bool analyzeGenerated) { var sonarLintXmlPath = AnalysisScaffolding.CreateSonarLintXml(TestContext, language: language, analyzeGeneratedCode: analyzeGenerated); var utilityAnalyzer = new TestUtilityAnalyzer(language, sonarLintXmlPath, DefaultSonarProjectConfig); utilityAnalyzer.Parameters.AnalyzeGeneratedCode.Should().Be(analyzeGenerated); utilityAnalyzer.Parameters.IsAnalyzerEnabled.Should().BeTrue(); } [TestMethod] [DataRow(LanguageNames.CSharp, true)] [DataRow(LanguageNames.CSharp, false)] [DataRow(LanguageNames.VisualBasic, true)] [DataRow(LanguageNames.VisualBasic, false)] public void ReadsSettings_IgnoreHeaderComments(string language, bool ignoreHeaderComments) { var sonarLintXmlPath = AnalysisScaffolding.CreateSonarLintXml(TestContext, language: language, ignoreHeaderComments: ignoreHeaderComments); var utilityAnalyzer = new TestUtilityAnalyzer(language, sonarLintXmlPath, DefaultSonarProjectConfig); utilityAnalyzer.Parameters.IgnoreHeaderComments.Should().Be(ignoreHeaderComments); utilityAnalyzer.Parameters.IsAnalyzerEnabled.Should().BeTrue(); } [TestMethod] public void NoSonarLintXml_AnalyzerNotEnabled() { new TestUtilityAnalyzer(LanguageNames.CSharp, DefaultProjectOutFolderPath).Parameters.IsAnalyzerEnabled.Should().BeFalse(); new TestUtilityAnalyzer(LanguageNames.CSharp, DefaultSonarProjectConfig).Parameters.IsAnalyzerEnabled.Should().BeFalse(); } [TestMethod] public void NoOutputPath_AnalyzerNotEnabled() => new TestUtilityAnalyzer(LanguageNames.CSharp, AnalysisScaffolding.CreateSonarLintXml(TestContext, analyzeGeneratedCode: true)).Parameters.IsAnalyzerEnabled.Should().BeFalse(); [TestMethod] public void GetTextRange() { var fileLinePositionSpan = new FileLinePositionSpan( "path", new LinePosition(55, 42), new LinePosition(99, 9313)); var result = UtilityAnalyzerBase.ToTextRange(fileLinePositionSpan); result.Should().NotBeNull(); // line number in SQ starts from 1, Roslyn starts from 0 result.StartLine.Should().Be(55 + 1); result.StartOffset.Should().Be(42); result.EndLine.Should().Be(99 + 1); result.EndOffset.Should().Be(9313); } [DiagnosticAnalyzer(LanguageNames.CSharp)] private class TestUtilityAnalyzer : UtilityAnalyzerBase { public override ImmutableArray SupportedDiagnostics => throw new NotSupportedException(); public UtilityAnalyzerParameters Parameters { get; } public TestUtilityAnalyzer(string language, params string[] additionalPaths) : base("S9999-test", "Title") { var additionalFiles = additionalPaths.Select(x => new AnalyzerAdditionalFile(x)).ToImmutableArray(); Compilation compilation = language switch { LanguageNames.CSharp => CSharpCompilation.Create(null), LanguageNames.VisualBasic => VisualBasicCompilation.Create(null), _ => throw new InvalidOperationException($"Unexpected {nameof(language)}: {language}") }; var context = new CompilationAnalysisContext(compilation, new AnalyzerOptions(additionalFiles), null, null, default); Parameters = ReadParameters(new SonarCompilationReportingContext(AnalysisScaffolding.CreateSonarAnalysisContext(), context)); } protected override void Initialize(SonarAnalysisContext context) => throw new NotSupportedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ValueTypeShouldImplementIEquatableTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ValueTypeShouldImplementIEquatableTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void ValueTypeShouldImplementIEquatable_CS() => builderCS.AddPaths("ValueTypeShouldImplementIEquatable.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void ValueTypeShouldImplementIEquatable_CSharp10() => builderCS.AddPaths("ValueTypeShouldImplementIEquatable.CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).VerifyNoIssues(); [TestMethod] public void ValueTypeShouldImplementIEquatable_VB() => builderVB.AddPaths("ValueTypeShouldImplementIEquatable.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/ValuesUselesslyIncrementedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class ValuesUselesslyIncrementedTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void ValuesUselesslyIncremented() => builder.AddPaths("ValuesUselesslyIncremented.cs").Verify(); [TestMethod] public void ValuesUselesslyIncremented_TopLevelStatements() => builder.AddPaths("ValuesUselesslyIncremented.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void ValuesUselesslyIncremented_CS_Latest() => builder.AddPaths("ValuesUselesslyIncremented.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/VariableShadowsFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class VariableShadowsFieldTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void VariableShadowsField() => builder.AddPaths("VariableShadowsField.cs").Verify(); [TestMethod] public void VariableShadowsField_TopLevelStatements() => builder.AddPaths("VariableShadowsField.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void VariableShadowsField_CS_Latest() => builder.AddPaths("VariableShadowsField.Latest.cs", "VariableShadowsField.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/VariableUnusedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class VariableUnusedTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); [TestMethod] public void VariableUnused_CS() => builderCS.AddPaths("VariableUnused.cs").Verify(); [TestMethod] public void VariableUnused_CS_TopLevelStatements() => builderCS.AddPaths("VariableUnused.TopLevelStatements.cs").WithTopLevelStatements().Verify(); [TestMethod] public void VariableUnused_CS_Latest() => builderCS.AddPaths("VariableUnused.Latest.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void VariableUnused_VB() => new VerifierBuilder().AddPaths("VariableUnused.vb").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/VirtualEventFieldTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class VirtualEventFieldTest { private readonly VerifierBuilder builder = new VerifierBuilder(); [TestMethod] public void VirtualEventField() => builder.AddPaths("VirtualEventField.cs").Verify(); [TestMethod] public void VirtualEventField_Latest() => builder.AddPaths("VirtualEventField.Latest.cs", "VirtualEventField.Latest.Partial.cs").WithOptions(LanguageOptions.CSharpLatest).Verify(); [TestMethod] public void VirtualEventField_Latest_CodeFix() => builder.WithCodeFix().AddPaths("VirtualEventField.Latest.cs").WithCodeFixedPaths("VirtualEventField.Latest.Fixed.cs").WithOptions(LanguageOptions.CSharpLatest).VerifyCodeFix(); [TestMethod] public void VirtualEventField_CodeFix() => builder.WithCodeFix().AddPaths("VirtualEventField.cs").WithCodeFixedPaths("VirtualEventField.Fixed.cs").VerifyCodeFix(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/WcfMissingContractAttributeTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class WcfMissingContractAttributeTest { [TestMethod] public void WcfMissingContractAttribute() => new VerifierBuilder().AddPaths("WcfMissingContractAttribute.cs") .AddReferences(MetadataReferenceFacade.SystemServiceModel) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/WcfNonVoidOneWayTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class WcfNonVoidOneWayTest { [TestMethod] public void WcfNonVoidOneWay_CS() => new VerifierBuilder().AddPaths("WcfNonVoidOneWay.cs") .AddReferences(MetadataReferenceFacade.SystemServiceModel) .Verify(); [TestMethod] public void WcfNonVoidOneWay_VB() => new VerifierBuilder().AddPaths("WcfNonVoidOneWay.vb") .AddReferences(MetadataReferenceFacade.SystemServiceModel) .Verify(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/WeakSslTlsProtocolsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using CS = SonarAnalyzer.CSharp.Rules; using VB = SonarAnalyzer.VisualBasic.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class WeakSslTlsProtocolsTest { private readonly VerifierBuilder builderCS = WithReferences(new VerifierBuilder()); private readonly VerifierBuilder builderVB = WithReferences(new VerifierBuilder()); [TestMethod] public void WeakSslTlsProtocols_CSharp() => builderCS.AddPaths("WeakSslTlsProtocols.cs").WithOptions(LanguageOptions.FromCSharp8).Verify(); [TestMethod] public void WeakSslTlsProtocols_CSharp12() => builderCS.AddPaths("WeakSslTlsProtocols.CSharp12.cs").WithOptions(LanguageOptions.FromCSharp12).Verify(); [TestMethod] public void WeakSslTlsProtocols_VB() => builderVB.AddPaths("WeakSslTlsProtocols.vb").Verify(); private static VerifierBuilder WithReferences(VerifierBuilder builder) => builder.AddReferences(MetadataReferenceFacade.SystemNetHttp).AddReferences(MetadataReferenceFacade.SystemSecurityCryptography); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/XMLSignatureCheckTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules; [TestClass] public class XmlSignatureCheckTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemSecurityCryptography) .AddReferences(NuGetMetadataReference.SystemSecurityCryptographyXml()); [TestMethod] public void XmlSignatureCheck_CS() => builder.AddPaths("XMLSignatureCheck.cs").Verify(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Rules/XmlExternalEntityShouldNotBeParsedTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Rules; namespace SonarAnalyzer.Test.Rules { [TestClass] public class XmlExternalEntityShouldNotBeParsedTest { private readonly VerifierBuilder builder = new VerifierBuilder() .AddReferences(MetadataReferenceFacade.SystemXml) .AddReferences(MetadataReferenceFacade.SystemData) .AddReferences(MetadataReferenceFacade.SystemXmlLinq) .AddReferences(NuGetMetadataReference.MicrosoftWebXdt()); [DataRow(NetFrameworkVersion.After452, "XmlExternalEntityShouldNotBeParsed_XmlDocument.cs")] [DataRow(NetFrameworkVersion.Probably35, "XmlExternalEntityShouldNotBeParsed_XmlDocument_Net35.cs")] [DataRow(NetFrameworkVersion.Between4And451, "XmlExternalEntityShouldNotBeParsed_XmlDocument_Net4.cs")] [DataRow(NetFrameworkVersion.Unknown, "XmlExternalEntityShouldNotBeParsed_XmlDocument_UnknownFrameworkVersion.cs")] [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlDocument(NetFrameworkVersion version, string testFilePath) => WithAnalyzer(version).AddPaths(testFilePath).Verify(); #if NET [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp9() => WithAnalyzer(NetFrameworkVersion.After452).AddPaths("XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp10() => WithAnalyzer(NetFrameworkVersion.After452) .AddPaths("XmlExternalEntityShouldNotBeParsed_XmlDocument_CSharp10.cs") .WithOptions(LanguageOptions.FromCSharp10) .WithTopLevelStatements() .Verify(); #endif [DataRow(NetFrameworkVersion.After452, "XmlExternalEntityShouldNotBeParsed_XmlTextReader.cs")] [DataRow(NetFrameworkVersion.Probably35, "XmlExternalEntityShouldNotBeParsed_XmlTextReader_Net35.cs")] [DataRow(NetFrameworkVersion.Between4And451, "XmlExternalEntityShouldNotBeParsed_XmlTextReader_Net4.cs")] [DataRow(NetFrameworkVersion.Unknown, "XmlExternalEntityShouldNotBeParsed_XmlTextReader_UnknownFrameworkVersion.cs")] [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlTextReader(NetFrameworkVersion version, string testFilePath) => WithAnalyzer(version).AddPaths(testFilePath).WithOptions(LanguageOptions.FromCSharp8).Verify(); #if NET [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp9() => WithAnalyzer(NetFrameworkVersion.After452).AddPaths("XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp10() => WithAnalyzer(NetFrameworkVersion.After452).AddPaths("XmlExternalEntityShouldNotBeParsed_XmlTextReader_CSharp10.cs").WithOptions(LanguageOptions.FromCSharp10).Verify(); #endif [DataRow(NetFrameworkVersion.After452, "XmlExternalEntityShouldNotBeParsed_AlwaysSafe.cs")] [DataRow(NetFrameworkVersion.Unknown, "XmlExternalEntityShouldNotBeParsed_AlwaysSafe.cs")] [TestMethod] public void XmlExternalEntityShouldNotBeParsed_AlwaysSafe(NetFrameworkVersion version, string testFilePath) => WithAnalyzer(version).AddPaths(testFilePath).Verify(); [DataRow(NetFrameworkVersion.Probably35, "XmlExternalEntityShouldNotBeParsed_XmlReader_Net35.cs")] [DataRow(NetFrameworkVersion.Between4And451, "XmlExternalEntityShouldNotBeParsed_XmlReader_Net4.cs")] [DataRow(NetFrameworkVersion.After452, "XmlExternalEntityShouldNotBeParsed_XmlReader_Net452.cs")] [DataRow(NetFrameworkVersion.Unknown, "XmlExternalEntityShouldNotBeParsed_XmlReader_Net452.cs")] [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlReader(NetFrameworkVersion version, string testFilePath) => WithAnalyzer(version).AddPaths(testFilePath).Verify(); #if NET [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XmlReader_CSharp9() => WithAnalyzer(NetFrameworkVersion.After452).AddPaths("XmlExternalEntityShouldNotBeParsed_XmlReader_CSharp9.cs").WithTopLevelStatements().Verify(); [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XPathDocument_CSharp9() => WithAnalyzer(NetFrameworkVersion.After452).AddPaths("XmlExternalEntityShouldNotBeParsed_XPathDocument_CSharp9.cs").WithTopLevelStatements().Verify(); #endif [DataRow(NetFrameworkVersion.Probably35, "XmlExternalEntityShouldNotBeParsed_XPathDocument_Net35.cs")] [DataRow(NetFrameworkVersion.Between4And451, "XmlExternalEntityShouldNotBeParsed_XPathDocument_Net4.cs")] [DataRow(NetFrameworkVersion.After452, "XmlExternalEntityShouldNotBeParsed_XPathDocument_Net452.cs")] [DataRow(NetFrameworkVersion.Unknown, "XmlExternalEntityShouldNotBeParsed_XPathDocument_Net452.cs")] [TestMethod] public void XmlExternalEntityShouldNotBeParsed_XPathDocument(NetFrameworkVersion version, string testFilePath) => WithAnalyzer(version).AddPaths(testFilePath).Verify(); [TestMethod] public void XmlExternalEntityShouldNotBeParsed_NoCrashOnExternalParameterUse() => WithAnalyzer(NetFrameworkVersion.After452) .AddPaths("XmlExternalEntityShouldNotBeParsed_XmlReader_ExternalParameter.cs", "XmlExternalEntityShouldNotBeParsed_XmlReader_ParameterProvider.cs") .Verify(); private VerifierBuilder WithAnalyzer(NetFrameworkVersion version) { var fxVersion = Substitute.For(); fxVersion.Version(Arg.Any()).Returns(version); return builder.AddAnalyzer(() => new XmlExternalEntityShouldNotBeParsed(fxVersion)); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/SonarAnalyzer.Test.csproj ================================================  net48;net10.0 false true true PreserveNewest PreserveNewest global,common global,csharp global,vbnet ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Extensions/IfStatementSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CSharp.Syntax.Extensions; namespace SonarAnalyzer.Test.Syntax.Extensions; [TestClass] public class IfStatementSyntaxExtensions { private const string Source = """ class TestClass { public void DoSomething(){} public void IfMethod() { if (true) DoSomething(); else if (true) DoSomething(); else { if(true) DoSomething(); else DoSomething(); } } public void NonChainedIfMethod() { if (true) DoSomething(); else { DoSomething(); if(true) DoSomething(); } } public void NonChainedIfDifferentIfFirstStatement() { if (true) DoSomething(); else { if (false) DoSomething(); DoSomething(); if(true) DoSomething(); } } } """; private MethodDeclarationSyntax ifMethod; [TestInitialize] public void TestSetup() => ifMethod = CSharpSyntaxTree.ParseText(Source).GetRoot().DescendantNodes().OfType().First(x => x.Identifier.ValueText == "IfMethod"); [TestMethod] public void GetPrecedingIfsInConditionChain() { var ifStatements = ifMethod.DescendantNodes().OfType().ToList(); var precedingIfStatements = new List(); foreach (var ifStatement in ifStatements) { var sut = ifStatement.PrecedingIfsInConditionChain(); sut.Should().BeEquivalentTo(precedingIfStatements); precedingIfStatements.Add(ifStatement); } } [TestMethod] public void GetPrecedingStatementsInConditionChain() { var ifStatements = ifMethod.DescendantNodes().OfType().ToList(); var precedingStatements = new List(); foreach (var ifStatement in ifStatements) { var sut = ifStatement.PrecedingStatementsInConditionChain(); sut.Should().BeEquivalentTo(precedingStatements); precedingStatements.Add(ifStatement.Statement); } } [TestMethod] public void GetPrecedingConditionsInConditionChain() { var ifStatements = ifMethod.DescendantNodes().OfType().ToList(); var precedingConditions = new List(); foreach (var ifStatement in ifStatements) { var sut = ifStatement.PrecedingConditionsInConditionChain(); sut.Should().BeEquivalentTo(precedingConditions); precedingConditions.Add(ifStatement.Condition); } } [TestMethod] public void GetPrecedingSections_Empty() { var sections = ifMethod.DescendantNodes().OfType().ToList(); sections.FirstOrDefault().PrecedingSections().Should().BeEmpty(); } [TestMethod] public void GetPrecedingIfsNonChainedIsEmpty() { var nonChained = CSharpSyntaxTree.ParseText(Source).GetRoot().DescendantNodes().OfType().First(x => x.Identifier.ValueText == "NonChainedIfMethod"); var ifStatements = nonChained.DescendantNodes().OfType().ToList(); foreach (var ifStatement in ifStatements) { var sut = ifStatement.PrecedingIfsInConditionChain(); sut.Should().BeEmpty(); } } [TestMethod] public void GetPrecedingIfsNonChainedDifferentIfIsEmpty() { var nonChained = CSharpSyntaxTree.ParseText(Source).GetRoot().DescendantNodes().OfType().First(x => x.Identifier.ValueText == "NonChainedIfDifferentIfFirstStatement"); var lastIf = nonChained.DescendantNodes().OfType().Last(); var sut = lastIf.PrecedingIfsInConditionChain(); sut.Should().BeEmpty(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Extensions/SwitchSectionSyntaxExtensionsTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CSharp.Syntax.Extensions; namespace SonarAnalyzer.Test.Syntax.Extensions; [TestClass] public class SwitchSectionSyntaxExtensionsTest { private const string Source = """ class TestClass { public void DoSomething(){} public void SwitchMethod() { var i = 5; switch(i) { case 3: DoSomething(); break; case 5: DoSomething(); break; default: DoSomething(); break; } } } """; private MethodDeclarationSyntax switchMethod; [TestInitialize] public void TestSetup() => switchMethod = CSharpSyntaxTree.ParseText(Source).GetRoot().DescendantNodes().OfType().First(x => x.Identifier.ValueText == "SwitchMethod"); [TestMethod] public void GetPrecedingSections() { var sections = switchMethod.DescendantNodes().OfType().ToList(); sections.Last().PrecedingSections().Should().HaveCount(2); sections.First().PrecedingSections().Should().BeEmpty(); sections.Last().PrecedingSections().First().Should().BeEquivalentTo(sections.First()); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Extensions/SyntaxNodeExtensionsTest.CSharp.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CSharp.Core.Syntax.Extensions; using SonarAnalyzer.CSharp.Syntax.Extensions; namespace SonarAnalyzer.Test.Syntax.Extensions; [TestClass] public class SyntaxNodeExtensionsTest { [TestMethod] public void NoDirectives() { var source = """ class Sample { void Main(){} } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEmpty(); } [TestMethod] public void ActiveBlocks_NonNestedIfs() { var source = """ #define BLOCK1 #define BLOCK2 #define BLOCK3 namespace Test { #if BLOCK1 #endif #if BLOCK2 #endif #if true // literal block #endif #if BLOCK3 class Sample { void Main() { } } #endif #if BLOCK1 #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().ContainSingle(); activeSections.Should().BeEquivalentTo("BLOCK3"); } [TestMethod] public void ActiveBlocks_NestedIfs() { // Arrange var source = """ #define BLOCK1 #define BLOCK2 #define BLOCK3 #define BLOCK4 namespace Test { #if BLOCK1 #if BLOCK2 #if BLOCK3 #if BLOCK4 // opened and closed, so should not appear #endif class Sample { void Main() { } } #endif #endif #endif #if BLOCK1 #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().HaveCount(3); activeSections.Should().BeEquivalentTo("BLOCK1", "BLOCK2", "BLOCK3"); } [TestMethod] public void ActiveBlocks_DirectivesInLeadingTrivia() { var source = """ #define BLOCK1 #define BLOCK2 #define BLOCK3 namespace Test { public class Sample { // trivia #if BLOCK2 // more trivia #if true // literal block // more trivia #endif #if BLOCK3 // more trivia void Main() { } } #endif #endif #if BLOCK1 #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK2", "BLOCK3"); } [TestMethod] public void ActiveBlocks_ElseInPrecedingCode() { var source = """ #define BLOCK2 #if BLOCK1 #else #if BLOCK2 #else #elseif BLOCK3 #endif #endif #if BLOCK2 namespace Test { class Sample { void Main() { } } } #endif """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK2"); } [TestMethod] public void ActiveBlocks_NegativeConditions_InIf() { var source = """ namespace Test { #if !BLOCK1 class Sample { void Main() { } } #else #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEmpty(); } [TestMethod] public void ActiveBlocks_NegativeConditions_InElse() { var source = """ #define BLOCK1 namespace Test { #if !BLOCK1 #else class Sample { void Main() { } } #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEmpty(); } [TestMethod] public void ActiveBlocks_Else_FirstBranchIsActive() { var source = """ #define BLOCK1 namespace Test { #if BLOCK1 class Sample { void Main() { } #else class Sample { void Main() {} #endif """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK1"); } [TestMethod] public void ActiveBlocks_Else_SecondBranchIsActive() { var source = """ #define BLOCK2 namespace Test { #if BLOCK1 class Sample { void Main() { } #else class Sample { void Main() {} #endif """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEmpty(); } [TestMethod] public void ActiveBlocks_Elif_FirstBranchIsActive() { var source = """ #define BLOCK1 #define BLOCK2 namespace Test { #if BLOCK1 class Sample { void Main() { } #elif BLOCK2 class Sample { void Main() {} #endif } } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK1"); } [TestMethod] public void ActiveBlocks_Elif_SecondBranchIsActive() { var source = """ #define BLOCK2 namespace Test { #if BLOCK1 class Sample { void Main() { } #elif BLOCK2 class Sample { void Main() {} #endif } } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK2"); } [TestMethod] public void ActiveBlocks_Elif_FirstBranchIsActive_InLeadingTrivia() { var source = """ #define BLOCK1 #define BLOCK2 namespace Test { class Sample { #if BLOCK1 void Main() { } #elif BLOCK2 void Main() {} #endif } } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK1"); } [TestMethod] public void ActiveBlocks_Elif_SecondBranchIsActive_InLeadingTrivia() { var source = """ #define BLOCK2 namespace Test { class Sample { #if BLOCK1 void Main() { } #elif BLOCK2 void Main() {} #endif } } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK2"); } [TestMethod] public void InactiveDirectives_ShouldBeIgnored() { var source = """ #define BLOCK1 #define BLOCK2 #define BLOCK3 #define BLOCK4 namespace Test { #if INACTIVE1 #if BLOCK1 // inside inactive block -> ignored #endif #endif #if BLOCK3 class Sample { #if BLOCK4 #if INACTIVE2 #if BLOCK2 // inside inactive block -> ignored #endif #endif void Main() { } } #endif #if BLOCK1 #endif } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK3", "BLOCK4"); } [TestMethod] public void BadDirectives_ShouldBeIgnored() { var source = """ #define BLOCK2 #if BLOCK1 #endif #else // bad directive #endif // bad directive #if BLOCK2 #FOO // bad directive namespace Test { class Sample { #BAR // bad directive void Main() { } } } """; var node = GetMainNode(source); var activeSections = node.ActiveConditionalCompilationSections(); activeSections.Should().NotBeNull(); activeSections.Should().BeEquivalentTo("BLOCK2"); } [TestMethod] public void GetName_CS() { const string code = """ using System; using C = System.Collections; namespace MyNamespace.MyNamespaceNested { class Example { delegate void MyDelegate(); enum MyEnum { MyEnumValue }; Example() { } ~Example() { } int MyProp { get; } unsafe ref byte Method(byte[] input) where T: new() { int? i = null; int* iPtr; System.Exception qualified; global::System.Exception globalVar; input.ToString()?.ToString(); Func fun = () => () => {}; fun()(); ref byte result = ref input[0]; return ref result; } public static explicit operator int(Example e) => 0; public static int operator +(Example e) => 0; ref struct MyRefStruct { } } } """; var nodes = CSharpSyntaxTree.ParseText(code).GetRoot().DescendantNodes().ToArray(); Assert("System"); Assert("byte"); Assert("Example", "MyEnum", "MyRefStruct"); Assert("Example"); Assert("int"); Assert("MyDelegate"); Assert("Example"); Assert("MyEnumValue"); Assert("System", "C", "System", "Collections", "MyNamespace", "MyNamespaceNested", "T", "System", "Exception", "global", "System", "Exception", "input", "ToString", "ToString", "Action", "fun", "input", "result", "Example", "Example"); Assert("ToString", "ToString", string.Empty, "fun"); Assert("Method"); Assert("ToString"); Assert("ToString"); Assert("MyNamespaceNested"); Assert("int"); Assert("+"); Assert("input", "e", "e"); Assert("MyProp"); Assert("int"); Assert("void", "int", "int", "int", "int", "int", "byte", "byte", "byte"); Assert("Collections", "MyNamespaceNested", "Exception", "Exception"); Assert("System", "C", "System", "Collections", "MyNamespace", "MyNamespaceNested", "T", "System", "Exception", "global", "System", "Exception", "input", "ToString", "ToString", "Func", "Action", "fun", "input", "result", "Example", "Example"); Assert("T"); Assert("T"); Assert(string.Empty, "C"); Assert("i", "iPtr", "qualified", "globalVar", "fun", "result"); Assert("byte", "byte"); Assert(string.Empty); void Assert(params string[] expectedNames) where T : SyntaxNode => nodes.OfType().Select(x => SyntaxNodeExtensionsCSharp.GetName(x)).Should().BeEquivalentTo(expectedNames, because: "GetName for {0} should return the identifier", typeof(T)); } [TestMethod] public void IsNullLiteral_Null_CS() => SyntaxNodeExtensionsCSharp.IsNullLiteral(null).Should().BeFalse(); private static MethodDeclarationSyntax GetMainNode(string source) => CSharpSyntaxTree.ParseText(source).GetRoot() .DescendantNodes() .OfType() .First(x => x.Identifier.ValueText == "Main"); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Utilities/EquivalenceCheckerTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.Core.Syntax.Utilities; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; using CS = Microsoft.CodeAnalysis.CSharp.Syntax; using VB = Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace SonarAnalyzer.Test.Syntax.Utilities; [TestClass] public class EquivalenceCheckerTest { private const string CsSource = """ namespace Test { class TestClass { int Property {get;set;} public void Method1() { var x = Property; Console.WriteLine(x); } public void Method2() { var x = Property; Console.WriteLine(x); } public void Method3() { var x = Property+2; Console.Write(x); } public void Method4() { var x = Property-2; Console.Write(x); } } } """; private const string VbSource = """ Namespace Test Class TestClass Public Property Value As Integer Public Sub Method1() If True Then Dim x As Integer = Value Console.WriteLine(x) End If End Sub Public Sub Method2() If True Then Dim x As Integer = Value Console.WriteLine(x) End If End Sub Public Sub Method3() If True Then Dim x As Integer = Value + 2 Console.Write(x) End If End Sub Public Sub Method4() If True Then Dim x As Integer = Value - 2 Console.Write(x) End If End Sub End Class End Namespace """; private CSharpMethods csMethods; private VisualBasicMethods vbMethods; [TestInitialize] public void TestSetup() { csMethods = new CSharpMethods(CsSource); vbMethods = new VisualBasicMethods(VbSource); } [TestMethod] public void AreEquivalent_Node_CS() { var result = CSharpEquivalenceChecker.AreEquivalent(csMethods.Method1, csMethods.Method2); result.Should().BeTrue(); result = CSharpEquivalenceChecker.AreEquivalent(csMethods.Method1, csMethods.Method3); result.Should().BeFalse(); } [TestMethod] public void AreEquivalent_List_CS() { var result = CSharpEquivalenceChecker.AreEquivalent(csMethods.Method1.Statements, csMethods.Method2.Statements); result.Should().BeTrue(); result = CSharpEquivalenceChecker.AreEquivalent(csMethods.Method1.Statements, csMethods.Method3.Statements); result.Should().BeFalse(); } [TestMethod] public void EqualityComparer_Node_CS() { var comparer = new CSharpSyntaxNodeEqualityComparer(); var result = comparer.Equals(csMethods.Method1, csMethods.Method2); result.Should().BeTrue(); result = comparer.Equals(csMethods.Method1, csMethods.Method3); result.Should().BeFalse(); var hashSet = new HashSet(new[] { csMethods.Method1, csMethods.Method2, csMethods.Method3 }, comparer); hashSet.Should().HaveCount(2); hashSet.Should().Contain(csMethods.Method1); hashSet.Should().NotContain(csMethods.Method4); } [TestMethod] public void EqualityComparer_List_CS() { var comparer = new CSharpSyntaxNodeEqualityComparer(); var result = comparer.Equals(csMethods.Method1.Statements, csMethods.Method2.Statements); result.Should().BeTrue(); result = comparer.Equals(csMethods.Method1.Statements, csMethods.Method3.Statements); result.Should().BeFalse(); var hashSet = new HashSet>(new[] { csMethods.Method1.Statements, csMethods.Method2.Statements, csMethods.Method3.Statements }, comparer); hashSet.Should().HaveCount(2); hashSet.Should().Contain(csMethods.Method1.Statements); hashSet.Should().NotContain(csMethods.Method4.Statements); } [TestMethod] public void AreEquivalent_Node_VB() { var result = VisualBasicEquivalenceChecker.AreEquivalent(vbMethods.Method1.First(), vbMethods.Method2.First()); result.Should().BeTrue(); result = VisualBasicEquivalenceChecker.AreEquivalent(vbMethods.Method1.First(), vbMethods.Method3.First()); result.Should().BeFalse(); } [TestMethod] public void AreEquivalent_List_VB() { var result = VisualBasicEquivalenceChecker.AreEquivalent(vbMethods.Method1, vbMethods.Method2); result.Should().BeTrue(); result = VisualBasicEquivalenceChecker.AreEquivalent(vbMethods.Method1, vbMethods.Method3); result.Should().BeFalse(); } [TestMethod] public void EqualityComparer_Node_VB() { var comparer = new VisualBasicSyntaxNodeEqualityComparer(); var result = comparer.Equals(vbMethods.Method1.First(), vbMethods.Method2.First()); result.Should().BeTrue(); result = comparer.Equals(vbMethods.Method1.First(), vbMethods.Method3.First()); result.Should().BeFalse(); var hashSet = new HashSet(new[] { vbMethods.Method1.First(), vbMethods.Method2.First(), vbMethods.Method3.First() }, comparer); hashSet.Should().HaveCount(2); hashSet.Should().Contain(vbMethods.Method1.First()); hashSet.Should().NotContain(vbMethods.Method4.First()); } [TestMethod] public void EqualityComparer_List_VB() { var comparer = new VisualBasicSyntaxNodeEqualityComparer(); var result = comparer.Equals(vbMethods.Method1, vbMethods.Method2); result.Should().BeTrue(); result = comparer.Equals(vbMethods.Method1, vbMethods.Method3); result.Should().BeFalse(); var hashSet = new HashSet>(new[] { vbMethods.Method1, vbMethods.Method2, vbMethods.Method3 }, comparer); hashSet.Should().HaveCount(2); hashSet.Should().Contain(vbMethods.Method1); hashSet.Should().NotContain(vbMethods.Method4); } [TestMethod] public void EqualityComparer_Node_CrossLanguage() => EquivalenceChecker.AreEquivalent(vbMethods.Method1.First(), csMethods.Method1, null).Should().BeFalse(); private class CSharpMethods { public readonly CS.BlockSyntax Method1; public readonly CS.BlockSyntax Method2; public readonly CS.BlockSyntax Method3; public readonly CS.BlockSyntax Method4; public CSharpMethods(string source) { var methods = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(source).GetRoot().DescendantNodes().OfType().ToArray(); Method1 = methods.Single(x => x.Identifier.ValueText == "Method1").Body; Method2 = methods.Single(x => x.Identifier.ValueText == "Method2").Body; Method3 = methods.Single(x => x.Identifier.ValueText == "Method3").Body; Method4 = methods.Single(x => x.Identifier.ValueText == "Method4").Body; } } private class VisualBasicMethods { public readonly SyntaxList Method1; public readonly SyntaxList Method2; public readonly SyntaxList Method3; public readonly SyntaxList Method4; public VisualBasicMethods(string source) { var methods = Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxTree.ParseText(source).GetRoot().DescendantNodes().OfType().ToArray(); Method1 = methods.Single(x => x.GetIdentifierText() == "Method1").Statements; Method2 = methods.Single(x => x.GetIdentifierText() == "Method2").Statements; Method3 = methods.Single(x => x.GetIdentifierText() == "Method3").Statements; Method4 = methods.Single(x => x.GetIdentifierText() == "Method4").Statements; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Utilities/MethodParameterLookupTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using System.Collections; using SonarAnalyzer.Core.Syntax.Utilities; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; using CSharpCodeAnalysis = Microsoft.CodeAnalysis.CSharp; using CSharpSyntax = Microsoft.CodeAnalysis.CSharp.Syntax; using VBCodeAnalysis = Microsoft.CodeAnalysis.VisualBasic; using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace SonarAnalyzer.Test.Syntax.Utilities; [TestClass] public class MethodParameterLookupTest { private const string SourceCS = """ namespace Test { class TestClass { void Main() { DoNothing(); DoSomething(1, true); DoSomething(b: true, a: 1); WithOptional(1); WithOptional(1, "Ipsum"); WithOptional(opt: "Ipsum", a: 1); WithParams(); WithParams(1, 2, 3); } void ThisShouldNotBeFoundInMain() { SpecialMethod(65535); } void DoNothing() { } void DoSomething(int a, bool b) { } void WithOptional(int a, string opt = "Lorem") { } void WithParams(params int[] arr) { } void SpecialMethod(int specialParameter) { } } } """; private const string SourceVB = """ Module MainModule Sub Main() DoNothing() DoSomething(1, True) WithOptional(1) WithOptional(1, "Ipsum") WithParams() WithParams(1, 2, 3) End Sub Sub ThisShouldNotBeFoundInMain() SpecialMethod(65535) End Sub Sub DoNothing() End Sub Sub DoSomething(a As Integer, b As Boolean) End Sub Sub WithOptional(a As Integer, Optional opt As String = "Lorem") End Sub Sub WithParams(ParamArray arr() As Integer) End Sub Sub SpecialMethod(SpecialParameter As Integer) End Sub End Module """; [TestMethod] public void TestMethodParameterLookup_CS() { var c = new CSharpInspection(SourceCS); c.CheckExpectedParameterMappings(0, "DoNothing", new { }); c.CheckExpectedParameterMappings(1, "DoSomething", new { a = 1, b = true }); c.CheckExpectedParameterMappings(2, "DoSomething", new { a = 1, b = true }); c.CheckExpectedParameterMappings(3, "WithOptional", new { a = 1 }); c.CheckExpectedParameterMappings(4, "WithOptional", new { a = 1, opt = "Ipsum" }); c.CheckExpectedParameterMappings(5, "WithOptional", new { a = 1, opt = "Ipsum" }); c.CheckExpectedParameterMappings(6, "WithParams", new { }); c.CheckExpectedParameterMappings(7, "WithParams", new { arr = new[] { 1, 2, 3 } }); c.MainInvocations.Length.Should().Be(8); // Self-Test of this test. If new Invocation is added to the Main(), this number has to be updated and test should be written for that case. } [TestMethod] public void TestMethodParameterLookup_VB() { var c = new VisualBasicInspection(SourceVB); c.CheckExpectedParameterMappings(0, "DoNothing", new { }); c.CheckExpectedParameterMappings(1, "DoSomething", new { a = 1, b = true }); c.CheckExpectedParameterMappings(2, "WithOptional", new { a = 1 }); c.CheckExpectedParameterMappings(3, "WithOptional", new { a = 1, opt = "Ipsum" }); c.CheckExpectedParameterMappings(4, "WithParams", new { }); c.CheckExpectedParameterMappings(5, "WithParams", new { arr = new[] { 1, 2, 3 } }); c.MainInvocations.Length.Should().Be(6); // Self-Test of this test. If new Invocation is added to the Main(), this number has to be updated and test should be written for that case. } [TestMethod] public void TestMethodParameterLookup_CS_ThrowsException() { var c = new CSharpInspection(SourceCS); var lookupThrow = c.CreateLookup(1, "DoSomething"); var invalidOperationEx = Assert.Throws(() => lookupThrow.TryGetNonParamsSyntax(lookupThrow.MethodSymbol.Parameters.Single(), out var argument)); invalidOperationEx.Message.Should().Be("Sequence contains more than one element"); var argumentEx = Assert.Throws(() => lookupThrow.TryGetSymbol(CSharpCodeAnalysis.SyntaxFactory.LiteralExpression(CSharpCodeAnalysis.SyntaxKind.StringLiteralExpression), out var parameter)); argumentEx.Message.Should().StartWith("argument must be of type Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax"); } [TestMethod] public void TestMethodParameterLookup_VB_ThrowsException() { var c = new VisualBasicInspection(SourceVB); var lookupThrow = c.CreateLookup(1, "DoSomething"); var invalidOperationEx = Assert.Throws(() => lookupThrow.TryGetNonParamsSyntax(lookupThrow.MethodSymbol.Parameters.Single(), out var argument)); invalidOperationEx.Message.Should().Be("Sequence contains more than one element"); var argumentEx = Assert.Throws(() => lookupThrow.TryGetSymbol(VBCodeAnalysis.SyntaxFactory.StringLiteralExpression(VBCodeAnalysis.SyntaxFactory.StringLiteralToken(string.Empty, string.Empty)), out var parameter)); argumentEx.Message.Should().StartWith("argument must be of type Microsoft.CodeAnalysis.VisualBasic.Syntax.ArgumentSyntax"); } [TestMethod] public void TestMethodParameterLookup_CS_ThrowsException_NonParams() { var c = new CSharpInspection(SourceCS); var lookupThrow = c.CreateLookup(7, "WithParams"); var invalidOperationEx = Assert.Throws(() => lookupThrow.TryGetNonParamsSyntax(lookupThrow.MethodSymbol.Parameters.Single(), out var argument)); invalidOperationEx.Message.Should().Be("Cannot call TryGetNonParamsSyntax on ParamArray/params parameters."); } [TestMethod] public void TestMethodParameterLookup_VB_ThrowsException_NonParams() { var c = new VisualBasicInspection(SourceVB); var lookupThrow = c.CreateLookup(5, "WithParams"); var invalidOperationEx = Assert.Throws(() => lookupThrow.TryGetNonParamsSyntax(lookupThrow.MethodSymbol.Parameters.Single(), out var argument)); invalidOperationEx.Message.Should().Be("Cannot call TryGetNonParamsSyntax on ParamArray/params parameters."); } [TestMethod] public void TestMethodParameterLookup_CS_MultipleCandidates() { var source = """ namespace Test { class TestClass { void Main(dynamic d) => AmbiguousCall(d); void AmbiguousCall(int p) { } void AmbiguousCall(string p) { } } } """; var (tree, model) = TestCompiler.CompileCS(source); var lookup = new CSharpMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("p", out var expressions).Should().BeTrue(); expressions.Should().BeEquivalentTo(new[] { new { Identifier = new { ValueText = "d" } } }); } [TestMethod] public void TestMethodParameterLookup_VB_MultipleCandidates() { var source = """ Module Test Sub Main() Overloaded(42, "") End Sub Sub Overloaded(a As Integer, b As Boolean) End Sub Sub Overloaded(a As Integer, b As Integer) End Sub End Module """; var (tree, model) = TestCompiler.CompileIgnoreErrorsVB(source); var lookup = new VisualBasicMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("a", out var expressions).Should().BeTrue(); expressions.Should().BeEquivalentTo(new[] { new { Token = new { ValueText = "42" } } }); } [TestMethod] public void TestMethodParameterLookup_CS_MultipleCandidatesWithDifferentParameters() { var source = """ namespace Test { class TestClass { void Main(dynamic d) => AmbiguousCall(d, d); void AmbiguousCall(int a, int b) { } void AmbiguousCall(string b, string a) { } } } """; var (tree, model) = TestCompiler.CompileCS(source); var lookup = new CSharpMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("a", out var expressions).Should().BeFalse(); expressions.Should().BeEmpty(); } [TestMethod] public void TestMethodParameterLookup_VB_MultipleCandidatesWithDifferentParameters() { var source = """ Module Test Sub Main() Overloaded(42, "") End Sub Sub Overloaded(a As Integer, b As Integer) End Sub Sub Overloaded(b As Boolean, a As Boolean) End Sub End Module """; var (tree, model) = TestCompiler.CompileIgnoreErrorsVB(source); var lookup = new VisualBasicMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("a", out var expressions).Should().BeFalse(); expressions.Should().BeEmpty(); } [TestMethod] public void TestMethodParameterLookup_CS_UnknownMethod() { var source = """ namespace Test { class TestClass { void Main() { UnknownMethod(42); } } } """; var (tree, model) = TestCompiler.CompileIgnoreErrorsCS(source); var lookup = new CSharpMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("p", out var expressions).Should().BeFalse(); expressions.Should().BeEmpty(); } [TestMethod] public void TestMethodParameterLookup_VB_UnknownMethod() { var source = """ Module Test Sub Main() UnknownMethod(42) End Sub End Module """; var (tree, model) = TestCompiler.CompileIgnoreErrorsVB(source); var lookup = new VisualBasicMethodParameterLookup(tree.GetRoot().DescendantNodes().OfType().Single(), model); lookup.TryGetSyntax("a", out var expressions).Should().BeFalse(); expressions.Should().BeEmpty(); } private abstract class InspectionBase where TArgumentSyntax : SyntaxNode where TInvocationSyntax : SyntaxNode { public abstract TInvocationSyntax[] FindInvocationsIn(string name); public abstract object ExtractArgumentValue(TArgumentSyntax argumentSyntax); public abstract TArgumentSyntax[] GetArguments(TInvocationSyntax invocation); public abstract MethodParameterLookupBase CreateLookup(TInvocationSyntax invocation, IMethodSymbol method); public SnippetCompiler Compiler { get; protected set; } public TInvocationSyntax[] MainInvocations { get; protected set; } public TArgumentSyntax SpecialArgument { get; private set; } public IParameterSymbol SpecialParameter { get; private set; } protected InspectionBase(string source, AnalyzerLanguage language) { Compiler = new SnippetCompiler(source, false, language); MainInvocations = FindInvocationsIn("Main"); } public MethodParameterLookupBase CreateLookup(int invocationIndex, string expectedMethod) { var invocation = MainInvocations[invocationIndex]; var method = Compiler.Model.GetSymbolInfo(invocation).Symbol as IMethodSymbol; method.Name.Should().Be(expectedMethod); return CreateLookup(invocation, method); } public void CheckExpectedParameterMappings(int invocationIndex, string expectedMethod, object expectedArguments) { var lookup = CreateLookup(invocationIndex, expectedMethod); InspectTryGetSyntax(lookup, expectedArguments, lookup.MethodSymbol); InspectTryGetSymbol(lookup, expectedArguments, GetArguments(MainInvocations[invocationIndex])); } protected void InitSpecial(TInvocationSyntax specialInvocation) { SpecialArgument = GetArguments(specialInvocation).Single(); SpecialParameter = (Compiler.Model.GetSymbolInfo(specialInvocation).Symbol as IMethodSymbol).Parameters.Single(); } private void InspectTryGetSyntax(MethodParameterLookupBase lookup, object expectedArguments, IMethodSymbol method) { lookup.TryGetSyntax(SpecialParameter, out var symbol).Should().Be(false); foreach (var parameter in method.Parameters) { if (parameter.IsParams && lookup.TryGetSyntax(parameter, out var expressions)) { var expected = ExtractExpectedValue(expectedArguments, parameter.Name).Should().BeAssignableTo().Subject.Cast(); expressions.Select(x => ConstantValue(x)).Should().Equal(expected); } else if (!parameter.IsParams && lookup.TryGetNonParamsSyntax(parameter, out var expression)) { ConstantValue(expression).Should().Be(ExtractExpectedValue(expectedArguments, parameter.Name)); } else if (!parameter.IsOptional && !parameter.IsParams) { Assert.Fail($"TryGetSyntax missing {parameter.Name}"); } // Else it's OK } } private void InspectTryGetSymbol(MethodParameterLookupBase lookup, object expectedArguments, TArgumentSyntax[] arguments) { lookup.TryGetSymbol(SpecialArgument, out var parameter).Should().Be(false); foreach (var argument in arguments) { if (lookup.TryGetSymbol(argument, out var symbol)) { var value = ExtractArgumentValue(argument); var expected = ExtractExpectedValue(expectedArguments, symbol.Name); if (symbol.IsParams) { // Expected contains all values {1, 2, 3} for ParamArray/params, but foreach is probing one at a time expected.Should().BeAssignableTo().Which.Cast().Should().Contain(value); } else { value.Should().Be(expected); } } else { Assert.Fail($"TryGetParameterSymbol missing {argument.ToString()}"); } } } private static object ExtractExpectedValue(object expected, string name) { var pi = expected.GetType().GetProperty(name); if (pi is null) { Assert.Fail($"Parameter name {name} was not expected."); } return pi.GetValue(expected, null); } private object ConstantValue(SyntaxNode node) => Compiler.Model.GetConstantValue(node).Value; } private class CSharpInspection : InspectionBase { public CSharpInspection(string source) : base(source, AnalyzerLanguage.CSharp) => InitSpecial(Compiler.Nodes() .Single(x => x.Expression is CSharpSyntax.IdentifierNameSyntax identifier && identifier.Identifier.ValueText == "SpecialMethod")); public override CSharpSyntax.InvocationExpressionSyntax[] FindInvocationsIn(string name) => Compiler.Nodes().Single(x => x.Identifier.ValueText == name).DescendantNodes().OfType().ToArray(); public override CSharpSyntax.ArgumentSyntax[] GetArguments(CSharpSyntax.InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.ToArray(); public override object ExtractArgumentValue(CSharpSyntax.ArgumentSyntax argumentSyntax) => Compiler.Model.GetConstantValue(argumentSyntax.Expression).Value; public override MethodParameterLookupBase CreateLookup(CSharpSyntax.InvocationExpressionSyntax invocation, IMethodSymbol method) => new CSharpMethodParameterLookup(invocation.ArgumentList, method); } private class VisualBasicInspection : InspectionBase { public VisualBasicInspection(string source) : base(source, AnalyzerLanguage.VisualBasic) => InitSpecial(Compiler.Nodes() .Single(x => x.Expression is VBSyntax.IdentifierNameSyntax identifier && identifier.Identifier.ValueText == "SpecialMethod")); public override VBSyntax.InvocationExpressionSyntax[] FindInvocationsIn(string name) => Compiler.Nodes().Single(x => x.SubOrFunctionStatement.Identifier.ValueText == "Main") .DescendantNodes().OfType().ToArray(); public override VBSyntax.ArgumentSyntax[] GetArguments(VBSyntax.InvocationExpressionSyntax invocation) => invocation.ArgumentList.Arguments.ToArray(); public override MethodParameterLookupBase CreateLookup(VBSyntax.InvocationExpressionSyntax invocation, IMethodSymbol method) => new VisualBasicMethodParameterLookup(invocation.ArgumentList, Compiler.Model); public override object ExtractArgumentValue(VBSyntax.ArgumentSyntax argumentSyntax) => Compiler.Model.GetConstantValue(argumentSyntax.GetExpression()).Value; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Utilities/RemovableDeclarationCollectorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.CodeAnalysis.VisualBasic.Syntax; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; using CodeAnalysisAccessibility = Microsoft.CodeAnalysis.Accessibility; // This is needed because there is an Accessibility namespace in the windows forms binaries. namespace SonarAnalyzer.Test.Syntax.Utilities; [TestClass] public class RemovableDeclarationCollectorTest { [TestMethod] public void RemovableFieldLikeDeclarations_SearchesInNestedTypes_VB() { const string code = """ Public Class Sample Public CompliantA, CompliantB As Integer Public CompliantC As Integer Private Class NestedClass Public FieldInNestedClass As Integer End Class Private Structure NestedStruct Public FieldInNestedStruct As Integer End Structure End Class """; var sut = CreateCollector(code); var ret = sut.RemovableFieldLikeDeclarations(new[] { SyntaxKind.FieldDeclaration }.ToHashSet(), CodeAnalysisAccessibility.Public); ret.Should().HaveCount(5); ret.Select(x => x.Symbol.Name).Should().BeEquivalentTo("CompliantA", "CompliantB", "CompliantC", "FieldInNestedClass", "FieldInNestedStruct"); } [TestMethod] public void RemovableDeclarations_VB() { const string code = """ Public Class Base Public Overridable Sub OverridableMethod_NotRemovable() End Sub End Class Public Interface IBase Sub InterfaceMethod_NotRemovable() End Interface Public Class Sample Inherits Base Implements IBase Public Sub RemovableMethod() End Sub Public Overrides Sub OverridableMethod_NotRemovable() End Sub Public Sub InterfaceMethod_NotRemovable() Implements IBase.InterfaceMethod_NotRemovable End Sub Public Sub WithAttributes_NotRemovable() End Sub Public Overridable Sub Overridable_NotRemovable() End Sub Public Interface INestedInterface Sub NestedInterfaceMethod_NotRemovable() End Interface Public MustInherit Class AbstractType Public MustOverride Sub Abstract_NotRemovable() End Class End Class """; var sut = CreateCollector(code); var ret = sut.RemovableDeclarations(new[] { SyntaxKind.SubBlock, SyntaxKind.SubStatement }.ToHashSet(), CodeAnalysisAccessibility.Public); ret.Should().ContainSingle(); ret.Single().Symbol.Name.Should().Be("RemovableMethod"); } [TestMethod] public void IsRemovable_Null_ReturnsFalse() => VisualBasicRemovableDeclarationCollector.IsRemovable(null, CodeAnalysisAccessibility.Public).Should().BeFalse(); private static VisualBasicRemovableDeclarationCollector CreateCollector(string code) { var (tree, semanticModel) = TestCompiler.CompileVB(code, MetadataReferenceFacade.SystemComponentModelPrimitives.ToArray()); var type = tree.GetRoot().DescendantNodes().OfType().Single(x => x.ClassStatement.Identifier.ValueText == "Sample"); return new VisualBasicRemovableDeclarationCollector(semanticModel.GetDeclaredSymbol(type), semanticModel.Compilation); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Utilities/SymbolUsageCollectorTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using Microsoft.CodeAnalysis.CSharp.Syntax; using SonarAnalyzer.CSharp.Syntax.Utilities; namespace SonarAnalyzer.Test.Syntax.Utilities; [TestClass] public class SymbolUsageCollectorTest { [TestMethod] public void VerifyUsagesBeingCollectedOnMatchingSyntaxNodes() { const string firstSnippet = """ public class Foo { private int Field = 42; public int FooMethod(int arg) { Field += arg; return Field; } } """; const string secondSnippet = """ public class Bar { private int Field = 42; public int BarMethod() { return Field; } } """; var firstCompilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).AddSnippet(firstSnippet).GetCompilation(); var secondCompilation = SolutionBuilder.Create().AddProject(AnalyzerLanguage.CSharp).AddSnippet(secondSnippet).GetCompilation(); var firstTree = firstCompilation.SyntaxTrees.Single(); var fooMethod = firstTree.Single(); var firstCompilationModel = firstCompilation.GetSemanticModel(firstTree); var firstCompilationFieldSymbol = firstCompilationModel.GetSymbolInfo(fooMethod.DescendantNodes().OfType().Single().Expression).Symbol; var firstCompilationKnownSymbols = new List { firstCompilationFieldSymbol }; // compilation matches semantic model and syntax node var firstCompilationUsageCollector = new SymbolUsageCollector(firstCompilation, firstCompilationKnownSymbols); firstCompilationUsageCollector.Visit(fooMethod); firstCompilationUsageCollector.UsedSymbols.Should().NotBeEmpty(); var firstCompilationUsedSymbols = firstCompilationUsageCollector.UsedSymbols; // compilation doesn't match syntax node, since it belongs to another compilation var secondTree = secondCompilation.SyntaxTrees.Single(); var barMethod = secondTree.Single(); firstCompilationUsageCollector.Visit(barMethod); firstCompilationUsageCollector.UsedSymbols.Should().BeEquivalentTo(firstCompilationUsedSymbols); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/Syntax/Utilities/SyntaxClassifierTest.cs ================================================ /* * SonarAnalyzer for .NET * Copyright (C) SonarSource Sàrl * mailto:info AT sonarsource DOT com * * You can redistribute and/or modify this program under the terms of * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. * * 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 Sonar Source-Available License for more details. * * You should have received a copy of the Sonar Source-Available License * along with this program; if not, see https://sonarsource.com/license/ssal/ */ using SonarAnalyzer.CSharp.Core.Syntax.Extensions; using SonarAnalyzer.CSharp.Core.Syntax.Utilities; using SonarAnalyzer.VisualBasic.Core.Syntax.Extensions; using SonarAnalyzer.VisualBasic.Core.Syntax.Utilities; using CS = Microsoft.CodeAnalysis.CSharp; using SyntaxCS = Microsoft.CodeAnalysis.CSharp.Syntax; using SyntaxVB = Microsoft.CodeAnalysis.VisualBasic.Syntax; using VB = Microsoft.CodeAnalysis.VisualBasic; namespace SonarAnalyzer.Test.Syntax.Utilities; [TestClass] public class SyntaxClassifierTest { [TestMethod] [DataRow("while (condition) { }")] [DataRow("while (a && (b || !condition)) { }")] [DataRow("do { } while (condition);")] [DataRow("do { } while (a && (b || !condition));")] [DataRow("for(; condition; ) { }")] [DataRow("for(; a && (b || !condition); ) { }")] public void IsInLoopCondition_Loops_CS(string code) => IsInLoopConditionCS(code).Should().BeTrue(); [TestMethod] public void IsInLoopCondition_If_CS() { const string code = """ while (a) { do { if (condition) // This is asserted { } } while (b); } """; IsInLoopConditionCS(code).Should().BeFalse(); } [TestMethod] [DataRow("While Condition : End While")] [DataRow("While A AndAlso (B OrElse Not Condition) : End While")] [DataRow("Do While Condition : Loop")] [DataRow("Do While A AndAlso (B OrElse Not Condition) : Loop")] [DataRow("Do : Loop While Condition")] [DataRow("Do : Loop While Condition")] [DataRow("Do : Loop While A AndAlso (B OrElse Not Condition)")] [DataRow("Do : Loop Until Condition")] [DataRow("Do : Loop Until Condition")] [DataRow("Do : Loop Until A AndAlso (B OrElse Not Condition)")] [DataRow("Dim Condition As Integer : For Condition = 0 To 9 : Next")] [DataRow("Dim Condition As Integer : For Condition = 9 To 0 Step -1 : Next")] public void IsInLoopCondition_Loops_VB(string code) => IsInLoopConditionVB(code).Should().BeTrue(); [TestMethod] public void IsInLoopCondition_If_VB() { const string code = """ While A Do If Condition Then ' This is asserted End If Loop While B End While """; IsInLoopConditionVB(code).Should().BeFalse(); } [TestMethod] public void IsInLoopCondition_NestedInLambda() { const string code = """ System.Action lambda = () => { while(condition) { } }; """; IsInLoopConditionCS(code).Should().BeTrue(); } [TestMethod] public void MemberAccessExpression_Null_CS() => CSharpSyntaxClassifier.Instance.MemberAccessExpression(CS.SyntaxFactory.IdentifierName("unexpectedNodeType")).Should().BeNull(); [TestMethod] public void MemberAccessExpression_Null_VB() => VisualBasicSyntaxClassifier.Instance.MemberAccessExpression(VB.SyntaxFactory.IdentifierName("unexpectedNodeType")).Should().BeNull(); [TestMethod] [DataRow("x => condition")] [DataRow("x => a && (b || !condition)")] [DataRow("_ => condition")] [DataRow("(item, index) => condition")] public void IsInLoopCondition_LambdaInLoop_CS(string lambda) => IsInLoopConditionCS($$"""while (Enumerable.Repeat(10, 10).Select({{lambda}}).Any()) { }""").Should().BeFalse(); [TestMethod] [DataRow("Function(X) Condition")] [DataRow("Function(X) A AndAlso (B OrElse Not Condition)")] [DataRow("Function(Item, Index) Condition")] [DataRow("Function(X) \n Return Condition \n End Function")] public void IsInLoopCondition_LambdaInLoop_VB(string lambda) => IsInLoopConditionVB($$"""While Enumerable.Repeat(10, 10).Select({{lambda}}).Any() : End While""").Should().BeFalse(); private static bool IsInLoopConditionCS(string code) => CSharpSyntaxClassifier.Instance.IsInLoopCondition(CreateConditionCS(code)); private static bool IsInLoopConditionVB(string code) => VisualBasicSyntaxClassifier.Instance.IsInLoopCondition(CreateConditionVB(code)); private static SyntaxCS.IdentifierNameSyntax CreateConditionCS(string code) { var tree = TestCompiler.CompileCS($$""" using System.Linq; public class Sample { public void Method(bool a, bool b, bool condition) { {{code}} } } """).Tree; return tree.GetRoot().DescendantNodes().OfType().Single(x => x.NameIs("condition")); } private static SyntaxVB.IdentifierNameSyntax CreateConditionVB(string code) { var tree = TestCompiler.CompileVB($$""" Public Class Sample Private A As Boolean, B As Boolean, Condition As Boolean Public Sub Method() {{code}} End Sub End Class """).Tree; return tree.GetRoot().DescendantNodes().OfType().Single(x => x.NameIs("Condition")); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractClassToInterface.Latest.Partial.cs ================================================ // namespace PartialProperties { public abstract partial class PartialPropertyAbstractOnly { public abstract string City { get; } } public abstract partial class PartialPropertyPartial { public partial string Name { get => "Kirk"; } } } namespace CSharp14 { public abstract partial class PartialConstructor { public partial PartialConstructor() { } } public abstract partial class PartialEventTest { public partial event System.EventHandler PartialEvent { add { } remove { } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractClassToInterface.Latest.cs ================================================ using System; using System.Collections.Generic; namespace Records { public abstract record Empty { } public abstract record Animal // Noncompliant {{Convert this 'abstract' record to an interface.}} { public abstract void move(); public abstract void feed(); } public record SomeBaseRecord { } public abstract record Animal2 : SomeBaseRecord // Compliant { public abstract void move(); public abstract void feed(); } public abstract record RecordWithProtectedAbstractMethod // Noncompliant { protected abstract void ProtectedMethod(); } public abstract record Color { private int red = 0; public int getRed() => red; } public interface AnimalCompliant { void move(); void feed(); } public class ColorCompliant { private int red = 0; private ColorCompliant() { } public int getRed() => red; } public abstract record LampCompliant { private bool switchLamp = false; public abstract void glow(); public void flipSwitch() { switchLamp = !switchLamp; if (switchLamp) { glow(); } } } public abstract record View // Noncompliant {{Convert this 'abstract' record to an interface.}} // ^^^^ { public abstract string Content { get; } } public abstract record View2() // Compliant, has abstract and non abstract members { public abstract string Content { get; } public abstract string Content1 { get; } public string Content2 { get; } } public abstract record Record(string X); public abstract record Record2(string X) // Compliant, this record has a propery X which is concrete { public abstract string Content { get; } } // https://github.com/SonarSource/sonar-dotnet/issues/9494 public abstract class AbstractClassWithStaticField // TN for .NET Framework, FN for .NET Core / .NET (where interfaces can have static members) { protected static int _data; public abstract void SomeMethod(); } } namespace FileAccessibility { file abstract class Empty { } file abstract class OnlyAbstract // Noncompliant {{Convert this 'abstract' class to an interface.}} // ^^^^^^^^^^^^ { public abstract void Move(); } file abstract class Animal2 //Compliant { public abstract void Move(); string Foo() => "FOO"; } } namespace PartialProperties { public abstract partial class PartialPropertyAbstractOnly //Noncompliant {{Convert this 'abstract' class to an interface.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ { public abstract string Name { get; } } public abstract partial class PartialPropertyPartial { public partial string Name { get; } } } namespace Events { public abstract class EventTest { public event System.EventHandler AbstractEvent { add { } remove { } } } public abstract class AbstractEventTest { public abstract event System.EventHandler AbstractEvent; } } namespace CSharp14 { public abstract partial class PartialConstructor { public partial PartialConstructor(); } public abstract partial class PartialEventTest { public partial event System.EventHandler PartialEvent; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractClassToInterface.cs ================================================ using System; using System.Collections.Generic; public abstract partial class PartialMixed { public abstract void X(); } public abstract partial class PartialMixed { public void Y() { } } public abstract partial class PartialAbstract // Noncompliant { public abstract void X(); } public abstract partial class PartialAbstract // Noncompliant { public abstract void Y(); } public abstract class Empty { } public abstract class Animal // Noncompliant {{Convert this 'abstract' class to an interface.}} // ^^^^^^ { public abstract void move(); public abstract void feed(); } public class SomeBaseClass { } public abstract class Animal2 : SomeBaseClass // Compliant { public abstract void move(); public abstract void feed(); } public abstract class Color { private int red = 0; private int green = 0; private int blue = 0; public int getRed() { return red; } } public interface AnimalCompliant { void move(); void feed(); } public class ColorCompliant { private int red = 0; private int green = 0; private int blue = 0; private ColorCompliant() { } public int getRed() { return red; } } public abstract class LampCompliant { private bool switchLamp = false; public abstract void glow(); public void flipSwitch() { switchLamp = !switchLamp; if (switchLamp) { glow(); } } } public abstract class View // Noncompliant, should be an interface { public abstract string Content { get; } } public abstract class View2 // Compliant, has abstract and non abstract members { public abstract string Content { get; } public abstract string Content1 { get; } public string Content2 { get; } } public abstract class View2Derived : View2 // Compliant, still has abstract parts { public string Content3 { get; } public override string Content1 { get { return ""; } } } public abstract class View3Derived : SomeUnknownType // Error [CS0246] { public string Content3 { get; } public override int Content1 { get { return 1; } } } public abstract class WithStaticConstructor // TN for .NET Framework, FN for .NET Core / .NET (where interfaces can have static constructors) { public abstract void ToOverride(); static WithStaticConstructor() { // Do something here } } public abstract class WithNonStaticConstructor // Compliant: the class has a non-static constructor, it cannot be converted to an interface { public abstract void ToOverride(); public WithNonStaticConstructor() { // Do something here } } // https://github.com/SonarSource/sonar-dotnet/issues/9494 public abstract class AbstractClassWithField // Compliant: the class has a field, it cannot be converted to an interface { protected int _data; public abstract void SomeMethod(); } public abstract class AbstractProtectedMethod // Noncompliant { protected abstract void SomeMethod(); } public abstract class AbstractProtectedInternalMethod // Noncompliant { protected internal abstract void SomeMethod(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractTypesShouldNotHaveConstructors.Latest.Partial.cs ================================================ namespace CSharp14 { public abstract partial class PartialConstructor { public partial PartialConstructor() { } // Noncompliant } public abstract partial class NonPartialConstructor { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractTypesShouldNotHaveConstructors.Latest.cs ================================================ using System; using System.Collections.Generic; namespace CSharp12 { abstract class PrimaryConstructor(int i) // Compliant { public int I { get; set; } = i; } } namespace Records { abstract record AbstractRecordOne { public string X { get; } public AbstractRecordOne(string x) => (X) = (x); // Noncompliant } record RecordOne : AbstractRecordOne { public RecordOne(string x) : base(x) { } // Compliant } abstract record AbstractRecordTwo(string Y); record RecordTwo(string Z) : AbstractRecordTwo(Z); public abstract record Person(string Name, string Surname) { public Person(string name) : this(name, "") // Noncompliant { } } public struct MyStruct { public MyStruct(string s) { } } } namespace CSharp14 { public abstract partial class PartialConstructor { public partial PartialConstructor(); } public abstract partial class NonPartialConstructor { public NonPartialConstructor() { } // Noncompliant } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractTypesShouldNotHaveConstructors.TopLevelStatements.cs ================================================ using System; Console.WriteLine("Hello World!"); abstract class Base { public Base() { } // Noncompliant {{Change the visibility of this constructor to 'protected'.}} private Base(int i) { } // Compliant } abstract record AbstractRecordOne { public string X { get; } public AbstractRecordOne(string x) => (X) = (x); // Noncompliant } record RecordOne : AbstractRecordOne { public RecordOne(string x) : base(x) { } // Compliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AbstractTypesShouldNotHaveConstructors.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { class Program { abstract class Base { public Base() // Noncompliant {{Change the visibility of this constructor to 'protected'.}} // ^^^^^^ { //... } private Base(int i) // Compliant { } protected Base(int i, int j) // Compliant { } internal Base(int i, int j, int k) // Noncompliant {{Change the visibility of this constructor to 'private protected'.}} { } internal protected Base(int i, int j, int k, int l) // Noncompliant { } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AllBranchesShouldNotHaveSameImplementation.CSharp9.cs ================================================ using System; int b = 0; if (b == 0) // Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} { DoSomething(); } else if (b == 1) { DoSomething(); } else { DoSomething(); } void DoSomething() { } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AllBranchesShouldNotHaveSameImplementation.cs ================================================ using System; namespace Tests.Diagnostics { public class Program { public void IfElseCases(int b, int c) { if (b == 0) // Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} // ^^ { DoSomething(); } else if (b == 1) { DoSomething(); } else { DoSomething(); } if (b == 0) // Noncompliant { if (c == 1) // Noncompliant // ^^ { DoSomething(); } else { DoSomething(); } } else { if (c == 1) // Noncompliant { DoSomething(); } else { DoSomething(); } } if (b == 0) { DoSomething(); } else { DoSomethingElse(); } if (b == 0) // Compliant - no else clause { DoSomething(); } else if (b == 1) { DoSomething(); } } public void SwitchCases(int i) { switch (i) // Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} // ^^^^^^ { case 1: DoSomething(); break; case 2: DoSomething(); break; case 3: DoSomething(); break; default: DoSomething(); break; } switch (i) // Noncompliant { case 1: { DoSomething(); break; } case 2: { DoSomething(); break; } case 3: { DoSomething(); break; } default: { DoSomething(); break; } } switch (i) { case 1: DoSomething(); break; default: DoSomethingElse(); break; } switch (i) // Compliant - no default section { case 1: DoSomething(); break; case 2: DoSomething(); break; case 3: DoSomething(); break; } } public void TernaryCases(bool c, int a) { int b = a > 12 ? 4 : 4; // Noncompliant {{This conditional operation returns the same value whether the condition is "true" or "false".}} // ^^^^^^^^ var x = 1 > 18 ? true : true; // Noncompliant var y = 1 > 18 ? true : false; y = 1 > 18 ? (true) : true; // Noncompliant TernaryCases(1 > 18 ? (true) : true, a); // Noncompliant } private void DoSomething() { } private void DoSomethingElse() { } public int SwitchExpressionNoncompliant(string type) => type switch // Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} // ^^^^^^ { "a" => GetNumber(), "b" => GetNumber(), _ => GetNumber() }; public int SwitchExpressionNested(string type) { return type switch { "a" => GetNumber(), _ => type switch // Noncompliant { "b" => GetNumber(), "c" => GetNumber(), _ => GetNumber() } }; } public int GetNumber() => 42; public int SwitchExpressionCompliant(string type) { var x = type switch // Compliant { "a" => 42, "b" => type.Length, _ => GetNumber(), }; string y = type switch { }; // Compliant var z = type switch { "a" => 42 }; // Compliant var withoutDefault = type switch // Compliant, does not have the discard default arm { "a" => GetNumber(), "b" => GetNumber(), "c" => GetNumber(), "d" => GetNumber(), "e" => GetNumber(), }; return x; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AllBranchesShouldNotHaveSameImplementation.vb ================================================ Imports System Namespace Tests.Diagnostics Public Class Program Public Sub IfElseCases(b As Integer, c As Integer) If b = 0 Then 'Noncompliant ' ^^ DoSomething() ElseIf b = 1 Then DoSomething() Else DoSomething() End If If b = 0 Then 'Noncompliant If c = 1 Then 'Noncompliant DoSomething() Else DoSomething() End If Else If c = 1 Then 'Noncompliant DoSomething() Else DoSomething() End If End If If b = 0 Then DoSomething() Else DoSomethingElse() End If If b = 0 Then 'Compliant, no else DoSomething() ElseIf b = 1 Then DoSomething() End If End Sub Public Sub SwitchCases(i As Integer) Select Case i ' Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} ' ^^^^^^ Case 1 DoSomething() Case 2 DoSomething() Case 3 DoSomething() Case Else DoSomething() End Select Select Case i ' Noncompliant Case 1 DoSomething() Exit Select Case 2 DoSomething() Exit Select Case 3 DoSomething() Exit Select Case Else DoSomething() Exit Select End Select Select Case i Case 1 DoSomething() Case Else DoSomethingElse() End Select Select Case i Case 1 DoSomething() Case 2 DoSomething() Case 3 DoSomething() End Select End Sub Public Sub TernaryCases(ByVal c As Boolean) Dim a As Integer = 1 Dim b As Integer = If(a > 12, 4, 4) 'Noncompliant {{This conditional operation returns the same value whether the condition is "true" or "false".}} ' ^^ Dim x = If(1 > 18, True, True) 'Noncompliant Dim y = If(1 > 18, True, False) y = If(1 > 18, (True), True) 'Noncompliant TernaryCases(If(1 > 18, (True), True)) 'Noncompliant End Sub Public Sub SingleLineIfCases(ByVal c As Boolean) Dim x As Integer If c Then x = 4 Else x = 4 'Noncompliant {{Remove this conditional structure or edit its code blocks so that they're not all the same.}} ' ^^ If c Then x = 0 Else x = 1 End Sub Private Sub DoSomething() End Sub Private Sub DoSomethingElse() End Sub End Class End Namespace ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AlwaysSetDateTimeKind.cs ================================================ using System; using System.Globalization; using MyAlias = System.DateTime; public class Program { public void Noncompliant() { var dt = new DateTime(); // Noncompliant {{Provide the "DateTimeKind" when creating this object.}} // ^^^^^^^^^^^^^^ dt = new DateTime(1623); // Noncompliant dt = new DateTime(1994, 07, 05); // Noncompliant dt = new DateTime(1994, 07, 05, new GregorianCalendar()); // Noncompliant dt = new DateTime(1994, 07, 05, 16, 23, 00); // Noncompliant dt = new DateTime(1994, 07, 05, 16, 23, 00, new GregorianCalendar()); // Noncompliant dt = new DateTime(1994, 07, 05, 16, 23, 00, 42); // Noncompliant dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, new GregorianCalendar()); // Noncompliant dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, new GregorianCalendar()); // Noncompliant dt = new MyAlias(); // FN dt = new System.DateTime(); // Noncompliant } public void Compliant() { var dt = new DateTime(1623, DateTimeKind.Unspecified); dt = new DateTime(1994, 07, 05, 16, 23, 00, DateTimeKind.Local); dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, new GregorianCalendar(), DateTimeKind.Unspecified); dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, DateTimeKind.Utc); dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, new GregorianCalendar(), DateTimeKind.Unspecified); dt = new DateTime(1994, 07, 05, 16, 23, 00, 42, DateTimeKind.Unspecified); dt = new System.(1623); // Error [CS1001] } } public class FakeDateTime { private class DateTime { } private void Compliant() { var dt = new DateTime(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AlwaysSetDateTimeKind.vb ================================================ Imports System.Globalization Imports MyAlias = System.DateTime Public Class Program Public Sub Noncompliant() Dim dt = New DateTime() ' Noncompliant {{Provide the "DateTimeKind" when creating this object.}} ' ^^^^^^^^^^^^^^ dt = New Date() ' Noncompliant dt = New dATEtIME() ' Noncompliant dt = New DateTime(1623) ' Noncompliant dt = New DateTime(1994, 7, 5) ' Noncompliant dt = New DateTime(1994, 7, 5, New GregorianCalendar()) ' Noncompliant dt = New DateTime(1994, 7, 5, 16, 23, 0) ' Noncompliant dt = New DateTime(1994, 7, 5, 16, 23, 0, New GregorianCalendar()) ' Noncompliant dt = New DateTime(1994, 7, 5, 16, 23, 0, 42) ' Noncompliant dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, New GregorianCalendar()) ' Noncompliant dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, New GregorianCalendar()) ' Noncompliant dt = New MyAlias() ' FN dt = New System.DateTime() ' Noncompliant End Sub Public Sub Compliant() Dim dt = New DateTime(1623, DateTimeKind.Unspecified) dt = New DateTime(1994, 7, 5, 16, 23, 0, DateTimeKind.Local) dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, New GregorianCalendar(), DateTimeKind.Unspecified) dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, DateTimeKind.Utc) dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, New GregorianCalendar(), DateTimeKind.Unspecified) dt = New DateTime(1994, 7, 5, 16, 23, 0, 42, DateTimeKind.Unspecified) End Sub End Class Class FakeDateTime Private Class DateTime End Class Private Sub Compliant() Dim dt = New DateTime() End Sub End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AnonymousDelegateEventUnsubscribe.Latest.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { class AnonymousDelegateEventUnsubscribe { public delegate void ChangedEventHandler(object sender, EventArgs e); public delegate void ChangedEventHandler2(object sender); public event ChangedEventHandler Changed; public event ChangedEventHandler2 Changed2; void Test_LambdaDiscard_StaticLambda() { Changed += (_, _) => { }; Changed -= (_, _) => { }; //Noncompliant Changed -= (_, _) => Console.WriteLine(); // Noncompliant Changed -= static (_, _) => Console.WriteLine("x"); // Noncompliant } void Test_LambdaDiscard_StaticLambda_Compliant() { ChangedEventHandler x = (_, _) => { }; Changed += x; Changed -= x; ChangedEventHandler2 y = static (sender) => { }; Changed2 += y; Changed2 -= y; } public void NullConditionalAssignment(WithEvent obj) { obj?.Changed += (_, _) => { }; obj?.Changed -= (_, _) => { }; // Noncompliant obj?.Changed -= (_, _) => Console.WriteLine(); // Noncompliant obj?.Changed -= static (_, _) => Console.WriteLine("x"); // Noncompliant } public class WithEvent { public event ChangedEventHandler Changed; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AnonymousDelegateEventUnsubscribe.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { class AnonymousDelegateEventUnsubscribe { public delegate void ChangedEventHandler(object sender, EventArgs e); public delegate void ChangedEventHandler2(object sender); public event ChangedEventHandler Changed; public event ChangedEventHandler2 Changed2; void Test() { Changed += (obj, args) => { }; Changed -= (obj, args) => { }; //Noncompliant {{Unsubscribe with the same delegate that was used for the subscription.}} // ^^^^^^^^^^^^^^^^^^^^^ Changed -= (obj, args) => Console.WriteLine(); // Noncompliant - single statement // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Changed -= delegate (object sender, EventArgs e) { }; // Noncompliant Changed2 -= delegate { }; // Noncompliant ChangedEventHandler x = (obj, args) => { }; Changed -= x; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/Corrupt/appsettings.json ================================================ { "ConnectionStrings": { ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/ArrayInside/appsettings.json ================================================ { "ConnectionStrings": [ {"DefaultConnection": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password="} ] } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/ConnectionStringComment/appsettings.json ================================================ { ////#if (IndividualLocalAuth) // "ConnectionStrings": { ////#if (UseLocalDB) // "DefaultConnection": "Server=(localdb)" ////#else // "DefaultConnection": "DataSource=app.db;Cache=Shared" ////#endif // }, ////#endif "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/EmptyArray/appsettings.json ================================================ [] ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/EmptyFile/appsettings.json ================================================  ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/Null/appsettings.json ================================================ { "ConnectionStrings": null } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/PropertyKinds/appsettings.json ================================================ { "ConnectionStrings": { "myDb1": "Server=myServer;Database=myDb1;Trusted_Connection=True;", "invalid1": [ "myServer", "myDb1" ], "invalid2": { "Server": "myServer", "Database": "myDb1" }, "invalid3": null, "invalid4": true } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/ValueKind/appsettings.json ================================================ { "ConnectionStrings": "Server=myServer;Database=myDb1;Trusted_Connection=True;" } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/UnexpectedContent/WrongStructure/appsettings.json ================================================ { "NotAConnectionString": { "DefaultConnection": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=" } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DatabasePasswordsShouldBeSecure/Values/appsettings.json ================================================ { "ConnectionStrings": { "DefaultConnection": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=", /* Noncompliant */ /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ "SomeOtherConnection": "PASSWORD=", /* FN */ "UnsecureConnection": "Password=", /* Noncompliant */ "AnotherConnection": "Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=FooBar", "SecuredConnection": "Server=myServerAddress;Database=myDataBase;Integrated Security=True" } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Corrupt/AppSettings.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Password=Secret123", <<< Corrupted, we don't raise here ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/UnexpectedContent/AppSettings.json ================================================ [ "S101", "S107" ] ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Valid/AppSettings.Custom.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Password=Secret123" /* Noncompliant {{"password" detected here, make sure this is not a hard-coded credential.}} */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Valid/AppSettings.Development.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Password=Secret123" /* Compliant, we don't raise in this file */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Valid/AppSettings.Production.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Password=Secret123" /* Noncompliant {{"password" detected here, make sure this is not a hard-coded credential.}} */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Valid/AppSettings.json ================================================ { "ConnectionStrings": { "fine": "", "name": "Server=localhost; Database=Test; User=SA; Password=Secret123", /* Noncompliant {{"password" detected here, make sure this is not a hard-coded credential.}} */ /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* Noncompliant@+2 */ "multiline": "Server=localhost; Database=Test; User=SA; Password=Secret123", "empty": "Server=localhost; Database=Test; User=SA; Password=", /* Compliant, should not raise on empty passwords */ "nopwd": "Server=localhost; Database=Test; Integrated Security=True" /* Compliant */ }, "AppSettings": { "connection": "Server=localhost; Database=Test; User=SA; Password=Secret123", /* Noncompliant */ "SomeUrl": "scheme://user:azerty123@domain.com" /* Noncompliant {{Review this hard-coded URI, which may contain a credential.}}" */ }, "CustomSection": { "CustomSubSection": { "Connection": "Server=localhost; Database=Test; User=SA; Password=Secret123" /* Noncompliant */ } }, "ValueArray": [ "InArray", "Server=localhost; Database=Test; User=SA; Password=Secret123", /* Noncompliant */ "Good", "Password=42" /* Noncompliant */ ], "ObjectArray": [ { "Nested": "Server=localhost; Database=Test; User=SA; Password=Secret123", /* Noncompliant */ "Simple": "Password=42", /* Noncompliant */ "Password": "42", /* Noncompliant */ "Compliant": "42" } ], "Simple": "Password=42", /* Noncompliant */ "Password": "42", /* Noncompliant */ "password": "42", /* Noncompliant */ "Compliant": "42", "Empty": { "Password": "" /* Compliant, this rule doesn't look for empty passwords */ }, "NotSupported": [ { "Password": [ "Not supported with nested arrays" ] }, { "Password": { "Key": "Not supported with nested object" } }, { "Password": true } ], "NumberFields": { "Field1": 42, "Field2": -42, "Int32Overflow": 2147483648, "Int32Underflow": -2147483649, "IntUnderFlow": -2147483649, "LongOverflow": 9223372036854775808, "LongUnderFlow": -9223372036854775809, "Password": 2147483648 /* FN */ } } /* Commented is not supported */ ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeCredentials/Valid/OtherFile.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Password=Secret123" /* We don't inspect this file */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Corrupt/AppSettings.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", <<< Corrupted, we don't raise here ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/UnexpectedContent/AppSettings.json ================================================ [ "S101", "S107" ] ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Valid/AppSettings.Custom.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* Noncompliant {{"Credential" detected here, make sure this is not a hard-coded secret.}} */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Valid/AppSettings.Development.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* Compliant, we don't raise in this file */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Valid/AppSettings.Production.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* Noncompliant {{"Credential" detected here, make sure this is not a hard-coded secret.}} */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Valid/AppSettings.json ================================================ { "ConnectionStrings": { "fine": "", "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant {{"Credential" detected here, make sure this is not a hard-coded secret.}} */ /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */ /* Noncompliant@+2 */ "multiline": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", "empty": "Server=localhost; Database=Test; User=SA; Password=", /* Compliant, should not raise on empty passwords */ "nopwd": "Server=localhost; Database=Test; Integrated Security=True" /* Compliant */ }, "AppSettings": { "connection": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* Noncompliant */ }, "CustomSection": { "CustomSubSection": { "Connection": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* Noncompliant */ } }, "ValueArray": [ "InArray", "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Good", "Auth=rf6acB24J//1FZLRrKpjmBUYSnUX5CHlt/iD5vVVcgVuAIOB6hzcWjDnv16V6hDLevW0Qs4hKPbP1M4YfuDI16sZna1/VGRLkAbTk6xMPs4epH6A3ZqSyyI-H92y" /* Noncompliant */ ], "ObjectArray": [ { "Nested": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Simple": "Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Credential": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Compliant": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" } ], "Simple": "Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Credential": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "credential": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", /* Noncompliant */ "Compliant": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=", "Empty": { "Token": "" /* Compliant, this rule doesn't look for empty Tokens */ }, "NotSupported": [ { "Token": [ "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" ] /*Not supported with nested arrays*/ }, { "Token": { "Key": "1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" } /*Not supported with nested object*/ }, { "Password": true } ], "NumberFields": { "Field1": 42, "Field2": -42, "Int32Overflow": 2147483648, "Int32Underflow": -2147483649, "IntUnderFlow": -2147483649, "LongOverflow": 9223372036854775808, "LongUnderFlow": -9223372036854775809, "credential": 2147483648 } } /* Commented is not supported */ ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AppSettings/DoNotHardcodeSecrets/Valid/OtherFile.json ================================================ { "ConnectionStrings": { "name": "Server=localhost; Database=Test; User=SA; Credential=1IfHMPanImzX8ZxC-Ud6+YhXiLwlXq$f_-3v~.=" /* We don't inspect this file */ } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArgumentSpecifiedForCallerInfoParameter.Latest.cs ================================================ using System.Runtime.CompilerServices; using System.Diagnostics; namespace Tests.Diagnostics { public class ArgumentSpecifiedForCallerInfoParameter { public void TraceMessage(bool condition, [CallerArgumentExpression("condition")] string expression = null) { } void MyMethod() { TraceMessage(true, "condition"); // Noncompliant TraceMessage(true); // Compliant TraceMessage(true, expression: "aaaa"); // Noncompliant } } public interface ISomeInterface { static abstract bool StaticVirtualMembersInInterfaces(bool flag, [CallerFilePath] string filePath = null); } public class SomeTestClass : ISomeInterface { public static bool StaticVirtualMembersInInterfaces(bool flag, [CallerFilePath] string filePath = null) => true; } public class SomeTestClass2 : ISomeInterface { public static bool StaticVirtualMembersInInterfaces(bool flag, [CallerFilePath] string filePath = null) => false; } public class SomeOtherClass { void MyMethod(T someTestClass) where T : ISomeInterface { T.StaticVirtualMembersInInterfaces(true, "C:"); // Noncompliant T.StaticVirtualMembersInInterfaces(false, filePath: "Something"); // Noncompliant T.StaticVirtualMembersInInterfaces(true); // Compliant } } // https://sonarsource.atlassian.net/browse/NET-1295 public class Repro_NET1252 { void MyMethod() { Debug.Assert(true, "message"); // Compliant, Debug.Assert is excluded } } public static class Extensions { extension(ArgumentSpecifiedForCallerInfoParameter obj) { void CallingFromExtension() { obj.TraceMessage(true, "condition"); // Noncompliant obj.TraceMessage(true); // Compliant obj.TraceMessage(true, expression: "aaaa"); // Noncompliant } } extension (ExtensionHasCallerInfo obj) { public bool ArgumentSpecifiedInExtension(bool flag, [CallerFilePath] string filePath = null) => true; } } public class ExtensionHasCallerInfo { void Test() { var instance = new ExtensionHasCallerInfo(); instance.ArgumentSpecifiedInExtension(true, "C:"); // Noncompliant instance.ArgumentSpecifiedInExtension(false, filePath: "Something"); // Noncompliant instance.ArgumentSpecifiedInExtension(true); // Compliant } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArgumentSpecifiedForCallerInfoParameter.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Tests.Diagnostics { class ArgumentSpecifiedForCallerInfoParameter { void TraceMessage(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) { /* ... */ } void MyMethod() { TraceMessage("my message", "MyMethod"); // Compliant "memberName" can be specified by the caller (e.g. raising OnPropertyChanged for another property in WPF) TraceMessage("my message"); // Compliant TraceMessage("my message", filePath: "aaaa"); // Noncompliant // ^^^^^^^^^^^^^^^^ TraceMessage("my message", lineNumber: 42); // Noncompliant TraceMessage("my message", filePath: "aaaa", // Noncompliant lineNumber: 42); // Noncompliant } void PassThrough(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) { TraceMessage(message, memberName, filePath, lineNumber); // Compliant TraceMessage(message, filePath: memberName); // Noncompliant parameters are switched TraceMessage(message, memberName: filePath); // Compliant "memberName" can be specified } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayCovariance.CSharp9.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { abstract class Fruit { } class Apple : Fruit { } class Orange : Fruit { } class Program { static void TargetTypedConditional(bool isTrue, Apple[] apples, Fruit[] givenFruits) { Fruit[] fruits1 = isTrue ? new Apple[1] // Noncompliant {{Refactor the code to not rely on potentially unsafe array conversions.}} // ^^^^^^^^^^^^ : new Orange[1]; // Noncompliant {{Refactor the code to not rely on potentially unsafe array conversions.}} // ^^^^^^^^^^^^^ Fruit[] fruits2 = apples ?? givenFruits; // Noncompliant // ^^^^^^ Fruit[] fruits3 = givenFruits ?? apples; // Noncompliant // ^^^^^^ AddToBasket(apples ?? givenFruits); // Noncompliant // ^^^^^^ AddToBasket(isTrue ? new Apple[1] : new Orange[1]); // Noncompliant // Noncompliant@-1 fruits1 = isTrue ? new Apple[1] : new Orange[1]; // Noncompliant // Noncompliant@-1 fruits1 = apples ?? givenFruits; // Noncompliant fruits1 = (Fruit[])(isTrue ? new Apple[1] : new Orange[1]); // Noncompliant // Noncompliant@-1 fruits1 = (Fruit[])(apples ?? givenFruits); // Noncompliant } static void AddToBasket(Fruit[] fruits) { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayCovariance.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { abstract class Fruit { } class Apple : Fruit { } class Orange : Fruit { } class Program { // Error@+1 [CS0029] public static object[] os = new int[0]; // Noncompliant {{Refactor the code to not rely on potentially unsafe array conversions.}} // ^^^^^^^^^^ public static object[] os2 = new object[0]; static void Main(string[] args) { Fruit[] fruits = new Apple[1]; // Noncompliant - array covariance is used // ^^^^^^^^^^^^ fruits = new Apple[1]; // Noncompliant FillWithOranges(fruits); var fruits2 = new Apple[1]; FillWithOranges(fruits2); // Noncompliant var fruits3 = (Fruit[])new Apple[1]; // Noncompliant } static void FillWithOranges(Fruit[] fruits) { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayCreationLongSyntax.Fixed.vb ================================================ Module Module1 Sub Main() Dim foo = {"a", "b", "c"} ' Fixed foo = New String() {} ' Compliant Dim foo2 = {} foo2 = {"a", "b", "c"} Dim foo3 = New A() {New B()} ' Compliant foo3 = {New B(), New A()} ' Fixed Dim myObjects As Object() = New Object(3) {} ' Compliant Dim guidStrings As String For Each guidString As String In guidStrings.Split({";"c}) ' Fixed Next myObjects = {} ' Fixed myObjects = New UnknownType() {1, 2, 3} ' Error [BC30002]: Type 'UnknownType' is not defined End Sub End Module Class A End Class Class B Inherits A End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayCreationLongSyntax.vb ================================================ Module Module1 Sub Main() Dim foo = New String() {"a", "b", "c"} ' Noncompliant {{Use an array literal here instead.}} ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ foo = New String() {} ' Compliant Dim foo2 = {} foo2 = {"a", "b", "c"} Dim foo3 = New A() {New B()} ' Compliant foo3 = New A() {New B(), New A()} ' Noncompliant Dim myObjects As Object() = New Object(3) {} ' Compliant Dim guidStrings As String For Each guidString As String In guidStrings.Split(New Char() {";"c}) ' Noncompliant Next myObjects = New Object() {} ' Noncompliant myObjects = New UnknownType() {1, 2, 3} ' Error [BC30002]: Type 'UnknownType' is not defined End Sub End Module Class A End Class Class B Inherits A End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayDesignatorOnVariable.Fixed.vb ================================================ Module Module1 Sub Main() Dim foo As String() ' Fixed Dim foo3(5) As String() ' Fixed Dim numbers1(4) As Integer Dim numbers2 = New Integer() {1, 2, 4, 8} ReDim Preserve numbers1(15) Dim matrix(5, 5) As Double Dim matrix2 As Double(,) ' Fixed Dim matrix3 As Double(,) ' Fixed Dim matrix4()(), aaa As Double Dim matrix5 As Double()() Dim sales As Double()() = New Double(11)() {} End Sub End Module ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayDesignatorOnVariable.vb ================================================ Module Module1 Sub Main() Dim foo() As String ' Noncompliant {{Move the array designator from the variable to the type.}} ' ^^^^^ Dim foo3(5)() As String ' Noncompliant Dim numbers1(4) As Integer Dim numbers2 = New Integer() {1, 2, 4, 8} ReDim Preserve numbers1(15) Dim matrix(5, 5) As Double Dim matrix2(,) As Double ' Noncompliant Dim matrix3 As Double(,) ' Noncompliant@+1 Dim matrix4()(), aaa As Double Dim matrix5 As Double()() Dim sales As Double()() = New Double(11)() {} End Sub End Module ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayInitializationMultipleStatements.vb ================================================ Module Module1 Sub Main() Const i As Integer = 0 Dim f2(i) As String ' Noncompliant {{Refactor this code to use the '... = {}' syntax.}} ' ^^^^^^^^^^^^^^^^^^^ f2(0) = "foo" Dim f As String Dim foo(1) As String ' Noncompliant foo(0) = "foo" foo(1) = "bar" foo = {"foo", "bar"} ' Compliant Dim foo2 As String(), foo4 As String() = {}, foo3(1) As String 'compliant, not a single VarDeclarator in the declaration foo3(0) = "foo" foo2(0) = "foo" foo2(1) = "bar" Dim f3(3) As String f3(0) = "foo" f3(2) = "foo" Dim f4(1) As String ' Noncompliant f4(-1) = "foo" f4(0) = "foo" f4(1) = "foo" End Sub End Module ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayPassedAsParams.Latest.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; const int a = 1; int[] array = [2, 3]; var class1 = new MyClass(1, [1, 2, 3]); // Noncompliant _ = new MyClass(1, []); // Noncompliant // repro for https://github.com/SonarSource/sonar-dotnet/issues/8510 _ = new MyClass(1, [a, .. array]); // Compliant _ = new MyClass2([1], [1, 2, 3]); // Noncompliant _ = new MyClass2([1, 2, 3], 1); _ = new MyClass3([1, 2, 3], [4, 5, 6]); // Compliant: jagged array _ = new MyClass4(class1, new(1, [1, .. array])); // Compliant _ = new MyClass4([class1, new(1, [1, .. array])]); // Noncompliant, outer collection raises, despite the nested spread operator // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ MyClass5 s = new(1, new int[] { 2, 3 }); // Noncompliant // ^^^^^^^^^^^^^^^^^^ MyClass5 s1 = new(1, 2, 3); // Compliant A(new int[] { 1, 2 }); // Noncompliant A([1]); // Noncompliant A(1, 2); // Compliant A(2); // Compliant B(new int[] { 1, 2 }); // Noncompliant B([1]); // Noncompliant B(1, 2); // Compliant B(2); // Compliant C(new int[] { 1, 2 }); // Noncompliant C([1]); // Noncompliant C(1, 2); // Compliant C(2); // Compliant D(new int[] { 1, 2 }); // Noncompliant D([1]); // Noncompliant D(1, 2); // Compliant D(2); // Compliant I(MyImmutableArray.Create(new int[] { 1, 2 })); // Compliant I(MyImmutableArray.Create([1, 2])); // Compliant I([1, 2]); // Noncompliant I(2); // Compliant static bool A(params int[] array) => true; static bool B(params Span span) => true; static bool C(params ReadOnlySpan span) => true; static bool D(params IEnumerable enumerable) => true; static bool E(params IReadOnlyCollection readonlyCollection) => true; static bool F(params IReadOnlyList readonlyList) => true; static bool G(params ICollection collection) => true; static bool H(params IList list) => true; static bool I(params MyImmutableArray collectionBuilder) => true; class MyClass(int a, params int[] args); class MyClass2(int[] a, params int[] args); class MyClass3(params int[][] args); class MyClass4(params MyClass[] args); class MyClass5 { public MyClass5(int a, params int[] args) { } } static class MyImmutableArray { public static MyImmutableArray Create(ReadOnlySpan items) => []; } [CollectionBuilder(typeof(MyImmutableArray), "Create")] public struct MyImmutableArray : IEnumerable { IEnumerator IEnumerable.GetEnumerator() => default!; IEnumerator IEnumerable.GetEnumerator() => default!; } // Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/6977 public class Repro6977_CollectionExpression { class ParamsAttribute : Attribute { public ParamsAttribute(params string[] values) { } public ParamsAttribute(int a, string b, params string[] values) { } } internal enum Foo { [Params(["1", "2" ])] // Noncompliant Red, [Params("1", "2")] Yellow } } public class ImplicitSpanConversion { public static void Test(string[] myArray) { DoSomething(new string[] { "s1", "s2" }); // Noncompliant {{Remove this array creation and simply pass the elements.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ DoSomething(new string[] { "s1" }); // Noncompliant DoSomething(new[] { "s1" }); // Noncompliant DoSomething(new string[] { }); // Noncompliant DoSomething("s1"); // Compliant DoSomething("s1", "s2"); // Compliant DoSomething(myArray); // Compliant DoSomething(new string[12]); // Compliant DoSomething(["1", "2", "3"]); // Noncompliant DoSomething(["1", "2", "3"].ToArray()); // Compliant // Error@-1 [CS9176] There is no target type for the collection expression. } public static void DoSomething(params Span arr) { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayPassedAsParams.cs ================================================ using System; using System.Globalization; public class Program { public void Base(string[] myArray) { Method(new string[] { "s1", "s2" }); // Noncompliant {{Remove this array creation and simply pass the elements.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Method(new string[] { "s1" }); // Noncompliant Method(new[] { "s1" }); // Noncompliant Method(new string[] { }); // Noncompliant Method("s1"); // Compliant Method("s1", "s2"); // Compliant Method(myArray); // Compliant Method(new string[12]); // Compliant Method2(1, new string[] { "s1", "s2" }); // Noncompliant {{Remove this array creation and simply pass the elements.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Method2(1, new string[] { "s1" }); // Noncompliant Method2(1, "s1"); // Compliant Method2(1, "s1", "s2"); // Compliant Method2(1, myArray); // Compliant Method2(1, new string[12]); // Compliant Method3(new string[] { "s1", "s2" }); // Compliant Method3(new string[] { "s1", "s2" }, "s1"); // Compliant Method3(new string[] { "s1", "s2" }, new string[12]); // Compliant Method3(new string[] { "s1", "s2" }, new string[] { "s1", "s2" }); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Method3(null, null); // Compliant Method4(new[] { "s1" }); // Compliant Method4(new[] { "s1", "s2" }); // Compliant Method3(args: new string[] { "s1", "s2" }, a: new string[12]); // Compliant (if you specifically require the arguments to be passed in this order there is no way of making this compliant, thus we shouldn't raise) Method3(args: new string[12], a: new string[] { "s1", "s2" }); // Compliant var s = new MyClass(1, new int[] { 2, 3 }); // Noncompliant // ^^^^^^^^^^^^^^^^^^ var s1 = new MyClass(1, 2, 3); // Compliant s1 = new MyClass(args: new int[] { 2, 3 }, a: 1); // Compliant (if you specifically require the arguments to be passed in this order there is no way of making this compliant, thus we shouldn't raise) var s2 = new MyOtherClass(args: new int[12], a: new int[] { 2, 3 }); // Compliant var s3 = new IndexerClass(); var indexer1 = s3[new int[] { 1, 2 }]; // FN var indexer2 = s3?[new int[] { 1, 2 }]; // FN var indexer3 = s3[1, 2]; // Compliant } public void Method(params string[] args) { } public void Method2(int a, params string[] args) { } public void Method3(string[] a, params string[] args) { } public void Method4(object[] a, params object[] args) { } public void Method5(params string[] a, params string[] args) { } // Error [CS0231] } public class MyClass { public MyClass(int a, params int[] args) { } } public class MyOtherClass { public MyOtherClass(int[] a, params int[] args) { } } public class IndexerClass { public int this[params int[] i] => 1; } public class Repro6894 { //Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/6894 public void Method(params object[] args) { } public void MethodMixed(int i, params object[] args) { } public void MethodArray(params Array[] args) { } public void MethodJaggedArray(params int[][] args) { } public void MethodImplicitArray(params string[] args) { } public void CallMethod(dynamic d) { Method(new String[] { "1", "2" }); // Noncompliant, elements in args: ["1", "2"] // The argument given for a parameter array can be a single expression that is implicitly convertible (§10.2) to the parameter array type. // In this case, the parameter array acts precisely like a value parameter. // see: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#14625-parameter-arrays Method(new object[] { new int[] { 1, 2 } }); // FN, elements in args: [System.Int32[]] Method(new int[] { 1, 2, 3, }); // Compliant, Elements in args: [System.Int32[]] Method(new String[] { "1", "2" }, new String[] { "1", "2" }); // Compliant, elements in args: [System.String[], System.String[]] Method(new String[] { "1", "2" }, new int[] { 1, 2 }); // Compliant, elements in args: [System.String[], System.Int32[]] MethodMixed(1, new String[] { "1", "2" }); // Noncompliant MethodArray(new String[] { "1", "2" }, new String[] { "1", "2" }); // Compliant, elements in args: [System.String[], System.String[]] MethodArray(new int[] { 1, 2 }, new int[] { 1, 2 }); // Compliant, elements in args: [System.Int32[], System.Int32[]] MethodJaggedArray(new int[] { 1, 2 }); // Compliant: jagged array [System.Object[]] Method(d); // Compliant Method("Hello", 2); // Compliant string.Format(CultureInfo.InvariantCulture, "{0}.{1}", new object[] { "", new object() }); MethodImplicitArray(new[] { "Hello", "Hi" }); // Noncompliant , Implicit array creation Method(new[] { 1, 2, 3, }); // Compliant, Elements in args: [System.Int32[]] Method(new[,] { { 1, 2 } }); // Compliant, Elements in args: [System.Int32[,]] Method(new object[] { null }); // Compliant Method(new object[,] { { null } }); // Compliant Method(new[,] { { new object() } }); // Compliant Method(new object[][] { new[] { new object() } }); // Compliant } } // Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/6893 public class Repro6893 { public void Method(int a, params object[] argumentArray) { } public void CallMethod() { Method(a: 1, argumentArray: new int[] { 1, 2 }); // Compliant } } // Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/6977 public class Repro6977 { class ParamsAttribute : Attribute { public ParamsAttribute(params string[] values) { } public ParamsAttribute(int a, string b, params string[] values) { } public ParamsAttribute() { } public string Name { get; set; } public object Other { get; set; } } internal enum Foo { [Params(new[] { "1", "2" })] // Noncompliant Red, [Params("1", "2")] Yellow, [Params(a: 1, b: "hello", values : new[] { "1", "2" })] // Noncompliant Blue, [Params(a: 1, values: new[] { "1", "2" }, b: "hello")] // FN Green, [Params] Violet, [Params(Name = "hello", Other = new[] { "1", "2" })] // Compliant, this is setting Properties Indigo, [Params(a: 1, b: "hello", values: new[] { "1", "2" }, Name = "hello")] // Compliant. Before C# 7.2 the 'correct' version does not compile Orange, [Params(1,"hello", new[] { "1", "2" }, Name = "hello")] // Compliant FN Grun, [Params(1, "hello", "1", "2", Name = "hello")] // Compliant OtherGrun } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/ArrayPassedAsParams.vb ================================================  Public Class Program Public Sub Base(ByVal myArray As String()) Method(New String() {"s1", "s2"}) ' Noncompliant {{Remove this array creation and simply pass the elements.}} ' ^^^^^^^^^^^^^^^^^^^^^^^^^ Method(New String() {"s1"}) ' Noncompliant Method("s1") ' Compliant Method("s1", "s2") ' Compliant Method(myArray) ' Compliant Method(New String(11) {}) ' Compliant Method2(1, New String() {"s1", "s2"}) ' Noncompliant {{Remove this array creation and simply pass the elements.}} ' ^^^^^^^^^^^^^^^^^^^^^^^^^ Method2(1, New String() {"s1"}) ' Noncompliant Method2(1, "s1") ' Compliant Method2(1, "s1", "s2") ' Compliant Method2(1, myArray) ' Compliant Method2(1, New String(11) {}) ' Compliant Method3(New String() {"s1", "s2"}, "s1") ' Compliant Method3(New String() {"s1", "s2"}, New String(11) {}) ' Compliant Method3(New String() {"s1", "s2"}, New String() {"s1", "s2"}) ' Noncompliant ' ^^^^^^^^^^^^^^^^^^^^^^^^^ Method3(Nothing, Nothing) ' Compliant Method3(args:=New String() {"s1", "s2"}, a:=New String(11) {}) ' Error [BC30587] Named argument cannot match a ParamArray parameter Method3(args:=New String(11) {}, a:=New String() {"s1", "s2"}) ' Error [BC30587] Named argument cannot match a ParamArray parameter Dim s = New [MyClass](1, New Integer() {2, 3}) ' Noncompliant ' ^^^^^^^^^^^^^^^^^^^^ Dim s1 = New [MyClass](1, 2, 3) ' Compliant s1 = New [MyClass](args:=New Integer() {2, 3}, a:=1) ' Error [BC30587] Named argument cannot match a ParamArray parameter Dim s2 = New MyOtherClass(args:=New Integer(11) {}, a:=New Integer() {2, 3}) ' Error [BC30587] Named argument cannot match a ParamArray parameter Dim s3 = Prop(New String() {"s1", "s2"}) ' FN Dim s4 = Prop("s1", "s2") ' Compliant End Sub Public Sub Method(ParamArray args As String()) End Sub Public Sub Method2(ByVal a As Integer, ParamArray args As String()) End Sub Public Sub Method3(ByVal a As String(), ParamArray args As String()) End Sub Public Sub Method4(ParamArray a As String(), ParamArray args As String()) 'Error [BC30192] End Sub Public ReadOnly Property Prop(ParamArray param() As String) As Integer Get End Get End Property End Class Public Class [MyClass] Public Sub New(ByVal a As Integer, ParamArray args As Integer()) End Sub End Class Public Class MyOtherClass Public Sub New(ByVal a As Integer(), ParamArray args As Integer()) End Sub End Class Public Class Repro6894 'Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/6894 Public Sub Method(ParamArray args As Object()) End Sub Public Sub MethodArray(ParamArray args As Array()) End Sub Public Sub MethodJaggedArray(ParamArray args As Integer()()) End Sub Public Sub CallMethod() Method(New String() {"1", "2"}) ' Noncompliant, elements in args: ["1", "2"] ' The argument given for a parameter array can be a single expression that is implicitly convertible (§10.2) to the parameter array type. ' In this case, the parameter array acts precisely like a value parameter. ' see: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#14625-parameter-arrays Method({"1", "2"}) ' FN Method(New Object() {New Integer() {1, 2}}) ' FN, elements in args: [System.Int32[]] Method(New Integer() {1, 2, 3}) ' Compliant, Elements in args: [System.Int32[]] Method(New String() {"1", "2"}, New String() {"1", "2"}) ' Compliant, elements in args: [System.String[], System.String[]] Method(New String() {"1", "2"}, New Integer() {1, 2}) ' Compliant, elements in args: [System.String[], System.Int32[]] MethodArray(New String() {"1", "2"}, New String() {"1", "2"}) ' Compliant, elements in args: [System.String[], System.String[]] MethodArray(New Integer() {1, 2}, New Integer() {1, 2}) ' Compliant, elements in args: [System.Int32[], System.Int32[]] MethodArray({1, 2}, {1, 2}) ' Compliant, elements in args: [System.Int32[], System.Int32[]] MethodJaggedArray(New Integer() {1, 2}) ' Compliant: jagged array [System.Object[]] End Sub End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/AnnotateApiActionsWithHttpVerb.cs ================================================ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using System; using System.Collections.Generic; namespace Baseline { [ApiController] public class NoncompliantController : ControllerBase { [Route("foo")] public string FooGet() => "Hi"; // Noncompliant {{REST API controller actions should be annotated with the appropriate HTTP verb attribute.}} // ^^^^^^ public int FooPost([FromBody] string id) => // Noncompliant StatusCodes.Status200OK; } [ApiController] public class CompliantController : ControllerBase { [HttpGet("foo")] public string Get() => "Hi"; [HttpPut("foo")] public int Put([FromBody] string id) => StatusCodes.Status200OK; [HttpPost("foo")] public int Post([FromBody] string id) => StatusCodes.Status200OK; [HttpDelete("foo")] public int Delete([FromBody] string id) => StatusCodes.Status200OK; [HttpPatch("foo")] public int Patch([FromBody] string id) => StatusCodes.Status200OK; [HttpHead("foo")] public int Head([FromBody] string id) => StatusCodes.Status200OK; [Route("foo")] [HttpOptions] public int Options([FromBody] string id) => StatusCodes.Status200OK; [AcceptVerbs("GET", "POST")] [Route("test")] public int AcceptVerbs([FromBody] string id) => StatusCodes.Status200OK; [Route("Error")] [ApiExplorerSettings(IgnoreApi = true)] public int IgnoresApi([FromBody] string id) => StatusCodes.Status200OK; } } namespace CustomHttpMethods { [ApiController] public class CompliantController : ControllerBase { [Route("foo")] [MyHttpGet] public int MyGet(string id) => StatusCodes.Status200OK; [Route("foo")] [MyHttpMethod] public int MyMethod() => StatusCodes.Status200OK; } public class MyHttpGetAttribute : HttpGetAttribute { } public class MyHttpMethodAttribute : HttpMethodAttribute { public MyHttpMethodAttribute() : base(null) { } } } namespace ApiExplorer { [ApiController] [ApiExplorerSettings(IgnoreApi = true)] public class ExcludedFromOpenApiController : ControllerBase { [Route("foo")] public string FooGet() => "Hi"; public int FooPost([FromBody] string id) => StatusCodes.Status200OK; } [ApiController] [ApiExplorerSettings(IgnoreApi = false)] public class MarkedWithApiExplorerButIgnoreApiSpecifiedAsFalse : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } [ApiController] [ApiExplorerSettings] public class MarkedWithApiExplorerButIgnoreApiUnspecified : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } [ApiController] [ApiExplorerSettings(GroupName = "group")] public class MarkedWithApiExplorerButIgnoreApiUnspecified2 : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } namespace CustomApiExplorer { class MyApiExplorerSettingsAttribute : ApiExplorerSettingsAttribute { } [ApiController] [MyApiExplorerSettings(IgnoreApi = true)] public class MarkedWithMyApiExplorer : ControllerBase { [Route("foo")] public string FooGet() => "hi"; } [ApiController] [MyApiExplorerSettings(IgnoreApi = false)] public class MarkedWithMyApiExplorerButIgnoreApiSpecifiedAsFalse : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } [ApiController] [MyApiExplorerSettings()] public class MarkedWithMyApiExplorerButIgnoreApiUnspecified : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } } } namespace Visibility { [ApiController] internal class NotPublicClass : ControllerBase { [Route("foo")] public string FooGet() => "Hi"; } [ApiController] public class NotPublicMethod : ControllerBase { [Route("foo")] protected string FooGet() => "Hi"; } } namespace ControllerAnnotations { [ApiController] [Controller] public class AnnotatedAsController { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } [NonController] [ApiController] public class NotAController : ControllerBase { [Route("foo")] public string FooGet() => "Hi"; } [ApiController] [Controller] [NonController] public class AnnotatedAsControllerAndNonController { [Route("foo")] public string FooGet() => "hi"; } public class NotApiController : ControllerBase { [Route("foo")] public string FooGet() => "hi"; } [Controller] public class NotApiController2 { [Route("foo")] public string FooGet() => "hi"; } [MyApiController] public class AnnotatedWithCustomApiController : ControllerBase { [Route("foo")] public string FooGet() => "hi"; // Noncompliant } class MyApiControllerAttribute : ApiControllerAttribute { } } namespace Inheritance { [ApiController] public class NotDerivingController { [Route("foo")] public string FooGet() => "hi"; } [ApiController] public class DerivingController : Controller { [Route("foo")] public string Foo() => "hi"; // Noncompliant public string Get() => "hi"; // Noncompliant } [ApiController] public class DerivingMyController : MyController { [Route("foo")] public string Foo() => "hi"; // Noncompliant public string Get() => "hi"; // Noncompliant } public class MyController : Controller { } [ApiController] public abstract class BaseController : ControllerBase { [Route("foo")] public virtual string BaseHasNoMethod() => "hi"; // Noncompliant [HttpGet("foo")] public virtual string BaseHasMethod() => "hi"; [AcceptVerbs("GET")] public virtual string BaseHasAcceptVerbs() => "hi"; [ApiExplorerSettings()] public virtual string BaseHasApiExplorerSettingsWithoutIgnoring() => "hi"; // Noncompliant [ApiExplorerSettings(IgnoreApi = true)] public virtual string BaseHasApiExplorerSettingsIgnoring() => "hi"; public abstract string BaseAbstractMethod(); } public class ImplementationNoBehaviorChangeController : BaseController { public override string BaseHasNoMethod() => "hi"; // Noncompliant public override string BaseHasMethod() => "hi"; public override string BaseHasAcceptVerbs() => "hi"; public override string BaseHasApiExplorerSettingsWithoutIgnoring() => "hi"; // Noncompliant public override string BaseHasApiExplorerSettingsIgnoring() => "hi"; public override string BaseAbstractMethod() => "hi"; // Noncompliant } public class ImplementationBehaviorChangedController : BaseController { [HttpGet] public override string BaseHasNoMethod() => "hi"; public override string BaseHasAcceptVerbs() => "hi"; [ApiExplorerSettings(IgnoreApi = true)] public override string BaseHasApiExplorerSettingsWithoutIgnoring() => "hi"; [ApiExplorerSettings(IgnoreApi = false)] public override string BaseHasApiExplorerSettingsIgnoring() => "hi"; // Noncompliant [HttpGet] public override string BaseAbstractMethod() => "hi"; } } namespace CustomHttpAttribute { // https://sonarsource.atlassian.net/browse/NET-3606 [ApiController] public class CompliantController : ControllerBase { [HttpRouteGet("foo")] public int MyGet(string id) => // Noncompliant FP: the attribute implements IActionHttpMethodProvider StatusCodes.Status200OK; } public class HttpRouteGetAttribute : Attribute, IActionHttpMethodProvider { public HttpRouteGetAttribute(string template) => Template = template; public IEnumerable HttpMethods => new List { "GET" }; public string Template { get; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ApiControllersShouldNotDeriveDirectlyFromController.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; class ChildAttribute : ApiControllerAttribute { } class GrandChildAttribute : ChildAttribute { } public class ParentController : Controller { } public class ParentControllerBase : ControllerBase { } public class ChildController : ParentController { } public class ChildControllerBase : ParentControllerBase { } namespace SimpleCases { [ApiController] public class Baseline : Controller { } // Noncompliant {{Inherit from ControllerBase instead of Controller.}} // ^^^^^^^^^^ [ApiController] public class ControllerWithInterface : Controller, ITestInterface { } // Noncompliant // ^^^^^^^^^^ [ApiController] public class ChildOfBase : ControllerBase { } // Compliant public class WithoutAttribute : Controller { } // Compliant public class PocoController { } // Compliant [ApiController] public class PocoWithApiAttribute { } // Compliant [Controller] public class PocoWithControllerAttribute { } // Compliant [ApiController] [Controller] public class PocoWithBothAttributes { } // Compliant, this is a very rare case, found <10 hits for [Controller] on SG [ApiController] [NonController] public class NotAController : Controller { } // Compliant, [NonController] is excluded [ApiController] internal class Internal : Controller { } // Compliant, only raises at public methods public interface ITestInterface { } } namespace SpecialAttributeUsages { [type: ApiController] public class WithType : Controller { } // Noncompliant // ^^^^^^^^^^ [ApiControllerAttribute] public class WithSuffix : Controller { } // Noncompliant // ^^^^^^^^^^ [ApiController()] public class WithParentheses : Controller { } // Noncompliant // ^^^^^^^^^^ [type: ApiControllerAttribute()] public class Everything : Controller { } // Noncompliant // ^^^^^^^^^^ } namespace Inheritance { [ApiController] public class Child : ParentController { } // Compliant, we only check direct inheritance from "Controller" [ApiController] public class GrandChild : ChildController { } // Compliant [ApiController] public class NoInheritance { } // Compliant } namespace CustomAttribute { [ChildAttribute] public class UsesChildAttribute : Controller { } // Noncompliant // ^^^^^^^^^^ [GrandChildAttribute] public class UsesGrandChildAttribute : Controller { } // Noncompliant // ^^^^^^^^^^ [ChildAttribute] public class UsesChildAttributeBase : ControllerBase { } // Compliant [GrandChildAttribute] public class UsesGrandChildAttributeBase : ControllerBase { } // Compliant } namespace Partial { [ApiController] public partial class Partial { } public partial class Partial : Controller { } // Noncompliant // ^^^^^^^^^^ [ApiController] public partial class PartialBase { } public partial class PartialBase : ControllerBase { } // Compliant [ApiController] public partial class PartialDoubleInheritance : Controller { } // Noncompliant // ^^^^^^^^^^ public partial class PartialDoubleInheritance : Controller { } // Noncompliant // ^^^^^^^^^^ } namespace Nested { public class Outer { [ApiController] public class Inner : Controller { } // Compliant, is in nested class - not accessible from the user. } } namespace MemberUsages { [ApiController] public class OverrideViewInvocation : Controller // Compliant { public object Foo() => this.View(); // overrides and uses View public override ViewResult View() => null; } [ApiController] public class FakeViewInvocation : Controller // FN, it's not an actual view { public object Foo() => this.View(); // hides View, does not override it public ViewResult View() => null; } [ApiController] public class BaseViewInvocation : Controller // Compliant { public object Foo() => base.View(); // uses Controller.View public ViewResult View() => null; } [ApiController] public partial class Partial { } public partial class Partial : Controller { } // Compliant public partial class Partial { public object Foo() => View(); } [ApiController] public class MemberReference : Controller // Compliant { public Func NotCalled() => this.View; // nothing is invoked, but the dependency is used. } [ApiController] public class NameOf : Controller // Compliant { public object Foo() => nameof(View); // same as above } [ApiController] public class PassByName : Controller // Compliant { public void Foo() => ExpectsAction(View); public void ExpectsAction(Func func) { // here func could be invoked or not. } } [ApiController] public class PassByLambda : Controller // Compliant { public void Foo() => ExpectsAction(() => View()); public void ExpectsAction(Func func) { // here func could be invoked or not. } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.Fixed.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace CodeFixCases { [ApiController] public class Baseline : ControllerBase { } [ApiController] public class SimpleController : ControllerBase { } // Fixed [ApiController] public class CodeFixRespectsCommentsController : ControllerBase /* I'm a small comment and I wish to be respected */ { } // Fixed [ApiController] public class CodeFixRespectsCommentsAlsoHasInterfaceController : ControllerBase /* Ditto */, ITestInterface { } // Fixed [ApiController] public class ControllerWithInterface : ControllerBase, ITestInterface { } // Fixed public interface ITestInterface { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ApiControllersShouldNotDeriveDirectlyFromControllerCodeFix.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace CodeFixCases { [ApiController] public class Baseline : ControllerBase { } [ApiController] public class SimpleController : Controller { } // Noncompliant [ApiController] public class CodeFixRespectsCommentsController : Controller /* I'm a small comment and I wish to be respected */ { } // Noncompliant [ApiController] public class CodeFixRespectsCommentsAlsoHasInterfaceController : Controller /* Ditto */, ITestInterface { } // Noncompliant [ApiController] public class ControllerWithInterface : Controller, ITestInterface { } // Noncompliant public interface ITestInterface { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/AvoidUnderPosting.AutogeneratedModel.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ // Repro https://github.com/SonarSource/sonar-dotnet/issues/9260 // Also related to https://github.com/SonarSource/sonar-dotnet/issues/8876 public partial class AutogeneratedModel { public int Property // Noncompliant, FP - we should not raise in autogenerated classes. { get; set; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/AvoidUnderPosting.Latest.Partial.cs ================================================ using System.Text.Json.Serialization; namespace CSharp13 { public partial class PartialPropertyClass { public partial int PartialProperty // Noncompliant { get; set; } public partial int HasAttributePartialProperty // Compliant { get; set; } [JsonRequired] public partial int HasAttributePartialPropertyOther // Compliant { get; set; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/AvoidUnderPosting.Latest.cs ================================================ using Microsoft.AspNetCore.Mvc; using System; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; namespace CSharp12 { public class ModelWithPrimaryConstructor(int vp, int rvp, int nvp) { public int ValueProperty { get; set; } = vp; // Compliant: no parameterless constructor, type cannot be used for Model Binding } public class ModelWithPrimaryAndParameterlessConstructor(int vp, int rvp, int nvp) { public ModelWithPrimaryAndParameterlessConstructor() : this(0, 0, 0) { } public int ValueProperty { get; set; } = vp; // Compliant - the property has default value } public class DerivedFromController : Controller { [HttpPost] public IActionResult Create(ModelWithPrimaryConstructor model) => View(model); [HttpDelete] public IActionResult Remove(ModelWithPrimaryAndParameterlessConstructor model) => View(model); } } namespace Repro9275 { // Repro https://github.com/SonarSource/sonar-dotnet/issues/9275 public class Model { public int ValueProperty { get; set; } // Noncompliant [Custom] public int ValuePropertyAnnotatedWithCustomAttribute { get; set; } // Noncompliant [JsonRequired] // Compliant - the property is annotated with JsonRequiredAttribute public int AnotherValueProperty { get; set; } public required int RequiredProperty { get; set; } // Compliant - because the property has the required modifier } public class DerivedFromController : Controller { [HttpPost] public IActionResult Create(Model model) => View(model); } public class CustomAttribute : Attribute { } } namespace CSharp9 { // https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding#constructor-binding-and-record-types public record RecordModel( int ValueProperty, // Noncompliant [property: JsonRequired] int RequiredValueProperty, // Without the property prefix the attribute would have been applied to the constructor parameter instead [Range(0, 2)] int RequiredValuePropertyWithoutPropertyPrefix, // Noncompliant, FP the attribute is applied on the parameter and still works for the property see: https://github.com/SonarSource/sonar-dotnet/issues/9363 int? NullableValueProperty, int PropertyWithDefaultValue = 42); public class ModelUsedInController { public int PropertyWithInit { get; init; } // Noncompliant } public class DerivedFromController : Controller { [HttpGet] public IActionResult Read(RecordModel model) => View(model); [HttpPost] public IActionResult Create(ModelUsedInController model) => View(model); } } namespace CSharp8 { namespace NullableReferences { public class ModelUsedInController { #nullable enable public string NonNullableReferenceProperty { get; set; } // Compliant - the JSON serializer will throw an exception if the value is missing from the request public object AnotherNonNullableReferenceProperty { get; set; } [Required] public string RequiredNonNullableReferenceProperty { get; set; } public string? NullableReferenceProperty { get; set; } public int ValueProperty { get; set; } // Noncompliant public int? NullableValueProperty { get; set; } #nullable disable public string ReferenceProperty { get; set; } public object AnotherReferenceProperty { get; set; } public int? AnotherNullableValueProperty { get; set; } } public class DerivedFromController : Controller { [HttpPost] public IActionResult Create(ModelUsedInController model) { return View(model); } } } namespace CustomGenerics { public class GenericType where TClass : class where TStruct : struct where TNotNull : notnull { public TNoContstraint NoConstraintProperty { get; set; } public TClass ClassProperty { get; set; } public TStruct StructProperty { get; set; } // Noncompliant [JsonRequired] public TStruct RequiredStructProperty { get; set; } public TStruct? NullableStructProperty { get; set; } public TNotNull NotNullProperty { get; set; } // Noncompliant } public class ControllerClass : Controller { [HttpPost] public IActionResult Create(GenericType model) => View(model); } } } namespace CSharp13 { public partial class PartialPropertyClass { private int _testProperty { get; set; } public int TestProperty //Noncompliant { get { return _testProperty; } set { _testProperty = value; } } private int _partialProperty; public partial int PartialProperty // Compliant - raises on the definition { get => _partialProperty; set { _partialProperty = value; } } [JsonRequired] public partial int HasAttributePartialProperty // Compliant { get => _partialProperty; set { } } public partial int HasAttributePartialPropertyOther // Compliant { get => _partialProperty; set { } } } public class DerivedFromController : Controller { [HttpPost] public IActionResult Create(PartialPropertyClass model) { return View(model); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/AvoidUnderPosting.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Newtonsoft.Json; using System.Text.Json.Serialization; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.Design; namespace Basics { public class ClassNotUsedInRequests { int ValueProperty { get; set; } // Compliant } public class ModelUsedInController { public int ValueProperty { get; set; } // Noncompliant {{Value type property used as input in a controller action should be nullable, required or annotated with the JsonRequiredAttribute to avoid under-posting.}} // ^^^^^^^^^^^^^ public int? NullableValueProperty { get; set; } [Required] public int RequiredValueProperty { get; set; } // Noncompliant, RequiredAttribute has no effect on value types [Range(0, 10)] public int ValuePropertyWithRangeValidation { get; set; } // Compliant [Required] public int? RequiredNullableValueProperty { get; set; } [JsonProperty(Required = Required.Always)] public int JsonRequiredValuePropertyAlways { get; set; } // Compliant [JsonProperty(Required = Required.AllowNull)] public int JsonRequiredValuePropertyAllowNull { get; set; } // Compliant [JsonProperty(Required = Required.DisallowNull)] public int JsonRequiredValuePropertyDisallowNull { get; set; } // Noncompliant [JsonProperty] public int JsonRequiredValuePropertyDefault { get; set; } // Noncompliant [Newtonsoft.Json.JsonIgnore] public int JsonIgnoredProperty { get; set; } // Compliant [Newtonsoft.Json.JsonRequired] public int JsonRequiredNewtonsoftValueProperty { get; set; } // Compliant [System.Text.Json.Serialization.JsonRequired] public int JsonRequiredValueProperty { get; set; } // Compliant [System.Text.Json.Serialization.JsonIgnore] public int JsonIgnoreValueProperty { get; set; } // Compliant [JsonProperty(Required = Required.AllowNull)] [FromQuery] public int PropertyWithMultipleAttributesCompliant { get; set; } // Compliant [Required] [FromQuery] public int PropertyWithMultipleAttributesNonCompliant { get; set; } // Noncompliant public int PropertyWithPrivateSetter { get; private set; } protected int ProtectedProperty { get; set; } internal int InternalProperty { get; set; } protected internal int ProtectedInternalProperty { get; set; } private int PrivateProperty { get; set; } public int PropertyWithDefaultValue { get; set; } = 42; public int ReadOnlyProperty => 42; public int field = 42; public (int, int) ImplicitTuple { get; set; } // Compliant - tuple types are not supported in model binding public Tuple TupleProperty { get; set; } public ValueTuple ValueTupleProperty { get; set; } public string ReferenceProperty { get; set; } public dynamic DynamicProprty { get; set; } } public class NoDefaultConstructor { public int ValueProperty { get; set; } // Compliant - non-record types cannot be used in model binding without having a default constructor public NoDefaultConstructor(int arg) { } } public class ExplicitDefaultConstructor { public int ValueProperty { get; set; } // Noncompliant public ExplicitDefaultConstructor() { } public ExplicitDefaultConstructor(int arg) { } } public class DerivedFromController : Controller { [HttpPost] public IActionResult Create(ModelUsedInController model) => View(model); [HttpPut] public IActionResult Update(NoDefaultConstructor model) => View(model); [HttpDelete] public IActionResult Remove(ExplicitDefaultConstructor model) => View(model); private void NotActionMethod(ClassNotUsedInRequests arg) { } } } namespace HttpVerbs { public class SingleModel { public int Property { get; set; } } // Noncompliant public class MultipleModel { public int Property { get; set; } } // Noncompliant public class VerbModel { public int Property { get; set; } } // Noncompliant public class MultipleVerbsModel { public int Property { get; set; } } // Noncompliant [ApiController] [Route("api/[controller]")] public class DecoratedWithApiControlerAttribute : ControllerBase { [HttpGet] public int Single(SingleModel model) => 42; [HttpGet] [HttpPost] [HttpPut] [HttpDelete] public int Multiple(MultipleModel model) => 42; [AcceptVerbs("POST")] public int Verb(VerbModel model) => 42; [AcceptVerbs("GET", "POST", "PUT", "DELETE")] public int MultipleVerbs(MultipleVerbsModel model) => 42; } } namespace ModelHierarchy { public class Parent { public int ParentProperty { get; set; } // Noncompliant } public class Child : Parent { public int ChildProperty { get; set; } // Noncompliant } public class GrandChild : Child { public int GrandChildProperty { get; set; } // Noncompliant } public class ControllerClass : Controller { [HttpPost] public IActionResult Create(GrandChild model) => View(model); [HttpPost] public IActionResult Update(Parent model) => View(model); } } namespace CompositeModels { public class Model { public int Property { get; set; } // Noncompliant public Model SameModel { get; set; } public AnotherModel AnotherModel { get; set; } } public class AnotherModel { public int AnotherProperty { get; set; } // Noncompliant public Model Model { get; set; } } public class ControllerClass : Controller { [HttpPost] public IActionResult Create(Model model) => View(model); } } namespace Collections { public class ArrayItem { public int Property { get; set; } } // Noncompliant public class NestedArrayItem { public int Property { get; set; } } // Noncompliant public class EnumerableItem { public int Property { get; set; } } // Noncompliant public class ListItem { public int Property { get; set; } } // Noncompliant public class DictionaryKeyItem { public int Property { get; set; } } // Noncompliant public class DictionaryValueItem { public int Property { get; set; } } // Noncompliant public class NestedCollectionItem { public int Property { get; set; } } // Noncompliant public class ControllerClass : Controller { [HttpPost] public IActionResult CreateArray(ArrayItem[] model) => View(model); [HttpPost] public IActionResult CreateNestedArray(NestedArrayItem[] model) => View(model); [HttpPost] public IActionResult CreateEnumerable(IEnumerable model) => View(model); [HttpPost] public IActionResult CreateList(List model) => View(model); [HttpPost] public IActionResult CreateDictionary(Dictionary model) => View(model); [HttpPost] public IActionResult CreateNestedCollection(Dictionary> model) => View(model); } } namespace GenericModelWithTypeConstraint { public class MyController : ControllerBase { public void Create(Model model) { } } public class Model where T : Person { public T Person { get; set; } } public abstract class Person { public int Age { get; set; } // Noncompliant } public class Developer : Person { public string ProgramingLanguage { get; set; } public int WorkExperienceInYears { get; set; } // Noncompliant } } namespace RecursiveTypeConstraint { public class MyController : ControllerBase { public void Create(MyModel model) { } } public class Model where T : Model { public Model SubModel { get; set; } public int ValueProperty { get; set; } // Noncompliant } public class MyModel : Model { } } namespace ValidateNeverOnCustomType { public class NotVisited { public int Prop { get; set; } // Compliant } public class Model { [ValidateNever] public NotVisited NotVisited { get; set; } [ValidateNever] public int NotValidatedValueProperty { get; set; } // Compliant } [ValidateNever] public class NeverValidatedModel { public int ValueProperty { get; set; } // Compliant } public class RegularModel { public int ValueProperty { get; set; } // Compliant } public class CustomController : Controller { [HttpGet] public IActionResult Get(NeverValidatedModel model) => View(model); [HttpPost] public IActionResult Post(Model model) => View(model); [HttpPut] public IActionResult Put([ValidateNever] RegularModel model) => View(model); } } namespace MutlipleModelsInSameAction { public class Model1 { public int ValueProperty { get; set; } // Noncompliant } public class Model2 { public int ValueProperty { get; set; } // Noncompliant } public class CustomController : Controller { [HttpPost] public IActionResult Post(Model1 model1, int other, Model2 model2) => View(model1); } } namespace Interfaces { public interface IPerson { int Age { get; set; } // Noncompliant } public class Model { public IPerson Person { get; set; } } public class CustomController : Controller { [HttpPost] public IActionResult Post(Model model) => View(model); } } namespace GeneralTypes { public class CustomController : Controller { [HttpPost] public IActionResult PostObject(object model) => View(model); [HttpPost] public IActionResult PostString(string model) => View(model); [HttpPost] public IActionResult PostDynamic(dynamic model) => View(model); } } namespace ModelIsAutogenerated { public class HasAutogeneratedModelController : Controller { [HttpGet] public int Single(AutogeneratedModel model) => 42; } } public partial class AutogeneratedModel { public bool IsProperty // Noncompliant { get; set; } } namespace UsingBindNeverAttribute { public class ModelWithBindNeverProperty { [BindNever] public int ValueProperty { get; set; } } [BindNever] public class EntireModelWithBindNeverAttribute { public int ValueProperty { get; set; } } public class CustomController : Controller { [HttpGet] public IActionResult Get(ModelWithBindNeverProperty model) => View(model); [HttpPost] public IActionResult Post(EntireModelWithBindNeverAttribute model) => View(model); } } // https://github.com/SonarSource/sonar-dotnet/issues/9690 namespace Repro_GH9690 { public class DataModel { [BindRequired] public int PasswordMinLength { get; set; } // Compliant } public class DataModelController : Controller { [HttpPost] public IActionResult Post([FromBody] DataModel model) => View(model); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.cs ================================================ using System.Web.Mvc; using System; [Route(@"A\[controller]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^ public class BackslashOnControllerUsingVerbatimStringController : Controller { } [Route("A\\[controller]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^ public class BackslashOnControllerUsingEscapeCharacterController : Controller { } [Route("A\\[controller]\\B")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^^^^ public class MultipleBackslashesOnController : Controller { } public class BackslashOnActionUsingVerbatimStringController : Controller { [Route(@"A\[action]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^ public ActionResult Index() => View(); } public class BackslashOnActionUsingEscapeCharacterController : Controller { [Route("A\\[action]")] // Noncompliant // ^^^^^^^^^^^^^ public ActionResult Index() => View(); } public class MultipleBackslashesOnActionController : Controller { [Route("A\\[action]\\B")] // Noncompliant // ^^^^^^^^^^^^^^^^ public ActionResult Index() => View(); } [Route("\\[controller]")] // Noncompliant // ^^^^^^^^^^^^^^^^ public class RouteOnControllerStartingWithBackslashController : Controller { } public class AController : Controller { //[Route("A\\[action]")] // Compliant: commented out public ActionResult WithoutRouteAttribute() => View(); [Route("A\\[action]", Name = "a", Order = 3)] // Noncompliant public ActionResult WithOptionalAttributeParameters() => View(); [RouteAttribute("A\\[action]")] // Noncompliant public ActionResult WithAttributeSuffix() => View(); [System.Web.Mvc.RouteAttribute("A\\[action]")] // Noncompliant public ActionResult WithFullQualifiedName() => View(); [Route("A\\[action]")] // Noncompliant [Route("B\\[action]")] // Noncompliant [Route("C/[action]")] // Compliant: forward slash is used public ActionResult WithMultipleRoutes() => View(); [Route("A%5C[action]")] // Compliant: URL-escaped backslash is used public ActionResult WithUrlEscapedBackslash() => View(); [Route("A/{s:regex(^(?!index\\b)[[a-zA-Z0-9-]]+$)}.html")] // Compliant: backslash is in regex public ActionResult WithRegexContainingBackslashInLookahead() => View(); [Route("A/{s:datetime:regex(\\d{{4}}-\\d{{2}}-\\d{{4}})}/B")] // Compliant: backslash is in regex public ActionResult WithRegexContainingBackslashInMetaEscape() => View(); [Route("A\\{s:regex(.*)\\[action]")] // Noncompliant public ActionResult WithFirstBackslashBeforeFirstRegex() => View(); [Route("{s:regex(.*)\\[action]")] // FN: the backslash is not in the regex public ActionResult WithFirstBackslashAfterFirstRegex() => View(); [Route("{s:regex([^\\\\]*)\\[action]")] // FN: the second backslash is not in the regex public ActionResult WithSecondBackslashNotInRegex() => View(); [Route("{s:regex([^\\\\]*)/regex([^\\\\]*)")] // Compliant: both backslashes are in the regex public ActionResult WithTwoBackslashesInRegex() => View(); } namespace WithAliases { using MyRoute = RouteAttribute; using ASP = System.Web; [MyRoute(@"A\[controller]")] // Noncompliant public class WithAliasedRouteAttributeController : Controller { } [ASP.Mvc.RouteAttribute("A\\[action]")] // Noncompliant public class WithFullQualifiedPartiallyAliasedNameController : Controller { } } namespace WithFakeRouteAttribute { [Route(@"A\[controller]")] // Compliant: not a real RouteAttribute public class AControllerController : Controller { } [AttributeUsage(AttributeTargets.Class)] public class RouteAttribute : Attribute { public RouteAttribute(string template) { } } } class WithTuples { void Test() { var tuple = (1, "A\\[controller]"); // Compliant: the argument is not in a method invocation } } // https://github.com/SonarSource/sonar-dotnet/issues/9193 namespace AttributeWithNamedArgument { [AttributeUsage(AttributeTargets.All)] public class MyAttribute : Attribute { public string Name { get; set; } } public class MyController : Controller { [MyAttribute(Name = "Display HR\\Recruitment report")] public const string Text = "ABC"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNet4x.vb ================================================ Imports System Imports System.Web.Mvc ' Noncompliant ^8#16 {{Replace '\' with '/'.}} Public Class BackslashOnController Inherits Controller End Class ' Noncompliant ^8#18 {{Replace '\' with '/'.}} Public Class MultipleBackslashesOnController Inherits Controller End Class Public Class BackslashOnActionController Inherits Controller ' Noncompliant ^12#12 {{Replace '\' with '/'.}} Public Function Index() As ActionResult Return View() End Function End Class Public Class MultipleBackslashesOnActionController Inherits Controller ' Noncompliant ^12#14 {{Replace '\' with '/'.}} Public Function Index() As ActionResult Return View() End Function End Class ' Noncompliant Public Class RouteOnControllerStartingWithBackslashController Inherits Controller End Class Public Class AController Inherits Controller Public Function WithoutRouteAttribute() As ActionResult ' Compliant Return View() End Function ' Noncompliant Public Function WithOptionalAttributeParameters() As ActionResult Return View() End Function ' Noncompliant Public Function WithAttributeSuffix() As ActionResult Return View() End Function ' Noncompliant Public Function WithFullQualifiedName() As ActionResult Return View() End Function ' Noncompliant ' Noncompliant ' Compliant: forward slash is used Public Function WithMultipleRoutes() As ActionResult Return View() End Function ' Compliant: URL-escaped backslash is used Public Function WithUrlEscapedBackslash() As ActionResult Return View() End Function ' Compliant: backslash is in regex Public Function WithRegexContainingBackslashInLookahead() As ActionResult Return View() End Function ' Compliant: backslash is in regex Public Function WithRegexContainingBackslashInMetaEscape() As ActionResult Return View() End Function End Class ' https://github.com/SonarSource/sonar-dotnet/issues/9193 Namespace AttributeWithNamedArgument Public Class MyAttribute Inherits Attribute Public Property Name As String End Class Public Class MyController Inherits Controller Public Const Text As String = "ABC" End Class End Namespace ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2AndAbove.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using System; using System.Diagnostics.CodeAnalysis; [Route(@"A\[controller]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^ public class BackslashOnControllerUsingVerbatimString : Controller { } [Route("A\\[controller]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^ public class BackslashOnControllerUsingEscapeCharacter : Controller { } [Route("A\\[controller]\\B")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^^^^^^^^ public class MultipleBackslashesOnController : Controller { } public class BackslashOnActionUsingVerbatimString : Controller { [Route(@"A\[action]")] // Noncompliant {{Replace '\' with '/'.}} // ^^^^^^^^^^^^^ public IActionResult Index() => View(); } public class BackslashOnActionUsingEscapeCharacter : Controller { [Route("A\\[action]")] // Noncompliant // ^^^^^^^^^^^^^ public IActionResult Index() => View(); } public class MultipleBackslashesOnAction : Controller { [Route("A\\[action]\\B")] // Noncompliant // ^^^^^^^^^^^^^^^^ public IActionResult Index() => View(); } [Route("\\[controller]")] // Noncompliant // ^^^^^^^^^^^^^^^^ public class RouteOnControllerStartingWithBackslash : Controller { } public class AController : Controller { //[Route("A\\[action]")] // Compliant: commented out public IActionResult WithoutRouteAttribute() => View(); [Route("A\\[action]", Name = "a", Order = 3)] // Noncompliant public IActionResult WithOptionalAttributeParameters() => View(); [Route("A/[action]", Name = @"a\b", Order = 3)] // Compliant: backslash is on the name public IActionResult WithBackslashInRouteName() => View(); [RouteAttribute("A\\[action]")] // Noncompliant public IActionResult WithAttributeSuffix() => View(); [Microsoft.AspNetCore.Mvc.RouteAttribute("A\\[action]")] // Noncompliant public IActionResult WithFullQualifiedName() => View(); [Route("A\\[action]")] // Noncompliant [Route("B\\[action]")] // Noncompliant [Route("C/[action]")] // Compliant: forward slash is used public IActionResult WithMultipleRoutes() => View(); [Route("A%5C[action]")] // Compliant: URL-escaped backslash is used public IActionResult WithUrlEscapedBackslash() => View(); [Route("A/{s:regex(^(?!index\\b)[[a-zA-Z0-9-]]+$)}.html")] public IActionResult WithRegexContainingBackslashInLookahead(string s) => View(); // Compliant: backslash is in regex [Route("A/{s:datetime:regex(\\d{{4}}-\\d{{2}}-\\d{{4}})}/B")] public IActionResult WithRegexContainingBackslashInMetaEscape(string s) => View(); // Compliant: backslash is in regex } namespace WithAliases { using MyRoute = RouteAttribute; using ASP = Microsoft.AspNetCore; [MyRoute(@"A\[controller]")] // Noncompliant public class WithAliasedRouteAttribute : Controller { } [ASP.Mvc.RouteAttribute("A\\[action]")] // Noncompliant public class WithFullQualifiedPartiallyAliasedName : Controller { } } namespace WithFakeRouteAttribute { [Route(@"A\[controller]")] // Compliant: not a real RouteAttribute public class AController : Controller { } [AttributeUsage(AttributeTargets.Class)] public class RouteAttribute : Attribute { public RouteAttribute(string template) { } } } class WithUserDefinedSyntaxRouteParameter { const string RouteConst = "Route"; void Test() { StringSyntaxWithRouteParameter("\\"); // Noncompliant StringSyntaxWithRouteParameterAfterOtherParameter("\\", "\\"); // Noncompliant // ^^^^ StringSyntaxWithRouteConstParameter("\\"); // Noncompliant StringSyntaxWithNonRouteParameter("\\"); // Compliant StringSyntaxWithRouteWrongCaseParameter("\\"); // Compliant StringSyntaxWithNullParameter("\\"); // Compliant StringSyntaxWithEmptyStringParameter("\\"); // Compliant MultipleStringSyntaxWithRouteParameterValidFirst("\\"); // Noncompliant MultipleStringSyntaxWithRouteParameterInvalidFirst("\\"); // FN: only the first StringSyntax is considered NoStringSyntax("\\"); // Compliant } private void StringSyntaxWithRouteParameter([StringSyntax("Route")]string route) { } private void StringSyntaxWithRouteParameterAfterOtherParameter(string anotherParameter, [StringSyntax("Route")]string route) { } private void StringSyntaxWithRouteConstParameter([StringSyntax(RouteConst)]string route) { } private void StringSyntaxWithNonRouteParameter([StringSyntax("NotRoute")]string route) { } private void StringSyntaxWithRouteWrongCaseParameter([StringSyntax("route")]string route) { } private void StringSyntaxWithNullParameter([StringSyntax(null)]string route) { } private void StringSyntaxWithEmptyStringParameter([StringSyntax("")]string route) { } private void MultipleStringSyntaxWithRouteParameterValidFirst([StringSyntax("Route")][StringSyntax("route")]string route) { } // Error [CS0579] private void MultipleStringSyntaxWithRouteParameterInvalidFirst([StringSyntax("route")][StringSyntax("Route")]string route) { } // Error [CS0579] private void NoStringSyntax(string route) { } } class InheritingFromFakeControllerDoesntInfluenceRouteCheck { [NonController] [Route(@"A\[controller]")] // Noncompliant public class NotAController : Controller { } public class Controller { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.Latest.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using System; using System.Diagnostics.CodeAnalysis; class ControllerRequirementsDontInfluenceRouteCheck { [NonController] [Route(@"A\[controller]")] // Noncompliant public class NotAController : Controller { } [Route(@"A\[controller]")] // Noncompliant public class ControllerWithoutControllerSuffix : Controller { } [Controller] [Route(@"A\[controller]")] // Noncompliant public class ControllerWithControllerAttribute : Controller { } [Route(@"A\[controller]")] // Noncompliant internal class InternalController : Controller { } [Route(@"A\[controller]")] // Noncompliant protected class ProtectedController : Controller { } [Route(@"A\[controller]")] // Noncompliant private protected class PrivateProtectedController : Controller { } [Route(@"A\[controller]")] // Noncompliant public class ControllerWithoutParameterlessConstructor : Controller { public ControllerWithoutParameterlessConstructor(int i) { } } [Route(@"A\e\[controller]")] // Noncompliant public class ControllerWithEscapeChar : Controller { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore2x.vb ================================================ Imports Microsoft.AspNetCore.Mvc ' Noncompliant ^8#16 {{Replace '\' with '/'.}} Public Class BackslashOnController : Inherits Controller : End Class ' Noncompliant ^8#18 {{Replace '\' with '/'.}} Public Class MultipleBackslashesOnController : Inherits Controller : End Class Public Class BackslashOnActionController Inherits Controller ' Noncompliant ^12#12 {{Replace '\' with '/'.}} Public Function Index() As IActionResult Return View() End Function End Class Public Class MultipleBackslashesOnActionController Inherits Controller ' Noncompliant ^12#14 {{Replace '\' with '/'.}} Public Function Index() As IActionResult Return View() End Function End Class ' Noncompliant Public Class RouteOnControllerStartingWithBackslashController Inherits Controller End Class Public Class AController Inherits Controller ' [Route("A\\[action]")] ' Compliant: commented out Public Function WithoutRouteAttribute() As IActionResult Return View() End Function ' Noncompliant Public Function WithOptionalAttributeParameters() As IActionResult Return View() End Function ' Compliant: backslash is on the name Public Function WithBackslashInRouteName() As IActionResult Return View() End Function ' Noncompliant Public Function WithAttributeSuffix() As IActionResult Return View() End Function ' Noncompliant Public Function WithFullQualifiedName() As IActionResult Return View() End Function ' Noncompliant ' Noncompliant ' Compliant: forward slash is used Public Function WithMultipleRoutes() As IActionResult Return View() End Function ' Compliant: URL-escaped backslash is used Public Function WithUrlEscapedBackslash() As IActionResult Return View() End Function ' Compliant: backslash is in regex Public Function WithRegexContainingBackslashInLookahead(ByVal s As String) As IActionResult Return View() End Function ' Compliant: backslash is in regex Public Function WithRegexContainingBackslashInMetaEscape(ByVal s As String) As IActionResult Return View() End Function End Class Public Class WithAllTypesOfStringsController Inherits Controller Private Const AConstStringIncludingABackslash As String = "A\" Private Const AConstStringNotIncludingABackslash As String = "A/" ' Noncompliant Public Function WithConstStringIncludingABackslash() As IActionResult Return View() End Function ' Compliant Public Function WithConstStringNotIncludingABackslash() As IActionResult Return View() End Function End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.Latest.cs ================================================ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using System; using System.Threading.Tasks; class WithAllControllerEndpointRouteBuilderExtensionsMethods { private const string ASlash = "/"; private const string ABackslash = @"\"; void MapControllerRoute(WebApplication app) { const string ASlashLocal = "A"; const string ABackslashLocal = @"\"; app.MapControllerRoute("default", "{controller=Home}\\{action=Index}/{id?}"); // Noncompliant app.MapControllerRoute("default", @"{controller=Home}\\{action=Index}/{id?}"); // Noncompliant app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); // Compliant app.MapControllerRoute("default", $$"""{controller=Home}{{ABackslash}}{action=Index}"""); // Noncompliant app.MapControllerRoute("default", $$"""{controller=Home}{{ASlash}}{action=Index}"""); // Compliant app.MapControllerRoute("default", $$"""{controller=Home}{{ABackslashLocal}}{action=Index}"""); // Noncompliant app.MapControllerRoute("default", $$"""{controller=Home}{{ASlashLocal}}{action=Index}"""); // Compliant app.MapControllerRoute( name: "default", pattern: "{controller=Home}\\{action=Index}/{id?}", // Noncompliant defaults: new { controller = "Home", action = "Index" }); app.MapControllerRoute( name: "default", pattern: "{controller=Home}\e\\{action=Index}/{id?}", // Noncompliant defaults: new { controller = "Home", action = "Index" }); app.MapControllerRoute( name: "default", pattern: "{controller=Home}\e{action=Index}/{id?}", // Compliant defaults: new { controller = "Home", action = "Index" }); app.MapControllerRoute( pattern: "{controller=Home}\\{action=Index}/{id?}", // Noncompliant name: "default", defaults: new { controller = "Home", action = "Index" }); ControllerEndpointRouteBuilderExtensions.MapControllerRoute(app, "default", "{controller=Home}\\{action=Index}/{id?}"); // Noncompliant } void OtherMethods(WebApplication app) { app.MapAreaControllerRoute("default", "area", "{controller=Home}\\{action=Index}/{id?}"); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ app.MapFallbackToAreaController("{controller=Home}\\{action=Index}", "action", "controller", "area"); // Noncompliant app.MapFallbackToAreaController("\\action", "\\controller", "\\area"); // Compliant app.MapFallbackToController(@"\action", @"\controller"); // Compliant app.MapFallbackToController("{controller=Home}\\{action=Index}", "\\action", "\\controller"); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ app.MapDynamicControllerRoute("{controller=Home}\\{action=Index}"); // Noncompliant app.MapDynamicControllerRoute("{controller=Home}\\{action=Index}", new object()); // Noncompliant app.MapDynamicControllerRoute("{controller=Home}\\{action=Index}", new object(), 3); // Noncompliant } private sealed class ATransformer : DynamicRouteValueTransformer { public override ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) => throw new NotImplementedException(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore3AndAbove.vb ================================================ Imports Microsoft.AspNetCore.Builder Class WithAllControllerEndpointRouteBuilderExtensionsMethods Private Sub MapControllerRoute(ByVal app As WebApplication) app.MapControllerRoute(name:="default", pattern:="{controller=Home}\{action=Index}/{id?}") ' Noncompliant app.MapControllerRoute("default", "{controller=Home}\{action=Index}/{id?}") ' Noncompliant app.MapAreaControllerRoute("default", "area", "{controller=Home}\{action=Index}/{id?}") ' Noncompliant app.MapFallbackToAreaController("{controller=Home}\{action=Index}", "action", "controller", "area") ' Noncompliant app.MapFallbackToAreaController("\action", "\controller", "\area") ' Compliant app.MapFallbackToController("\action", "\controller") ' Compliant app.MapFallbackToController("{controller=Home}\{action=Index}", "\action", "\controller") ' Noncompliant ControllerEndpointRouteBuilderExtensions.MapControllerRoute(app, "default", "{controller=Home}\{action=Index}/{id?}") ' Noncompliant End Sub End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.Latest.cs ================================================ using Microsoft.AspNetCore.Builder; class WithAllControllerEndpointRouteBuilderExtensionsMethods { // MapFallbackToPage decorates the pattern with StringSyntax("Route") since ASP.NET Core 8.0.0 void MapFallbackToPage(WebApplication app) { app.MapFallbackToPage("\\action"); // Compliant app.MapFallbackToPage("{controller=Home}\\{action=Index}", "\\page"); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ app.MapFallbackToPage("\eaction"); // Compliant app.MapFallbackToPage("{controller=Home}\\\e{action=Index}", "\e\\page"); // Noncompliant } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/BackslashShouldBeAvoidedInAspNetRoutes.AspNetCore8AndAbove.vb ================================================ Imports Microsoft.AspNetCore.Builder Class WithAllControllerEndpointRouteBuilderExtensionsMethods ' MapFallbackToPage decorates the pattern with StringSyntax("Route") since ASP.NET Core 8.0.0 Private Sub MapControllerRoute(ByVal app As WebApplication) app.MapFallbackToPage("\action") ' Compliant app.MapFallbackToPage("{controller=Home}\{action=Index}", "\page") ' Noncompliant ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End Sub End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/CallModelStateIsValid.AutogeneratedController.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ // Repro https://github.com/SonarSource/sonar-dotnet/issues/9260 // Also related to https://github.com/SonarSource/sonar-dotnet/issues/8876 using Microsoft.AspNetCore.Mvc; public partial class AutogeneratedController : Controller { // Repro for https://github.com/SonarSource/sonar-dotnet/issues/9261 // this will be fixed once https://github.com/SonarSource/sonar-dotnet/issues/8876 is fixed [HttpPost] public void Post(int id) { } // Noncompliant, FP we should not raise in autogenerated code. } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/CallModelStateIsValid.Latest.cs ================================================ using Microsoft.AspNetCore.Mvc; namespace Repro_NET1044 { public class PositionalPatternClause : Controller { public int Pattern((int X, int Y) tuple) => // Noncompliant tuple switch { (_, _) => 42 // This was throwing NullReferenceException }; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/CallModelStateIsValid.cs ================================================ using System.ComponentModel.DataAnnotations; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; public class NonCompliantController : ControllerBase { [HttpGet("/[controller]")] public string Get([Required, FromQuery] string id) // Noncompliant {{ModelState.IsValid should be checked in controller actions.}} // ^^^ { return "Hello!"; } [HttpPost("/[controller]")] public string Post(Movie movie) // Noncompliant { return "Hello!"; } [HttpPut("/[controller]")] public string Put(Movie movie) // Noncompliant { return "Hello!"; } [HttpDelete("/[controller]")] public string Delete(Movie movie) // Noncompliant { return "Hello!"; } [HttpPatch("/[controller]")] public string Patch(Movie movie) // Noncompliant { return "Hello!"; } [HttpGet] [HttpPost] [HttpPut] [HttpDelete] [HttpPatch] [Route("/[controller]/mix")] public string Mix([Required, FromQuery, EmailAddress] string email) // Noncompliant { return "Hello!"; } [AcceptVerbs("GET")] [Route("/[controller]/accept-verbs")] public string AGet([Required] string id) // Noncompliant { return "Hello!"; } [AcceptVerbs("POST")] [Route("/[controller]/accept-verbs")] public string APost(Movie movie) // Noncompliant { return "Hello!"; } [AcceptVerbs("PUT")] [Route("/[controller]/accept-verbs")] public string APut(Movie movie) // Noncompliant { return "Hello!"; } [AcceptVerbs("DELETE")] [Route("/[controller]/accept-verbs")] public string ADelete(Movie movie) // Noncompliant { return "Hello!"; } [AcceptVerbs("PATCH")] [Route("/[controller]/accept-verbs")] public string APatch(Movie movie) // Noncompliant { return "Hello!"; } [AcceptVerbs("GET", "POST", "PUT", "DELETE", "PATCH")] [Route("/[controller]/many")] public string Many([Required, FromQuery, EmailAddress] string email) // Noncompliant { return "Hello!"; } [HttpGet("/[controller]/list")] public string[] List() => null; // Compliant [HttpGet("/[controller]/listasync")] public Task ListAsync(CancellationToken token) => null; // Compliant } [ApiController] public class ControllerWithApiAttributeAtTheClassLevel : ControllerBase { [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant, ApiController attribute is applied at the class level. { return "Hello!"; } } public class BaseClassHasApiControllerAttribute : ControllerWithApiAttributeAtTheClassLevel { [HttpDelete("/[controller]")] public string Remove(Movie movie) // Compliant, base class is decorated with the ApiController attribute { return "Hello!"; } } [Controller] public class ControllerThatDoesNotInheritFromControllerBase { [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant, ModelState is not available in this context. { return "Hello!"; } } public class SimpleController { [HttpPost("/[controller]")] public string Add(Movie movie) // Compliant, ModelState is not available in this context. { return "Hello!"; } } public class CompliantController : ControllerBase { [HttpGet("/[controller]")] public string Get([Required, FromQuery] string id) // Compliant { if (!ModelState.IsValid) { return null; } return "Hello"; } [HttpGet("/[controller]/GetNoParam")] public string GetNoParam() // Compliant { return "Hello"; } [HttpPost("/[controller]")] public string Post([Required, FromBody] string id) // Compliant { var state = ModelState; if (!state.IsValid) { return null; } return "Hello"; } } [ValidateModel] public class ControllerClassWithActionFilter : ControllerBase { [HttpPost("/[controller]/create")] public string Create(Movie movie) // Compliant, Controller class is decorated with an Action Filter { return "Hello!"; } } public class ControllerMethodsWithActionFilter : ControllerBase { [ValidateModel] [HttpPost("/[controller]/create")] public string Create(Movie movie) // Compliant, Controller action is decorated with an Action Filter { return "Hello!"; } } public class Movie { [Required] public string Title { get; set; } [Range(1900, 2200)] public int Year { get; set; } } public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } } public class ControllerCallsTryValidate : ControllerBase { [HttpPost] public string CallsTryValidateModel1([Required] string email) // Compliant, calls TryValidateModel { return TryValidateModel(email) ? "Hi!" : "Hello!"; } [HttpPost] public string CallsTryValidateModel([Required] string email) // Compliant, calls TryValidateModel { return TryValidateModel(email, "prefix") ? "Hi!" : "Hello!"; } } public class ControllerCallsTryValidateOverride : ControllerBase { public override bool TryValidateModel(object model) => true; public override bool TryValidateModel(object model, string prefix) => true; [HttpPost] public string CallsTryValidateModel1([Required] string email) // Compliant, calls TryValidateModel { return TryValidateModel(email) ? "Hi!" : "Hello!"; } [HttpPost] public string CallsTryValidateModel([Required] string email) // Compliant, calls TryValidateModel { return TryValidateModel(email, "prefix") ? "Hi!" : "Hello!"; } } // https://github.com/SonarSource/sonar-dotnet/issues/9325 namespace Repro_9325 { public class MyController : ControllerBase { [HttpGet("/[controller]")] public string Get([Required, FromQuery] string id) // Noncompliant - FP: the Model State is checked in a method that's called from the Controller Action { if (!CheckModelState()) { return "Error!"; } return "Hello!"; } private bool CheckModelState() => ModelState.IsValid; } } // https://github.com/SonarSource/sonar-dotnet/issues/9262 namespace Repro_9262 { public class MyController : ControllerBase { [HttpGet("/[controller]")] public string OnlyIngoredTypes(string str, object obj, dynamic dyn) { return "Hello!"; } [HttpGet("/[controller]")] public string WithValidationAttribute(string str, [Required] object obj, dynamic dyn) // Noncompliant { return "Hello!"; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllerReuseClient.CSharp12.cs ================================================ using Microsoft.AspNetCore.Mvc; using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; [ApiController] [Route("SomeRoute")] public class C(HttpClient client) : ControllerBase; [ApiController] [Route("SomeRoute")] public class D(HttpClient client) : C(new HttpClient()) // Compliant { public D() : this(new HttpClient()) { } // Compliant private HttpClient Client { get => new HttpClient(); } // Noncompliant } [ApiController] [Route("SomeRoute")] public class E : C { public E() : base(new HttpClient()) { } // Compliant } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersHaveMixedResponsibilities.Latest.Partial.cs ================================================ using Microsoft.AspNetCore.Mvc; using TestInstrumentation.ResponsibilitySpecificServices; namespace CSharp13 { public partial class WithFieldBackedPartialProperties : ControllerBase // Noncompliant { public partial IS1 S1 { get; } public partial IS2 S2 { get; init; } } public partial class WithPartialIndexer : ControllerBase // Noncompliant { public partial int this[int i] { get; set; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersHaveMixedResponsibilities.Latest.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System; using TestInstrumentation.ResponsibilitySpecificServices; using TestInstrumentation.WellKnownInterfacesExcluded; using TestInstrumentation; using WithInjectionViaNormalConstructor; using System.Runtime.CompilerServices; using WithInjectionViaPrimaryConstructors.TwoActions; // Remark: secondary messages are asserted extensively, to ensure that grouping is done correctly and deterministically. namespace TestInstrumentation { public interface IServiceWithAnAPI { void Use(); } public abstract class ServiceWithAnApi : IServiceWithAnAPI { public void Use() { } } // These interfaces have been kept to simulate the actual ones, from MediatR, AutoMapper, etc. // For performance reasons, the analyzer ignores namespace and assembly, and only consider the name. // While that may comes with some false positives, the likelihood of that happening is very low. namespace WellKnownInterfacesExcluded { public interface ILogger : IServiceWithAnAPI { } // From Microsoft.Extensions.Logging public interface IHttpClientFactory : IServiceWithAnAPI { } // From Microsoft.Extensions.Http public interface IMediator : IServiceWithAnAPI { } // From MediatR public interface IMapper : IServiceWithAnAPI { } // From AutoMapper public interface IConfiguration : IServiceWithAnAPI { } // From Microsoft.Extensions.Configuration public interface IBus : IServiceWithAnAPI { } // From MassTransit public interface IMessageBus : IServiceWithAnAPI { } // From NServiceBus } namespace WellKnownInterfacesNotExcluded { public interface IOption : IServiceWithAnAPI { } // From Microsoft.Extensions.Options } namespace ResponsibilitySpecificServices { public interface IS1 : IServiceWithAnAPI { } public interface IS2 : IServiceWithAnAPI { } public interface IS3 : IServiceWithAnAPI { } public interface IS4 : IServiceWithAnAPI { } public interface IS5 : IServiceWithAnAPI { } public interface IS6 : IServiceWithAnAPI { } public interface IS7 : IServiceWithAnAPI { } public interface IS8 : IServiceWithAnAPI { } public interface IS9 : IServiceWithAnAPI { } public interface IS10 : IServiceWithAnAPI { } public class Class1: ServiceWithAnApi, IS1 { } public class Class2: ServiceWithAnApi, IS2 { } public class Class3: ServiceWithAnApi, IS3 { } public class Class4: ServiceWithAnApi, IS4 { } public class Struct1: IS1 { public void Use() { } } public class Struct2: IS2 { public void Use() { } } public class Struct3: IS3 { public void Use() { } } public class Struct4: IS4 { public void Use() { } } } } namespace WithInjectionViaPrimaryConstructors { using TestInstrumentation.ResponsibilitySpecificServices; using TestInstrumentation.WellKnownInterfacesExcluded; using TestInstrumentation.WellKnownInterfacesNotExcluded; using TestInstrumentation; namespace AssertIssueLocationsAndMessage { // Noncompliant@+2 {{This controller has multiple responsibilities and could be split into 2 smaller controllers.}} [ApiController] public class TwoResponsibilities(IS1 s1, IS2 s2) : ControllerBase { public IActionResult A1() { s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithGenerics(IS1 s1, IS2 s2) : ControllerBase // Noncompliant // ^^^^^^^^^^^^ { public IActionResult GenericAction() { s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // ^^^^^^^^^^^^^ public IActionResult NonGenericAction() { s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} // ^^^^^^^^^^^^^^^^ } [ApiController] public class @event(IS1 s1, IS2 s2) : ControllerBase // Noncompliant // ^^^^^^ { public IActionResult @private() { s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // ^^^^^^^^ public IActionResult @public() { s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} // ^^^^^^^ } [ApiController] public class ThreeResponsibilities(IS1 s1, IS2 s2, IS3 s3) : ControllerBase // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^ { public IActionResult A1() { s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } } namespace WithIOptions { // Compliant@+2: 4 deps injected, all well-known => 4 singletons sets, merged into one [ApiController] public class WellKnownDepsController( ILogger logger, IMediator mediator, IMapper mapper, IConfiguration configuration) : ControllerBase { public IActionResult A1() { logger.Use(); return Ok(); } public IActionResult A2() { mediator.Use(); return Ok(); } public IActionResult A3() { mapper.Use(); return Ok(); } public IActionResult A4() { configuration.Use(); return Ok(); } } // Noncompliant@+2: 4 different Option injected, that are not excluded [ApiController] public class FourDifferentOptionDepsController( IOption o1, IOption o2, IOption o3, IOption o4) : ControllerBase { public IActionResult A1() { o1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { o2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { o3.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} public IActionResult A4() { o4.Use(); return Ok(); } // Secondary {{May belong to responsibility #4.}} } // Compliant@+2: 5 different Option injected, used in couples to form a single responsibility [ApiController] public class FourDifferentOptionDepsUsedInCouplesController( IOption o1, IOption o2, IOption o3, IOption o4, IOption o5) : ControllerBase { public IActionResult A1() { o1.Use(); o2.Use(); return Ok(); } public IActionResult A2() { o2.Use(); o3.Use(); return Ok(); } public IActionResult A3() { o3.Use(); o4.Use(); return Ok(); } public IActionResult A4() { o4.Use(); o5.Use(); return Ok(); } } // Noncompliant@+2: 3 Option deps injected, the rest are well-known dependencies (used as well as unused) [ApiController] public class ThreeOptionDepsController( ILogger logger, IMediator mediator, IMapper mapper, IConfiguration configuration, IOption o1, IOption o2, IOption o3) : ControllerBase { public IActionResult A1() { o1.Use(); logger.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { o2.Use(); mediator.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { o3.Use(); configuration.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} public IActionResult A4() { logger.Use(); return Ok(); } } } namespace TwoActions { // Noncompliant@+2: 2 specific deps injected, each used in a separate responsibility, plus well-known dependencies [ApiController] public class TwoSeparateResponsibilitiesPlusSharedWellKnown( ILogger logger, IMediator mediator, IMapper mapper, IConfiguration configuration, IBus bus, IMessageBus messageBus, IS1 s1, IS2 s2) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); bus.Use(); configuration.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); bus.Use(); configuration.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Noncompliant@+2: 4 specific deps injected, two for A1 and two for A2 [ApiController] public class FourSpecificDepsTwoForA1AndTwoForA2( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A1() { logger.Use(); s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Compliant@+1: 4 specific deps injected, two for A1 and two for A2, in non-API controller derived from Controller public class FourSpecificDepsTwoForA1AndTwoForA2NonApiFromController( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : Controller { public void A1() { logger.Use(); s1.Use(); s2.Use(); } public void A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); } } // Noncompliant@+1: 4 specific deps injected, two for A1 and two for A2, in non-API controller derived from ControllerBase public class FourSpecificDepsTwoForA1AndTwoForA2NonApiFromControllerBase( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public void A1() { logger.Use(); s1.Use(); s2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); } // Secondary {{May belong to responsibility #2.}} } // Compliant@+2: 4 specific deps injected, two for A1 and two for A2, in an API controller marked as NonController [ApiController] [NonController] public class FourSpecificDepsTwoForA1AndTwoForA2NoController( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A1() { logger.Use(); s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return Ok(); } } // Noncompliant@+2: 4 specific deps injected, two for A1 and two for A2, in a PoCo controller with controller suffix [ApiController] public class FourSpecificDepsTwoForA1AndTwoForA2PoCoController( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public string A1() { logger.Use(); s1.Use(); s2.Use(); return "Ok"; } // Secondary {{May belong to responsibility #1.}} public string A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return "Ok"; } // Secondary {{May belong to responsibility #2.}} } // Compliant@+1: 4 specific deps injected, two for A1 and two for A2, in a PoCo controller without controller suffix public class PoCoControllerWithoutControllerSuffix( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) { public string A1() { logger.Use(); s1.Use(); s2.Use(); return "Ok"; } public string A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return "Ok"; } } // Noncompliant@+3: 4 specific deps injected, two for A1 and two for A2, in a PoCo controller without controller suffix but with [Controller] attribute [ApiController] [Controller] public class PoCoControllerWithoutControllerSuffixWithControllerAttribute( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public string A1() { logger.Use(); s1.Use(); s2.Use(); return "Ok"; } // Secondary {{May belong to responsibility #1.}} public string A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return "Ok"; } // Secondary {{May belong to responsibility #2.}} } // Noncompliant@+2: 4 specific deps injected, two for A1 and two for A2, with responsibilities in a different order [ApiController] public class FourSpecificDepsTwoForA1AndTwoForA2DifferentOrderOfResponsibilities( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A2() { logger.Use(); s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A1() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} } // Noncompliant@+2: 4 specific deps injected, two for A1 and two for A2, with dependencies used in a different order [ApiController] public class FourSpecificDepsTwoForA1AndTwoForA2DifferentOrderOfDependencies( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A1() { logger.Use(); s2.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s3.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Noncompliant@+2: 4 specific deps injected, three for A1 and one for A2 [ApiController] public class FourSpecificDepsThreeForA1AndOneForA2( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS3 s4) : ControllerBase { public IActionResult A1() { logger.Use(); s1.Use(); s2.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Compliant@+2: 4 specific deps injected, all for A1 and none for A2 [ApiController] public class FourSpecificDepsFourForA1AndNoneForA2( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS3 s4) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); s2.Use(); s3.Use(); s4.Use(); return Ok(); } public IActionResult A2() { mediator.Use(); mapper.Use(); return Ok(); } } // Compliant@+2: 4 specific deps injected, one in common between responsibility 1 and 2 [ApiController] public class ThreeSpecificDepsOneInCommonBetweenA1AndA2( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); s3.Use(); return Ok(); } } } namespace ThreeActions { // Noncompliant@+2: 2 specific deps injected, each used in a separate responsibility [ApiController] public class ThreeResponsibilities( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { mapper.Use(); return Ok(); } } // Noncompliant@+2: 3 specific deps injected, each used in a separate responsibility, possibly multiple times [ApiController] public class UpToThreeSpecificDepsController( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3) : ControllerBase { public IActionResult A1() { logger.Use(); s1.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { logger.Use(); mapper.Use(); s2.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { mediator.Use(); mapper.Use(); s3.Use(); s3.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } // Noncompliant@+2: 3 specific deps injected, each used in a separate responsibility [ApiController] public class ThreeResponsibilities2( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } // Noncompliant@+2: 3 specific deps injected, forming a chain and an isolated action [ApiController] public class ThreeSpecificDepsFormingAChainAndAnIsolatedAction( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3) : ControllerBase { // Chain: A1, A2 with s1 and s2 public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // Isolated: A3 with s3 public IActionResult A3() { s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Noncompliant@+2: 4 specific deps injected, two for A1, one for A2, and one for A3 [ApiController] public class FourSpecificDepsTwoForA1OneForA2AndOneForA3( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } // Noncompliant@+2: 4 specific deps injected, one for A1, one for A2, one for A3, one unused [ApiController] public class FourSpecificDepsOneForA1OneForA2OneForA3OneUnused( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A3() { s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } // Compliant@+2: 4 specific deps injected, forming a single 3-cycle [ApiController] public class FourSpecificDepsFormingACycle( ILogger logger, IMediator mediator, IMapper mapper, IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { // Cycle: A1, A2, A3 public IActionResult A1() { logger.Use(); mediator.Use(); s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { mediator.Use(); mapper.Use(); s2.Use(); s3.Use(); return Ok(); } public IActionResult A3() { s3.Use(); s4.Use(); s1.Use(); return Ok(); } } } namespace SixActions { // Noncompliant@+2: 6 specific deps injected, forming 2 disconnected 3-cycles [ApiController] public class FourSpecificDepsFormingTwoDisconnectedCycles( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2, A3 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { s2.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A3() { s3.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // Cycle 2: A4, A5, A6 (disconnected from cycle 1) public IActionResult A4() { s4.Use(); s5.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A5() { s5.Use(); s6.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A6() { s6.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Compliant@+2: 5 specific deps injected, forming 2 connected 3-cycles [ApiController] public class FourSpecificDepsFormingTwoConnectedCycles( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5) : ControllerBase { // Cycle 1: A1, A2, A3 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { s2.Use(); s3.Use(); return Ok(); } public IActionResult A3() { s3.Use(); s1.Use(); return Ok(); } // Cycle 2: A4, A5, A6 (connected to cycle 1 via s1) public IActionResult A4() { s1.Use(); s4.Use(); return Ok(); } public IActionResult A5() { s4.Use(); s5.Use(); return Ok(); } public IActionResult A6() { s5.Use(); s1.Use(); return Ok(); } } // Compliant@+2: 4 specific deps injected, forming 2 3-cycles, connected by two dependencies (s1 and s2) [ApiController] public class FourSpecificDepsFormingTwoConnectedCycles2( IS1 s1, IS2 s2, IS3 s3, IS4 s4) : ControllerBase { // Cycle 1: A1, A2, A3 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { s2.Use(); s3.Use(); return Ok(); } public IActionResult A3() { s3.Use(); s1.Use(); return Ok(); } // Cycle 2: A4, A5, A6 public IActionResult A4() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A5() { s2.Use(); s4.Use(); return Ok(); } public IActionResult A6() { s4.Use(); s1.Use(); return Ok(); } } // Compliant@+2: 4 specific deps injected, forming 2 3-cycles, connected by action invocations [ApiController] public class FourSpecificDepsFormingTwoConnectedCycles3( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2, A3 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { s2.Use(); s3.Use(); return Ok(); } public IActionResult A3() { s3.Use(); s1.Use(); return Ok(); } // Cycle 2: A4, A5, A6, connected to cycle 1 via A1 invocation public IActionResult A4() { A1(); s4.Use(); return Ok(); } public IActionResult A5() { s5.Use(); s6.Use(); return Ok(); } public IActionResult A6() { s6.Use(); s4.Use(); return Ok(); } } // Noncompliant@+2: 6 specific deps injected, forming 3 disconnected 2-cycles [ApiController] public class FourSpecificDepsFormingThreeDisconnectedCycles( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { s2.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // Cycle 2: A3, A4 public IActionResult A3() { s3.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A4() { s4.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} // Cycle 3: A5, A6 public IActionResult A5() { s5.Use(); s6.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} public IActionResult A6() { s6.Use(); s5.Use(); return Ok(); } // Secondary {{May belong to responsibility #3.}} } // Noncompliant@+2: 6 specific deps injected, forming 2 connected 2-cycles and 1 disconnected 2-cycle [ApiController] public class FourSpecificDepsFormingTwoConnectedCyclesAndOneDisconnectedCycle( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A2() { s2.Use(); s1.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // Cycle 2: A3, A4, connected to cycle 1 via A1 invocation public IActionResult A3() { A1(); s3.Use(); s4.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} public IActionResult A4() { s4.Use(); s3.Use(); return Ok(); } // Secondary {{May belong to responsibility #1.}} // Cycle 3: A5, A6 public IActionResult A5() { s5.Use(); s6.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} public IActionResult A6() { s6.Use(); s5.Use(); return Ok(); } // Secondary {{May belong to responsibility #2.}} } // Compliant@+2: 6 specific deps injected, forming 3 connected 2-cycles [ApiController] public class FourSpecificDepsFormingThreeConnectedCycles( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { s2.Use(); s1.Use(); return Ok(); } // Cycle 2: A3, A4, connected to cycle 1 via A1 invocation public IActionResult A3() { A1(); s3.Use(); s4.Use(); return Ok(); } public IActionResult A4() { s4.Use(); s3.Use(); return Ok(); } // Cycle 3: A5, A6, connected to cycle 1 via A2 invocation public IActionResult A5() { A2(); s5.Use(); s6.Use(); return Ok(); } public IActionResult A6() { s6.Use(); s5.Use(); return Ok(); } } // Compliant@+2: 6 specific deps injected, forming 3 connected 2-cycles - transitivity of connection [ApiController] public class FourSpecificDepsFormingThreeConnectedCyclesTransitivity( IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) : ControllerBase { // Cycle 1: A1, A2 public IActionResult A1() { s1.Use(); s2.Use(); return Ok(); } public IActionResult A2() { s2.Use(); s1.Use(); return Ok(); } // Cycle 2: A3, A4, connected to cycle 1 via A1 invocation public IActionResult A3() { A1(); s3.Use(); s4.Use(); return Ok(); } public IActionResult A4() { s4.Use(); s3.Use(); return Ok(); } // Cycle 3: A5, A6, connected to cycle 1 via A2 invocation public IActionResult A5() { A3(); s5.Use(); s6.Use(); return Ok(); } public IActionResult A6() { s6.Use(); s5.Use(); return Ok(); } } } } namespace WithInjectionViaNormalConstructor { using TestInstrumentation.ResponsibilitySpecificServices; using TestInstrumentation.WellKnownInterfacesExcluded; using TestInstrumentation.WellKnownInterfacesNotExcluded; using TestInstrumentation; [ApiController] public class WithFields : ControllerBase // Noncompliant { private readonly IS1 s1; private IS2 s2; public WithFields(IS1 s1, IS2 s2) { this.s1 = s1; this.s2 = s2; } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithDifferentVisibilities : ControllerBase // Noncompliant { private IS1 s1; protected IS2 s2; internal IS3 s3; private protected IS4 s4; protected internal IS5 s5; public IS6 s6; public WithDifferentVisibilities(IS1 s1, IS2 s2, IS3 s3, IS4 s4, IS5 s5, IS6 s6) { this.s1 = s1; this.s2 = s2; this.s3 = s3; this.s4 = s4; this.s5 = s5; this.s6 = s6; } private void A1() { s1.Use(); s2.Use(); } // Secondary {{May belong to responsibility #1.}} protected void A2() { s2.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} internal void A3() { s3.Use(); s4.Use(); } // Secondary {{May belong to responsibility #2.}} private protected void A4() { s4.Use(); s3.Use(); } // Secondary {{May belong to responsibility #2.}} protected internal void A5() { s5.Use(); s6.Use(); } // Secondary {{May belong to responsibility #3.}} public void A6() { s6.Use(); s5.Use(); } // Secondary {{May belong to responsibility #3.}} } [ApiController] public class WithStaticFields : ControllerBase // Compliant, we don't take into account static fields and methods. { private static IS1 s1; private static IS2 s2 = null; public WithStaticFields(IS1 s1, IS2 s2) { WithStaticFields.s1 = s1; WithStaticFields.s2 = s2; } static WithStaticFields() { s1 = S1.Instance; } public void A1() { s1.Use(); } public void A2() { s2.Use(); } class S1 : IS1 { public void Use() { } public static IS1 Instance => new S1(); } } [ApiController] public class WithAutoProperties : ControllerBase // Noncompliant { public IS1 S1 { get; } public IS2 S2 { get; set; } protected IS3 S3 { get; init; } private ILogger S4 { get; init; } // Well-known public WithAutoProperties(IS1 s1, IS2 s2, IS3 s3) { S1 = s1; S2 = s2; S3 = s3; } public void A1() { S1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { S2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { S3.Use(); S2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A4() { S4.Use(); } // Only well-known service used => Ignored } [ApiController] public class WithFieldBackedProperties : ControllerBase // Noncompliant { private IS1 _s1; private IS2 _s2; public IS1 S1 { get => _s1; } public IS2 S2 { get => _s2; init => _s2 = value; } public WithFieldBackedProperties(IS1 s1, IS2 s2) { _s1 = s1; _s2 = s2; } public void A1() { S1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { S2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithMixedStorageMechanismsAndPropertyDependency : ControllerBase // Compliant: single responsibility { // Property dependency: A3 -> S3 -> { _s3, _s1 } private IS1 _s1; private IS3 _s3; public IS2 S2 { get; set; } public IS3 S3 { get => _s3; set { _s3 = value; _s1 = default; } } public WithMixedStorageMechanismsAndPropertyDependency(IS1 s1, IS2 s2, IS3 s3) { _s1 = s1; S2 = s2; _s3 = s3; } public void A1() { _s1.Use(); S2.Use(); } public void A2() { S2.Use(); S3.Use(); } public void A3() { S3.Use(); } } [ApiController] public class WithMixedStorageMechanismsAndPropertyDependencyTransitivity : ControllerBase { // Property dependency transitivity: A4 -> S4 -> { _s4, S3 } -> { _s4, _s3, S2 } private IS1 _s1; private IS3 _s3; private IS4 _s4; public IS2 S2 { get; set; } public IS3 S3 { get => _s3; set { _s3 = value; S2 = default; } } // Also resets S2 public IS4 S4 { get => _s4; set { _s4 = value; S3 = default; } } // Also resets S3 public WithMixedStorageMechanismsAndPropertyDependencyTransitivity(IS1 s1, IS2 s2, IS3 s3, IS4 s4) { _s1 = s1; S2 = s2; _s3 = s3; _s4 = s4; } public void A1() { _s1.Use(); S2.Use(); } public void A2() { S2.Use(); S3.Use(); } public void A3() { S3.Use(); _s1.Use(); } public void A4() { S4.Use(); } } [ApiController] public class WithLambdaCapturingService : ControllerBase // Noncompliant: s1Provider and s2Provider explicitly wrap services { private readonly Func s1Provider; private readonly Func s2Provider; public WithLambdaCapturingService(IS1 s1, IS2 s2) { s1Provider = () => s1; s2Provider = x => s2; } public void A1() { s1Provider().Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2Provider(42).Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithNonPublicConstructor : ControllerBase // Noncompliant: ctor visibility is irrelevant { private readonly IS1 s1; protected IS2 S2 { get; init; } private WithNonPublicConstructor(IS1 s1, IS2 s2) { this.s1 = s1; this.S2 = s2; } public void A1() { s1.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { S2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithCtorNotInitializingInjectedServices : ControllerBase // Noncompliant: initialization is irrelevant { private readonly IS1 s1; internal IS2 s2; public WithCtorNotInitializingInjectedServices(IS1 s1, IS2 s2) { } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithServicesNotInjectedAtAll : ControllerBase // Noncompliant: ctor injection is irrelevant { private IS1 s1; private IS2 s2; public WithServicesNotInjectedAtAll() { } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithServicesInitializedWithServiceProvider : ControllerBase // Noncompliant: service locator pattern is irrelevant { private readonly IS1 s1; private readonly IS2 s2; public WithServicesInitializedWithServiceProvider(IServiceProvider serviceProvider) { s1 = serviceProvider.GetRequiredService(); s2 = serviceProvider.GetRequiredService(); } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithServicesInitializedWithSingletons : ControllerBase // Noncompliant: singleton pattern is irrelevant { private readonly IS1 s1 = S1.Instance; private readonly IS2 s2 = S2.Instance; public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} class S1 : IS1 { public void Use() { } public static IS1 Instance => new S1(); } class S2 : IS2 { public void Use() { } public static IS2 Instance => new S2(); } } [ApiController] public class WithServicesInitializedWithMixedStrategies : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2 = S2.Instance; private readonly IS3 s3; public WithServicesInitializedWithMixedStrategies(IS1 s1, IServiceProvider serviceProvider) { this.s1 = s1; s3 = serviceProvider.GetRequiredService(); } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #3.}} class S2 : IS2 { public void Use() { } public static IS2 Instance => new S2(); } } [ApiController] public class WithAWellKnownInterfaceIncluded : ControllerBase // Noncompliant { private ILogger Logger { get; } private readonly IS1 s1; private readonly IS2 s2 = S2.Instance; private readonly IS3 s3; public WithAWellKnownInterfaceIncluded(ILogger logger, IS1 s1, IServiceProvider serviceProvider) { Logger = logger; this.s1 = s1; s3 = serviceProvider.GetRequiredService(); } public void A1() { Logger.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { Logger.Use(); s2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { Logger.Use(); s3.Use(); } // Secondary {{May belong to responsibility #3.}} class S2 : IS2 { public void Use() { } public static IS2 Instance => new S2(); } } } [ApiController] public class WithHttpClientFactory : ControllerBase // Noncompliant { private readonly IHttpClientFactory _httpClientFactory; // Well-known private readonly IS1 s1; private readonly IS2 s2; public void A1() { _httpClientFactory.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { _httpClientFactory.Use(); s2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { _httpClientFactory.Use(); } } [ApiController] public class WithUseInComplexBlocks : ControllerBase // Noncompliant { IS1 s1; IS2 s2; IS3 s3; IS4 s4; IS5 s5; IS6 s6; IS7 s7; IS8 s8; IS9 s9; IS10 s10; public void If() // Secondary {{May belong to responsibility #2.}} { if (true) { s1.Use(); } else { if (false) { s2.Use(); } } } public void Switch() // Secondary {{May belong to responsibility #2.}} { switch (0) { case 0: s2.Use(); break; case 1: s3.Use(); break; } } public void For() // Secondary {{May belong to responsibility #2.}} { for (int i = 0; i < 1; i++) { s1.Use(); s3.Use(); } } public void TryCatchFinally() // Secondary {{May belong to responsibility #3.}} { try { s4.Use(); } catch { s5.Use(); } finally { try { s6.Use(); } catch { s4.Use(); } } } public void Using() // Secondary {{May belong to responsibility #3.}} { using (new ADisposable()) { s5.Use(); s7.Use(); } } public void BlocksAndParentheses() // Secondary {{May belong to responsibility #1.}} { { { ((s8)).Use(); } } } public void NestedLocalFunctions() // Secondary {{May belong to responsibility #1.}} { void LocalFunction() { void NestedLocalFunction() { s8.Use(); } s9.Use(); } LocalFunction(); StaticLocalFunction(s10); static void StaticLocalFunction(IS10 s10) { s10.Use(); } } class ADisposable : IDisposable { public void Dispose() { } } } [ApiController] public class WithMethodsDependingOnEachOther : ControllerBase // Noncompliant { IS1 s1; IS2 s2; IS3 s3; IS4 s4; IS5 s5; IS6 s6; IS7 s7; // Chain: A2 to A1 void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} void A2() { s2.Use(); A1(); } // Secondary {{May belong to responsibility #1.}} void A3() { s2.Use(); } // Secondary {{May belong to responsibility #1.}} // 1-cycle A4 => no service used => ignore void A4() { A4(); } // 2-cycle A5, A6 => no service used => ignore void A5() { A6(); } void A6() { A5(); } // 3-cycle A7, A8, A9 => no service used => ignore void A7() { A8(); } void A8() { A9(); } void A9() { A7(); } // 3-cycle A10, A11, A12 with A13 depending on A12 via s3 void A10() { A11(); } // Secondary {{May belong to responsibility #2.}} void A11() { A12(); } // Secondary {{May belong to responsibility #2.}} void A12() { A10(); s3.Use(); } // Secondary {{May belong to responsibility #2.}} void A13() { s3.Use(); } // Secondary {{May belong to responsibility #2.}} // 3-cycle A14, A15, A16 with chain A18 -> A15 via s4 void A14() { A15(); s4.Use(); } // Secondary {{May belong to responsibility #3.}} void A15() { A16(); } // Secondary {{May belong to responsibility #3.}} void A16() { A14(); } // Secondary {{May belong to responsibility #3.}} void A17() { s4.Use(); } // Secondary {{May belong to responsibility #3.}} // Independent method => no service used => ignore void A18() { } // Independent method with its own service void A19() { s5.Use(); } // Secondary {{May belong to responsibility #4.}} // Two actions calling a third one void A20() { A22(); } // Secondary {{May belong to responsibility #5.}} void A21() { A22(); } // Secondary {{May belong to responsibility #5.}} void A22() { s6.Use(); s7.Use(); } // Secondary {{May belong to responsibility #5.}} } [ApiController] public class WithServiceProvidersInjectionCoupled : ControllerBase // Compliant: s1Provider and s2Provider are known to provide services { private readonly Func s1Provider; private readonly Func s2Provider; public WithServiceProvidersInjectionCoupled(Func s1Provider, Func s2Provider) { this.s1Provider = s1Provider; this.s2Provider = s2Provider; } public void A1() { s1Provider().Use(); var s2 = s2Provider(42); s2.Use(); } public void A2() { (s2Provider(42)).Use(); } } [ApiController] public class ApiController : ControllerBase { } public class DoesNotInheritDirectlyFromControllerBase(IS1 s1, IS2 s2) : ApiController // Compliant, we report only in classes that inherit directly from ControllerBase { public IActionResult A1() { s1.Use(); return Ok(); } public IActionResult A2() { s2.Use(); return Ok(); } } public class InheritsFromController(IS1 s1, IS2 s2) : Controller // Compliant, we report only in classes that inherit directly from ControllerBase { public IActionResult A1() { s1.Use(); return Ok(); } public IActionResult A2() { s2.Use(); return Ok(); } } public abstract class AbstractController (IS1 s1, IS2 s2) : ControllerBase // Compliant, we don't report on abstract controllers { public IActionResult A1() { s1.Use(); return Ok(); } public IActionResult A2() { s2.Use(); return Ok(); } public abstract IActionResult A3(); } [ApiController] public class WithServiceProvidersInjectionUsedInGroups : ControllerBase // Noncompliant { private IS1 _s1; private Func, IS5> _s5Provider; private Func S2Provider { get; } private Func S3Provider => i => null; private Func S4Provider { get; set; } = (s, c) => null; private Func, IS5> S5Provider => _s5Provider; public void A1() { _s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { S2Provider().Use(); S3Provider(42).Use(); } // Secondary {{May belong to responsibility #3.}} public void A3() { S3Provider(42).Use(); } // Secondary {{May belong to responsibility #3.}} public void A4() { S4Provider("42", '4').Use(); _s5Provider(false, 3u, () => 42.0).Use(); } // Secondary {{May belong to responsibility #2.}} public void A5() { _s5Provider(true, 3u, () => 42.0).Use(); } // Secondary {{May belong to responsibility #2.}} public void A6() { S5Provider(true, 3u, () => 42.0).Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithServiceWrappersInjection : ControllerBase // Compliant: no way to know whether s2Invoker wraps a service { private readonly Func s1Provider; private readonly Action s2Invoker; public WithServiceWrappersInjection(Func s1Provider, Action s2Invoker) { this.s1Provider = s1Provider; this.s2Invoker = s2Invoker; } public void A1() { s1Provider().Use(); s2Invoker(); } public void A2() { s2Invoker(); } } [ApiController] public class WithLazyServicesInjection : ControllerBase // Noncompliant { private readonly Lazy s1Lazy; private readonly Lazy s2Lazy; private readonly Lazy s3Lazy; private readonly IS4 s4; public void A1() { s1Lazy.Value.Use(); s2Lazy.Value.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2Lazy.Value.Use(); s4.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s3Lazy.Value.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithMixOfLazyServicesAndServiceProviders : ControllerBase // Noncompliant { private readonly Lazy s1Lazy; private readonly Lazy s2Lazy; private readonly IS3 s3; private readonly IS4 s4; private readonly Func s5Provider; private readonly Func s6Provider; public void A1() { s1Lazy.Value.Use(); s3.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s1Lazy.Value.Use(); s2Lazy.Value.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s4.Use(); var s5 = s5Provider(); s5.Use(); } // Secondary {{May belong to responsibility #2.}} public void A4() { s5Provider().Use(); var s6ProviderAlias = s6Provider(); s6ProviderAlias.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithIApi : ControllerBase // Compliant { private readonly IApi _api; public WithIApi(IApi api) { _api = api; } public void A1() { _api.Use(); } public void A2() { _api.Use(); } public interface IApi : IServiceWithAnAPI { } } [ApiController] public class WithUnusedServices : ControllerBase // Compliant { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; private readonly IS4 s4; public void A1() { } public void A2() { } public void A3() { } public void A4() { } } [ApiController] public class WithUnusedAndUsedServicesFormingTwoGroups : ControllerBase // Compliant { // s2, s3 and s4 form a non-singleton, but no action use any of them => the set is filtered out private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; private readonly IS4 s4; public void A1() { s1.Use(); } } [ApiController] public class WithUnusedAndUsedServicesFormingThreeGroups : ControllerBase // Noncompliant {{This controller has multiple responsibilities and could be split into 3 smaller controllers.}} { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; private readonly IS4 s4; public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #2.}} public void A4() { s4.Use(); } // Secondary {{May belong to responsibility #3.}} } [ApiController] public class WithMethodOverloads : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; private readonly IS4 s4; private readonly IS5 s5; private readonly IS6 s6; public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A1(int i) { s2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A1(string s) { s3.Use(); } // Secondary {{May belong to responsibility #1.}} public void A1(int i, string s) { s4.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s5.Use(); } // Secondary {{May belong to responsibility #2.}} public void A2(int i) { s6.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithIndexer : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; public int this[int i] { get { s1.Use(); return 42; } set { s2.Use(); } } // Clamp A1 and A2 together public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithIndexerOverloads : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; private readonly IS4 s4; private readonly IS5 s5; public int this[int i] { get { s1.Use(); return 42; } set { s2.Use(); } } // Clamp A1 and A2 together public int this[long i] { get { s3.Use(); return 42; } set { s4.Use(); } } // Clamp A1 and A2 together public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #1.}} public void A4() { s4.Use(); } // Secondary {{May belong to responsibility #1.}} public void A5() { s5.Use(); } // Secondary {{May belong to responsibility #2.}} } [ApiController] public class WithIndexerArrow : ControllerBase // Noncompliant { private readonly IS1ReturningInt s1; private readonly IS2ReturningInt s2; private readonly IS3ReturningInt s3; public int this[int i] => 42 + s1.GetValue() + s2.GetValue(); // Clamp A1 and A2 together public void A1() { s1.GetValue(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.GetValue(); } // Secondary {{May belong to responsibility #1.}} public void A3() { s3.GetValue(); } // Secondary {{May belong to responsibility #2.}} public interface IS1ReturningInt { int GetValue(); } public interface IS2ReturningInt { int GetValue(); } public interface IS3ReturningInt { int GetValue(); } } [ApiController] public class WithClassInjection : ControllerBase // Noncompliant { private readonly Class1 s1; private readonly Class2 s2; private readonly Class3 s3; public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #1.}} public void A4() { s3.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A5() { } // No service used => ignore } [ApiController] public class WithStructInjection : ControllerBase // Noncompliant { private readonly Struct1 s1; private readonly Struct2 s2; private readonly Struct3 s3; public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #1.}} public void A4() { s3.Use(); s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A5() { } // No service used => ignore } [ApiController] public class WithMixOfInjectionTypes : ControllerBase // Noncompliant { private readonly IS1 interface1; private readonly IS2 interface2; private readonly Class1 class1; private readonly Class2 class2; private readonly Class3 class3; // Unused private readonly Struct1 struct1; private readonly Struct2 struct2; private readonly Struct3 struct3; // Unused private readonly Lazy lazy1; private readonly Lazy lazy2; private readonly Lazy lazy3; private readonly Lazy lazy4; private readonly Lazy lazy5; private readonly Lazy> lazy6; private readonly Lazy> lazy7; // Unused private readonly Func delegate1; private readonly Func delegate2; private readonly Func delegate3; private readonly Func> delegate4; private readonly Func> delegate5; // Unused public void A1() { interface1.Use(); class1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { interface2.Use(); class1.Use(); struct2.Use(); } // Secondary {{May belong to responsibility #1.}} public void A3() { class2.Use(); } // Secondary {{May belong to responsibility #4.}} public void A4() { struct1.Use(); class2.Use(); _ = lazy4; } // Secondary {{May belong to responsibility #4.}} public void A5() { struct2.Use(); lazy1.Value.Use(); } // Secondary {{May belong to responsibility #1.}} public void A6() { _ = lazy1.Value; lazy3.Value.Use(); } // Secondary {{May belong to responsibility #1.}} public void A7() { _ = lazy3.ToString(); } // Secondary {{May belong to responsibility #1.}} public void A8() { lazy4.Value.Use(); _ = lazy5.IsValueCreated; } // Secondary {{May belong to responsibility #4.}} public void A9() { _ = lazy6.GetHashCode(); delegate1().Use(); } // Secondary {{May belong to responsibility #2.}} public void A10() { _ = delegate2(); delegate3.Invoke().Use(); } // Secondary {{May belong to responsibility #2.}} public void A11() { _ = delegate1.Target; ((delegate3())).Use(); } // Secondary {{May belong to responsibility #2.}} public void A12() { _ = delegate4(); } // Secondary {{May belong to responsibility #3.}} public void A13() { } // No service used => ignored } [ApiController] public class WithDestructor : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2; ~WithDestructor() { s1.Use(); s2.Use(); } public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #2.}} } public class NotAControllerForCoverage { private readonly IS1 s1; private readonly IS2 s2; } namespace CSharp13 { //https://sonarsource.atlassian.net/browse/NET-509 [ApiController] public partial class WithFieldBackedPartialProperties : ControllerBase // Noncompliant { private IS1 _s1; private IS2 _s2; public partial IS1 S1 { get => _s1; } public partial IS2 S2 { get => _s2; init => _s2 = value; } public WithFieldBackedPartialProperties(IS1 s1, IS2 s2) { _s1 = s1; _s2 = s2; } public void A1() { S1.Use(); } // Secondary {{May belong to responsibility #1.}} // Secondary @-1 {{May belong to responsibility #1.}} public void A2() { S2.Use(); } // Secondary {{May belong to responsibility #2.}} // Secondary @-1 {{May belong to responsibility #2.}} } [ApiController] public partial class WithPartialIndexer : ControllerBase // Noncompliant { private readonly IS1 s1; private readonly IS2 s2; private readonly IS3 s3; public partial int this[int i] { get { s1.Use(); return 42; } set { s2.Use(); } } // Clamp A1 and A2 together public void A1() { s1.Use(); } // Secondary {{May belong to responsibility #1.}} // Secondary @-1 {{May belong to responsibility #1.}} public void A2() { s2.Use(); } // Secondary {{May belong to responsibility #1.}} // Secondary @-1 {{May belong to responsibility #1.}} public void A3() { s3.Use(); } // Secondary {{May belong to responsibility #2.}} // Secondary @-1 {{May belong to responsibility #2.}} } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.CSharp9.cs ================================================ using Microsoft.AspNetCore.Mvc; using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; [ApiController] [Route("Hello")] public class SomeController : ControllerBase { [HttpGet("foo")] public async Task Foo() { HttpClient client = new(); // Noncompliant return "bar"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.Csharp8.cs ================================================ using Microsoft.AspNetCore.Mvc; using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; [ApiController] public class SomeController : ControllerBase { private readonly IHttpClientFactory _clientFactory; private HttpClient clientField = new HttpClient(); [HttpGet("foo")] public async Task Foo() { using var clientA = new HttpClient(); // Noncompliant // ^^^^^^^^^^^^^^^^ await clientA.GetStringAsync(""); clientField ??= new HttpClient(); // Compliant using var pooledClient = _clientFactory.CreateClient(); // Compliant _ = true switch { true => new HttpClient() }; // Compliant return "bar"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/ControllersReuseClient.cs ================================================ using Microsoft.AspNetCore.Mvc; using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; [ApiController] [Route("Hello")] public class SomeController : ControllerBase { private readonly IHttpClientFactory _clientFactory; private HttpClient clientField = new HttpClient(); // Compliant, it can be reused between actions private HttpClient ClientProperty { get; set; } = new HttpClient(); // Compliant, it can be reused between actions private HttpClient ClientPropertyAccessorArrowClause { get => new HttpClient(); } // Noncompliant private HttpClient ClientPropertyAccessorMethodBody { get { var anotherStatement = 1; return new HttpClient(); } } // Noncompliant private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Noncompliant public SomeController() { clientField = new HttpClient(); // Compliant } [HttpGet("foo")] public async Task Foo() { using (var clientB = new HttpClient()) //Noncompliant {{Reuse HttpClient instances rather than create new ones with each controller action invocation.}} // ^^^^^^^^^^^^^^^^ { await clientB.GetStringAsync(""); } var client = new HttpClient(); // Noncompliant clientField = new HttpClient(); // Noncompliant ClientProperty = new HttpClient(); // Noncompliant var local = new HttpClient(); // Noncompliant local = new System.Net.Http.HttpClient(); // Noncompliant var fromStaticMethod = StaticCreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167 var fromMethod = CreateClient(); // FN - see https://github.com/SonarSource/rspec/pull/3847#discussion_r1559510167 local = ClientPropertyAccessorArrow; // Compliant local = ClientPropertyAccessorArrow; // Compliant // Lambda _ = new Lazy(() => new HttpClient()); // Noncompliant FP // Conditional code if (true) _ = new HttpClient(); // Compliant switch (true) { case true: _ = new HttpClient(); // Compliant break; } _ = true ? new HttpClient() : null; // Compliant return "bar"; } private static HttpClient StaticCreateClient() { return new HttpClient(); // Compliant, we raise only in actions } private HttpClient CreateClient() { return new HttpClient(); // Compliant, we raise only in actions } } public class NotAController { private HttpClient ClientPropertyAccessorArrow => new HttpClient(); // Compliant, it's not in a controller. } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNet4x.PartialAutogenerated.cs ================================================ // using System.Web.Mvc; [Route("[controller]")] public partial class NonCompliantPartialController : Controller // This is non-compliant but primary issues are not reported on auto-generated classes { [Route("/[action]")] // Secondary [first, second] public ActionResult Index5() => View(); [Route("/SubPath/Index6_1")] // Secondary [first, second] [Route("/[controller]/Index6_2")] // Secondary [first, second] public ActionResult Index6() => View(); } [Route("[controller]")] public partial class PartialCompliantController : Controller // This class makes its partial partial part in RouteTemplateShouldNotStartWithSlash.AspNet4x.cs compliant { [Route("[action]")] public ActionResult Index4() => View(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNet4x.cs ================================================ using System.Web.Mvc; [Route("[controller]")] public class NoncompliantController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} // ^^^^^^^^^^^^^^^^^^^^^^ { [Route("/Index1")] // Secondary // ^^^^^^^^^^^^^^^^ public ActionResult Index1() => View(); [Route("/SubPath/Index2_1")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^ [Route("/[controller]/Index2_2")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public ActionResult Index2() => View(); [Route("/[action]")] // Secondary // ^^^^^^^^^^^^^^^^^^ public ActionResult Index3() => View(); [Route("/SubPath/Index4_1")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^ [Route("/[controller]/Index4_2")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public ActionResult Index4() => View(); } [RoutePrefix("[controller]")] public class NoncompliantWithRoutePrefixController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ { [Route("/Index1")] // Secondary // ^^^^^^^^^^^^^^^^ [Route("/Index1")] // Secondary // ^^^^^^^^^^^^^^^^ public ActionResult Index1() => View(); } [Route("[controller]")] [Route("[controller]/[action]")] public class NoncompliantMultiRouteController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ { [Route("/Index1")] // Secondary // ^^^^^^^^^^^^^^^^ public ActionResult Index1() => View(); [Route("/SubPath/Index2_1")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^ [Route("/[controller]/Index2_2")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public ActionResult Index2() => View(); [Route("/[action]")] // Secondary // ^^^^^^^^^^^^^^^^^^ public ActionResult Index3() => View(); } [Route("[controller]")] public class CompliantController : Controller // Compliant: at least one action has at least a relative route { [Route("/Index1")] public ActionResult Index1() => View(); [Route("/SubPath/Index2")] public ActionResult Index2() => View(); [Route("/[action]")] public ActionResult Index3() => View(); [Route("/[controller]/Index4_1")] [Route("SubPath/Index4_2")] // The relative route public ActionResult Index4() => View(); } public class NoncompliantNoControllerRouteController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and add a controller route with the common prefix.}} { [Route("/Index1")] // Secondary public ActionResult Index1() => View(); [Route("/SubPath/Index2_1")] // Secondary [Route("/[controller]/Index2_2")] // Secondary public ActionResult Index2() => View(); [Route("/[action]")] // Secondary public ActionResult Index3() => View(); } public class CompliantNoControllerRouteNoActionRouteController : Controller // Compliant { public ActionResult Index1() => View(); // Default route -> relative [Route("/SubPath/Index2")] public ActionResult Index2() => View(); [Route("/[action]")] public ActionResult Index3() => View(); [Route("/SubPath/Index4_1")] [Route("/[controller]/Index4_2")] public ActionResult Index4() => View(); } public class CompliantNoControllerRouteEmptyActionRouteController : Controller // Compliant { [Route] public ActionResult Index1() => View(); // Empty route -> relative [Route("/SubPath/Index2")] public ActionResult Index2() => View(); [Route("/[action]")] public ActionResult Index3() => View(); [Route("/SubPath/Index4_1")] [Route("/[controller]/Index4_2")] public ActionResult Index4() => View(); } public class ControllerWithoutActions : Controller // Compliant { public int NotAnAction() { return 1; } } public class EmptyController : Controller { } // Compliant public class NotAController { } // For coverage namespace WithAliases { using MyRoute = RouteAttribute; using ASP = System.Web; public class WithAliasedRouteAttributeController : Controller // Noncompliant { [MyRoute(@"/[controller]")] // Secondary public ActionResult Index() => View(); } public class WithFullQualifiedPartiallyAliasedNameController : Controller // Noncompliant { [ASP.Mvc.RouteAttribute("/[action]")] // Secondary public ActionResult Index() => View(); } } [Route("[controller]")] public partial class NonCompliantPartialController : Controller // Noncompliant [first] { [Route("/Index1")] // Secondary [first, second] public ActionResult Index1() => View(); [Route("/SubPath/Index2_1")] // Secondary [first, second] [Route("/[controller]/Index2_2")] // Secondary [first, second] public ActionResult Index2() => View(); } [Route("[controller]")] public partial class NonCompliantPartialController : Controller // Noncompliant [second] { [Route("/[action]")] // Secondary [first, second] public ActionResult Index3() => View(); [Route("/SubPath/Index4_1")] // Secondary [first, second] [Route("/[controller]/Index4_2")] // Secondary [first, second] public ActionResult Index4() => View(); } [Route("[controller]")] public partial class CompliantPartialController : Controller // Compliant, due to the other partial part of this class { [Route("/Index1")] public ActionResult Index1() => View(); [Route("/SubPath/Index2")] public ActionResult Index2() => View(); } [Route("[controller]")] public partial class CompliantPartialController : Controller { [Route("[action]")] public ActionResult Index3() => View(); } [Route("[controller]")] public partial class PartialCompliantController : Controller // Compliant - its autogenerated part has at least one compliant action { [Route("/[action]")] public ActionResult Index3() => View(); } // https://github.com/SonarSource/sonar-dotnet/issues/9002 public class Repro_9002 : Controller // Noncompliant { [Route("~/B")] // Secondary public ActionResult Index() => View(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNet4x.vb ================================================ Imports System.Web.Mvc Public Class NoncompliantController ' Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} Inherits Controller ' Secondary Public Function Index1() As ActionResult Return View() End Function ' Secondary ' Secondary Public Function Index2() As ActionResult Return View() End Function ' Secondary Public Function Index3() As ActionResult Return View() End Function ' Secondary ' Secondary Public Function Index4() As ActionResult Return View() End Function End Class Public Class NoncompliantWithRoutePrefixController ' Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} Inherits Controller ' Secondary Public Function Index1() As ActionResult Return View() End Function End Class Public Class NoncompliantMultiRouteController ' Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} Inherits Controller ' Secondary Public Function Index1() As ActionResult Return View() End Function ' Secondary ' Secondary Public Function Index2() As ActionResult Return View() End Function ' Secondary Public Function Index3() As ActionResult Return View() End Function End Class Public Class CompliantController ' Compliant: at least one action has at least a relative route Inherits Controller Public Function Index1() As ActionResult Return View() End Function Public Function Index2() As ActionResult Return View() End Function Public Function Index3() As ActionResult Return View() End Function ' The relative route Public Function Index4() As ActionResult Return View() End Function End Class Public Class NoncompliantNoControllerRouteController ' Noncompliant {{Change the paths of the actions of this controller to be relative and add a controller route with the common prefix.}} Inherits Controller ' Secondary Public Function Index1() As ActionResult Return View() End Function ' Secondary ' Secondary Public Function Index2() As ActionResult Return View() End Function ' Secondary Public Function Index3() As ActionResult Return View() End Function End Class Public Class CompliantNoControllerRouteNoActionRouteController ' Compliant Inherits Controller Public Function Index1() As ActionResult Return View() End Function ' Default route -> relative Public Function Index2() As ActionResult Return View() End Function Public Function Index3() As ActionResult Return View() End Function Public Function Index4() As ActionResult Return View() End Function End Class Public Class CompliantNoControllerRouteEmptyActionRouteController ' Compliant Inherits Controller Public Function Index1() As ActionResult Return View() End Function ' Empty route -> relative Public Function Index2() As ActionResult Return View() End Function Public Function Index3() As ActionResult Return View() End Function Public Function Index4() As ActionResult Return View() End Function End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNetCore.CSharp12.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; public class WithUserDefinedAttributeDerivedFromHttpMethodAttributeController : Controller // Noncompliant: MyHttpMethodAttribute derives from HttpMethodAttribute { [MyHttpMethod("/Index")] // Secondary public IActionResult WithUserDefinedAttribute() => View(); private sealed class MyHttpMethodAttribute(string template) : HttpMethodAttribute([template]) { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNetCore.PartialAutogenerated.cs ================================================ // using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; [Route("[controller]")] public partial class NoncompliantPartialAutogeneratedController : Controller // This is non-compliant but primary issues are not reported on auto-generated classes { [HttpGet("/[action]")] // Secondary public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] // Secondary public IActionResult Index4() => View(); } [Route("[controller]")] public partial class CompliantPartialAutogeneratedController : Controller // This class makes its partial partial part in RouteTemplateShouldNotStartWithSlash.AspNet4x.cs compliant { [HttpGet("[action]")] public IActionResult Index3() => View(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNetCore.cs ================================================ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; [Route("[controller]")] public class NoncompliantController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} // ^^^^^^^^^^^^^^^^^^^^^^ { [Route("/Index1")] // Secondary {{Change this path to be relative to the controller route defined on class level.}} // ^^^^^^^^^^^^^^^^ public IActionResult Index1() => View(); [Route("/SubPath/Index2")] // Secondary {{Change this path to be relative to the controller route defined on class level.}} // ^^^^^^^^^^^^^^^^^^^^^^^^ public IActionResult Index2() => View(); [HttpGet("/[action]")] // Secondary {{Change this path to be relative to the controller route defined on class level.}} // ^^^^^^^^^^^^^^^^^^^^ public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] // Secondary {{Change this path to be relative to the controller route defined on class level.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [HttpGet("/[controller]/Index4_2")] // Secondary {{Change this path to be relative to the controller route defined on class level.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public IActionResult Index4() => View(); } [Route("[controller]")] [Route("[controller]/[action]")] public class NoncompliantMultiRouteController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ { [Route("/Index1")] // Secondary // ^^^^^^^^^^^^^^^^ public IActionResult Index1() => View(); [Route("/SubPath/Index2")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^ public IActionResult Index2() => View(); [HttpGet("/[action]")] // Secondary // ^^^^^^^^^^^^^^^^^^^^ public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [HttpGet("/[controller]/Index4_2")] // Secondary // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ public IActionResult Index4() => View(); } [Route("[controller]")] public class CompliantController : Controller // Compliant: at least one action has at least a relative route { [Route("/Index1")] public IActionResult Index1() => View(); [Route("/SubPath/Index2")] public IActionResult Index2() => View(); [HttpGet("/[action]")] public IActionResult Index3() => View(); [HttpGet("/[controller]/Index4_1")] [HttpGet("SubPath/Index4_2")] // The relative route public IActionResult Index4() => View(); } public class NoncompliantNoControllerRouteController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and add a controller route with the common prefix.}} { [Route("/Index1")] // Secondary {{Add a controller route with a common prefix and change this path to be relative it.}} public IActionResult Index1() => View(); [Route("/SubPath/Index2")] // Secondary {{Add a controller route with a common prefix and change this path to be relative it.}} public IActionResult Index2() => View(); [HttpGet("/[action]")] // Secondary {{Add a controller route with a common prefix and change this path to be relative it.}} public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] // Secondary {{Add a controller route with a common prefix and change this path to be relative it.}} [HttpGet("/[controller]/Index4_2")] // Secondary {{Add a controller route with a common prefix and change this path to be relative it.}} public IActionResult Index4() => View(); } public class CompliantNoControllerRouteNoActionRouteController : Controller // Compliant { public IActionResult Index1() => View(); // Conventional route -> relative [Route("/SubPath/Index2")] public IActionResult Index2() => View(); [HttpGet("/[action]")] public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] [HttpGet("/[controller]/Index4_2")] public IActionResult Index4() => View(); } public class CompliantNoControllerRouteEmptyActionRouteController : Controller // Compliant { [HttpGet] public IActionResult Index1() => View(); // Empty route template -> relative conventional routing [Route("/SubPath/Index2")] public IActionResult Index2() => View(); [HttpGet("/[action]")] public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] [HttpGet("/[controller]/Index4_2")] public IActionResult Index4() => View(); } namespace WithAliases { using MyRoute = RouteAttribute; using ASP = Microsoft.AspNetCore; public class WithAliasedRouteAttributeController : Controller // Noncompliant { [MyRoute(@"/[controller]")] // Secondary public IActionResult Index() => View(); } public class WithFullQualifiedPartiallyAliasedNameController : Controller // Noncompliant { [ASP.Mvc.RouteAttribute("/[action]")] // Secondary public IActionResult Index() => View(); } } public class MultipleActionsAllRoutesStartingWithSlash1Controller : Controller // Noncompliant { [HttpGet("/Index1")] // Secondary public IActionResult WithHttpAttribute() => View(); [Route("/Index2")] // Secondary public IActionResult WithRouteAttribute() => View(); } public class MultipleActionsAllRoutesStartingWithSlash2Controller : Controller // Noncompliant { [HttpGet("/Index1")] // Secondary [HttpGet("/Index3")] // Secondary public IActionResult WithHttpAttributes() => View(); [Route("/Index2")] // Secondary [Route("/Index4")] // Secondary [HttpGet("/Index5")] // Secondary public IActionResult WithRouteAndHttpAttributes() => View(); } [Route("[controller]")] public class MultipleActionsAllRoutesStartingWithSlash3Controller : Controller // Noncompliant { [HttpGet("/Index1")] // Secondary [HttpGet("/Index3")] // Secondary public IActionResult WithHttpAttributes() => View(); [Route("/Index2")] // Secondary [Route("/Index4")] // Secondary [HttpGet("/Index5")] // Secondary public IActionResult WithRouteAndHttpAttributes() => View(); } public class MultipleActionsSomeRoutesStartingWithSlash1Controller : Controller // Compliant: some routes are relative { [HttpGet("Index1")] public IActionResult WithHttpAttribute() => View(); [Route("/Index2")] public IActionResult WithRouteAttribute() => View(); } public class MultipleActionsSomeRoutesStartingWithSlash2Controller : Controller // Compliant: some routes are relative { [HttpGet("Index1")] [HttpGet("/Index1")] public IActionResult WithHttpAttributes() => View(); [Route("/Index2")] public IActionResult WithRouteAttribute() => View(); } public class MultipleActionsSomeRoutesStartingWithSlash3Controller : Controller // Compliant: some routes are relative { [HttpGet("Index1")] [HttpPost("/Index1")] public IActionResult WithHttpAttributes() => View(); [Route("/Index2")] public IActionResult WithRouteAttribute() => View(); } [NonController] public class NotAController : Controller // Compliant: not a controller { [Route("/Index1")] public IActionResult Index() => View(); } public class ControllerWithoutControllerSuffix : Controller // Noncompliant { [Route("/Index1")] // Secondary public IActionResult Index() => View(); } [Controller] public class ControllerWithControllerAttribute : Controller // Noncompliant { [Route("/Index1")] // Secondary public IActionResult Index() => View(); } public class ControllerWithoutParameterlessConstructor : Controller // Noncompliant { public ControllerWithoutParameterlessConstructor(int i) { } [Route("/Index1")] // Secondary public IActionResult Index() => View(); } class NonPublicController : Controller // Compliant, actions in non-public classes are not reachable { [Route("/Index1")] public IActionResult Index() => View(); } public class ControllerRequirementsInfluenceActionsCheck { internal class InternalController : Controller // Compliant, nested classes are not reachable { [Route("/Index1")] public IActionResult Index() => View(); } protected class ProtectedController : Controller // Compliant, nested classes are not reachable { [Route("/Index1")] public IActionResult Index() => View(); } public class PublicNestedController : Controller // Compliant, actions in nested classes are not reachable { [Route("/Index1")] public IActionResult Index() => View(); } } public struct AStruct { public class PublicNestedController : Controller // Compliant, actions in nested types are not reachable { [Route("/Index1")] public IActionResult Index() => View(); } } [Route("[controller]")] public partial class NoncompliantPartialController : Controller // Noncompliant [first] { [Route("/Index1")] // Secondary [first, second] public IActionResult Index1() => View(); [Route("/SubPath/Index2")] // Secondary [first, second] public IActionResult Index2() => View(); } [Route("[controller]")] public partial class NoncompliantPartialController : Controller // Noncompliant [second] { [HttpGet("/[action]")] // Secondary [first, second] public IActionResult Index3() => View(); [HttpGet("/SubPath/Index4_1")] // Secondary [first, second] public IActionResult Index4() => View(); } [Route("[controller]")] public partial class CompliantPartialController : Controller // Compliant, due to the other partial part of this class { [Route("/Index1")] public IActionResult Index1() => View(); [Route("/SubPath/Index2")] public IActionResult Index2() => View(); } [Route("[controller]")] public partial class CompliantPartialController : Controller { [HttpGet("[action]")] public IActionResult Index3() => View(); } [Route("[controller]")] public partial class NoncompliantPartialAutogeneratedController : Controller // Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} { [Route("/Index1")] // Secondary public IActionResult Index1() => View(); [Route("/SubPath/Index2")] // Secondary public IActionResult Index2() => View(); } [Route("[controller]")] public partial class CompliantPartialAutogeneratedController : Controller // Compliant, as its autogenerated partial class is compliant { [Route("/Index1")] public IActionResult Index1() => View(); [Route("/SubPath/Index2")] public IActionResult Index2() => View(); } // https://github.com/SonarSource/sonar-dotnet/issues/9002 public class Repro_9002 : Controller // Noncompliant { [Route("~/B")] // Secondary public IActionResult Index() => View(); } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/RouteTemplateShouldNotStartWithSlash.AspNetCore.vb ================================================ Imports Microsoft.AspNetCore.Mvc Imports Microsoft.AspNetCore.Mvc.Routing Imports MyRoute = Microsoft.AspNetCore.Mvc.RouteAttribute Imports ASP = Microsoft.AspNetCore Public Class NoncompliantController ' Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} Inherits Controller ' Secondary Public Function Index1() As IActionResult Return View() End Function ' Secondary Public Function Index2() As IActionResult Return View() End Function ' Secondary Public Function Index3() As IActionResult Return View() End Function ' Secondary ' Secondary Public Function Index4() As IActionResult Return View() End Function End Class Public Class NoncompliantMultiRouteController ' Noncompliant {{Change the paths of the actions of this controller to be relative and adapt the controller route accordingly.}} Inherits Controller ' Secondary Public Function Index1() As IActionResult Return View() End Function ' Secondary Public Function Index2() As IActionResult Return View() End Function ' Secondary Public Function Index3() As IActionResult Return View() End Function ' Secondary ' Secondary Public Function Index4() As IActionResult Return View() End Function End Class Public Class CompliantController ' Compliant: at least one action has at least a relative route Inherits Controller Public Function Index1() As IActionResult Return View() End Function Public Function Index2() As IActionResult Return View() End Function Public Function Index3() As IActionResult Return View() End Function ' The relative route Public Function Index4() As IActionResult Return View() End Function End Class Public Class NoncompliantNoControllerRouteController ' Noncompliant {{Change the paths of the actions of this controller to be relative and add a controller route with the common prefix.}} Inherits Controller ' Secondary Public Function Index1() As IActionResult Return View() End Function ' Secondary Public Function Index2() As IActionResult Return View() End Function ' Secondary Public Function Index3() As IActionResult Return View() End Function ' Secondary ' Secondary Public Function Index4() As IActionResult Return View() End Function End Class Public Class CompliantNoControllerRouteNoActionRouteController ' Compliant Inherits Controller Public Function Index1() As IActionResult Return View() End Function ' Default route -> relative Public Function Index2() As IActionResult Return View() End Function Public Function Index3() As IActionResult Return View() End Function Public Function Index4() As IActionResult Return View() End Function End Class Public Class CompliantNoControllerRouteEmptyActionRouteController ' Compliant Inherits Controller Public Function Index1() As IActionResult Return View() End Function ' Empty route -> relative Public Function Index2() As IActionResult Return View() End Function Public Function Index3() As IActionResult Return View() End Function Public Function Index4() As IActionResult Return View() End Function End Class Namespace WithAliases Public Class WithAliasedRouteAttributeController ' Noncompliant Inherits Controller ' Secondary Public Function Index() As IActionResult Return View() End Function End Class Public Class WithFullQualifiedPartiallyAliasedNameController ' Noncompliant Inherits Controller ' Secondary Public Function Index() As IActionResult Return View() End Function End Class End Namespace Public Class MultipleActionsAllRoutesStartingWithSlash1Controller ' Noncompliant Inherits Controller ' Secondary Public Function WithHttpAttribute() As IActionResult Return View() End Function ' Secondary Public Function WithRouteAttribute() As IActionResult Return View() End Function End Class Public Class MultipleActionsAllRoutesStartingWithSlash2Controller ' Noncompliant Inherits Controller ' Secondary ' Secondary Public Function WithHttpAttributes() As IActionResult Return View() End Function ' Secondary ' Secondary ' Secondary Public Function WithRouteAndHttpAttributes() As IActionResult Return View() End Function End Class Public Class MultipleActionsAllRoutesStartingWithSlash3Controller ' Noncompliant Inherits Controller ' Secondary ' Secondary Public Function WithHttpAttributes() As IActionResult Return View() End Function ' Secondary ' Secondary ' Secondary Public Function WithRouteAndHttpAttributes() As IActionResult Return View() End Function End Class Public Class MultipleActionsSomeRoutesStartingWithSlash1Controller ' Compliant: some routes are relativ Inherits Controller Public Function WithHttpAttribute() As IActionResult Return View() End Function Public Function WithRouteAttribute() As IActionResult Return View() End Function End Class Public Class MultipleActionsSomeRoutesStartingWithSlash2Controller ' Compliant: some routes are relative Inherits Controller Public Function WithHttpAttributes() As IActionResult Return View() End Function Public Function WithRouteAttribute() As IActionResult Return View() End Function End Class Public Class MultipleActionsSomeRoutesStartingWithSlash3Controller ' Compliant: some routes are relative Inherits Controller Public Function WithHttpAttributes() As IActionResult Return View() End Function Public Function WithRouteAttribute() As IActionResult Return View() End Function End Class Public Class NotAController ' Compliant, not a controller Inherits Controller Public Function Index() As IActionResult Return View() End Function End Class Public Class ControllerWithoutControllerSuffix ' Noncompliant Inherits Controller ' Secondary Public Function Index() As IActionResult Return View() End Function End Class Public Class ControllerWithControllerAttribute ' Noncompliant Inherits Controller ' Secondary Public Function Index() As IActionResult Return View() End Function End Class Public Class ControllerWithoutParameterlessConstructor ' Noncompliant Inherits Controller Public Sub New(i As Integer) End Sub ' Secondary Public Function Index() As IActionResult Return View() End Function End Class Public Class ControllerRequirementsInfluenceActionsCheck Friend Class InternalController ' Compliant, actions in nested classes are not reachable Inherits Controller Public Function Index() As IActionResult Return View() End Function End Class Protected Class ProtectedController ' Compliant, actions in nested classes are not reachable Inherits Controller Public Function Index() As IActionResult Return View() End Function End Class End Class ' https://github.com/SonarSource/sonar-dotnet/issues/9002 Public Class Repro_9002 ' Noncompliant Inherits Controller ' Secondary Public Function Index() As ActionResult Return View() End Function End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/SpecifyRouteAttribute.CSharp12.cs ================================================ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using MA = Microsoft.AspNetCore; public class RouteTemplateIsNotSpecifiedController : Controller { public IActionResult NoAttribute() => View(); // Compliant [HttpGet] public IActionResult WithHttpGetAttribute() => View(); // Compliant [HttpGet()] public IActionResult WithHttpGetAttributeWithParanthesis() => View(); // Compliant [HttpGetAttribute] public IActionResult WithFullAttributeName() => View(); // Compliant [Microsoft.AspNetCore.Mvc.HttpGet] public IActionResult WithNamespaceAttribute() => View(); // Compliant [MA.Mvc.HttpGet] public IActionResult WithAliasedNamespaceAttribute() => View(); // Compliant [method: HttpGet] public IActionResult WithScopedAttribute() => View(); // Compliant [HttpGet("/[controller]/[action]/{sortBy}")] public IActionResult AbsoluteUri1(string sortBy) => View(); // Compliant, absolute uri [HttpGet("~/[controller]/[action]/{sortBy}")] public IActionResult AbsoluteUri2(string sortBy) => View(); // Compliant, absolute uri public IActionResult Error() => View(); // Compliant } public class RouteTemplatesAreSpecifiedController : Controller // Noncompliant [controller] {{Specify the RouteAttribute when an HttpMethodAttribute or RouteAttribute is specified at an action level.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ { private const string ConstantRoute = "ConstantRoute"; [HttpGet("GetObject")] public IActionResult Get() => View(); // ^^^ Secondary [controller] [HttpGet("GetFirst")] [HttpGet("GetSecond")] public IActionResult GetMultipleTemplates() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("GetFirst")] [HttpPut("PutFirst")] public IActionResult MixGetAndPut() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("GetFirst")] [HttpPut()] public IActionResult MixWithTemplateAndWithout() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet()] [HttpPut()] public IActionResult MixWithoutTemplate() => View(); [HttpPost("CreateObject")] public IActionResult Post() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpPut("UpdateObject")] public IActionResult Put() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpDelete("DeleteObject")] public IActionResult Delete() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpPatch("PatchObject")] public IActionResult Patch() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpHead("Head")] public IActionResult HttpHead() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpOptions("Options")] public IActionResult HttpOptions() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("details")] public IActionResult WithRoute() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("details", Order = 1)] public IActionResult WithRouteAndProperties1() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("details", Order = 1, Name = "Details")] public IActionResult WithRouteAndProperties2() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("details", Name = "Details", Order = 1)] public IActionResult WithRouteAndProperties3() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("[controller]/List/{sortBy}/{direction}")] [HttpGet("[controller]/Search/{sortBy}/{direction}")] public IActionResult RouteAndMethodMix(string sortBy) => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("details", Order = 1)] public IActionResult MultipleProperties1() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("details", Order = 1, Name = "Details")] public IActionResult MultipleProperties2() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("details", Name = "Details", Order = 1)] public IActionResult MultipleProperties3() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet(ConstantRoute)] public IActionResult Constant() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet(""" ConstantRoute """)] public IActionResult Constant2() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet($"Route {ConstantRoute}")] public IActionResult Interpolation1() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet($""" {ConstantRoute} """)] public IActionResult Interpolation2() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [HttpGet("GetObject")] public ActionResult WithActionResult() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route(" ")] public IActionResult WithSpace() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} [Route("\t")] public IActionResult WithTab() => View(); // Secondary [controller] {{By specifying an HttpMethodAttribute or RouteAttribute here, you will need to specify the RouteAttribute at the class level.}} // [HttpPost("Comment")] public IActionResult Comment() => View(); } [Route("api/[controller]")] public class WithRouteAttributeIsCompliantController : Controller { [HttpGet("Test")] public IActionResult Index() => View(); } public class WithUserDefinedAttributeController : Controller // Noncompliant [customAttribute] { [MyHttpMethod("Test")] public IActionResult Index() => View(); // Secondary [customAttribute] private sealed class MyHttpMethodAttribute(string template) : HttpMethodAttribute([template]) { } } public class WithCustomGetAttributeController : Controller // Noncompliant [custom-get-attribute] { [HttpGet("Test")] public IActionResult Index() => View(); // Secondary [custom-get-attribute] private sealed class HttpGetAttribute(string template) : HttpMethodAttribute([template]) { } } public class WithCustomController : DerivedController // Noncompliant [derivedController] { [HttpGet("Test")] public IActionResult Index() => View(); // Secondary [derivedController] } [Controller] public class WithAttributeController // Noncompliant [attribute-controller] { [HttpGet("Test")] public string Index() => "Hi!"; // Secondary [attribute-controller] } public class WithAttributeControllerUsingInheritanceController : Endpoint // FN { [HttpGet("Test")] public string Index() => "Hi!"; // FN } public class NamedController // FN { [HttpGet("Test")] public string Index() => "Hi!"; // FN } [NonController] public class NonController { [HttpGet("Test")] public string Index() => "Hi!"; } public class DerivedController : Controller { } [Controller] public class Endpoint { } [Route("api/[controller]")] public class ControllerWithRouteAttribute : Controller { } public class ControllerWithInheritedRoute : ControllerWithRouteAttribute // Compliant, attribute is inherited { [HttpGet("Test")] // Route: api/ControllerWithInheritedRoute/Test public string Index() => "Hi!"; } public class BaseControllerWithActionWithRoute : Controller // Noncompliant { [HttpGet("Test")] //Route: /Test (AmbiguousMatchException raised because of the override in ControllerOverridesActionWithRoute) public virtual string Index() => "Hi!"; // Secondary // Route: BaseControllerWithActionWithRoute/Index/1 public virtual string Index(int id) => "Hi!"; // Compliant } public class ControllerOverridesActionWithRoute : BaseControllerWithActionWithRoute // Noncompliant { // Route: /Test (AmbiguousMatchException raised because the base method is also in scope) public override string Index() => "Hi!"; // Secondary // Route: ControllerOverridesActionWithRoute/Index/1 public override string Index(int id) => "Hi!"; // Compliant } // Repro: https://github.com/SonarSource/sonar-dotnet/issues/8985 public sealed class ExtendedRouteAttribute() : RouteAttribute("[controller]/[action]"); [ExtendedRoute] public class SomeController : ControllerBase // Compliant - the route attribute template is set in the base class of ExtendedRouteAttribute { [HttpGet("foo")] public string Foo() => "Hi"; } // https://github.com/SonarSource/sonar-dotnet/issues/9252 namespace AbstractControllerClass { public abstract class BaseController : Controller // Compliant - class is abstract { [HttpGet] [Route("list")] public IActionResult List() { // ... load and return items return View(); } } [Route("/api/user")] public sealed class UserController : BaseController { // other controller code } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AspNet/UseAspNetModelBinding_AspNetCore_Latest.cs ================================================ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Linq; using System.Threading.Tasks; public class TestController : Controller { private readonly string Key = "id"; public IActionResult Post(string key) { _ = Request.Form["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^ _ = Request.Form.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Form.ContainsKey("id"); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Headers["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^ _ = Request.Headers.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Headers.ContainsKey("id"); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Query["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^ _ = Request.Query.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.RouteValues["id"]; // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^ _ = Request.RouteValues.TryGetValue("id", out _); // Noncompliant {{Use model binding instead of accessing the raw request data}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.File\u0073; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files["file"]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files[key]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files[0]; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files.Any(); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files.Count; // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files.GetFile("file"); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ _ = Request.Form.Files.GetFiles("file"); // Noncompliant {{Use IFormFile or IFormFileCollection binding instead}} // ^^^^^^^^^^^^^^^^^^ return default; } void MixedAccess_Form(string key) { _ = Request.Form["id"]; // Compliant (a mixed access with constant and non-constant keys is compliant) _ = Request.Form[key]; // Compliant } void MixedAccess_Form_Query(string key) { _ = Request.Form["id"]; // Compliant (a mixed access with constant and non-constant keys is compliant) _ = Request.Query[key]; // Compliant } void FalseNegatives() { string localKey = "id"; _ = Request.Form[localKey]; // FN (Requires SE) _ = Request.Form[Key]; // FN: Key is a readonly field with a constant initializer (Requires cross procedure SE) } void FormCollection(IFormCollection form) { _ = form["id"]; // Compliant. Using IFormCollection is model binding } void WriteAccess() { Request.Headers["id"] = "Assignment"; // Compliant Request.RouteValues["id"] = "Assignment"; // Compliant } async Task Compliant() { _ = Request.Cookies["cookie"]; // Compliant: Cookies are not bound by default _ = Request.QueryString; // Compliant: Accessing the whole raw string is fine. } async Task CompliantFormAccess() { var form = await Request.ReadFormAsync(); // Compliant: This might be used for optimization purposes e.g. conditional form value access. _ = form["id"]; } } public class CodeBlocksController : Controller { public CodeBlocksController() { _ = Request.Form["id"]; // Noncompliant } public CodeBlocksController(object o) => _ = Request.Form["id"]; // Noncompliant HttpRequest ValidRequest => Request; IFormCollection Form => Request.Form; string P1 => Request.Form["id"]; // Noncompliant string P2 { get => Request.Form["id"]; // Noncompliant } string P3 { get { return Request.Form["id"]; // Noncompliant } } void M1() => _ = Request.Form["id"]; // Noncompliant void M2() { Func f1 = () => Request.Form["id"]; // Noncompliant Func f2 = x => Request.Form["id"]; // Noncompliant Func f3 = delegate (object x) { return Request.Form["id"]; }; // Noncompliant } void M3() { // see also parameterized test "DottedExpressions" _ = Request.Form["id"][0]; // Noncompliant _ = Request?.Form["id"][0]; // Noncompliant _ = Request.Form?["id"][0]; // Noncompliant _ = Request?.Form?["id"][0]; // Noncompliant _ = Request.Form?["id"][0]; // Noncompliant _ = Request.Form!["id"][0]; // Noncompliant _ = Request.Form!?["id"][0]; // Noncompliant _ = Request.Form["id"]![0]; // Noncompliant _ = Request.Form?["id"][0][0]; // Noncompliant _ = Request.Form?["id"][0]?[0]; // Noncompliant _ = Request.Form["id"][0]?[0]; // Noncompliant } ~CodeBlocksController() => _ = Request.Form["id"]; // Noncompliant } public class OverridesController : Controller { public void Action() { _ = Request.Form["id"]; // Noncompliant } private void Undecidable(HttpContext context) { // Implementation: It might be difficult to distinguish between access to "Request" that originate from overrides vs. "Request" access that originate from action methods. // This is especially true for "Request" which originate from parameters like here. We may need to redeclare such cases as FNs (see e.g HandleRequest above). _ = context.Request.Form["id"]; // Undecidable: request may originate from an action method (which supports binding), or from one of the following overrides (which don't). _ = context.Request.Form["id"][0]; _ = context.Request?.Form["id"][0]; _ = context.Request.Form?["id"][0]; _ = context.Request?.Form?["id"][0]; _ = context.Request.Form?["id"][0]; _ = context.Request.Form?["id"][0][0]; _ = context.Request.Form?["id"][0]?[0]; _ = context.Request.Form["id"][0]?[0]; } private void Undecidable(HttpRequest request) { _ = request.Form["id"]; // Undecidable: request may originate from an action method (which supports binding), or from one of the following overloads (which don't). } public override void OnActionExecuted(ActionExecutedContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } public override void OnActionExecuting(ActionExecutingContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here return base.OnActionExecutionAsync(context, next); } } [Controller] public class PocoController : IActionFilter, IAsyncActionFilter { public void OnActionExecuted(ActionExecutedContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } void IActionFilter.OnActionExecuted(Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } public void OnActionExecuting(ActionExecutingContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } void IActionFilter.OnActionExecuting(ActionExecutingContext context) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here } public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here return Task.CompletedTask; } Task IAsyncActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { _ = context.HttpContext.Request.Form["id"]; // Compliant: Model binding is not supported here return Task.CompletedTask; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionArgsShouldBePassedInCorrectOrder.MsTest.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.Diagnostics { [TestClass] class Program { [TestMethod] public void Simple(string str, double d) { Assert.AreEqual("", str); // Compliant Assert.AreEqual(str, ""); // Noncompliant {{Make sure these 2 arguments are in the correct order: expected value, actual value.}} // ^^^^^^^ Assert.AreEqual(42, d); // Compliant Assert.AreEqual(d, 42); // Noncompliant // ^^^^^ Assert.AreNotEqual("", str); // Compliant Assert.AreNotEqual(str, ""); // Noncompliant // ^^^^^^^ Assert.AreNotEqual(42, d); // Compliant Assert.AreNotEqual(d, 42); // Noncompliant // ^^^^^ Assert.AreSame("", str); // Compliant Assert.AreSame(str, ""); // Noncompliant // ^^^^^^^ Assert.AreSame(42, d); // Compliant Assert.AreSame(d, 42); // Noncompliant // ^^^^^ Assert.AreNotSame("", str); // Compliant Assert.AreNotSame(str, ""); // Noncompliant // ^^^^^^^ Assert.AreNotSame(42, d); // Compliant Assert.AreNotSame(d, 42); // Noncompliant // ^^^^^ Assert.AreEqual("", str, "message"); // Compliant Assert.AreEqual(str, "", "message"); // Noncompliant Assert.AreNotEqual("", str, "message"); // Compliant Assert.AreNotEqual(str, "", "message"); // Noncompliant Assert.AreSame("", str, "message"); // Compliant Assert.AreSame(str, "", "message"); // Noncompliant Assert.AreNotSame("", str, "message"); // Compliant Assert.AreNotSame(str, "", "message"); // Noncompliant Assert.IsNull(str); } [TestMethod] public void Dynamic() { dynamic d = 42; Assert.AreEqual(d, 35); // Noncompliant Assert.AreEqual(35, d); // Compliant Assert.AreEqual(actual: d, expected: 35); // Compliant Assert.AreEqual(actual: 35, expected: d); // Noncompliant } [TestMethod] public void BrokeSyntax() { double d = 42; Assert.Equual(d, 42); // Error [CS0117] } } } // https://github.com/SonarSource/sonar-dotnet/issues/6630 namespace Repro_6630 { [TestClass] class Program { [TestMethod] public void Foo() { var str = ""; Assert.AreEqual(actual: "", expected: str); // Noncompliant Assert.AreEqual(expected: "", actual: str); // Compliant Assert.AreEqual(actual: str, expected: ""); // Compliant Assert.AreEqual(expected: str, actual: ""); // Noncompliant Assert.AreNotEqual(actual: "", notExpected: str); // Noncompliant Assert.AreSame(actual: "", expected: str); // Noncompliant Assert.AreNotSame(actual: "", notExpected: str); // Noncompliant int d = 42; Assert.AreEqual(actual: 1, expected: d); // Noncompliant Assert.AreEqual(actual: null, expected: new Program()); // Noncompliant Assert.AreEqual(message: "", expected: str, actual: ""); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^ Assert.AreEqual(expected: str, message: "", actual: ""); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } } } // https://github.com/SonarSource/sonar-dotnet/issues/6547 namespace Repro_6547 { [TestClass] class Program { public enum Seasons { Spring, Summer, Autumn, Winter } [TestMethod] public void TestString() { string stringToTest = RetrieveString(); const string constString = "Spring"; Assert.AreEqual(expected: stringToTest, actual: constString); // Noncompliant Assert.AreEqual(expected: constString, actual: stringToTest); // Compliant } [TestMethod] public void TestEnum() { Seasons seasonToTest = RetrieveSeason(); Assert.AreEqual(expected: seasonToTest, actual: Seasons.Spring); // Noncompliant Assert.AreEqual(expected: Seasons.Spring, actual: seasonToTest); // Compliant } public Seasons RetrieveSeason() => Seasons.Spring; public string RetrieveString() => "Spring"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionArgsShouldBePassedInCorrectOrder.NUnit.cs ================================================ using System; using NUnit.Framework; namespace Tests.Diagnostics { [TestFixture] class Program { void FakeAssert(object a, object b) { } [Test] public void Simple(string str, double d) { Assert.AreEqual("", str); // Compliant Assert.AreEqual(str, ""); // Noncompliant {{Make sure these 2 arguments are in the correct order: expected value, actual value.}} // ^^^^^^^ Assert.AreEqual(42, d); // Compliant Assert.AreEqual(d, 42); // Noncompliant // ^^^^^ Assert.AreNotEqual("", str); // Compliant Assert.AreNotEqual(str, ""); // Noncompliant // ^^^^^^^ Assert.AreNotEqual(42, d); // Compliant Assert.AreNotEqual(d, 42); // Noncompliant // ^^^^^ Assert.AreSame("", str); // Compliant Assert.AreSame(str, ""); // Noncompliant // ^^^^^^^ Assert.AreSame(42, d); // Compliant Assert.AreSame(d, 42); // Noncompliant // ^^^^^ Assert.AreNotSame("", str); // Compliant Assert.AreNotSame(str, ""); // Noncompliant // ^^^^^^^ Assert.AreNotSame(42, d); // Compliant Assert.AreNotSame(d, 42); // Noncompliant // ^^^^^ Assert.AreEqual("", str, "message"); // Compliant Assert.AreEqual(str, "", "message"); // Noncompliant Assert.AreNotEqual("", str, "message"); // Compliant Assert.AreNotEqual(str, "", "message"); // Noncompliant Assert.AreSame("", str, "message"); // Compliant Assert.AreSame(str, "", "message"); // Noncompliant Assert.AreNotSame("", str, "message"); // Compliant Assert.AreNotSame(str, "", "message"); // Noncompliant Assert.IsNull(str); FakeAssert(d, 42); } } } // https://github.com/SonarSource/sonar-dotnet/issues/6630 namespace Repro_6630 { [TestFixture] class Program { [Test] public void Foo() { var str = ""; Assert.AreEqual(actual: "", expected: str); // Noncompliant Assert.AreEqual(expected: "", actual: str); // Compliant Assert.AreEqual(actual: str, expected: ""); // Compliant Assert.AreEqual(expected: str, actual: ""); // Noncompliant Assert.AreNotEqual(actual: "", expected: str); // Noncompliant Assert.AreSame(actual: "", expected: str); // Noncompliant Assert.AreNotSame(actual: "", expected: str); // Noncompliant Assert.AreEqual(actual: null, expected: new Program()); // Noncompliant Assert.AreEqual(message: "", expected: str, actual: ""); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^ Assert.AreEqual(expected: str, message: "", actual: ""); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } [Test] public void Dynamic() { dynamic d = 42; Assert.AreEqual(d, 35); // Noncompliant Assert.AreEqual(35, d); // Compliant Assert.AreEqual(actual: d, expected: 35); // Compliant Assert.AreEqual(actual: 35, expected: d); // Noncompliant } [Test] public void BrokeSyntax() { double d = 42; Assert.Equual(d, 42); // Error [CS0117] } } } // https://github.com/SonarSource/sonar-dotnet/issues/6547 namespace Repro_6547 { [TestFixture] class Program { public enum Seasons { Spring, Summer, Autumn, Winter } [Test] public void TestString() { string stringToTest = RetrieveString(); const string constString = "Spring"; Assert.AreEqual(expected: stringToTest, actual: constString); // Noncompliant Assert.AreEqual(expected: constString, actual: stringToTest); // Compliant } [Test] public void TestEnum() { Seasons seasonToTest = RetrieveSeason(); Assert.AreEqual(expected: seasonToTest, actual: Seasons.Spring); //Noncompliant Assert.AreEqual(expected: Seasons.Spring, actual: seasonToTest); // Compliant } public Seasons RetrieveSeason() => Seasons.Spring; public string RetrieveString() => "Spring"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionArgsShouldBePassedInCorrectOrder.NUnit4.cs ================================================ using System; using NUnit.Framework; using NUnit.Framework.Legacy; namespace Tests.Diagnostics { [TestFixture] class ProgramNUnit4 { void FakeAssert(object a, object b) { } [Test] public void Simple(string str, double d) { ClassicAssert.AreEqual("", str); // Compliant ClassicAssert.AreEqual(str, ""); // Noncompliant {{Make sure these 2 arguments are in the correct order: expected value, actual value.}} // ^^^^^^^ ClassicAssert.AreEqual(42, d); // Compliant ClassicAssert.AreEqual(d, 42); // Noncompliant // ^^^^^ ClassicAssert.AreNotEqual("", str); // Compliant ClassicAssert.AreNotEqual(str, ""); // Noncompliant // ^^^^^^^ ClassicAssert.AreNotEqual(42, d); // Compliant ClassicAssert.AreNotEqual(d, 42); // Noncompliant // ^^^^^ ClassicAssert.AreSame("", str); // Compliant ClassicAssert.AreSame(str, ""); // Noncompliant // ^^^^^^^ ClassicAssert.AreSame(42, d); // Compliant ClassicAssert.AreSame(d, 42); // Noncompliant // ^^^^^ ClassicAssert.AreNotSame("", str); // Compliant ClassicAssert.AreNotSame(str, ""); // Noncompliant // ^^^^^^^ ClassicAssert.AreNotSame(42, d); // Compliant ClassicAssert.AreNotSame(d, 42); // Noncompliant // ^^^^^ ClassicAssert.AreEqual("", str, "message"); // Compliant ClassicAssert.AreEqual(str, "", "message"); // Noncompliant ClassicAssert.AreNotEqual("", str, "message"); // Compliant ClassicAssert.AreNotEqual(str, "", "message"); // Noncompliant ClassicAssert.AreSame("", str, "message"); // Compliant ClassicAssert.AreSame(str, "", "message"); // Noncompliant ClassicAssert.AreNotSame("", str, "message"); // Compliant ClassicAssert.AreNotSame(str, "", "message"); // Noncompliant ClassicAssert.IsNull(str); FakeAssert(d, 42); } } } namespace Repro_NUnit4_NamedArgs { [TestFixture] class Program { [Test] public void Foo() { var str = ""; ClassicAssert.AreEqual(actual: "", expected: str); // Noncompliant ClassicAssert.AreEqual(expected: "", actual: str); // Compliant ClassicAssert.AreEqual(actual: str, expected: ""); // Compliant ClassicAssert.AreEqual(expected: str, actual: ""); // Noncompliant ClassicAssert.AreNotEqual(actual: "", expected: str); // Noncompliant ClassicAssert.AreSame(actual: "", expected: str); // Noncompliant ClassicAssert.AreNotSame(actual: "", expected: str); // Noncompliant } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionArgsShouldBePassedInCorrectOrder.Xunit.cs ================================================ using System; using Xunit; namespace Tests.Diagnostics { class Program { [Fact] public void Simple(string str, double d) { Assert.Equal("", str); // Compliant Assert.Equal(str, ""); // Noncompliant {{Make sure these 2 arguments are in the correct order: expected value, actual value.}} // ^^^^^^^ Assert.Equal(42, d); // Compliant Assert.Equal(d, 42); // Noncompliant // ^^^^^ Assert.NotEqual("", str); // Compliant Assert.NotEqual(str, ""); // Noncompliant // ^^^^^^^ Assert.NotEqual(42, d); // Compliant Assert.NotEqual(d, 42); // Noncompliant // ^^^^^ Assert.Same("", str); // Compliant Assert.Same(str, ""); // Noncompliant // ^^^^^^^ Assert.Same(42, d); // Compliant Assert.Same(d, 42); // Noncompliant // ^^^^^ Assert.NotSame("", str); // Compliant Assert.NotSame(str, ""); // Noncompliant // ^^^^^^^ Assert.NotSame(42, d); // Compliant Assert.NotSame(d, 42); // Noncompliant // ^^^^^ Assert.Null(str); } [Fact] public void Dynamic() { dynamic d = 42; Assert.Equal(d, 35); // Noncompliant Assert.Equal(35, d); // Compliant Assert.Equal(actual: d, expected: 35); // Compliant Assert.Equal(actual: 35, expected: d); // Noncompliant } [Fact] public void BrokeSyntax() { double d = 42; Assert.Equual(d, 42); // Error [CS0117] } } } // https://github.com/SonarSource/sonar-dotnet/issues/6630 namespace Repro_6630 { class Program { [Fact] public void Foo() { var str = ""; Assert.Equal(actual: "", expected: str); // Noncompliant Assert.Equal(expected: "", actual: str); // Compliant Assert.Equal(actual: str, expected: ""); // Compliant Assert.Equal(expected: str, actual: ""); // Noncompliant Assert.Same(actual: "", expected: str); // Noncompliant Assert.NotSame(actual: "", expected: str); // Noncompliant int d = 42; Assert.Equal(actual: 1, expected: d); // Noncompliant Assert.Equal(actual: null, expected: new Program()); // Noncompliant Assert.Equal(expected: str, actual: ""); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^^^^^ } } } // https://github.com/SonarSource/sonar-dotnet/issues/6547 namespace Repro_6547 { class Program { public enum Seasons { Spring, Summer, Autumn, Winter } [Fact] public void TestString() { string stringToTest = RetrieveString(); const string constString = "Spring"; Assert.Same(expected: stringToTest, actual: constString); // Noncompliant Assert.Same(expected: constString, actual: stringToTest); // Compliant } [Fact] public void TestEnum() { Seasons seasonToTest = RetrieveSeason(); Assert.Same(expected: seasonToTest, actual: Seasons.Spring); // Noncompliant Assert.Same(expected: Seasons.Spring, actual: seasonToTest); // Compliant } public Seasons RetrieveSeason() => Seasons.Spring; public string RetrieveString() => "Spring"; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionArgsShouldBePassedInCorrectOrder.XunitV3.cs ================================================ using System; using Xunit; namespace Tests.Diagnostics { public class XUnitV3Tests { [Fact] public void Simple(string str) { Assert.Equivalent("", str); // Compliant Assert.Equivalent(str, ""); // Noncompliant {{Make sure these 2 arguments are in the correct order: expected value, actual value.}} Assert.EquivalentWithExclusions("", str); // Compliant Assert.EquivalentWithExclusions(str, ""); // Noncompliant Assert.StrictEqual("", str); // Compliant Assert.StrictEqual(str, ""); // Noncompliant Assert.NotStrictEqual("", str); // Compliant Assert.NotStrictEqual(str, ""); // Noncompliant Assert.Contains("", str); // Compliant Assert.Contains(str, ""); // Noncompliant Assert.DoesNotContain("", str); // Compliant Assert.DoesNotContain(str, ""); // Noncompliant Assert.StartsWith("", str); // Compliant Assert.StartsWith(str, ""); // Noncompliant Assert.EndsWith("", str); // Compliant Assert.EndsWith(str, ""); // Noncompliant } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.AllFrameworks.cs ================================================ using FluentAssertions; using NFluent; using NSubstitute; using System; namespace AllFrameworksTests { internal class Tests { public void FluentAssertions(string s) { s.Should(); // Noncompliant } public void NFluent() { Check.That(0); // Noncompliant } public void NSubstitute(IComparable comparable) { comparable.Received(); // Noncompliant } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.FluentAssertions.CSharp7.cs ================================================ using System; using System.Collections.Generic; using FluentAssertions; using FluentAssertions.Primitives; public class Program { public void StringAssertions() { var s = "Test"; s.Should(); // Noncompliant {{Complete the assertion}} //^^^^^^ s[0].Should(); // Error [CS0121] ambiguous calls // Noncompliant@-1 s.Should().Be("Test"); // Compliant } public void CollectionAssertions() { var collection = new[] { "Test", "Test" }; collection.Should(); // Error [CS0121] ambiguous calls // Noncompliant@-1 collection.Should(); // Noncompliant collection.Should().Equal("Test", "Test"); // Compliant } public void DictionaryAssertions() { var dict = new Dictionary(); dict["A"].Should(); // Noncompliant } public StringAssertions ReturnedByReturn() { var s = "Test"; return s.Should(); // Compliant } public StringAssertions ReturnedByArrow(string s) => s.Should(); // Compliant public object ReturnedByArrowWithConversion(string s) => (object)s.Should(); // Compliant public void CalledByArrow(string s) => s.Should(); // Noncompliant public void Assigned() { var s = "Test"; var assertion = s.Should(); // Compliant assertion = s.Should(); // Compliant } public void PassedAsArgument() { var s = "Test"; ValidateString(s.Should()); // Compliant } private void ValidateString(StringAssertions assertion) { } public void UnreducedCall() { var s = "Test"; FluentAssertions.AssertionExtensions.Should(s); // Noncompliant } public void ReturnedInLambda() { Func a = () => "Test".Should(); } public void CustomAssertions() { var custom = new Custom(); custom.Should(); // Noncompliant The custom assertion derives from ReferenceTypeAssertions custom.Should().BeCustomAsserted(); // Compliant } public void CustomStructAssertions() { var custom = new CustomStruct(); custom.Should(); // Compliant Potential FN. CustomStructAssertion does not derive from ReferenceTypeAssertions and is not considered a custom validation. custom.Should().BeCustomAsserted(); // Compliant } } public static class CustomAssertionExtension { public static CustomAssertion Should(this Custom instance) => new CustomAssertion(instance); } public class CustomAssertion : ReferenceTypeAssertions { public CustomAssertion(Custom instance) : base(instance) { } protected override string Identifier => "custom"; public void BeCustomAsserted() { // Not implemented } } public class Custom { } public static class CustomStructAssertionExtension { public static CustomStructAssertion Should(this CustomStruct instance) => new CustomStructAssertion(instance); } public class CustomStructAssertion // Does not derive from ReferenceTypeAssertions { public CustomStructAssertion(CustomStruct instance) { } public void BeCustomAsserted() { // Not implemented } } public struct CustomStruct { } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.FluentAssertions.Latest.cs ================================================ #nullable enable using System; using System.Collections.Generic; using FluentAssertions; using FluentAssertions.Primitives; public class Program { public void StringAssertions() { var s = "Test"; s.Should(); // Noncompliant s?.Should(); // Noncompliant s[0].Should(); // Noncompliant (no "ambiguous calls" compiler error as in C# 7) s?[0].Should(); // Noncompliant s.Should().Be("Test"); // Compliant s.Should()?.Be("Test"); // Compliant s.Should()!?.Be("Test"); // Compliant } public void CollectionAssertions() { var collection = new[] { "Test", "Test" }; collection.Should(); // Noncompliant (no "ambiguous calls" compiler error as in C# 7) } public void DictAssertions() { var dict = new Dictionary(); dict["A"]?.Should(); // Noncompliant // ^^^^^^ dict?["A"]?.Should(); // Noncompliant // ^^^^^^ dict?["A"]!.Should(); // Noncompliant // ^^^^^^ } public void LocalFunction() { var s = "Test"; StringAssertions ExpressionBodyLocalFunction() => s.Should(); void VoidReturningExpressionBodyLocalFunction() => s.Should(); // Noncompliant StringAssertions ReturnLocalFunction() { return s.Should(); } } public StringAssertions PropertyArrow => "Test".Should(); public StringAssertions PropertyGetterArrow { get => "Test".Should(); set => "Test".Should(); // Noncompliant } public StringAssertions PropertyGetterReturn { get { return "Test".Should(); } } public void NullConditionalAssignment(Wrapper wrapper) { wrapper?.Result = "test".Should(); // Compliant https://sonarsource.atlassian.net/browse/NET-2671 wrapper.Result = "test".Should(); // Compliant } public class Wrapper { public StringAssertions? Result { get; set; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.NFluent.Latest.cs ================================================ using System; using System.Threading.Tasks; using NFluent; using static NFluent.Check; public class Program { public void CheckThat() { That(0); // Noncompliant {{Complete the assertion}} // ^^^^ That(); // Noncompliant That(1).IsEqualTo(1); } public void NullConditionalAssignment(Wrapper wrapper) { wrapper?.Result = Check.That(1); // Compliant https://sonarsource.atlassian.net/browse/NET-2671 wrapper.Result = Check.That(1); // Compliant } public class Wrapper { public ICheck Result; } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.NFluent.cs ================================================ using System; using System.Threading.Tasks; using NFluent; public class Program { public void CheckThat() { Check.That(0); // Noncompliant {{Complete the assertion}} // ^^^^ Check.That(); // Noncompliant Check.That(1).IsEqualTo(1); } public void CheckThatEnum() { Check.ThatEnum(AttributeTargets.All); // Noncompliant {{Complete the assertion}} // ^^^^^^^^ Check.ThatEnum(AttributeTargets.All); // Noncompliant Check.ThatEnum(AttributeTargets.All).IsEqualTo(AttributeTargets.All); } public void CheckThatCode() { Check.ThatCode(() => { }); // Noncompliant {{Complete the assertion}} // ^^^^^^^^ Check.ThatCode(() => 1); // Noncompliant Check.ThatCode(CheckThatCode); // Noncompliant Check.ThatCode(() => { }).DoesNotThrow(); } public async Task CheckThatAsyncCode() { Check.ThatAsyncCode(async () => await Task.CompletedTask); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^^^ Check.ThatAsyncCode(async () => await Task.FromResult(1)); // Noncompliant Check.ThatAsyncCode(CheckThatAsyncCode); // Noncompliant Check.ThatAsyncCode(async () => await Task.CompletedTask).DoesNotThrow(); } public async Task CheckThatDynamic(dynamic expando) { Check.ThatDynamic(1); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^ Check.ThatDynamic(expando); // Noncompliant Check.ThatDynamic(1).IsNotNull(); } public ICheck CheckReturnedByReturn() { return Check.That(1); } public ICheck CheckReturnedByExpressionBody() => Check.That(1); public void AnonymousInvocation(Func a) => a()(); } namespace OtherCheck { public static class Check { public static void That(int i) { } } public class Test { public void CheckThat() { Check.That(1); // Compliant. Not NFluent } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssertionsShouldBeComplete.NSubstitute.cs ================================================ using NSubstitute; public interface ICommand { void Execute(); } namespace NSubstituteTests { internal class Tests { public void Received() { var command = Substitute.For(); command.Received(); // Noncompliant {{Complete the assertion}} // ^^^^^^^^ command.Received(requiredNumberOfCalls: 2); // Noncompliant command.Received(); // Noncompliant SubstituteExtensions.Received(command); // Noncompliant command.Received().Execute(); } public void DidNotReceive() { var command = Substitute.For(); command.DidNotReceive(); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^^^ command.DidNotReceive(); // Noncompliant command.DidNotReceive().Execute(); } public void ReceivedWithAnyArgs() { var command = Substitute.For(); command.ReceivedWithAnyArgs(); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^^^^^^^^^ command.ReceivedWithAnyArgs(requiredNumberOfCalls: 2); // Noncompliant command.ReceivedWithAnyArgs(); // Noncompliant command.ReceivedWithAnyArgs().Execute(); } public void DidNotReceiveWithAnyArgs() { var command = Substitute.For(); command.DidNotReceiveWithAnyArgs(); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^^^^^^^^^^^^^^ command.DidNotReceiveWithAnyArgs(); // Noncompliant command.DidNotReceiveWithAnyArgs().Execute(); } public void ReceivedCalls() { var command = Substitute.For(); command.ReceivedCalls(); // Noncompliant {{Complete the assertion}} // ^^^^^^^^^^^^^ command.ReceivedCalls(); // Noncompliant var calls = command.ReceivedCalls(); } } } namespace OtherReceived { public static class OtherExtensions { public static void Received(this T something) { } } public class Test { public void Received(ICommand command) { command.Received(); // Compliant. Other Received call } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssignmentInsideSubExpression.Latest.cs ================================================ using System; using System.Threading.Tasks; namespace Tests.Diagnostics { public class CoalescingAssignment { public void Test() { int? val = null; SomeMethod(val ??= 1); // Compliant, see e.g. https://stackoverflow.com/a/64666607 val ??= 1; bool? value = null; if (value ??= true) { } // Compliant, see. e.g. https://stackoverflow.com/a/64666607 } void SomeMethod(int val) { } } public class AssignmentInsideSubExpression { void foo(int a) { } void Foo() { int i = 0; foo(i >>>= 1); // Noncompliant } } // https://sonarsource.atlassian.net/browse/NET-1136 public class Bar { private async Task GetFooNullableAsyncTriggered() => await (a ??= _fooProvider.GetFooAsync()); // Compliant see e.g. https://stackoverflow.com/a/64666607 public class FooProvider { public Task GetFooAsync() => Task.FromResult(42); } private Task a; private readonly FooProvider _fooProvider = new FooProvider(); } } namespace CSharp14 { public class OverriddenCompoundAssignment { void Test() { var a = new C1 { Value = 1 }; SomeMethod(a += 1); // Noncompliant } void SomeMethod(C1 c) { } class C1 { public int Value; public void operator +=(int x) { Value += x; } } } public class NullConditionalAssignment { void Test() { var a = new C2(); if ((bool)a?.Value = false) // Noncompliant { } a?.Value = true; // Compliant https://sonarsource.atlassian.net/browse/NET-2391 if (a is not null) { a.Value = true; // Compliant } } class C2 { public bool Value; } public class Nesting { public Nesting Prop { get; set; } public Nesting this[int index] { get { return new Nesting(); } set { } } } void CompliantTest() { var prop = new Nesting(); prop?.Prop = new Nesting(); prop?.Prop?.Prop = new Nesting(); prop?.Prop.Prop = new Nesting(); prop.Prop?.Prop = new Nesting(); prop.Prop?.Prop?.Prop = new Nesting(); prop?.Prop.Prop?.Prop = new Nesting(); prop?.Prop.Prop.Prop = new Nesting(); var indexer = new Nesting(); indexer?[0] = new Nesting(); indexer?[0]?[0] = new Nesting(); indexer?[0]?[0] = new Nesting(); indexer?[0]?[0]?[0] = new Nesting(); indexer?[0]?[0][0] = new Nesting(); indexer?[0][0]?[0] = new Nesting(); indexer[0]?[0]?[0] = new Nesting(); var mixed = new Nesting(); mixed?[0]?.Prop = new Nesting(); mixed?.Prop?[0] = new Nesting(); mixed?[0]?.Prop?[0] = new Nesting(); mixed?.Prop?[0]?.Prop = new Nesting(); Action dontMindMeIAmHappyLittleInnocentLambda = () => { var prop = new Nesting(); prop?.Prop = new Nesting(); // Compliant }; } void NonCompliantTest() { var prop = new Nesting(); SomeMethod(prop?.Prop = new Nesting()); // Noncompliant SomeMethod(prop?.Prop?.Prop = new Nesting()); // Noncompliant SomeMethod(prop?.Prop.Prop = new Nesting()); // Noncompliant SomeMethod(prop.Prop?.Prop = new Nesting()); // Noncompliant SomeMethod(prop.Prop?.Prop?.Prop = new Nesting()); // Noncompliant SomeMethod(prop?.Prop.Prop?.Prop = new Nesting()); // Noncompliant SomeMethod(prop?.Prop?.Prop.Prop = new Nesting()); // Noncompliant SomeMethod(prop?.Prop.Prop.Prop = new Nesting()); // Noncompliant var indexer = new Nesting(); SomeMethod(indexer?[0] = new Nesting()); // Noncompliant SomeMethod(indexer?[0]?[0] = new Nesting()); // Noncompliant SomeMethod(indexer?[0]?[0] = new Nesting()); // Noncompliant SomeMethod(indexer?[0]?[0]?[0] = new Nesting()); // Noncompliant SomeMethod(indexer?[0]?[0][0] = new Nesting()); // Noncompliant SomeMethod(indexer?[0][0]?[0] = new Nesting()); // Noncompliant SomeMethod(indexer[0]?[0]?[0] = new Nesting()); // Noncompliant var mixed = new Nesting(); SomeMethod(mixed?[0]?.Prop = new Nesting()); // Noncompliant SomeMethod(mixed?.Prop?[0] = new Nesting()); // Noncompliant SomeMethod(mixed?[0]?.Prop?[0] = new Nesting()); // Noncompliant SomeMethod(mixed?.Prop?[0]?.Prop = new Nesting()); // Noncompliant if ((prop?.Prop?.Prop.Prop = new Nesting())) { } // Noncompliant // Error@-1 [CS0029] if ((indexer?[0]?[0][0] = new Nesting())) { } // Noncompliant // Error@-1 [CS0029] } void SomeMethod(Nesting nesting) { } public class ConditionalAccessExpressionOutsideSubExpression { public void Test(Sample sample) { string mappingSpan = null; sample?.Invoke(new Sample(mappingSpan = "7")); // Noncompliant } public class Sample(string x) { public void Invoke(object b) { } } } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssignmentInsideSubExpression.TopLevelStatements.cs ================================================ using System.IO; var topLevel = 0; Method(topLevel = 42); // Noncompliant var person = new { FirstName = "Scott", LastName = "Hunter", Age = 25 }; var otherPerson = person with { LastName = "Hanselman", Age = topLevel = 42 }; // Noncompliant void Method(int arg) { var i = 0; Method(i = 42); // Noncompliant } void WithRecordClone() { var zero = new Record() { Value = 0 }; var answer = zero with { Value = 42 }; var badAnswer = zero with { Value = topLevel = 42 }; // Noncompliant var one = new PositionalRecord(1) { Value = 1 }; var clone = one with { Value = 2 }; var badClone = one with { Value = topLevel = 2 }; // Noncompliant } void TargetTypedNew() { Record r; Method1(r = new()); // Noncompliant PositionalRecord p; Method2(p = new(42)); // Noncompliant void Method1(Record arg) { } void Method2(PositionalRecord arg) { } } void WithPositionalRecord() { var x = 42; new PositionalRecord(x); new PositionalRecord(x = 42); // Noncompliant } async void IsNotNull(StreamReader reader) { string line; // See: https://github.com/SonarSource/sonar-dotnet/issues/4264 while ((line = await reader.ReadLineAsync()) is not null) { } while ((line = await reader.ReadLineAsync()) is null) { } } void WithRecordStructClone() { var zero = new RecordStruct() { Value = 0 }; var answer = zero with { Value = 42 }; var badAnswer = zero with { Value = topLevel = 42 }; // Noncompliant var one = new PositionalRecordStruct(1) { Value = 1 }; var clone = one with { Value = 2 }; var badClone = one with { Value = topLevel = 2 }; // Noncompliant } record Record { public int Value { get; init; } } record PositionalRecord(int Input) { public int Value { get; init; } = Input; private void Method(int arg = 42) { var i = 0; Method(i = 42); // Noncompliant } private void Method2() { int y, z; var x = (y, z) = (16, 23); } } // See https://github.com/SonarSource/sonar-dotnet/issues/4446 interface ICustomMsgQueue { string? Pop(); } class MessageQueueUseCase { void Process(ICustomMsgQueue queue) { string msg; while ((msg = queue.Pop()) is not null) { } do { } while ((msg = queue.Pop()) is null); } } record struct RecordStruct { public int Value { get; init; } } record struct PositionalRecordStruct(int Input) { public int Value { get; init; } = Input; private void Method(int arg = 42) { var i = 0; Method(i = 42); // Noncompliant } private void Method2() { int y; (y, var x) = (16, 23); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AssignmentInsideSubExpression.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Tests.Diagnostics { public class AssignmentInsideSubExpression { void foo(int a) { } void foo(bool a) { } int foo(Func f) { throw new Exception(); } private class MyClass { public int MyField; } void Foo() { int i = 0; foo(i = 42); // Noncompliant {{Extract the assignment of 'i' from this expression.}} // ^ foo(i += 42); // Noncompliant foo(i -= 42); // Noncompliant foo(i *= 42); // Noncompliant foo(i /= 42); // Noncompliant foo(i %= 42); // Noncompliant foo(i &= 1); // Noncompliant foo(i ^= 1); // Noncompliant foo(i |= 1); // Noncompliant foo(i <<= 1); // Noncompliant foo(i >>= 1); // Noncompliant // ^^^ int? val = null; foo(val ?? 1); i = 42; foo(i == 42); foo( (int xx) => { int a; a = 42; return a; }); var b = true; if (b = false) { } // Noncompliant // ^ if ((b = false)) { } // Noncompliant {{Extract the assignment of 'b' from this expression.}} for (int j = 0; b &= false; j++) { } // Noncompliant for (int j = 0; b == false; j++) { } bool? value = null; if (value ?? true) { } // Fix S1121: NullReferenceException when while loop with assignment expression is within a for loop with no condition (#725) for (;;) { while ((b = GetBool()) == true) { } } while (b &= false) { } // Noncompliant do { } while (b &= false); // Noncompliant while ((i = 1) == 1) { } // Compliant do { } while ((i = 1) == 1); // Compliant if (i == 0) i = 2; if ((i = 1) <= 1) // Compliant { } b = (i = 1) <= 1; // Noncompliant var y = (b &= false) ? i : i * 2; // Noncompliant string result = ""; if (!string.IsNullOrEmpty(result)) result = result + " "; var v1 = new Action(delegate { }); var v2 = new Action(delegate { var foo = 42; }); var v3 = new Func((xx) => xx = 42); var v4 = new Action((xx) => { xx = 42; }); var v5 = new { MyField = 42 }; var v6 = new MyClass { MyField = 42 }; var v7 = new MyClass() { MyField = 42 }; var v8 = foo(xx => { xx = 42; return 0; }); var v9 = new MyClass { MyField = i = 42 }; // Noncompliant var v10 = new MyClass() { MyField = i = 42 }; // Noncompliant var index = 0; new int[] { 0, 1, 2 }[index = 2] = 10; // Noncompliant new int[] { 0, 1, 2 }[(index = 2)] = 10; // Noncompliant var o = new object(); var oo = new object(); if (false && (oo = o) != null) // Compliant { } oo = (o) ?? (o = new object()); // Compliant oo = (o) ?? (object)(o = new object()); // Compliant oo = oo ?? (o = new object()); // Noncompliant int xa = 0, xb = 0; if ((xa = xb = 0) != 0) { } // Compliant int x = (xa = xb) + 5; // Noncompliant } public void TestMethod1() { var j = 5; var k = 5; var i = j = k = 10; i = j = k = 10; } public bool GetBool() => true; } // See https://github.com/SonarSource/sonar-dotnet/issues/4446 abstract class WaitingInLoop { public async void Process() { bool evaluated; while ((evaluated = await GetTask().ConfigureAwait(false))) // Noncompliant FP { // do processing } } internal abstract Task GetTask(); } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncAwaitIdentifier.Latest.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Tests.Diagnostics { public class AsyncAwaitIdentifier { public void Cs9_nuint() { nuint await = 42; // Noncompliant } private static int GetDiscount(object p) => p switch { A => 0, B await => 75, // Noncompliant _ => 25 }; } public class A { } public class B { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncAwaitIdentifier.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Tests.Diagnostics { public class AsyncAwaitIdentifier { public AsyncAwaitIdentifier() { int async = 42; // Noncompliant {{Rename 'async' to not use a contextual keyword as an identifier.}} // ^^^^^ int await = 42; // Noncompliant {{Rename 'await' to not use a contextual keyword as an identifier.}} await = 42*2; } public void Foo(int async) // Noncompliant { var x = from await in new List() {5,6,7 } // Noncompliant // ^^^^^ select await; } public static async Task Foo(string a) { return await Foo(a); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncVoidMethod.Latest.cs ================================================ using System; using System.Threading.Tasks; public class Sample : EventArgs { } public record EventHandlerCasesInRecord { async void MyMethod() // Noncompliant {{Return 'Task' instead.}} // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Sample e) { await Task.Run(() => Console.WriteLine("test")); } public event EventHandler MyEvent; public EventHandlerCasesInRecord() { MyEvent += EventHandlerCases_MyEvent; } private async void EventHandlerCases_MyEvent(object sender, bool e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } } public record EventHandlerCasesInPositionalRecord(string Param) { async void MyMethod() // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Sample e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } } public interface EventHandlerCasesInInterface { async void MyMethod() // Compliant because it can be implemented as a non async method { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Sample e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } } public interface ISomeInterface { event EventHandler MyEvent; public void SomeMethod() { MyEvent += EventHandlerCases_MyEvent; } private async void EventHandlerCases_MyEvent(object sender, bool e) { await Task.Run(() => Console.WriteLine("test")); } } public class Reproducer5432 { public void SomeMethod() { var _timer = new System.Threading.Timer(RunOnceAsync); } private async void RunOnceAsync(object? _) // Compliant, see: https://github.com/SonarSource/sonar-dotnet/issues/5432 { } } public record struct EventHandlerCasesInRecordStruct { async void MyMethod() // Noncompliant {{Return 'Task' instead.}} // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Sample e) { await Task.Run(() => Console.WriteLine("test")); } public event EventHandler MyEvent; public void SomeMethod() { MyEvent += EventHandlerCases_MyEvent; } private async void EventHandlerCases_MyEvent(object sender, bool e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } } public record struct EventHandlerCasesInPositionalRecordStruct(string Param) { async void MyMethod() // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Sample e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } } public interface IVirtualMethodInterface { static abstract void SomeMethod1(); static virtual async void SomeVirtualMethod() // Compliant (virtual member) { return; } } public class SomeClass : IVirtualMethodInterface { public static async void SomeMethod1() // Compliant as it comes from the interface { return; } public static async void SomeMethod2() // Noncompliant { return; } } public static class Extensions { extension(Sample e) { async void NonCompliant() // Noncompliant {{Return 'Task' instead.}} // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } async void Compliant(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void NonCompliant(object sender) // Noncompliant { await Task.Run(() => Console.WriteLine("test")); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncVoidMethod.MsTestTestFramework.cs ================================================ using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.Diagnostics { [TestClass] public class MyUnitTests // MSTest V2 has proper support for async so people should avoid async void { [AssemblyCleanup] public static async void AssemblyCleanup() { } // Noncompliant [AssemblyInitialize] public static async void AssemblyInitialize() { } // Noncompliant [ClassCleanup] public static async void ClassCleanup() { } // Noncompliant [ClassInitialize] public static async void ClassInitialize() { } // Noncompliant [TestCleanup] public async void TestCleanup() { } // Noncompliant [TestInitialize] public async void TestInitialize() { } // Noncompliant [TestMethod] public async void MyTest() { } // Noncompliant } [TestClass] public class MyOtherUnitTests { [AssemblyCleanup] public static async Task AssemblyCleanup() { } [AssemblyInitialize] public static async Task AssemblyInitialize() { } [ClassCleanup] public static async Task ClassCleanup() { } [ClassInitialize] public static async Task ClassInitialize() { } [TestCleanup] public async Task TestCleanup() { } [TestInitialize] public async Task TestInitialize() { } [TestMethod] public async Task MyTest() { } } } namespace Net6Poc.AsyncVoidMethod { internal class MsTestCases { [Generic] public static async void M() { } // Noncompliant } public class GenericAttribute : TestMethodAttribute { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncVoidMethod.VsUtFramework.cs ================================================  using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests.Diagnostics { [TestClass] public class MyUnitTests // MSTest V1 doesn't have proper support for async so people are forced to use async void { [AssemblyCleanup] public static async void AssemblyCleanup() { } [AssemblyInitialize] public static async void AssemblyInitialize() { } [ClassCleanup] public static async void ClassCleanup() { } [ClassInitialize] public static async void ClassInitialize() { } [TestCleanup] public async void TestCleanup() { } [TestInitialize] public async void TestInitialize() { } [TestMethod] public async void MyTest() { } // Noncompliant } internal class MsTestCases { public void Method() { [TestMethod] async void Get1() => await Task.FromResult(1); [TestMethod] async Task Get1s() => await Task.FromResult(1); async void Get2() => await Task.FromResult(2); // Compliant - FN async Task Get2s() => await Task.FromResult(2); Action a = [TestMethod] async () => { }; Action b = async () => { }; // Compliant - FN Action c = [TestMethod] async () => { }; // Compliant - FN Func d = [TestMethod] async () => await Task.Delay(0); Func e = async () => await Task.Delay(0); } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AsyncVoidMethod.cs ================================================ using System; using System.Threading.Tasks; namespace Tests.Diagnostics { public class Foo : EventArgs { } public class EventHandlerCases { async void MyMethod() // Noncompliant {{Return 'Task' instead.}} // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } public virtual async void MyVirtualMethod() // Compliant as it is virtual { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Foo e) { await Task.Run(() => Console.WriteLine("test")); } void MyMethod3() { } void MyMethod4(object sender, EventArgs args) { } async Task MyMethod5() { return 5; } public event EventHandler MyEvent; public EventHandlerCases() { MyEvent += EventHandlerCases_MyEvent; } private async void EventHandlerCases_MyEvent(object sender, bool e) { await Task.Run(() => Console.WriteLine("test")); } static async void OnValueChanged() // Compliant, has OnXxx name { } async void OnX() { } async void O() { } // Noncompliant async void On() { } // Noncompliant async void Onboard() { } // Noncompliant async void ToX() { } // Noncompliant async void ONCAPS() { } // Noncompliant async void On3People() { } // Noncompliant, 3People is not a valid event name async void On_Underscore() { } // Noncompliant async void onEvent() { } // Noncompliant async void Onřád() { } // Noncompliant async void OnŘád() { } async void OnΘ() { } // Compliant, Uppercase Theta async void Onθ() { } // Noncompliant, Lowercase Theta static async void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue) // Compliant, Xamarin style, has OnXxx name { // Property changed implementation goes here } private async void ArbreDesClefs_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args) { } // Compliant, doesn't have sender as object and doesn't inherit from EventArgs, but looks like it by argument names // Substitute for reference to Xamarin.Forms, Windows.UI.Xaml.Controls public class BindableObject { } public class TreeView { } public class TreeViewItemInvokedEventArgs { } // Type doesn't inherit from event args } public struct EventHandlerCasesInStruct { async void MyMethod() // Noncompliant {{Return 'Task' instead.}} // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod(object sender, EventArgs args) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod1(object o, EventArgs e) { await Task.Run(() => Console.WriteLine("test")); } async void MyMethod2(object o, Foo e) { await Task.Run(() => Console.WriteLine("test")); } private async void NotAHandler(object sender) // Noncompliant // ^^^^ { await Task.Run(() => Console.WriteLine("test")); } } public class UwpCases { // A lot of classes/interfaces in UWP do not inherit from EventArgs so we had to change the detection mechanism // See issue https://github.com/SonarSource/sonar-dotnet/issues/704 private interface ISuspendingEventArgs { } async void MyOtherMethod1(object o, ISuspendingEventArgs args) { await Task.Run(() => Console.WriteLine("test")); } private async void OnSuspending(object sender, ISuspendingEventArgs e) { await Task.Run(() => Console.WriteLine("test")); } } public struct StructExample { event EventHandler MyEvent; public void SomeMethod() { MyEvent += EventHandlerCases_MyEvent; } private async void EventHandlerCases_MyEvent(object sender, bool e) { await Task.Run(() => Console.WriteLine("test")); } } public class Reproducer5432 { public delegate void CustomDelegate(int value); public void SomeMethod() { var _timer = new System.Threading.Timer(RunOnceAsync); _ = new DateTime { }; // For coverage, check constructor without an ArgumentList. CallAction(Do); CallDelegate(Do); } private void CallAction(Action action) { } private void CallDelegate(CustomDelegate @delegate) { } private async void RunOnceAsync(object _) { } // Compliant, see: https://github.com/SonarSource/sonar-dotnet/issues/5432 private async void Do(bool b) { } private async void Do(int i) { } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidDateTimeNowForBenchmarking.cs ================================================ using System; public class Program { void Benchmark() { var start = DateTime.Now; // Some method Console.WriteLine($"{(DateTime.Now - start).TotalMilliseconds} ms"); // Noncompliant {{Avoid using "DateTime.Now" for benchmarking or timespan calculation operations.}} // ^^^^^^^^^^^^^^^^^^^^ start = DateTime.Now; // Some method Console.WriteLine($"{DateTime.Now.Subtract(start).TotalMilliseconds} ms"); // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^ } private const int MinRefreshInterval = 100; void Timing() { var lastRefresh = DateTime.Now; if ((DateTime.Now - lastRefresh).TotalMilliseconds > MinRefreshInterval) // Noncompliant { lastRefresh = DateTime.Now; // Refresh } } void Combinations(TimeSpan timeSpan, DateTime dateTime) { _ = (DateTime.Now - dateTime).Milliseconds; // Noncompliant _ = DateTime.Now.Subtract(dateTime).Milliseconds; // Noncompliant _ = (DateTime.Now - TimeSpan.FromSeconds(1)).Millisecond; // Compliant _ = DateTime.Now.Subtract(TimeSpan.FromDays(1)).Millisecond; // Compliant _ = (DateTime.Now - timeSpan).Millisecond; // Compliant _ = DateTime.Now.Subtract(timeSpan).Millisecond; // Compliant _ = (DateTime.UtcNow - dateTime).Milliseconds; // Compliant _ = DateTime.UtcNow.Subtract(dateTime).Milliseconds; // Compliant _ = (new DateTime(1) - dateTime).Milliseconds; // Compliant _ = new DateTime(1).Subtract(dateTime).Milliseconds; // Compliant void LocalMethod() { var sec = DateTime.Now; // something Console.WriteLine($"{(DateTime.Now - sec).TotalMilliseconds} ms"); // Noncompliant } } private TimeSpan time; public TimeSpan Time { get => time; set { var start = DateTime.Now; time = DateTime.Now - start; // Noncompliant } } void SwitchExpression(DateTime start) { var a = 1; switch (a) { case 1: time = DateTime.Now - start - new TimeSpan(1); // Noncompliant break; } } void NonInLineDateTimeNow() { var start = DateTime.Now; // Some method var end = DateTime.Now; var elapsedTime = end - start; // FN } void EdgeCases(DateTime dateTime, TimeSpan timeSpan) { (true ? DateTime.Now : new DateTime(1)).Subtract(dateTime); // FN (true ? DateTime.Now : new DateTime(1)).Subtract(timeSpan); // Compliant DateTime.Now.AddDays(1).Subtract(dateTime); // FN DateTime.Now.Subtract(); // Error [CS1501] } } public class FakeDateTimeSubtract { void MyMethod(System.DateTime dateTime) { _ = DateTime.Now.Subtract(dateTime).Milliseconds; // Compliant } public static class DateTime { public static System.DateTime Now { get; } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidDateTimeNowForBenchmarking.vb ================================================ Imports System Public Class Program Private Sub Benchmark() Dim start = Date.Now ' Some method Console.WriteLine($"{(Date.Now - start).TotalMilliseconds} ms") ' Noncompliant {{Avoid using "DateTime.Now" for benchmarking or timespan calculation operations.}} ' ^^^^^^^^^^^^^^^^ start = Date.Now ' Some method Console.WriteLine($"{Date.Now.Subtract(start).TotalMilliseconds} ms") ' Noncompliant ' ^^^^^^^^^^^^^^^^^ End Sub Private Const MinRefreshInterval As Integer = 100 Private Sub Timing() Dim lastRefresh = Date.Now If (Date.Now - lastRefresh).TotalMilliseconds > MinRefreshInterval Then ' Noncompliant lastRefresh = Date.Now ' Refresh End If End Sub Private Sub Combinations(ByVal timeSpan As TimeSpan, ByVal dateTime As Date) Dim a1 = (Date.Now - dateTime).Milliseconds ' Noncompliant Dim a2 = Date.Now.Subtract(dateTime).Milliseconds ' Noncompliant Dim b1 = (Date.Now - TimeSpan.FromSeconds(1)).Millisecond ' Compliant Dim b2 = Date.Now.Subtract(TimeSpan.FromDays(1)).Millisecond ' Compliant Dim c1 = (Date.Now - timeSpan).Millisecond ' Compliant Dim c2 = Date.Now.Subtract(timeSpan).Millisecond ' Compliant Dim d1 = (Date.UtcNow - dateTime).Milliseconds ' Compliant Dim d2 = Date.UtcNow.Subtract(dateTime).Milliseconds ' Compliant Dim e1 = (New DateTime(1) - dateTime).Milliseconds ' Compliant Dim e2 = New DateTime(1).Subtract(dateTime).Milliseconds ' Compliant End Sub Private timeField As TimeSpan Public Property Time As TimeSpan Get Return timeField End Get Set(ByVal value As TimeSpan) Dim start = Date.Now timeField = Date.Now - start ' Noncompliant End Set End Property Private Sub SwitchExpression(ByVal start As Date) Dim a = 1 Select Case a Case 1 timeField = Date.Now - start - New TimeSpan(1) ' Noncompliant End Select End Sub Private Sub NonInLineDateTimeNow() Dim start = Date.Now ' Some method Dim [end] = Date.Now Dim elapsedTime = [end] - start ' FN End Sub Private Sub EdgeCases(ByVal dateTime As Date, ByVal timeSpan As TimeSpan) Call (If(True, Date.Now, New DateTime(1))).Subtract(dateTime) ' FN Call (If(True, Date.Now, New DateTime(1))).Subtract(timeSpan) ' Compliant Date.Now.AddDays(1).Subtract(dateTime) ' FN Date.Now.Subtract() ' Error [BC30516] Date.Now.Subtract ' Error [BC30516] Date.Now.Subtract(dateTime) ' Noncompliant Dim span = Date.Now - dateTime ' Noncompliant End Sub End Class Public Class FakeDateTimeSubtract Private Sub MyMethod(ByVal dateTime As Date) Dim a = FakeDateTimeSubtract.DateTime.Now.Subtract(dateTime).Milliseconds ' Compliant End Sub Public NotInheritable Class DateTime Public Shared ReadOnly Property Now As Date End Class End Class ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveClassCoupling.Latest.cs ================================================ using System; using System.ComponentModel; using System.IO; using System.Collections.Generic; namespace CSharp9 { public interface IFoo { } class FooBase : IFoo { } class Foo1 : FooBase { } public struct MyStruct { } public record FirstRecord { } public record SecondRecord // Noncompliant {{Split this record into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 1 or less.}} { private FirstRecord field1; // +1 private MyStruct field2; // +1 private Foo1 field3; // +1 private nuint field5; // Primitives don't count private UIntPtr field6; // Primitives don't count public SecondRecord(IFoo interfaceFoo) { } // +1 private static FooBase Property1 { get; } // +1 public FirstRecord FooMethod() => field1; // already in field1 public void BarMethod(Stream s) { } // +1 } public record PositionalRecord(int Parameter) // Noncompliant {{Split this record into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 1 or less.}} { private FirstRecord field1; // +1 private MyStruct field2; // +1 private Foo1 field3; // +1 private nuint field5; // Primitives don't count private UIntPtr field6; // Primitives don't count public PositionalRecord(IFoo interfaceFoo) : this(5) { } // +1 private static FooBase Property1 { get; } // +1 public FirstRecord FooMethod() => field1; // already in field1 public void BarMethod(Stream s) { } // +1 } public record OutterRecord { InnerRecord whatever = new InnerRecord(); public record InnerRecord // Noncompliant { public Stream stream = new FileStream("", FileMode.Open); } } } namespace CSharp10 { public interface IFoo { } class FooBase : IFoo { } class Foo1 : FooBase { } public struct MyStruct { } public record struct FirstRecordStruct { } public record struct SecondRecordStruct // Noncompliant {{Split this record into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 1 or less.}} // ^^^^^^^^^^^^^^^^^^ { private FirstRecordStruct field1 = new FirstRecordStruct(); // +1 private MyStruct field2 = new MyStruct(); // +1 private Foo1 field3 = null; // +1 private nuint field5 = 42; // Primitives don't count public SecondRecordStruct(IFoo interfaceFoo) { } // +1 private static FooBase Property1 { get; } // +1 public FirstRecordStruct FooMethod() => field1; // already in field1 public void BarMethod(Stream s) { } // +1 } public record struct PositionalRecordStruct(int Parameter) // Noncompliant { private FirstRecordStruct field1 = new FirstRecordStruct(); // +1 private MyStruct field2 = new MyStruct(); // +1 private Foo1 field3 = null; // +1 private nuint field5 = 42; // Primitives don't count public PositionalRecordStruct(IFoo interfaceFoo) : this(5) { } // +1 private static FooBase Property1 { get; } // +1 public FirstRecordStruct FooMethod() => field1; // already in field1 public void BarMethod(Stream s) { } // +1 } public record struct OutterRecordStruct // Compliant: nested record struct types are not counted as dependencies of the outer type { public OutterRecordStruct() { } InnerRecordStruct whatever = new InnerRecordStruct(); public record struct InnerRecordStruct // Noncompliant { public InnerRecordStruct() { } public Stream stream = new FileStream("", FileMode.Open); } } } namespace CSharp11 { public interface IFoo { } class FooBase : IFoo { } class Foo : FooBase { } public struct MyStruct { } public class ZeroDependencies { } // Compliant public class ZeroNonPrimitiveDependencies // Compliant { private nint nativeInt; // Primitives don't count private nuint nativeUint; // Primitives don't count private IntPtr intPtr; // Primitives don't count private UIntPtr uIntPtr; // Primitives don't count private void Method_IntPtr(IntPtr arg) { } // Primitives don't count private void Method_UIntPtr(UIntPtr arg) { } // Primitives don't count private void Method_nint(nint arg) { } // Primitives don't count private void Method_nuint(nuint arg) { } // Primitives don't count } public class OneDependency // Compliant { private Foo foo; // +1 private nint nativeInt; // Primitives don't count private nuint nativeUint; // Primitives don't count private IntPtr intPtr; // Primitives don't count private UIntPtr uIntPtr; // Primitives don't count private class NestedClass // Noncompliant // ^^^^^^^^^^^ { private Foo nestedFoo; // +1 private FooBase GetFooBase() => default; // +1 } } public class TwoDependencies // Noncompliant // ^^^^^^^^^^^^^^^ { private Foo foo; // +1 private MyStruct myStruct; // +1 private nint nativeInt; // Primitives don't count private nuint nativeUint; // Primitives don't count private IntPtr intPtr; // Primitives don't count private UIntPtr uIntPtr; // Primitives don't count private class NestedClass // Compliant { private IFoo nestedIFoo; // +1 private nint nativeInt; // Primitives don't count private nuint nativeUint; // Primitives don't count private IntPtr intPtr; // Primitives don't count private UIntPtr uIntPtr; // Primitives don't count private void DoWork(IFoo iFoo) { } // Already counted in private field } private class NestedEmptyClass // Compliant { } } public class ScopedRefParameterUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Foo _foo; // +1 Foo void M(scoped ref MyStruct p) { } // +1 MyStruct } // file-scoped types file interface IFooFile { } file class FooFileBase : IFooFile { } file class FooFileClass1 : FooFileBase { } file class FooSecond // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 1 or less.}} // ^^^^^^^^^ { private FooFileClass1 field2 = new FooFileClass1(); // +1 private FooFileBase field3 = null; // +1 private static FooBase Property1 { get; } // +1 } } namespace CSharp13 { public interface IFoo { } public class FooBase : IFoo { } public class Foo1 : FooBase { } public class Foo2 : FooBase { } public struct MyStruct { } public partial class Partial // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 1 or less.}} { public partial FooBase property { get; set; } public MyStruct myStruct; public Foo2 foo2; } public partial class Partial // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { public partial FooBase property { get => new Foo1(); set { } } } } namespace CSharp14 { public class Foo1 { } public static class Foo1Extensions // Compliant: 1 dep (Foo1) { extension(Foo1) { public static Foo1 Create() => new Foo1(); // +1 Foo1 } } public class UsesStaticExtension // Compliant: 1 dep (Foo1) — Foo1Extensions (container) is not counted { Foo1 M() => Foo1.Create(); // +1 Foo1 } } namespace RuleExceptions { // https://sonarsource.atlassian.net/browse/NET-3553 public static class CollectionExtensions // Noncompliant FP - static classes containing only extension methods should be exempt { public static bool IsNullOrEmpty(this IList list) => list == null || list.Count == 0; // +1 IList public static bool IsNullOrEmpty(this ICollection collection) => collection == null || collection.Count == 0; // +1 ICollection } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveClassCoupling.cs ================================================ using System; using System.Collections.Generic; using System.IO; using MyFileStream = System.IO.FileStream; using FooBaseAlias = Tests.Diagnostics.FooBase; namespace Tests.Diagnostics { public interface IFoo { } class FooBase : IFoo { } class Foo1 : FooBase { } public struct MyStruct { } public interface ISelfReferencing where T : ISelfReferencing { } public class OuterForGenericArg { public class Inner { } } public abstract class TestCases // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 8 to the maximum authorized 1 or less.}} // ^^^^^^^^^ { // ================================================================================ // ==== FIELDS // ================================================================================ private IFoo field1 = new FooBase(); private FooBase field2 = Method3(); private static IFoo field3 = Property1; private int field4; // Primitives don't count private MyStruct str; private System.Threading.Tasks.Task myTask; Action myAction; Func myFunct; unsafe int* myPointer; // ================================================================================ // ==== PROPERTIES // ================================================================================ private static Foo1 Property1 { get; } public IFoo Property2 { get; set; } public IFoo Property3 { get { return new FooBase(); } } public IFoo Property4 { set { var x = value.ToString(); } } public IFoo Property5 => Method3(); // ================================================================================ // ==== EVENTS // ================================================================================ public event EventHandler Event1 { add { var x = Method3(); } remove { IFoo xx = Method3(); } } // ================================================================================ // ==== CTORS // ================================================================================ public TestCases() { var x = new object(); Stream y = new System.IO.FileStream("", System.IO.FileMode.Open); } // ================================================================================ // ==== DTORS // ================================================================================ ~TestCases() { Stream y; y = new FileStream("", FileMode.Open); } // ================================================================================ // ==== METHODS // ================================================================================ IDisposable Method1(object o) { Stream y = new FileStream("", FileMode.Open); return y; } Stream Method2() => new FileStream("", FileMode.Open); private static FooBase Method3() => null; protected abstract IFoo Method4(); } public class OutterClass { InnerClass whatever = new InnerClass(); public class InnerClass // Noncompliant { public Stream stream = new FileStream("", FileMode.Open); } } public class WithConstraint where T : IDisposable { } // Compliant public class UnboundGenericUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { void M() { _ = typeof(WithConstraint<>); } // +1 WithConstraint, +1 IDisposable (constraint of unbound generic) } public class ThisMemberAccessUsage // Compliant { private Stream _s; Stream M() => this._s; // coverage: this.X is not a simple name chain and adds no new dependencies } public class AliasInCastUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream void M(object obj) { var x = (MyFileStream)obj; } // +1 FileStream (via alias to BCL type) } public class AliasForUserDefinedTypeUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream void M(object obj) { var x = (FooBaseAlias)obj; } // +1 FooBase (via alias to user-defined type) } public class GlobalQualifiedCustomTypeUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream private global::Tests.Diagnostics.FooBase _fb; // +1 FooBase (global:: qualified custom type) } public class ArrayInitializerUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream void M() { FooBase[] arr = null; } // +1 FooBase (array element type via initializer ConvertedType) } public class PointerInitializerUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream unsafe void M() { MyStruct* ptr = null; } // +1 MyStruct (pointer initializer ConvertedType) } public class SelfReferencingGenericUsage // Compliant { void M() { _ = typeof(ISelfReferencing<>); } // +1 ISelfReferencing; constraint loops back to ISelfReferencing — deduped, no infinite recursion } public class SelfReferencingGenericUsage where T : ISelfReferencing // Compliant { // constraint T : ISelfReferencing adds +1 ISelfReferencing; T is ITypeParameterSymbol — filtered by ExpandGenericTypes, no infinite recursion } public class NullableWrapperUsage // Compliant { private MyStruct? field; // +1 MyStruct (Nullable not counted separately) } public class ConstraintAndNestedTypeArgUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 1 or less.}} where T : IFoo // +1 IFoo { void M() { _ = EqualityComparer>.Default; // +1 EqualityComparer, +1 Dictionary via ExpandGenericTypes (int, T not counted) } } public class GenericMethodTypeArgCounted // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 6 to the maximum authorized 1 or less.}} { void M() { Method(); // +1 IDisposable this.Method(); // +1 ICloneable var list = new List(); // +1 List list.ConvertAll(x => null); // +1 IComparable Method>(); // +1 Dictionary, +1 IFormattable via ExpandGenericTypes (int not counted) } void Method() { } } public class NestedTypeAsGenericArgUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 3 to the maximum authorized 1 or less.}} { private List field; // +1 List, +1 Inner, +1 OuterForGenericArg (ContainingType of Inner) } public class ChainedMemberAccessThroughPropertyUsage // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { private Stream _s; // +1 Stream void M() { System.Console.Out.WriteLine("hello"); } // +1 Console (Out is a property, not a type — must recurse deeper to find Console) } public class OuterWithNestedInterface // Compliant: nested interface types are not counted as dependencies of the outer type { InnerInterface whatever = null; interface InnerInterface // Noncompliant {{Split this interface into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { Stream M(FileStream fs); // +1 Stream, +1 FileStream } } public class OuterWithNestedClass // Compliant: nested class types are not counted as dependencies of the outer type { InnerClass whatever = null; class InnerClass // Noncompliant {{Split this class into smaller and more specialized ones to reduce its dependencies on other types from 2 to the maximum authorized 1 or less.}} { Stream field = null; // +1 Stream FileStream field2 = null; // +1 FileStream } } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_CustomValues.Records.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { record Record_0 { } record Record_1 : Record_0 { } record Record_2 : Record_1 { } record Record_3 : Record_2 { } // Noncompliant {{This record has 3 parents which is greater than 2 authorized.}} record SecondSubRecord : Record_3 { } // Noncompliant {{This record has 4 parents which is greater than 2 authorized.}} record ThirdSubRecord : SecondSubRecord { } record FourthSubRecord : ThirdSubRecord { } record FifthSubRecord : FourthSubRecord { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_CustomValues.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { class Class_0 : Exception { } class Class_1 : Class_0 { } class Class_2 : Class_1 { } class Class_3 : Class_2 { } // Noncompliant {{This class has 3 parents which is greater than 2 authorized.}} class SecondSubClass : Class_3 { } // Noncompliant {{This class has 4 parents which is greater than 2 authorized.}} class ThirdSubClass : SecondSubClass { } class FourthSubClass : ThirdSubClass { } class FifthSubClass : FourthSubClass { } } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_DefaultValues.Concurrent.cs ================================================ using System; using System.Collections.Generic; namespace AppendedNamespaceForConcurrencyTest.Tests.Diagnostics { public class BaseClass { } public class DerivedClass_1 : BaseClass { } public class DerivedClass_2 : DerivedClass_1 { } public class DerivedClass_3 : DerivedClass_2 { } public class DerivedClass_4 : DerivedClass_3 { } public class DerivedClass_5 : DerivedClass_4 { } public class DerivedClass_6 : DerivedClass_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} // ^^^^^^^^^^^^^^ public class DerivedClass_7 : DerivedClass_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} // ^^^^^^^^^^^^^^ public class SubClass_0 : Exception { } public class SubClass_1 : SubClass_0 { } public class SubClass_2 : SubClass_1 { } public class SubClass_3 : SubClass_2 { } public class SubClass_4 : SubClass_3 { } public class SubClass_5 : SubClass_4 { } public class SubClass_6 : SubClass_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} // ^^^^^^^^^^ public class SubClass_7 : SubClass_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} // ^^^^^^^^^^ } namespace AppendedNamespaceForConcurrencyTest.Tests { public class Tests_5 : Tests.Diagnostics.DerivedClass_4 { } public class Tests_6 : Tests.Diagnostics.DerivedClass_5 { } // Noncompliant public class Tests_7 : Tests.Diagnostics.SubClass_5 { } // Noncompliant } namespace AppendedNamespaceForConcurrencyTest.Tests.Foo.Bar.Baz { public class Tests_5 : Tests.Diagnostics.DerivedClass_4 { } public class Tests_6 : Tests.Diagnostics.DerivedClass_5 { } // Noncompliant public class Tests_7 : Tests.Diagnostics.SubClass_5 { } // Noncompliant } namespace SomeOtherNamespace { public class Other_0 : AppendedNamespaceForConcurrencyTest.Tests.Diagnostics.SubClass_6 { } public class Other_1 : Other_0 { } public class Other_2 : Other_1 { } public class Other_3 : Other_2 { } public class Other_4 : Other_3 { } public class Other_5 : Other_4 { } public class Other_6 : Other_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} public class Other_7 : Other_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_DefaultValues.FileScopedTypes.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { file class Class_0 { } file class Class_1 : Class_0 { } file class Class_2 : Class_1 { } file class Class_3 : Class_2 { } file class Class_4 : Class_3 { } file class Class_5 : Class_4 { } file class Class_6 : Class_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} file class Class_7 : Class_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} file record Record_0 { } file record Record_1 : Record_0 { } file record Record_2 : Record_1 { } file record Record_3 : Record_2 { } file record Record_4 : Record_3 { } file record Record_5 : Record_4 { } file record Record_6 : Record_5 { } // Noncompliant {{This record has 6 parents which is greater than 5 authorized.}} file record Record_7 : Record_6 { } // Noncompliant {{This record has 7 parents which is greater than 5 authorized.}} } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_DefaultValues.Records.Concurrent.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.Intrinsics.Arm; namespace AppendedNamespaceForConcurrencyTest.Tests.Diagnostics { public record BaseRecord { } } namespace OtherRecordNamespace { public record Record_0 : AppendedNamespaceForConcurrencyTest.Tests.Diagnostics.BaseRecord { } public record Record_1 : Record_0 { } public record Record_2 : Record_1 { } public record Record_3 : Record_2 { } public record Record_4 : Record_3 { } public record Record_5 : Record_4 { } public record Record_6 : Record_5 { } // Noncompliant {{This record has 6 parents which is greater than 5 authorized.}} public record Record_7 : Record_6 { } // Noncompliant {{This record has 7 parents which is greater than 5 authorized.}} } ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_DefaultValues.Records.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.Intrinsics.Arm; namespace Tests.Diagnostics { public record BaseRecord { } } namespace RecordNamespace { public record Record_0 : Tests.Diagnostics.BaseRecord { } public record Record_1 : Record_0 { } public record Record_2 : Record_1 { } public record Record_3 : Record_2 { } public record Record_4 : Record_3 { } public record Record_5 : Record_4 { } public record Record_6 : Record_5 { } // Noncompliant {{This record has 6 parents which is greater than 5 authorized.}} public record Record_7 : Record_6 { } // Noncompliant {{This record has 7 parents which is greater than 5 authorized.}} } public record Record_0(string s); public record Record_1(string s) : Record_0(s); public record Record_2(string s) : Record_1(s); public record Record_3(string s) : Record_2(s); public record Record_4(string s) : Record_3(s); public record Record_5(string s) : Record_4(s); public record Record_6(string s) : Record_5(s); // Noncompliant {{This record has 6 parents which is greater than 5 authorized.}} public record Record_7(string s) : Record_6(s); // Noncompliant {{This record has 7 parents which is greater than 5 authorized.}} ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidExcessiveInheritance_DefaultValues.cs ================================================ using System; using System.Collections.Generic; namespace Tests.Diagnostics { public class BaseClass { } public class DerivedClass_1 : BaseClass { } public class DerivedClass_2 : DerivedClass_1 { } public class DerivedClass_3 : DerivedClass_2 { } public class DerivedClass_4 : DerivedClass_3 { } public class DerivedClass_5 : DerivedClass_4 { } public class DerivedClass_6 : DerivedClass_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} // ^^^^^^^^^^^^^^ public class DerivedClass_7 : DerivedClass_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} // ^^^^^^^^^^^^^^ public class SubClass_0 : Exception { } public class SubClass_1 : SubClass_0 { } public class SubClass_2 : SubClass_1 { } public class SubClass_3 : SubClass_2 { } public class SubClass_4 : SubClass_3 { } public class SubClass_5 : SubClass_4 { } public class SubClass_6 : SubClass_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} // ^^^^^^^^^^ public class SubClass_7 : SubClass_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} // ^^^^^^^^^^ } namespace Tests { public class Tests_5 : Tests.Diagnostics.DerivedClass_4 { } public class Tests_6 : Tests.Diagnostics.DerivedClass_5 { } // Noncompliant public class Tests_7 : Tests.Diagnostics.SubClass_5 { } // Noncompliant } namespace Tests.Foo.Bar.Baz { public class Tests_5 : Tests.Diagnostics.DerivedClass_4 { } public class Tests_6 : Tests.Diagnostics.DerivedClass_5 { } // Noncompliant public class Tests_7 : Tests.Diagnostics.SubClass_5 { } // Noncompliant } namespace OtherNamespace { public class Other_0 : Tests.Diagnostics.SubClass_6 { } public class Other_1 : Other_0 { } public class Other_2 : Other_1 { } public class Other_3 : Other_2 { } public class Other_4 : Other_3 { } public class Other_5 : Other_4 { } public class Other_6 : Other_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} public class Other_7 : Other_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} } public class Other_0 : Exception { } public class Other_1 : Other_0 { } public class Other_2 : Other_1 { } public class Other_3 : Other_2 { } public class Other_4 : Other_3 { } public class Other_5 : Other_4 { } public class Other_6 : Other_5 { } // Noncompliant {{This class has 6 parents which is greater than 5 authorized.}} public class Other_7 : Other_6 { } // Noncompliant {{This class has 7 parents which is greater than 5 authorized.}} ================================================ FILE: analyzers/tests/SonarAnalyzer.Test/TestCases/AvoidLambdaExpressionInLoopsInBlazor.LoopsWithNoBody.razor ================================================ @using Microsoft.AspNetCore.Components.Web @* https://github.com/SonarSource/sonar-dotnet/issues/8394 *@ @foreach (var item in Buttons.Where(x => x.Id == "idToFind")) if (item.Id == "idToFind") { @* FN *@ } @for (int i = 0; i < Buttons.Count; i++) @if (i % 2 == 0) { var buttonNumber = i; } @{ var j = 0; while (j < 5) if (j % 2 == 0) { j += 2; j += 2; } do if (j % 2 == 0) { j += 2; } while (j < 10); } @foreach (var item in Buttons.Where(x => x.Id == "idToFind")) @if (item.Id == "idToFind") { @* FN *@ } else if (item.Id == "idToFind") { @* FN *@ } else { @* FN *@ } @foreach (var item in Buttons.Where(x => x.Id == "idToFind")) @if (true) @if (item.Id == "idToFind") { @* FN *@ } @foreach (var item in Buttons.Where(x => x.Id == "idToFind")) @switch(item.Id) { case "idToFind": @* FN *@ break; default: { @* FN *@ break; } } @foreach (var button in Buttons) { { } } @code { private List } @foreach (var button in Buttons.Where(x => x.Id == "SomeId")) @* Compliant *@ { } @code { private List } @foreach (var button in Buttons) { } @for (int i = 0; i < Buttons.Count; i++) { var buttonNumber = i; } @{ var j = 0; while (j < 5) { j += 1; } do { j += 1; } while (j < 10); } @* Compliant *@ @if (Buttons.Count > 0) { @* Compliant *@ } @foreach (var button in Buttons.OrderByDescending(x => x.Id)) @* Compliant, the lambda is executed outside of the loop *@ {

@button.Id

} @code { private List